From 302e3834a0bfa860f9d06b42a2955b0cbd135c38 Mon Sep 17 00:00:00 2001 From: Lewis Marshall Date: Tue, 31 Mar 2015 01:33:27 +0100 Subject: [PATCH 001/332] Prevent Upstart post-start stanza from hanging Once the job has failed and is respawned, the status becomes `docker respawn/post-start` after subsequent failures (as opposed to `docker stop/post-start`), so the post-start script needs to take this into account. I could not find specific documentation on the job transitioning to the `respawn/post-start` state, but this was observed on Ubuntu 14.04.2. Signed-off-by: Lewis Marshall --- contrib/init/upstart/docker.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index f9930bd3962ea..4ad6058ed03ec 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -49,7 +49,7 @@ post-start script fi if ! printf "%s" "$DOCKER_OPTS" | grep -qE -e '-H|--host'; then while ! [ -e /var/run/docker.sock ]; do - initctl status $UPSTART_JOB | grep -q "stop/" && exit 1 + initctl status $UPSTART_JOB | grep -qE "(stop|respawn)/" && exit 1 echo "Waiting for /var/run/docker.sock" sleep 0.1 done From f011d722ce2ec7ef00654130f7f4ff8d295f025f Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 17 Mar 2015 11:03:56 -0700 Subject: [PATCH 002/332] Vendor distribution v2 api Signed-off-by: Derek McGowan (github: dmcgowan) --- hack/vendor.sh | 7 +- .../registry/api/v2/descriptors.go | 1459 +++++++++++++++++ .../distribution/registry/api/v2/doc.go | 9 + .../distribution/registry/api/v2/errors.go | 194 +++ .../registry/api/v2/errors_test.go | 165 ++ .../distribution/registry/api/v2/names.go | 100 ++ .../registry/api/v2/names_test.go | 100 ++ .../distribution/registry/api/v2/routes.go | 47 + .../registry/api/v2/routes_test.go | 315 ++++ .../distribution/registry/api/v2/urls.go | 217 +++ .../distribution/registry/api/v2/urls_test.go | 225 +++ vendor/src/github.com/gorilla/mux/mux.go | 6 +- vendor/src/github.com/gorilla/mux/mux_test.go | 45 + vendor/src/github.com/gorilla/mux/old_test.go | 48 +- vendor/src/github.com/gorilla/mux/regexp.go | 62 +- vendor/src/github.com/gorilla/mux/route.go | 63 +- 16 files changed, 2970 insertions(+), 92 deletions(-) create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/doc.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/errors.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/names.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/routes.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/urls.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 0246f03cc4495..abaa4d7204386 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -43,7 +43,7 @@ clone git github.com/kr/pty 05017fcccf clone git github.com/gorilla/context 14f550f51a -clone git github.com/gorilla/mux 136d54f81f +clone git github.com/gorilla/mux e444e69cbd clone git github.com/tchap/go-patricia v1.0.1 @@ -68,12 +68,15 @@ if [ "$1" = '--go' ]; then mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar fi -# get digest package from distribution +# get distribution packages clone git github.com/docker/distribution d957768537c5af40e4f4cd96871f7b2bde9e2923 mv src/github.com/docker/distribution/digest tmp-digest +mv src/github.com/docker/distribution/registry/api tmp-api rm -rf src/github.com/docker/distribution mkdir -p src/github.com/docker/distribution mv tmp-digest src/github.com/docker/distribution/digest +mkdir -p src/github.com/docker/distribution/registry +mv tmp-api src/github.com/docker/distribution/registry/api clone git github.com/docker/libcontainer c8512754166539461fd860451ff1a0af7491c197 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go new file mode 100644 index 0000000000000..5f091bbc927b6 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go @@ -0,0 +1,1459 @@ +package v2 + +import ( + "net/http" + "regexp" + + "github.com/docker/distribution/digest" +) + +var ( + nameParameterDescriptor = ParameterDescriptor{ + Name: "name", + Type: "string", + Format: RepositoryNameRegexp.String(), + Required: true, + Description: `Name of the target repository.`, + } + + tagParameterDescriptor = ParameterDescriptor{ + Name: "tag", + Type: "string", + Format: TagNameRegexp.String(), + Required: true, + Description: `Tag of the target manifiest.`, + } + + uuidParameterDescriptor = ParameterDescriptor{ + Name: "uuid", + Type: "opaque", + Required: true, + Description: `A uuid identifying the upload. This field can accept almost anything.`, + } + + digestPathParameter = ParameterDescriptor{ + Name: "digest", + Type: "path", + Required: true, + Format: digest.DigestRegexp.String(), + Description: `Digest of desired blob.`, + } + + hostHeader = ParameterDescriptor{ + Name: "Host", + Type: "string", + Description: "Standard HTTP Host Header. Should be set to the registry host.", + Format: "", + Examples: []string{"registry-1.docker.io"}, + } + + authHeader = ParameterDescriptor{ + Name: "Authorization", + Type: "string", + Description: "An RFC7235 compliant authorization header.", + Format: " ", + Examples: []string{"Bearer dGhpcyBpcyBhIGZha2UgYmVhcmVyIHRva2VuIQ=="}, + } + + authChallengeHeader = ParameterDescriptor{ + Name: "WWW-Authenticate", + Type: "string", + Description: "An RFC7235 compliant authentication challenge header.", + Format: ` realm="", ..."`, + Examples: []string{ + `Bearer realm="https://auth.docker.com/", service="registry.docker.com", scopes="repository:library/ubuntu:pull"`, + }, + } + + contentLengthZeroHeader = ParameterDescriptor{ + Name: "Content-Length", + Description: "The `Content-Length` header must be zero and the body must be empty.", + Type: "integer", + Format: "0", + } + + dockerUploadUUIDHeader = ParameterDescriptor{ + Name: "Docker-Upload-UUID", + Description: "Identifies the docker upload uuid for the current request.", + Type: "uuid", + Format: "", + } + + digestHeader = ParameterDescriptor{ + Name: "Docker-Content-Digest", + Description: "Digest of the targeted content for the request.", + Type: "digest", + Format: "", + } + + unauthorizedResponse = ResponseDescriptor{ + Description: "The client does not have access to the repository.", + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: unauthorizedErrorsBody, + }, + } + + unauthorizedResponsePush = ResponseDescriptor{ + Description: "The client does not have access to push to the repository.", + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: unauthorizedErrorsBody, + }, + } +) + +const ( + manifestBody = `{ + "name": , + "tag": , + "fsLayers": [ + { + "blobSum": + }, + ... + ] + ], + "history": , + "signature": +}` + + errorsBody = `{ + "errors:" [ + { + "code": , + "message": "", + "detail": ... + }, + ... + ] +}` + + unauthorizedErrorsBody = `{ + "errors:" [ + { + "code": "UNAUTHORIZED", + "message": "access to the requested resource is not authorized", + "detail": ... + }, + ... + ] +}` +) + +// APIDescriptor exports descriptions of the layout of the v2 registry API. +var APIDescriptor = struct { + // RouteDescriptors provides a list of the routes available in the API. + RouteDescriptors []RouteDescriptor + + // ErrorDescriptors provides a list of the error codes and their + // associated documentation and metadata. + ErrorDescriptors []ErrorDescriptor +}{ + RouteDescriptors: routeDescriptors, + ErrorDescriptors: errorDescriptors, +} + +// RouteDescriptor describes a route specified by name. +type RouteDescriptor struct { + // Name is the name of the route, as specified in RouteNameXXX exports. + // These names a should be considered a unique reference for a route. If + // the route is registered with gorilla, this is the name that will be + // used. + Name string + + // Path is a gorilla/mux-compatible regexp that can be used to match the + // route. For any incoming method and path, only one route descriptor + // should match. + Path string + + // Entity should be a short, human-readalbe description of the object + // targeted by the endpoint. + Entity string + + // Description should provide an accurate overview of the functionality + // provided by the route. + Description string + + // Methods should describe the various HTTP methods that may be used on + // this route, including request and response formats. + Methods []MethodDescriptor +} + +// MethodDescriptor provides a description of the requests that may be +// conducted with the target method. +type MethodDescriptor struct { + + // Method is an HTTP method, such as GET, PUT or POST. + Method string + + // Description should provide an overview of the functionality provided by + // the covered method, suitable for use in documentation. Use of markdown + // here is encouraged. + Description string + + // Requests is a slice of request descriptors enumerating how this + // endpoint may be used. + Requests []RequestDescriptor +} + +// RequestDescriptor covers a particular set of headers and parameters that +// can be carried out with the parent method. Its most helpful to have one +// RequestDescriptor per API use case. +type RequestDescriptor struct { + // Name provides a short identifier for the request, usable as a title or + // to provide quick context for the particalar request. + Name string + + // Description should cover the requests purpose, covering any details for + // this particular use case. + Description string + + // Headers describes headers that must be used with the HTTP request. + Headers []ParameterDescriptor + + // PathParameters enumerate the parameterized path components for the + // given request, as defined in the route's regular expression. + PathParameters []ParameterDescriptor + + // QueryParameters provides a list of query parameters for the given + // request. + QueryParameters []ParameterDescriptor + + // Body describes the format of the request body. + Body BodyDescriptor + + // Successes enumerates the possible responses that are considered to be + // the result of a successful request. + Successes []ResponseDescriptor + + // Failures covers the possible failures from this particular request. + Failures []ResponseDescriptor +} + +// ResponseDescriptor describes the components of an API response. +type ResponseDescriptor struct { + // Name provides a short identifier for the response, usable as a title or + // to provide quick context for the particalar response. + Name string + + // Description should provide a brief overview of the role of the + // response. + Description string + + // StatusCode specifies the status recieved by this particular response. + StatusCode int + + // Headers covers any headers that may be returned from the response. + Headers []ParameterDescriptor + + // ErrorCodes enumerates the error codes that may be returned along with + // the response. + ErrorCodes []ErrorCode + + // Body describes the body of the response, if any. + Body BodyDescriptor +} + +// BodyDescriptor describes a request body and its expected content type. For +// the most part, it should be example json or some placeholder for body +// data in documentation. +type BodyDescriptor struct { + ContentType string + Format string +} + +// ParameterDescriptor describes the format of a request parameter, which may +// be a header, path parameter or query parameter. +type ParameterDescriptor struct { + // Name is the name of the parameter, either of the path component or + // query parameter. + Name string + + // Type specifies the type of the parameter, such as string, integer, etc. + Type string + + // Description provides a human-readable description of the parameter. + Description string + + // Required means the field is required when set. + Required bool + + // Format is a specifying the string format accepted by this parameter. + Format string + + // Regexp is a compiled regular expression that can be used to validate + // the contents of the parameter. + Regexp *regexp.Regexp + + // Examples provides multiple examples for the values that might be valid + // for this parameter. + Examples []string +} + +// ErrorDescriptor provides relevant information about a given error code. +type ErrorDescriptor struct { + // Code is the error code that this descriptor describes. + Code ErrorCode + + // Value provides a unique, string key, often captilized with + // underscores, to identify the error code. This value is used as the + // keyed value when serializing api errors. + Value string + + // Message is a short, human readable decription of the error condition + // included in API responses. + Message string + + // Description provides a complete account of the errors purpose, suitable + // for use in documentation. + Description string + + // HTTPStatusCodes provides a list of status under which this error + // condition may arise. If it is empty, the error condition may be seen + // for any status code. + HTTPStatusCodes []int +} + +var routeDescriptors = []RouteDescriptor{ + { + Name: RouteNameBase, + Path: "/v2/", + Entity: "Base", + Description: `Base V2 API route. Typically, this can be used for lightweight version checks and to validate registry authorization.`, + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Check that the endpoint implements Docker Registry API V2.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + Successes: []ResponseDescriptor{ + { + Description: "The API implements V2 protocol and is accessible.", + StatusCode: http.StatusOK, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The client is not authorized to access the registry.", + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + { + Description: "The registry does not implement the V2 API.", + StatusCode: http.StatusNotFound, + }, + }, + }, + }, + }, + }, + }, + { + Name: RouteNameTags, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list", + Entity: "Tags", + Description: "Retrieve information about tags.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Fetch the tags under the repository identified by `name`.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Description: "A list of tags for the named repository.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "name": , + "tags": [ + , + ... + ] +}`, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + StatusCode: http.StatusNotFound, + Description: "The repository is not known to the registry.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Description: "The client does not have access to the repository.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + }, + }, + }, + }, + }, + }, + { + Name: RouteNameManifest, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + digest.DigestRegexp.String() + "}", + Entity: "Manifest", + Description: "Create, update and retrieve manifests.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Fetch the manifest identified by `name` and `reference` where `reference` can be a tag or digest.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + tagParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Description: "The manifest idenfied by `name` and `reference`. The contents can be used to identify and resolve resources required to run the specified image.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + digestHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: manifestBody, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The name or reference was invalid.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Description: "The client does not have access to the repository.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + { + Description: "The named manifest is not known to the registry.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeManifestUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + { + Method: "PUT", + Description: "Put the manifest identified by `name` and `reference` where `reference` can be a tag or digest.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + tagParameterDescriptor, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: manifestBody, + }, + Successes: []ResponseDescriptor{ + { + Description: "The manifest has been accepted by the registry and is stored under the specified `name` and `tag`.", + StatusCode: http.StatusAccepted, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Description: "The canonical location url of the uploaded manifest.", + Format: "", + }, + contentLengthZeroHeader, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Manifest", + Description: "The received manifest was invalid in some way, as described by the error codes. The client should resolve the issue and retry the request.", + StatusCode: http.StatusBadRequest, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + ErrorCodeManifestInvalid, + ErrorCodeManifestUnverified, + ErrorCodeBlobUnknown, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Description: "The client does not have permission to push to the repository.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + { + Name: "Missing Layer(s)", + Description: "One or more layers may be missing during a manifest upload. If so, the missing layers will be enumerated in the error response.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "errors:" [{ + "code": "BLOB_UNKNOWN", + "message": "blob unknown to registry", + "detail": { + "digest": + } + }, + ... + ] +}`, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + { + Method: "DELETE", + Description: "Delete the manifest identified by `name` and `reference` where `reference` can be a tag or digest.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + tagParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusAccepted, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Tag", + Description: "The specified `name` or `tag` were invalid and the delete was unable to proceed.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Name: "Unknown Manifest", + Description: "The specified `name` or `tag` are unknown to the registry and the delete was unable to proceed. Clients can assume the manifest was already deleted if this response is returned.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeManifestUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + }, + }, + + { + Name: RouteNameBlob, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}", + Entity: "Blob", + Description: "Fetch the blob identified by `name` and `digest`. Used to fetch layers by tarsum digest.", + Methods: []MethodDescriptor{ + + { + Method: "GET", + Description: "Retrieve the blob from the registry identified by `digest`. A `HEAD` request can also be issued to this endpoint to obtain resource information without receiving all data.", + Requests: []RequestDescriptor{ + { + Name: "Fetch Blob", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + digestPathParameter, + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob identified by `digest` is available. The blob content will be present in the body of the request.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "The length of the requested blob content.", + Format: "", + }, + digestHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + }, + { + Description: "The blob identified by `digest` is available at the provided location.", + StatusCode: http.StatusTemporaryRedirect, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Description: "The location where the layer should be accessible.", + Format: "", + }, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `tag`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + Description: "The blob, identified by `name` and `digest`, is unknown to the registry.", + StatusCode: http.StatusNotFound, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeBlobUnknown, + }, + }, + }, + }, + { + Name: "Fetch Blob Part", + Description: "This endpoint may also support RFC7233 compliant range requests. Support can be detected by issuing a HEAD request. If the header `Accept-Range: bytes` is returned, range requests can be used to fetch partial content.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Range", + Type: "string", + Description: "HTTP Range header specifying blob chunk.", + Format: "bytes=-", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + digestPathParameter, + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob identified by `digest` is available. The specified chunk of blob content will be present in the body of the request.", + StatusCode: http.StatusPartialContent, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "The length of the requested blob chunk.", + Format: "", + }, + { + Name: "Content-Range", + Type: "byte range", + Description: "Content range of blob chunk.", + Format: "bytes -/", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `tag`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeBlobUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The range specification cannot be satisfied for the requested content. This can happen when the range is not formatted correctly or if the range is outside of the valid size of the content.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + }, + }, + }, + }, + }, + // TODO(stevvooe): We may want to add a PUT request here to + // kickoff an upload of a blob, integrated with the blob upload + // API. + }, + }, + + { + Name: RouteNameBlobUpload, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/", + Entity: "Intiate Blob Upload", + Description: "Initiate a blob upload. This endpoint can be used to create resumable uploads or monolithic uploads.", + Methods: []MethodDescriptor{ + { + Method: "POST", + Description: "Initiate a resumable blob upload. If successful, an upload location will be provided to complete the upload. Optionally, if the `digest` parameter is present, the request body will be used to complete the upload in a single request.", + Requests: []RequestDescriptor{ + { + Name: "Initiate Monolithic Blob Upload", + Description: "Upload a blob identified by the `digest` parameter in single request. This upload will not be resumable unless a recoverable error is returned.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Length", + Type: "integer", + Format: "", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + QueryParameters: []ParameterDescriptor{ + { + Name: "digest", + Type: "query", + Format: "", + Regexp: digest.DigestRegexp, + Description: `Digest of uploaded blob. If present, the upload will be completed, in a single request, with contents of the request body as the resulting blob.`, + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octect-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob has been created in the registry and is available at the provided location.", + StatusCode: http.StatusCreated, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + unauthorizedResponsePush, + }, + }, + { + Name: "Initiate Resumable Blob Upload", + Description: "Initiate a resumable blob upload with an empty request body.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + contentLengthZeroHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Description: "The upload has been created. The `Location` header must be used to complete the upload. The response should be identical to a `GET` request on the contents of the returned `Location` header.", + StatusCode: http.StatusAccepted, + Headers: []ParameterDescriptor{ + contentLengthZeroHeader, + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the created upload. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Format: "0-0", + Description: "Range header indicating the progress of the upload. When starting an upload, it will return an empty range, since no content has been received.", + }, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + unauthorizedResponsePush, + }, + }, + }, + }, + }, + }, + + { + Name: RouteNameBlobUploadChunk, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}", + Entity: "Blob Upload", + Description: "Interact with blob uploads. Clients should never assemble URLs for this endpoint and should only take it through the `Location` header on related API requests. The `Location` header and its parameters should be preserved by clients, using the latest value returned via upload related API calls.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Retrieve status of upload identified by `uuid`. The primary purpose of this endpoint is to resolve the current status of a resumable upload.", + Requests: []RequestDescriptor{ + { + Description: "Retrieve the progress of the current upload, as reported by the `Range` header.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Progress", + Description: "The upload is known and in progress. The last received offset is available in the `Range` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + { + Method: "PATCH", + Description: "Upload a chunk of data for the specified upload.", + Requests: []RequestDescriptor{ + { + Description: "Upload a chunk of data to specified upload without completing the upload.", + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Required: true, + Description: "Range of bytes identifying the desired block of content represented by the body. Start must the end offset retrieved via status check plus one. Note that this is a non-standard use of the `Content-Range` header.", + }, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the chunk being uploaded, corresponding the length of the request body.", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Name: "Chunk Accepted", + Description: "The chunk of data has been accepted and the current progress is available in the range header. The updated upload location is available in the `Location` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the upload. Clients should assume this changes after each request. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponsePush, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The `Content-Range` specification cannot be accepted, either because it does not overlap with the current progress or it is invalid.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + }, + }, + }, + }, + }, + { + Method: "PUT", + Description: "Complete the upload specified by `uuid`, optionally appending the body as the final chunk.", + Requests: []RequestDescriptor{ + { + // TODO(stevvooe): Break this down into three separate requests: + // 1. Complete an upload where all data has already been sent. + // 2. Complete an upload where the entire body is in the PUT. + // 3. Complete an upload where the final, partial chunk is the body. + + Description: "Complete the upload, providing the _final_ chunk of data, if necessary. This method may take a body with all the data. If the `Content-Range` header is specified, it may include the final chunk. A request without a body will just complete the upload with previously uploaded content.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Description: "Range of bytes identifying the block of content represented by the body. Start must the end offset retrieved via status check plus one. Note that this is a non-standard use of the `Content-Range` header. May be omitted if no data is provided.", + }, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the chunk being uploaded, corresponding to the length of the request body. May be zero if no data is provided.", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + QueryParameters: []ParameterDescriptor{ + { + Name: "digest", + Type: "string", + Format: "", + Regexp: digest.DigestRegexp, + Required: true, + Description: `Digest of uploaded blob.`, + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Complete", + Description: "The upload has been completed and accepted by the registry. The canonical location will be available in the `Location` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "", + }, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Description: "Range of bytes identifying the desired block of content represented by the body. Start must match the end of offset retrieved via status check. Note that this is a non-standard use of the `Content-Range` header.", + }, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the chunk being uploaded, corresponding the length of the request body.", + }, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponsePush, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The `Content-Range` specification cannot be accepted, either because it does not overlap with the current progress or it is invalid. The contents of the `Range` header may be used to resolve the condition.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the upload. Clients should assume this changes after each request. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + }, + }, + }, + }, + }, + }, + { + Method: "DELETE", + Description: "Cancel outstanding upload processes, releasing associated resources. If this is not called, the unfinished uploads will eventually timeout.", + Requests: []RequestDescriptor{ + { + Description: "Cancel the upload specified by `uuid`.", + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + contentLengthZeroHeader, + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Deleted", + Description: "The upload has been successfully deleted.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + contentLengthZeroHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "An error was encountered processing the delete. The client may ignore this error.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + Description: "The upload is unknown to the registry. The client may ignore this error and assume the upload has been deleted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + }, + }, +} + +// ErrorDescriptors provides a list of HTTP API Error codes that may be +// encountered when interacting with the registry API. +var errorDescriptors = []ErrorDescriptor{ + { + Code: ErrorCodeUnknown, + Value: "UNKNOWN", + Message: "unknown error", + Description: `Generic error returned when the error does not have an + API classification.`, + }, + { + Code: ErrorCodeUnsupported, + Value: "UNSUPPORTED", + Message: "The operation is unsupported.", + Description: `The operation was unsupported due to a missing + implementation or invalid set of parameters.`, + }, + { + Code: ErrorCodeUnauthorized, + Value: "UNAUTHORIZED", + Message: "access to the requested resource is not authorized", + Description: `The access controller denied access for the operation on + a resource. Often this will be accompanied by a 401 Unauthorized + response status.`, + }, + { + Code: ErrorCodeDigestInvalid, + Value: "DIGEST_INVALID", + Message: "provided digest did not match uploaded content", + Description: `When a blob is uploaded, the registry will check that + the content matches the digest provided by the client. The error may + include a detail structure with the key "digest", including the + invalid digest string. This error may also be returned when a manifest + includes an invalid layer digest.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + { + Code: ErrorCodeSizeInvalid, + Value: "SIZE_INVALID", + Message: "provided length did not match content length", + Description: `When a layer is uploaded, the provided size will be + checked against the uploaded content. If they do not match, this error + will be returned.`, + HTTPStatusCodes: []int{http.StatusBadRequest}, + }, + { + Code: ErrorCodeNameInvalid, + Value: "NAME_INVALID", + Message: "invalid repository name", + Description: `Invalid repository name encountered either during + manifest validation or any API operation.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + { + Code: ErrorCodeTagInvalid, + Value: "TAG_INVALID", + Message: "manifest tag did not match URI", + Description: `During a manifest upload, if the tag in the manifest + does not match the uri tag, this error will be returned.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + { + Code: ErrorCodeNameUnknown, + Value: "NAME_UNKNOWN", + Message: "repository name not known to registry", + Description: `This is returned if the name used during an operation is + unknown to the registry.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, + { + Code: ErrorCodeManifestUnknown, + Value: "MANIFEST_UNKNOWN", + Message: "manifest unknown", + Description: `This error is returned when the manifest, identified by + name and tag is unknown to the repository.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, + { + Code: ErrorCodeManifestInvalid, + Value: "MANIFEST_INVALID", + Message: "manifest invalid", + Description: `During upload, manifests undergo several checks ensuring + validity. If those checks fail, this error may be returned, unless a + more specific error is included. The detail will contain information + the failed validation.`, + HTTPStatusCodes: []int{http.StatusBadRequest}, + }, + { + Code: ErrorCodeManifestUnverified, + Value: "MANIFEST_UNVERIFIED", + Message: "manifest failed signature verification", + Description: `During manifest upload, if the manifest fails signature + verification, this error will be returned.`, + HTTPStatusCodes: []int{http.StatusBadRequest}, + }, + { + Code: ErrorCodeBlobUnknown, + Value: "BLOB_UNKNOWN", + Message: "blob unknown to registry", + Description: `This error may be returned when a blob is unknown to the + registry in a specified repository. This can be returned with a + standard get or if a manifest references an unknown layer during + upload.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + + { + Code: ErrorCodeBlobUploadUnknown, + Value: "BLOB_UPLOAD_UNKNOWN", + Message: "blob upload unknown to registry", + Description: `If a blob upload has been cancelled or was never + started, this error code may be returned.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, + { + Code: ErrorCodeBlobUploadInvalid, + Value: "BLOB_UPLOAD_INVALID", + Message: "blob upload invalid", + Description: `The blob upload encountered an error and can no + longer proceed.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, +} + +var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor +var idToDescriptors map[string]ErrorDescriptor +var routeDescriptorsMap map[string]RouteDescriptor + +func init() { + errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(errorDescriptors)) + idToDescriptors = make(map[string]ErrorDescriptor, len(errorDescriptors)) + routeDescriptorsMap = make(map[string]RouteDescriptor, len(routeDescriptors)) + + for _, descriptor := range errorDescriptors { + errorCodeToDescriptors[descriptor.Code] = descriptor + idToDescriptors[descriptor.Value] = descriptor + } + for _, descriptor := range routeDescriptors { + routeDescriptorsMap[descriptor.Name] = descriptor + } +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/doc.go b/vendor/src/github.com/docker/distribution/registry/api/v2/doc.go new file mode 100644 index 0000000000000..cde0119594dd0 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/doc.go @@ -0,0 +1,9 @@ +// Package v2 describes routes, urls and the error codes used in the Docker +// Registry JSON HTTP API V2. In addition to declarations, descriptors are +// provided for routes and error codes that can be used for implementation and +// automatically generating documentation. +// +// Definitions here are considered to be locked down for the V2 registry api. +// Any changes must be considered carefully and should not proceed without a +// change proposal in docker core. +package v2 diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/errors.go b/vendor/src/github.com/docker/distribution/registry/api/v2/errors.go new file mode 100644 index 0000000000000..cbae020efb1a6 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/errors.go @@ -0,0 +1,194 @@ +package v2 + +import ( + "fmt" + "strings" +) + +// ErrorCode represents the error type. The errors are serialized via strings +// and the integer format may change and should *never* be exported. +type ErrorCode int + +const ( + // ErrorCodeUnknown is a catch-all for errors not defined below. + ErrorCodeUnknown ErrorCode = iota + + // ErrorCodeUnsupported is returned when an operation is not supported. + ErrorCodeUnsupported + + // ErrorCodeUnauthorized is returned if a request is not authorized. + ErrorCodeUnauthorized + + // ErrorCodeDigestInvalid is returned when uploading a blob if the + // provided digest does not match the blob contents. + ErrorCodeDigestInvalid + + // ErrorCodeSizeInvalid is returned when uploading a blob if the provided + // size does not match the content length. + ErrorCodeSizeInvalid + + // ErrorCodeNameInvalid is returned when the name in the manifest does not + // match the provided name. + ErrorCodeNameInvalid + + // ErrorCodeTagInvalid is returned when the tag in the manifest does not + // match the provided tag. + ErrorCodeTagInvalid + + // ErrorCodeNameUnknown when the repository name is not known. + ErrorCodeNameUnknown + + // ErrorCodeManifestUnknown returned when image manifest is unknown. + ErrorCodeManifestUnknown + + // ErrorCodeManifestInvalid returned when an image manifest is invalid, + // typically during a PUT operation. This error encompasses all errors + // encountered during manifest validation that aren't signature errors. + ErrorCodeManifestInvalid + + // ErrorCodeManifestUnverified is returned when the manifest fails + // signature verfication. + ErrorCodeManifestUnverified + + // ErrorCodeBlobUnknown is returned when a blob is unknown to the + // registry. This can happen when the manifest references a nonexistent + // layer or the result is not found by a blob fetch. + ErrorCodeBlobUnknown + + // ErrorCodeBlobUploadUnknown is returned when an upload is unknown. + ErrorCodeBlobUploadUnknown + + // ErrorCodeBlobUploadInvalid is returned when an upload is invalid. + ErrorCodeBlobUploadInvalid +) + +// ParseErrorCode attempts to parse the error code string, returning +// ErrorCodeUnknown if the error is not known. +func ParseErrorCode(s string) ErrorCode { + desc, ok := idToDescriptors[s] + + if !ok { + return ErrorCodeUnknown + } + + return desc.Code +} + +// Descriptor returns the descriptor for the error code. +func (ec ErrorCode) Descriptor() ErrorDescriptor { + d, ok := errorCodeToDescriptors[ec] + + if !ok { + return ErrorCodeUnknown.Descriptor() + } + + return d +} + +// String returns the canonical identifier for this error code. +func (ec ErrorCode) String() string { + return ec.Descriptor().Value +} + +// Message returned the human-readable error message for this error code. +func (ec ErrorCode) Message() string { + return ec.Descriptor().Message +} + +// MarshalText encodes the receiver into UTF-8-encoded text and returns the +// result. +func (ec ErrorCode) MarshalText() (text []byte, err error) { + return []byte(ec.String()), nil +} + +// UnmarshalText decodes the form generated by MarshalText. +func (ec *ErrorCode) UnmarshalText(text []byte) error { + desc, ok := idToDescriptors[string(text)] + + if !ok { + desc = ErrorCodeUnknown.Descriptor() + } + + *ec = desc.Code + + return nil +} + +// Error provides a wrapper around ErrorCode with extra Details provided. +type Error struct { + Code ErrorCode `json:"code"` + Message string `json:"message,omitempty"` + Detail interface{} `json:"detail,omitempty"` +} + +// Error returns a human readable representation of the error. +func (e Error) Error() string { + return fmt.Sprintf("%s: %s", + strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), + e.Message) +} + +// Errors provides the envelope for multiple errors and a few sugar methods +// for use within the application. +type Errors struct { + Errors []Error `json:"errors,omitempty"` +} + +// Push pushes an error on to the error stack, with the optional detail +// argument. It is a programming error (ie panic) to push more than one +// detail at a time. +func (errs *Errors) Push(code ErrorCode, details ...interface{}) { + if len(details) > 1 { + panic("please specify zero or one detail items for this error") + } + + var detail interface{} + if len(details) > 0 { + detail = details[0] + } + + if err, ok := detail.(error); ok { + detail = err.Error() + } + + errs.PushErr(Error{ + Code: code, + Message: code.Message(), + Detail: detail, + }) +} + +// PushErr pushes an error interface onto the error stack. +func (errs *Errors) PushErr(err error) { + switch err.(type) { + case Error: + errs.Errors = append(errs.Errors, err.(Error)) + default: + errs.Errors = append(errs.Errors, Error{Message: err.Error()}) + } +} + +func (errs *Errors) Error() string { + switch errs.Len() { + case 0: + return "" + case 1: + return errs.Errors[0].Error() + default: + msg := "errors:\n" + for _, err := range errs.Errors { + msg += err.Error() + "\n" + } + return msg + } +} + +// Clear clears the errors. +func (errs *Errors) Clear() { + errs.Errors = errs.Errors[:0] +} + +// Len returns the current number of errors. +func (errs *Errors) Len() int { + return len(errs.Errors) +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go new file mode 100644 index 0000000000000..9cc831c440189 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go @@ -0,0 +1,165 @@ +package v2 + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/docker/distribution/digest" +) + +// TestErrorCodes ensures that error code format, mappings and +// marshaling/unmarshaling. round trips are stable. +func TestErrorCodes(t *testing.T) { + for _, desc := range errorDescriptors { + if desc.Code.String() != desc.Value { + t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value) + } + + if desc.Code.Message() != desc.Message { + t.Fatalf("incorrect message for error code %v: %q != %q", desc.Code, desc.Code.Message(), desc.Message) + } + + // Serialize the error code using the json library to ensure that we + // get a string and it works round trip. + p, err := json.Marshal(desc.Code) + + if err != nil { + t.Fatalf("error marshaling error code %v: %v", desc.Code, err) + } + + if len(p) <= 0 { + t.Fatalf("expected content in marshaled before for error code %v", desc.Code) + } + + // First, unmarshal to interface and ensure we have a string. + var ecUnspecified interface{} + if err := json.Unmarshal(p, &ecUnspecified); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) + } + + if _, ok := ecUnspecified.(string); !ok { + t.Fatalf("expected a string for error code %v on unmarshal got a %T", desc.Code, ecUnspecified) + } + + // Now, unmarshal with the error code type and ensure they are equal + var ecUnmarshaled ErrorCode + if err := json.Unmarshal(p, &ecUnmarshaled); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) + } + + if ecUnmarshaled != desc.Code { + t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, desc.Code) + } + } +} + +// TestErrorsManagement does a quick check of the Errors type to ensure that +// members are properly pushed and marshaled. +func TestErrorsManagement(t *testing.T) { + var errs Errors + + errs.Push(ErrorCodeDigestInvalid) + errs.Push(ErrorCodeBlobUnknown, + map[string]digest.Digest{"digest": "sometestblobsumdoesntmatter"}) + + p, err := json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + expectedJSON := "{\"errors\":[{\"code\":\"DIGEST_INVALID\",\"message\":\"provided digest did not match uploaded content\"},{\"code\":\"BLOB_UNKNOWN\",\"message\":\"blob unknown to registry\",\"detail\":{\"digest\":\"sometestblobsumdoesntmatter\"}}]}" + + if string(p) != expectedJSON { + t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) + } + + errs.Clear() + errs.Push(ErrorCodeUnknown) + expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}" + p, err = json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + if string(p) != expectedJSON { + t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) + } +} + +// TestMarshalUnmarshal ensures that api errors can round trip through json +// without losing information. +func TestMarshalUnmarshal(t *testing.T) { + + var errors Errors + + for _, testcase := range []struct { + description string + err Error + }{ + { + description: "unknown error", + err: Error{ + + Code: ErrorCodeUnknown, + Message: ErrorCodeUnknown.Descriptor().Message, + }, + }, + { + description: "unknown manifest", + err: Error{ + Code: ErrorCodeManifestUnknown, + Message: ErrorCodeManifestUnknown.Descriptor().Message, + }, + }, + { + description: "unknown manifest", + err: Error{ + Code: ErrorCodeBlobUnknown, + Message: ErrorCodeBlobUnknown.Descriptor().Message, + Detail: map[string]interface{}{"digest": "asdfqwerqwerqwerqwer"}, + }, + }, + } { + fatalf := func(format string, args ...interface{}) { + t.Fatalf(testcase.description+": "+format, args...) + } + + unexpectedErr := func(err error) { + fatalf("unexpected error: %v", err) + } + + p, err := json.Marshal(testcase.err) + if err != nil { + unexpectedErr(err) + } + + var unmarshaled Error + if err := json.Unmarshal(p, &unmarshaled); err != nil { + unexpectedErr(err) + } + + if !reflect.DeepEqual(unmarshaled, testcase.err) { + fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, testcase.err) + } + + // Roll everything up into an error response envelope. + errors.PushErr(testcase.err) + } + + p, err := json.Marshal(errors) + if err != nil { + t.Fatalf("unexpected error marshaling error envelope: %v", err) + } + + var unmarshaled Errors + if err := json.Unmarshal(p, &unmarshaled); err != nil { + t.Fatalf("unexpected error unmarshaling error envelope: %v", err) + } + + if !reflect.DeepEqual(unmarshaled, errors) { + t.Fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, errors) + } +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/names.go b/vendor/src/github.com/docker/distribution/registry/api/v2/names.go new file mode 100644 index 0000000000000..e4a98861cbadd --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/names.go @@ -0,0 +1,100 @@ +package v2 + +import ( + "fmt" + "regexp" + "strings" +) + +// TODO(stevvooe): Move these definitions back to an exported package. While +// they are used with v2 definitions, their relevance expands beyond. +// "distribution/names" is a candidate package. + +const ( + // RepositoryNameComponentMinLength is the minimum number of characters in a + // single repository name slash-delimited component + RepositoryNameComponentMinLength = 2 + + // RepositoryNameMinComponents is the minimum number of slash-delimited + // components that a repository name must have + RepositoryNameMinComponents = 1 + + // RepositoryNameTotalLengthMax is the maximum total number of characters in + // a repository name + RepositoryNameTotalLengthMax = 255 +) + +// RepositoryNameComponentRegexp restricts registry path component names to +// start with at least one letter or number, with following parts able to +// be separated by one period, dash or underscore. +var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9]+)*`) + +// RepositoryNameComponentAnchoredRegexp is the version of +// RepositoryNameComponentRegexp which must completely match the content +var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`) + +// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow +// multiple path components, separated by a forward slash. +var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/)*` + RepositoryNameComponentRegexp.String()) + +// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go. +var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`) + +// TODO(stevvooe): Contribute these exports back to core, so they are shared. + +var ( + // ErrRepositoryNameComponentShort is returned when a repository name + // contains a component which is shorter than + // RepositoryNameComponentMinLength + ErrRepositoryNameComponentShort = fmt.Errorf("respository name component must be %v or more characters", RepositoryNameComponentMinLength) + + // ErrRepositoryNameMissingComponents is returned when a repository name + // contains fewer than RepositoryNameMinComponents components + ErrRepositoryNameMissingComponents = fmt.Errorf("repository name must have at least %v components", RepositoryNameMinComponents) + + // ErrRepositoryNameLong is returned when a repository name is longer than + // RepositoryNameTotalLengthMax + ErrRepositoryNameLong = fmt.Errorf("repository name must not be more than %v characters", RepositoryNameTotalLengthMax) + + // ErrRepositoryNameComponentInvalid is returned when a repository name does + // not match RepositoryNameComponentRegexp + ErrRepositoryNameComponentInvalid = fmt.Errorf("repository name component must match %q", RepositoryNameComponentRegexp.String()) +) + +// ValidateRespositoryName ensures the repository name is valid for use in the +// registry. This function accepts a superset of what might be accepted by +// docker core or docker hub. If the name does not pass validation, an error, +// describing the conditions, is returned. +// +// Effectively, the name should comply with the following grammar: +// +// alpha-numeric := /[a-z0-9]+/ +// separator := /[._-]/ +// component := alpha-numeric [separator alpha-numeric]* +// namespace := component ['/' component]* +// +// The result of the production, known as the "namespace", should be limited +// to 255 characters. +func ValidateRespositoryName(name string) error { + if len(name) > RepositoryNameTotalLengthMax { + return ErrRepositoryNameLong + } + + components := strings.Split(name, "/") + + if len(components) < RepositoryNameMinComponents { + return ErrRepositoryNameMissingComponents + } + + for _, component := range components { + if len(component) < RepositoryNameComponentMinLength { + return ErrRepositoryNameComponentShort + } + + if !RepositoryNameComponentAnchoredRegexp.MatchString(component) { + return ErrRepositoryNameComponentInvalid + } + } + + return nil +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go new file mode 100644 index 0000000000000..de6a168f0f650 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go @@ -0,0 +1,100 @@ +package v2 + +import ( + "strings" + "testing" +) + +func TestRepositoryNameRegexp(t *testing.T) { + for _, testcase := range []struct { + input string + err error + }{ + { + input: "short", + }, + { + input: "simple/name", + }, + { + input: "library/ubuntu", + }, + { + input: "docker/stevvooe/app", + }, + { + input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb", + }, + { + input: "aa/aa/bb/bb/bb", + }, + { + input: "a/a/a/b/b", + err: ErrRepositoryNameComponentShort, + }, + { + input: "a/a/a/a/", + err: ErrRepositoryNameComponentShort, + }, + { + input: "foo.com/bar/baz", + }, + { + input: "blog.foo.com/bar/baz", + }, + { + input: "asdf", + }, + { + input: "asdf$$^/aa", + err: ErrRepositoryNameComponentInvalid, + }, + { + input: "aa-a/aa", + }, + { + input: "aa/aa", + }, + { + input: "a-a/a-a", + }, + { + input: "a", + err: ErrRepositoryNameComponentShort, + }, + { + input: "a-/a/a/a", + err: ErrRepositoryNameComponentInvalid, + }, + { + input: strings.Repeat("a", 255), + }, + { + input: strings.Repeat("a", 256), + err: ErrRepositoryNameLong, + }, + } { + + failf := func(format string, v ...interface{}) { + t.Logf(testcase.input+": "+format, v...) + t.Fail() + } + + if err := ValidateRespositoryName(testcase.input); err != testcase.err { + if testcase.err != nil { + if err != nil { + failf("unexpected error for invalid repository: got %v, expected %v", err, testcase.err) + } else { + failf("expected invalid repository: %v", testcase.err) + } + } else { + if err != nil { + // Wrong error returned. + failf("unexpected error validating repository name: %v, expected %v", err, testcase.err) + } else { + failf("unexpected error validating repository name: %v", err) + } + } + } + } +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/routes.go b/vendor/src/github.com/docker/distribution/registry/api/v2/routes.go new file mode 100644 index 0000000000000..69f9d9012a436 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/routes.go @@ -0,0 +1,47 @@ +package v2 + +import "github.com/gorilla/mux" + +// The following are definitions of the name under which all V2 routes are +// registered. These symbols can be used to look up a route based on the name. +const ( + RouteNameBase = "base" + RouteNameManifest = "manifest" + RouteNameTags = "tags" + RouteNameBlob = "blob" + RouteNameBlobUpload = "blob-upload" + RouteNameBlobUploadChunk = "blob-upload-chunk" +) + +var allEndpoints = []string{ + RouteNameManifest, + RouteNameTags, + RouteNameBlob, + RouteNameBlobUpload, + RouteNameBlobUploadChunk, +} + +// Router builds a gorilla router with named routes for the various API +// methods. This can be used directly by both server implementations and +// clients. +func Router() *mux.Router { + return RouterWithPrefix("") +} + +// RouterWithPrefix builds a gorilla router with a configured prefix +// on all routes. +func RouterWithPrefix(prefix string) *mux.Router { + rootRouter := mux.NewRouter() + router := rootRouter + if prefix != "" { + router = router.PathPrefix(prefix).Subrouter() + } + + router.StrictSlash(true) + + for _, descriptor := range routeDescriptors { + router.Path(descriptor.Path).Name(descriptor.Name) + } + + return rootRouter +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go new file mode 100644 index 0000000000000..afab71fce0d7a --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go @@ -0,0 +1,315 @@ +package v2 + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "testing" + "time" + + "github.com/gorilla/mux" +) + +type routeTestCase struct { + RequestURI string + ExpectedURI string + Vars map[string]string + RouteName string + StatusCode int +} + +// TestRouter registers a test handler with all the routes and ensures that +// each route returns the expected path variables. Not method verification is +// present. This not meant to be exhaustive but as check to ensure that the +// expected variables are extracted. +// +// This may go away as the application structure comes together. +func TestRouter(t *testing.T) { + testCases := []routeTestCase{ + { + RouteName: RouteNameBase, + RequestURI: "/v2/", + Vars: map[string]string{}, + }, + { + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/manifests/bar", + Vars: map[string]string{ + "name": "foo", + "reference": "bar", + }, + }, + { + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/bar/manifests/tag", + Vars: map[string]string{ + "name": "foo/bar", + "reference": "tag", + }, + }, + { + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/bar/manifests/sha256:abcdef01234567890", + Vars: map[string]string{ + "name": "foo/bar", + "reference": "sha256:abcdef01234567890", + }, + }, + { + RouteName: RouteNameTags, + RequestURI: "/v2/foo/bar/tags/list", + Vars: map[string]string{ + "name": "foo/bar", + }, + }, + { + RouteName: RouteNameBlob, + RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234", + Vars: map[string]string{ + "name": "foo/bar", + "digest": "tarsum.dev+foo:abcdef0919234", + }, + }, + { + RouteName: RouteNameBlob, + RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234", + Vars: map[string]string{ + "name": "foo/bar", + "digest": "sha256:abcdef0919234", + }, + }, + { + RouteName: RouteNameBlobUpload, + RequestURI: "/v2/foo/bar/blobs/uploads/", + Vars: map[string]string{ + "name": "foo/bar", + }, + }, + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/bar/blobs/uploads/uuid", + Vars: map[string]string{ + "name": "foo/bar", + "uuid": "uuid", + }, + }, + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + Vars: map[string]string{ + "name": "foo/bar", + "uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + }, + }, + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", + Vars: map[string]string{ + "name": "foo/bar", + "uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", + }, + }, + { + // Check ambiguity: ensure we can distinguish between tags for + // "foo/bar/image/image" and image for "foo/bar/image" with tag + // "tags" + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/bar/manifests/manifests/tags", + Vars: map[string]string{ + "name": "foo/bar/manifests", + "reference": "tags", + }, + }, + { + // This case presents an ambiguity between foo/bar with tag="tags" + // and list tags for "foo/bar/manifest" + RouteName: RouteNameTags, + RequestURI: "/v2/foo/bar/manifests/tags/list", + Vars: map[string]string{ + "name": "foo/bar/manifests", + }, + }, + } + + checkTestRouter(t, testCases, "", true) + checkTestRouter(t, testCases, "/prefix/", true) +} + +func TestRouterWithPathTraversals(t *testing.T) { + testCases := []routeTestCase{ + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + ExpectedURI: "/blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + StatusCode: http.StatusNotFound, + }, + { + // Testing for path traversal attack handling + RouteName: RouteNameTags, + RequestURI: "/v2/foo/../bar/baz/tags/list", + ExpectedURI: "/v2/bar/baz/tags/list", + Vars: map[string]string{ + "name": "bar/baz", + }, + }, + } + checkTestRouter(t, testCases, "", false) +} + +func TestRouterWithBadCharacters(t *testing.T) { + if testing.Short() { + testCases := []routeTestCase{ + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/blob/uploads/不95306FA-FAD3-4E36-8D41-CF1C93EF8286", + StatusCode: http.StatusNotFound, + }, + { + // Testing for path traversal attack handling + RouteName: RouteNameTags, + RequestURI: "/v2/foo/不bar/tags/list", + StatusCode: http.StatusNotFound, + }, + } + checkTestRouter(t, testCases, "", true) + } else { + // in the long version we're going to fuzz the router + // with random UTF8 characters not in the 128 bit ASCII range. + // These are not valid characters for the router and we expect + // 404s on every test. + rand.Seed(time.Now().UTC().UnixNano()) + testCases := make([]routeTestCase, 1000) + for idx := range testCases { + testCases[idx] = routeTestCase{ + RouteName: RouteNameTags, + RequestURI: fmt.Sprintf("/v2/%v/%v/tags/list", randomString(10), randomString(10)), + StatusCode: http.StatusNotFound, + } + } + checkTestRouter(t, testCases, "", true) + } +} + +func checkTestRouter(t *testing.T, testCases []routeTestCase, prefix string, deeplyEqual bool) { + router := RouterWithPrefix(prefix) + + testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + testCase := routeTestCase{ + RequestURI: r.RequestURI, + Vars: mux.Vars(r), + RouteName: mux.CurrentRoute(r).GetName(), + } + + enc := json.NewEncoder(w) + + if err := enc.Encode(testCase); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + // Startup test server + server := httptest.NewServer(router) + + for _, testcase := range testCases { + testcase.RequestURI = strings.TrimSuffix(prefix, "/") + testcase.RequestURI + // Register the endpoint + route := router.GetRoute(testcase.RouteName) + if route == nil { + t.Fatalf("route for name %q not found", testcase.RouteName) + } + + route.Handler(testHandler) + + u := server.URL + testcase.RequestURI + + resp, err := http.Get(u) + + if err != nil { + t.Fatalf("error issuing get request: %v", err) + } + + if testcase.StatusCode == 0 { + // Override default, zero-value + testcase.StatusCode = http.StatusOK + } + if testcase.ExpectedURI == "" { + // Override default, zero-value + testcase.ExpectedURI = testcase.RequestURI + } + + if resp.StatusCode != testcase.StatusCode { + t.Fatalf("unexpected status for %s: %v %v", u, resp.Status, resp.StatusCode) + } + + if testcase.StatusCode != http.StatusOK { + // We don't care about json response. + continue + } + + dec := json.NewDecoder(resp.Body) + + var actualRouteInfo routeTestCase + if err := dec.Decode(&actualRouteInfo); err != nil { + t.Fatalf("error reading json response: %v", err) + } + // Needs to be set out of band + actualRouteInfo.StatusCode = resp.StatusCode + + if actualRouteInfo.RequestURI != testcase.ExpectedURI { + t.Fatalf("URI %v incorrectly parsed, expected %v", actualRouteInfo.RequestURI, testcase.ExpectedURI) + } + + if actualRouteInfo.RouteName != testcase.RouteName { + t.Fatalf("incorrect route %q matched, expected %q", actualRouteInfo.RouteName, testcase.RouteName) + } + + // when testing deep equality, the actualRouteInfo has an empty ExpectedURI, we don't want + // that to make the comparison fail. We're otherwise done with the testcase so empty the + // testcase.ExpectedURI + testcase.ExpectedURI = "" + if deeplyEqual && !reflect.DeepEqual(actualRouteInfo, testcase) { + t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase) + } + } + +} + +// -------------- START LICENSED CODE -------------- +// The following code is derivative of https://github.com/google/gofuzz +// gofuzz is licensed under the Apache License, Version 2.0, January 2004, +// a copy of which can be found in the LICENSE file at the root of this +// repository. + +// These functions allow us to generate strings containing only multibyte +// characters that are invalid in our URLs. They are used above for fuzzing +// to ensure we always get 404s on these invalid strings +type charRange struct { + first, last rune +} + +// choose returns a random unicode character from the given range, using the +// given randomness source. +func (r *charRange) choose() rune { + count := int64(r.last - r.first) + return r.first + rune(rand.Int63n(count)) +} + +var unicodeRanges = []charRange{ + {'\u00a0', '\u02af'}, // Multi-byte encoded characters + {'\u4e00', '\u9fff'}, // Common CJK (even longer encodings) +} + +func randomString(length int) string { + runes := make([]rune, length) + for i := range runes { + runes[i] = unicodeRanges[rand.Intn(len(unicodeRanges))].choose() + } + return string(runes) +} + +// -------------- END LICENSED CODE -------------- diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/urls.go b/vendor/src/github.com/docker/distribution/registry/api/v2/urls.go new file mode 100644 index 0000000000000..4b42dd1624c83 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/urls.go @@ -0,0 +1,217 @@ +package v2 + +import ( + "net/http" + "net/url" + "strings" + + "github.com/docker/distribution/digest" + "github.com/gorilla/mux" +) + +// URLBuilder creates registry API urls from a single base endpoint. It can be +// used to create urls for use in a registry client or server. +// +// All urls will be created from the given base, including the api version. +// For example, if a root of "/foo/" is provided, urls generated will be fall +// under "/foo/v2/...". Most application will only provide a schema, host and +// port, such as "https://localhost:5000/". +type URLBuilder struct { + root *url.URL // url root (ie http://localhost/) + router *mux.Router +} + +// NewURLBuilder creates a URLBuilder with provided root url object. +func NewURLBuilder(root *url.URL) *URLBuilder { + return &URLBuilder{ + root: root, + router: Router(), + } +} + +// NewURLBuilderFromString workes identically to NewURLBuilder except it takes +// a string argument for the root, returning an error if it is not a valid +// url. +func NewURLBuilderFromString(root string) (*URLBuilder, error) { + u, err := url.Parse(root) + if err != nil { + return nil, err + } + + return NewURLBuilder(u), nil +} + +// NewURLBuilderFromRequest uses information from an *http.Request to +// construct the root url. +func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { + var scheme string + + forwardedProto := r.Header.Get("X-Forwarded-Proto") + + switch { + case len(forwardedProto) > 0: + scheme = forwardedProto + case r.TLS != nil: + scheme = "https" + case len(r.URL.Scheme) > 0: + scheme = r.URL.Scheme + default: + scheme = "http" + } + + host := r.Host + forwardedHost := r.Header.Get("X-Forwarded-Host") + if len(forwardedHost) > 0 { + host = forwardedHost + } + + basePath := routeDescriptorsMap[RouteNameBase].Path + + requestPath := r.URL.Path + index := strings.Index(requestPath, basePath) + + u := &url.URL{ + Scheme: scheme, + Host: host, + } + + if index > 0 { + // N.B. index+1 is important because we want to include the trailing / + u.Path = requestPath[0 : index+1] + } + + return NewURLBuilder(u) +} + +// BuildBaseURL constructs a base url for the API, typically just "/v2/". +func (ub *URLBuilder) BuildBaseURL() (string, error) { + route := ub.cloneRoute(RouteNameBase) + + baseURL, err := route.URL() + if err != nil { + return "", err + } + + return baseURL.String(), nil +} + +// BuildTagsURL constructs a url to list the tags in the named repository. +func (ub *URLBuilder) BuildTagsURL(name string) (string, error) { + route := ub.cloneRoute(RouteNameTags) + + tagsURL, err := route.URL("name", name) + if err != nil { + return "", err + } + + return tagsURL.String(), nil +} + +// BuildManifestURL constructs a url for the manifest identified by name and +// reference. The argument reference may be either a tag or digest. +func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) { + route := ub.cloneRoute(RouteNameManifest) + + manifestURL, err := route.URL("name", name, "reference", reference) + if err != nil { + return "", err + } + + return manifestURL.String(), nil +} + +// BuildBlobURL constructs the url for the blob identified by name and dgst. +func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, error) { + route := ub.cloneRoute(RouteNameBlob) + + layerURL, err := route.URL("name", name, "digest", dgst.String()) + if err != nil { + return "", err + } + + return layerURL.String(), nil +} + +// BuildBlobUploadURL constructs a url to begin a blob upload in the +// repository identified by name. +func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameBlobUpload) + + uploadURL, err := route.URL("name", name) + if err != nil { + return "", err + } + + return appendValuesURL(uploadURL, values...).String(), nil +} + +// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid, +// including any url values. This should generally not be used by clients, as +// this url is provided by server implementations during the blob upload +// process. +func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameBlobUploadChunk) + + uploadURL, err := route.URL("name", name, "uuid", uuid) + if err != nil { + return "", err + } + + return appendValuesURL(uploadURL, values...).String(), nil +} + +// clondedRoute returns a clone of the named route from the router. Routes +// must be cloned to avoid modifying them during url generation. +func (ub *URLBuilder) cloneRoute(name string) clonedRoute { + route := new(mux.Route) + root := new(url.URL) + + *route = *ub.router.GetRoute(name) // clone the route + *root = *ub.root + + return clonedRoute{Route: route, root: root} +} + +type clonedRoute struct { + *mux.Route + root *url.URL +} + +func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { + routeURL, err := cr.Route.URL(pairs...) + if err != nil { + return nil, err + } + + if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" { + routeURL.Path = routeURL.Path[1:] + } + + return cr.root.ResolveReference(routeURL), nil +} + +// appendValuesURL appends the parameters to the url. +func appendValuesURL(u *url.URL, values ...url.Values) *url.URL { + merged := u.Query() + + for _, v := range values { + for k, vv := range v { + merged[k] = append(merged[k], vv...) + } + } + + u.RawQuery = merged.Encode() + return u +} + +// appendValues appends the parameters to the url. Panics if the string is not +// a url. +func appendValues(u string, values ...url.Values) string { + up, err := url.Parse(u) + + if err != nil { + panic(err) // should never happen + } + + return appendValuesURL(up, values...).String() +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go new file mode 100644 index 0000000000000..237d0f6159d3a --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go @@ -0,0 +1,225 @@ +package v2 + +import ( + "net/http" + "net/url" + "testing" +) + +type urlBuilderTestCase struct { + description string + expectedPath string + build func() (string, error) +} + +func makeURLBuilderTestCases(urlBuilder *URLBuilder) []urlBuilderTestCase { + return []urlBuilderTestCase{ + { + description: "test base url", + expectedPath: "/v2/", + build: urlBuilder.BuildBaseURL, + }, + { + description: "test tags url", + expectedPath: "/v2/foo/bar/tags/list", + build: func() (string, error) { + return urlBuilder.BuildTagsURL("foo/bar") + }, + }, + { + description: "test manifest url", + expectedPath: "/v2/foo/bar/manifests/tag", + build: func() (string, error) { + return urlBuilder.BuildManifestURL("foo/bar", "tag") + }, + }, + { + description: "build blob url", + expectedPath: "/v2/foo/bar/blobs/tarsum.v1+sha256:abcdef0123456789", + build: func() (string, error) { + return urlBuilder.BuildBlobURL("foo/bar", "tarsum.v1+sha256:abcdef0123456789") + }, + }, + { + description: "build blob upload url", + expectedPath: "/v2/foo/bar/blobs/uploads/", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadURL("foo/bar") + }, + }, + { + description: "build blob upload url with digest and size", + expectedPath: "/v2/foo/bar/blobs/uploads/?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{ + "size": []string{"10000"}, + "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, + }) + }, + }, + { + description: "build blob upload chunk url", + expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part") + }, + }, + { + description: "build blob upload chunk url with digest and size", + expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{ + "size": []string{"10000"}, + "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, + }) + }, + }, + } +} + +// TestURLBuilder tests the various url building functions, ensuring they are +// returning the expected values. +func TestURLBuilder(t *testing.T) { + roots := []string{ + "http://example.com", + "https://example.com", + "http://localhost:5000", + "https://localhost:5443", + } + + for _, root := range roots { + urlBuilder, err := NewURLBuilderFromString(root) + if err != nil { + t.Fatalf("unexpected error creating urlbuilder: %v", err) + } + + for _, testCase := range makeURLBuilderTestCases(urlBuilder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := root + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} + +func TestURLBuilderWithPrefix(t *testing.T) { + roots := []string{ + "http://example.com/prefix/", + "https://example.com/prefix/", + "http://localhost:5000/prefix/", + "https://localhost:5443/prefix/", + } + + for _, root := range roots { + urlBuilder, err := NewURLBuilderFromString(root) + if err != nil { + t.Fatalf("unexpected error creating urlbuilder: %v", err) + } + + for _, testCase := range makeURLBuilderTestCases(urlBuilder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := root[0:len(root)-1] + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} + +type builderFromRequestTestCase struct { + request *http.Request + base string +} + +func TestBuilderFromRequest(t *testing.T) { + u, err := url.Parse("http://example.com") + if err != nil { + t.Fatal(err) + } + + forwardedProtoHeader := make(http.Header, 1) + forwardedProtoHeader.Set("X-Forwarded-Proto", "https") + + testRequests := []struct { + request *http.Request + base string + }{ + { + request: &http.Request{URL: u, Host: u.Host}, + base: "http://example.com", + }, + { + request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader}, + base: "https://example.com", + }, + } + + for _, tr := range testRequests { + builder := NewURLBuilderFromRequest(tr.request) + + for _, testCase := range makeURLBuilderTestCases(builder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := tr.base + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} + +func TestBuilderFromRequestWithPrefix(t *testing.T) { + u, err := url.Parse("http://example.com/prefix/v2/") + if err != nil { + t.Fatal(err) + } + + forwardedProtoHeader := make(http.Header, 1) + forwardedProtoHeader.Set("X-Forwarded-Proto", "https") + + testRequests := []struct { + request *http.Request + base string + }{ + { + request: &http.Request{URL: u, Host: u.Host}, + base: "http://example.com/prefix/", + }, + { + request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader}, + base: "https://example.com/prefix/", + }, + } + + for _, tr := range testRequests { + builder := NewURLBuilderFromRequest(tr.request) + + for _, testCase := range makeURLBuilderTestCases(builder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := tr.base[0:len(tr.base)-1] + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} diff --git a/vendor/src/github.com/gorilla/mux/mux.go b/vendor/src/github.com/gorilla/mux/mux.go index 8b23c39d397d0..5b5f8e7db5dc1 100644 --- a/vendor/src/github.com/gorilla/mux/mux.go +++ b/vendor/src/github.com/gorilla/mux/mux.go @@ -87,10 +87,10 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { setCurrentRoute(req, match.Route) } if handler == nil { - if r.NotFoundHandler == nil { - r.NotFoundHandler = http.NotFoundHandler() - } handler = r.NotFoundHandler + if handler == nil { + handler = http.NotFoundHandler() + } } if !r.KeepContext { defer context.Clear(req) diff --git a/vendor/src/github.com/gorilla/mux/mux_test.go b/vendor/src/github.com/gorilla/mux/mux_test.go index 0e2e48067ae30..e455bce8fdfee 100644 --- a/vendor/src/github.com/gorilla/mux/mux_test.go +++ b/vendor/src/github.com/gorilla/mux/mux_test.go @@ -462,6 +462,15 @@ func TestQueries(t *testing.T) { path: "", shouldMatch: true, }, + { + title: "Queries route, match with a query string out of order", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, { title: "Queries route, bad query", route: new(Route).Queries("foo", "bar", "baz", "ding"), @@ -471,6 +480,42 @@ func TestQueries(t *testing.T) { path: "", shouldMatch: false, }, + { + title: "Queries route with pattern, match", + route: new(Route).Queries("foo", "{v1}"), + request: newRequest("GET", "http://localhost?foo=bar"), + vars: map[string]string{"v1": "bar"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with multiple patterns, match", + route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=10"), + vars: map[string]string{"v1": "10"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, regexp does not match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=a"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, } for _, test := range tests { diff --git a/vendor/src/github.com/gorilla/mux/old_test.go b/vendor/src/github.com/gorilla/mux/old_test.go index 42530590e7788..1f7c190c0f972 100644 --- a/vendor/src/github.com/gorilla/mux/old_test.go +++ b/vendor/src/github.com/gorilla/mux/old_test.go @@ -329,35 +329,6 @@ var pathMatcherTests = []pathMatcherTest{ }, } -type queryMatcherTest struct { - matcher queryMatcher - url string - result bool -} - -var queryMatcherTests = []queryMatcherTest{ - { - matcher: queryMatcher(map[string]string{"foo": "bar", "baz": "ding"}), - url: "http://localhost:8080/?foo=bar&baz=ding", - result: true, - }, - { - matcher: queryMatcher(map[string]string{"foo": "", "baz": ""}), - url: "http://localhost:8080/?foo=anything&baz=anything", - result: true, - }, - { - matcher: queryMatcher(map[string]string{"foo": "ding", "baz": "bar"}), - url: "http://localhost:8080/?foo=bar&baz=ding", - result: false, - }, - { - matcher: queryMatcher(map[string]string{"bar": "foo", "ding": "baz"}), - url: "http://localhost:8080/?foo=bar&baz=ding", - result: false, - }, -} - type schemeMatcherTest struct { matcher schemeMatcher url string @@ -519,23 +490,8 @@ func TestPathMatcher(t *testing.T) { } } -func TestQueryMatcher(t *testing.T) { - for _, v := range queryMatcherTests { - request, _ := http.NewRequest("GET", v.url, nil) - var routeMatch RouteMatch - result := v.matcher.Match(request, &routeMatch) - if result != v.result { - if v.result { - t.Errorf("%#v: should match %v.", v.matcher, v.url) - } else { - t.Errorf("%#v: should not match %v.", v.matcher, v.url) - } - } - } -} - func TestSchemeMatcher(t *testing.T) { - for _, v := range queryMatcherTests { + for _, v := range schemeMatcherTests { request, _ := http.NewRequest("GET", v.url, nil) var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) @@ -735,7 +691,7 @@ func TestNewRegexp(t *testing.T) { } for pattern, paths := range tests { - p, _ = newRouteRegexp(pattern, false, false, false) + p, _ = newRouteRegexp(pattern, false, false, false, false) for path, result := range paths { matches = p.regexp.FindStringSubmatch(path) if result == nil { diff --git a/vendor/src/github.com/gorilla/mux/regexp.go b/vendor/src/github.com/gorilla/mux/regexp.go index 925f268abefa4..a6305483d5ae5 100644 --- a/vendor/src/github.com/gorilla/mux/regexp.go +++ b/vendor/src/github.com/gorilla/mux/regexp.go @@ -14,7 +14,7 @@ import ( ) // newRouteRegexp parses a route template and returns a routeRegexp, -// used to match a host or path. +// used to match a host, a path or a query string. // // It will extract named variables, assemble a regexp to be matched, create // a "reverse" template to build URLs and compile regexps to validate variable @@ -23,7 +23,7 @@ import ( // Previously we accepted only Python-like identifiers for variable // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that // name and pattern can't be empty, and names can't contain a colon. -func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*routeRegexp, error) { +func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { // Check if it is well-formed. idxs, errBraces := braceIndices(tpl) if errBraces != nil { @@ -33,11 +33,15 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout template := tpl // Now let's parse it. defaultPattern := "[^/]+" - if matchHost { + if matchQuery { + defaultPattern = "[^?&]+" + matchPrefix = true + } else if matchHost { defaultPattern = "[^.]+" - matchPrefix, strictSlash = false, false + matchPrefix = false } - if matchPrefix { + // Only match strict slash if not matching + if matchPrefix || matchHost || matchQuery { strictSlash = false } // Set a flag for strictSlash. @@ -48,7 +52,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout } varsN := make([]string, len(idxs)/2) varsR := make([]*regexp.Regexp, len(idxs)/2) - pattern := bytes.NewBufferString("^") + pattern := bytes.NewBufferString("") + if !matchQuery { + pattern.WriteByte('^') + } reverse := bytes.NewBufferString("") var end int var err error @@ -100,6 +107,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout return &routeRegexp{ template: template, matchHost: matchHost, + matchQuery: matchQuery, strictSlash: strictSlash, regexp: reg, reverse: reverse.String(), @@ -113,8 +121,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout type routeRegexp struct { // The unmodified template. template string - // True for host match, false for path match. + // True for host match, false for path or query string match. matchHost bool + // True for query string match, false for path and host match. + matchQuery bool // The strictSlash value defined on the route, but disabled if PathPrefix was used. strictSlash bool // Expanded regexp. @@ -130,7 +140,11 @@ type routeRegexp struct { // Match matches the regexp against the URL host or path. func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { if !r.matchHost { - return r.regexp.MatchString(req.URL.Path) + if r.matchQuery { + return r.regexp.MatchString(req.URL.RawQuery) + } else { + return r.regexp.MatchString(req.URL.Path) + } } return r.regexp.MatchString(getHost(req)) } @@ -196,8 +210,9 @@ func braceIndices(s string) ([]int, error) { // routeRegexpGroup groups the route matchers that carry variables. type routeRegexpGroup struct { - host *routeRegexp - path *routeRegexp + host *routeRegexp + path *routeRegexp + queries []*routeRegexp } // setMatch extracts the variables from the URL once a route matches. @@ -234,17 +249,28 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) } } } + // Store query string variables. + rawQuery := req.URL.RawQuery + for _, q := range v.queries { + queryVars := q.regexp.FindStringSubmatch(rawQuery) + if queryVars != nil { + for k, v := range q.varsN { + m.Vars[v] = queryVars[k+1] + } + } + } } // getHost tries its best to return the request host. func getHost(r *http.Request) string { - if !r.URL.IsAbs() { - host := r.Host - // Slice off any port information. - if i := strings.Index(host, ":"); i != -1 { - host = host[:i] - } - return host + if r.URL.IsAbs() { + return r.URL.Host } - return r.URL.Host + host := r.Host + // Slice off any port information. + if i := strings.Index(host, ":"); i != -1 { + host = host[:i] + } + return host + } diff --git a/vendor/src/github.com/gorilla/mux/route.go b/vendor/src/github.com/gorilla/mux/route.go index 5cb2526d61390..c310e66bc7c3f 100644 --- a/vendor/src/github.com/gorilla/mux/route.go +++ b/vendor/src/github.com/gorilla/mux/route.go @@ -135,12 +135,12 @@ func (r *Route) addMatcher(m matcher) *Route { } // addRegexpMatcher adds a host or path matcher and builder to a route. -func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error { +func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { if r.err != nil { return r.err } r.regexp = r.getRegexpGroup() - if !matchHost { + if !matchHost && !matchQuery { if len(tpl) == 0 || tpl[0] != '/' { return fmt.Errorf("mux: path must start with a slash, got %q", tpl) } @@ -148,10 +148,15 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl } } - rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, r.strictSlash) + rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) if err != nil { return err } + for _, q := range r.regexp.queries { + if err = uniqueVars(rr.varsN, q.varsN); err != nil { + return err + } + } if matchHost { if r.regexp.path != nil { if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { @@ -165,7 +170,11 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error return err } } - r.regexp.path = rr + if matchQuery { + r.regexp.queries = append(r.regexp.queries, rr) + } else { + r.regexp.path = rr + } } r.addMatcher(rr) return nil @@ -219,7 +228,7 @@ func (r *Route) Headers(pairs ...string) *Route { // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Host(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, true, false) + r.err = r.addRegexpMatcher(tpl, true, false, false) return r } @@ -278,7 +287,7 @@ func (r *Route) Methods(methods ...string) *Route { // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Path(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, false, false) + r.err = r.addRegexpMatcher(tpl, false, false, false) return r } @@ -294,35 +303,42 @@ func (r *Route) Path(tpl string) *Route { // 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.err = r.addRegexpMatcher(tpl, false, true) + r.err = r.addRegexpMatcher(tpl, false, true, false) return r } // Query ---------------------------------------------------------------------- -// queryMatcher matches the request against URL queries. -type queryMatcher map[string]string - -func (m queryMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchMap(m, r.URL.Query(), false) -} - // Queries adds a matcher for URL query values. -// It accepts a sequence of key/value pairs. For example: +// It accepts a sequence of key/value pairs. Values may define variables. +// For example: // // r := mux.NewRouter() -// r.Queries("foo", "bar", "baz", "ding") +// r.Queries("foo", "bar", "id", "{id:[0-9]+}") // // The above route will only match if the URL contains the defined queries -// values, e.g.: ?foo=bar&baz=ding. +// values, e.g.: ?foo=bar&id=42. // // It the value is an empty string, it will match any value if the key is set. +// +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. func (r *Route) Queries(pairs ...string) *Route { - if r.err == nil { - var queries map[string]string - queries, r.err = mapFromPairs(pairs...) - return r.addMatcher(queryMatcher(queries)) + length := len(pairs) + if length%2 != 0 { + r.err = fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + return nil + } + for i := 0; i < length; i += 2 { + if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil { + return r + } } + return r } @@ -498,8 +514,9 @@ func (r *Route) getRegexpGroup() *routeRegexpGroup { } else { // Copy. r.regexp = &routeRegexpGroup{ - host: regexp.host, - path: regexp.path, + host: regexp.host, + path: regexp.path, + queries: regexp.queries, } } } From 62009ef77efcbe30afea0cd124f3fbff0d5030cd Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 31 Mar 2015 15:02:27 -0700 Subject: [PATCH 003/332] Use vendored v2 registry api Update registry package to use the v2 registry api from distribution. Update interfaces to directly take in digests. Signed-off-by: Derek McGowan (github: dmcgowan) --- graph/pull.go | 2 +- graph/push.go | 12 +-- registry/endpoint.go | 2 +- registry/session_v2.go | 24 ++--- registry/v2/descriptors.go | 144 ---------------------------- registry/v2/doc.go | 13 --- registry/v2/errors.go | 185 ----------------------------------- registry/v2/errors_test.go | 163 ------------------------------- registry/v2/regexp.go | 22 ----- registry/v2/routes.go | 66 ------------- registry/v2/routes_test.go | 192 ------------------------------------- registry/v2/urls.go | 179 ---------------------------------- registry/v2/urls_test.go | 113 ---------------------- 13 files changed, 20 insertions(+), 1097 deletions(-) delete mode 100644 registry/v2/descriptors.go delete mode 100644 registry/v2/doc.go delete mode 100644 registry/v2/errors.go delete mode 100644 registry/v2/errors_test.go delete mode 100644 registry/v2/regexp.go delete mode 100644 registry/v2/routes.go delete mode 100644 registry/v2/routes_test.go delete mode 100644 registry/v2/urls.go delete mode 100644 registry/v2/urls_test.go diff --git a/graph/pull.go b/graph/pull.go index 023b7cbc00aca..1524860cf2759 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -499,7 +499,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri return err } - r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest.Algorithm(), di.digest.Hex(), auth) + r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest, auth) if err != nil { return err } diff --git a/graph/push.go b/graph/push.go index 767f118c502d0..1f747c55b31a9 100644 --- a/graph/push.go +++ b/graph/push.go @@ -8,10 +8,10 @@ import ( "io/ioutil" "os" "path" - "strings" "sync" "github.com/Sirupsen/logrus" + "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" @@ -376,13 +376,13 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o var exists bool if len(checksum) > 0 { - sumParts := strings.SplitN(checksum, ":", 2) - if len(sumParts) < 2 { - return fmt.Errorf("Invalid checksum: %s", checksum) + dgst, err := digest.ParseDigest(checksum) + if err != nil { + return fmt.Errorf("Invalid checksum %s: %s", checksum, err) } // Call mount blob - exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], sumParts[1], auth) + exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, dgst, auth) if err != nil { out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil)) return err @@ -468,7 +468,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * // Send the layer logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) - if err := r.PutV2ImageBlob(endpoint, imageName, dgst.Algorithm(), dgst.Hex(), + if err := r.PutV2ImageBlob(endpoint, imageName, dgst, progressreader.New(progressreader.Config{ In: tf, Out: out, diff --git a/registry/endpoint.go b/registry/endpoint.go index 69a718e12f5c9..84b11a987bda8 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -11,8 +11,8 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/distribution/registry/api/v2" "github.com/docker/docker/pkg/requestdecorator" - "github.com/docker/docker/registry/v2" ) // for mocking in unit tests diff --git a/registry/session_v2.go b/registry/session_v2.go index a01c8b9ab2441..fb1d18e8e7d12 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -11,7 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/registry/v2" + "github.com/docker/distribution/registry/api/v2" "github.com/docker/docker/utils" ) @@ -109,8 +109,8 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au // - Succeeded to head image blob (already exists) // - Failed with no error (continue to Push the Blob) // - Failed with error -func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (bool, error) { - routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) +func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (bool, error) { + routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) if err != nil { return false, err } @@ -141,11 +141,11 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, return false, nil } - return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s:%s", res.StatusCode, imageName, sumType, sum), res) + return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res) } -func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, blobWrtr io.Writer, auth *RequestAuthorization) error { - routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) +func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error { + routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) if err != nil { return err } @@ -175,8 +175,8 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, b return err } -func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (io.ReadCloser, int64, error) { - routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) +func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (io.ReadCloser, int64, error) { + routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) if err != nil { return nil, 0, err } @@ -198,7 +198,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str if res.StatusCode == 401 { return nil, 0, errLoginRequired } - return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s:%s", res.StatusCode, imageName, sumType, sum), res) + return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res) } lenStr := res.Header.Get("Content-Length") l, err := strconv.ParseInt(lenStr, 10, 64) @@ -212,7 +212,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str // Push the image to the server for storage. // 'layer' is an uncompressed reader of the blob to be pushed. // The server will generate it's own checksum calculation. -func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string, blobRdr io.Reader, auth *RequestAuthorization) error { +func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobRdr io.Reader, auth *RequestAuthorization) error { location, err := r.initiateBlobUpload(ep, imageName, auth) if err != nil { return err @@ -225,7 +225,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string return err } queryParams := req.URL.Query() - queryParams.Add("digest", sumType+":"+sumStr) + queryParams.Add("digest", dgst.String()) req.URL.RawQuery = queryParams.Encode() if err := auth.Authorize(req); err != nil { return err @@ -245,7 +245,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string return err } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s:%s", res.StatusCode, imageName, sumType, sumStr), res) + return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res) } return nil diff --git a/registry/v2/descriptors.go b/registry/v2/descriptors.go deleted file mode 100644 index 68d182411d9c1..0000000000000 --- a/registry/v2/descriptors.go +++ /dev/null @@ -1,144 +0,0 @@ -package v2 - -import "net/http" - -// TODO(stevvooe): Add route descriptors for each named route, along with -// accepted methods, parameters, returned status codes and error codes. - -// ErrorDescriptor provides relevant information about a given error code. -type ErrorDescriptor struct { - // Code is the error code that this descriptor describes. - Code ErrorCode - - // Value provides a unique, string key, often captilized with - // underscores, to identify the error code. This value is used as the - // keyed value when serializing api errors. - Value string - - // Message is a short, human readable decription of the error condition - // included in API responses. - Message string - - // Description provides a complete account of the errors purpose, suitable - // for use in documentation. - Description string - - // HTTPStatusCodes provides a list of status under which this error - // condition may arise. If it is empty, the error condition may be seen - // for any status code. - HTTPStatusCodes []int -} - -// ErrorDescriptors provides a list of HTTP API Error codes that may be -// encountered when interacting with the registry API. -var ErrorDescriptors = []ErrorDescriptor{ - { - Code: ErrorCodeUnknown, - Value: "UNKNOWN", - Message: "unknown error", - Description: `Generic error returned when the error does not have an - API classification.`, - }, - { - Code: ErrorCodeDigestInvalid, - Value: "DIGEST_INVALID", - Message: "provided digest did not match uploaded content", - Description: `When a blob is uploaded, the registry will check that - the content matches the digest provided by the client. The error may - include a detail structure with the key "digest", including the - invalid digest string. This error may also be returned when a manifest - includes an invalid layer digest.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - { - Code: ErrorCodeSizeInvalid, - Value: "SIZE_INVALID", - Message: "provided length did not match content length", - Description: `When a layer is uploaded, the provided size will be - checked against the uploaded content. If they do not match, this error - will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest}, - }, - { - Code: ErrorCodeNameInvalid, - Value: "NAME_INVALID", - Message: "manifest name did not match URI", - Description: `During a manifest upload, if the name in the manifest - does not match the uri name, this error will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - { - Code: ErrorCodeTagInvalid, - Value: "TAG_INVALID", - Message: "manifest tag did not match URI", - Description: `During a manifest upload, if the tag in the manifest - does not match the uri tag, this error will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - { - Code: ErrorCodeNameUnknown, - Value: "NAME_UNKNOWN", - Message: "repository name not known to registry", - Description: `This is returned if the name used during an operation is - unknown to the registry.`, - HTTPStatusCodes: []int{http.StatusNotFound}, - }, - { - Code: ErrorCodeManifestUnknown, - Value: "MANIFEST_UNKNOWN", - Message: "manifest unknown", - Description: `This error is returned when the manifest, identified by - name and tag is unknown to the repository.`, - HTTPStatusCodes: []int{http.StatusNotFound}, - }, - { - Code: ErrorCodeManifestInvalid, - Value: "MANIFEST_INVALID", - Message: "manifest invalid", - Description: `During upload, manifests undergo several checks ensuring - validity. If those checks fail, this error may be returned, unless a - more specific error is included. The detail will contain information - the failed validation.`, - HTTPStatusCodes: []int{http.StatusBadRequest}, - }, - { - Code: ErrorCodeManifestUnverified, - Value: "MANIFEST_UNVERIFIED", - Message: "manifest failed signature verification", - Description: `During manifest upload, if the manifest fails signature - verification, this error will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest}, - }, - { - Code: ErrorCodeBlobUnknown, - Value: "BLOB_UNKNOWN", - Message: "blob unknown to registry", - Description: `This error may be returned when a blob is unknown to the - registry in a specified repository. This can be returned with a - standard get or if a manifest references an unknown layer during - upload.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - - { - Code: ErrorCodeBlobUploadUnknown, - Value: "BLOB_UPLOAD_UNKNOWN", - Message: "blob upload unknown to registry", - Description: `If a blob upload has been cancelled or was never - started, this error code may be returned.`, - HTTPStatusCodes: []int{http.StatusNotFound}, - }, -} - -var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor -var idToDescriptors map[string]ErrorDescriptor - -func init() { - errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(ErrorDescriptors)) - idToDescriptors = make(map[string]ErrorDescriptor, len(ErrorDescriptors)) - - for _, descriptor := range ErrorDescriptors { - errorCodeToDescriptors[descriptor.Code] = descriptor - idToDescriptors[descriptor.Value] = descriptor - } -} diff --git a/registry/v2/doc.go b/registry/v2/doc.go deleted file mode 100644 index 30fe2271a19b5..0000000000000 --- a/registry/v2/doc.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package v2 describes routes, urls and the error codes used in the Docker -// Registry JSON HTTP API V2. In addition to declarations, descriptors are -// provided for routes and error codes that can be used for implementation and -// automatically generating documentation. -// -// Definitions here are considered to be locked down for the V2 registry api. -// Any changes must be considered carefully and should not proceed without a -// change proposal. -// -// Currently, while the HTTP API definitions are considered stable, the Go API -// exports are considered unstable. Go API consumers should take care when -// relying on these definitions until this message is deleted. -package v2 diff --git a/registry/v2/errors.go b/registry/v2/errors.go deleted file mode 100644 index 8c85d3a97f164..0000000000000 --- a/registry/v2/errors.go +++ /dev/null @@ -1,185 +0,0 @@ -package v2 - -import ( - "fmt" - "strings" -) - -// ErrorCode represents the error type. The errors are serialized via strings -// and the integer format may change and should *never* be exported. -type ErrorCode int - -const ( - // ErrorCodeUnknown is a catch-all for errors not defined below. - ErrorCodeUnknown ErrorCode = iota - - // ErrorCodeDigestInvalid is returned when uploading a blob if the - // provided digest does not match the blob contents. - ErrorCodeDigestInvalid - - // ErrorCodeSizeInvalid is returned when uploading a blob if the provided - // size does not match the content length. - ErrorCodeSizeInvalid - - // ErrorCodeNameInvalid is returned when the name in the manifest does not - // match the provided name. - ErrorCodeNameInvalid - - // ErrorCodeTagInvalid is returned when the tag in the manifest does not - // match the provided tag. - ErrorCodeTagInvalid - - // ErrorCodeNameUnknown when the repository name is not known. - ErrorCodeNameUnknown - - // ErrorCodeManifestUnknown returned when image manifest is unknown. - ErrorCodeManifestUnknown - - // ErrorCodeManifestInvalid returned when an image manifest is invalid, - // typically during a PUT operation. This error encompasses all errors - // encountered during manifest validation that aren't signature errors. - ErrorCodeManifestInvalid - - // ErrorCodeManifestUnverified is returned when the manifest fails - // signature verfication. - ErrorCodeManifestUnverified - - // ErrorCodeBlobUnknown is returned when a blob is unknown to the - // registry. This can happen when the manifest references a nonexistent - // layer or the result is not found by a blob fetch. - ErrorCodeBlobUnknown - - // ErrorCodeBlobUploadUnknown is returned when an upload is unknown. - ErrorCodeBlobUploadUnknown -) - -// ParseErrorCode attempts to parse the error code string, returning -// ErrorCodeUnknown if the error is not known. -func ParseErrorCode(s string) ErrorCode { - desc, ok := idToDescriptors[s] - - if !ok { - return ErrorCodeUnknown - } - - return desc.Code -} - -// Descriptor returns the descriptor for the error code. -func (ec ErrorCode) Descriptor() ErrorDescriptor { - d, ok := errorCodeToDescriptors[ec] - - if !ok { - return ErrorCodeUnknown.Descriptor() - } - - return d -} - -// String returns the canonical identifier for this error code. -func (ec ErrorCode) String() string { - return ec.Descriptor().Value -} - -// Message returned the human-readable error message for this error code. -func (ec ErrorCode) Message() string { - return ec.Descriptor().Message -} - -// MarshalText encodes the receiver into UTF-8-encoded text and returns the -// result. -func (ec ErrorCode) MarshalText() (text []byte, err error) { - return []byte(ec.String()), nil -} - -// UnmarshalText decodes the form generated by MarshalText. -func (ec *ErrorCode) UnmarshalText(text []byte) error { - desc, ok := idToDescriptors[string(text)] - - if !ok { - desc = ErrorCodeUnknown.Descriptor() - } - - *ec = desc.Code - - return nil -} - -// Error provides a wrapper around ErrorCode with extra Details provided. -type Error struct { - Code ErrorCode `json:"code"` - Message string `json:"message,omitempty"` - Detail interface{} `json:"detail,omitempty"` -} - -// Error returns a human readable representation of the error. -func (e Error) Error() string { - return fmt.Sprintf("%s: %s", - strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), - e.Message) -} - -// Errors provides the envelope for multiple errors and a few sugar methods -// for use within the application. -type Errors struct { - Errors []Error `json:"errors,omitempty"` -} - -// Push pushes an error on to the error stack, with the optional detail -// argument. It is a programming error (ie panic) to push more than one -// detail at a time. -func (errs *Errors) Push(code ErrorCode, details ...interface{}) { - if len(details) > 1 { - panic("please specify zero or one detail items for this error") - } - - var detail interface{} - if len(details) > 0 { - detail = details[0] - } - - if err, ok := detail.(error); ok { - detail = err.Error() - } - - errs.PushErr(Error{ - Code: code, - Message: code.Message(), - Detail: detail, - }) -} - -// PushErr pushes an error interface onto the error stack. -func (errs *Errors) PushErr(err error) { - switch err.(type) { - case Error: - errs.Errors = append(errs.Errors, err.(Error)) - default: - errs.Errors = append(errs.Errors, Error{Message: err.Error()}) - } -} - -func (errs *Errors) Error() string { - switch errs.Len() { - case 0: - return "" - case 1: - return errs.Errors[0].Error() - default: - msg := "errors:\n" - for _, err := range errs.Errors { - msg += err.Error() + "\n" - } - return msg - } -} - -// Clear clears the errors. -func (errs *Errors) Clear() { - errs.Errors = errs.Errors[:0] -} - -// Len returns the current number of errors. -func (errs *Errors) Len() int { - return len(errs.Errors) -} diff --git a/registry/v2/errors_test.go b/registry/v2/errors_test.go deleted file mode 100644 index 4a80cdfe2d5d6..0000000000000 --- a/registry/v2/errors_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package v2 - -import ( - "encoding/json" - "reflect" - "testing" -) - -// TestErrorCodes ensures that error code format, mappings and -// marshaling/unmarshaling. round trips are stable. -func TestErrorCodes(t *testing.T) { - for _, desc := range ErrorDescriptors { - if desc.Code.String() != desc.Value { - t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value) - } - - if desc.Code.Message() != desc.Message { - t.Fatalf("incorrect message for error code %v: %q != %q", desc.Code, desc.Code.Message(), desc.Message) - } - - // Serialize the error code using the json library to ensure that we - // get a string and it works round trip. - p, err := json.Marshal(desc.Code) - - if err != nil { - t.Fatalf("error marshaling error code %v: %v", desc.Code, err) - } - - if len(p) <= 0 { - t.Fatalf("expected content in marshaled before for error code %v", desc.Code) - } - - // First, unmarshal to interface and ensure we have a string. - var ecUnspecified interface{} - if err := json.Unmarshal(p, &ecUnspecified); err != nil { - t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) - } - - if _, ok := ecUnspecified.(string); !ok { - t.Fatalf("expected a string for error code %v on unmarshal got a %T", desc.Code, ecUnspecified) - } - - // Now, unmarshal with the error code type and ensure they are equal - var ecUnmarshaled ErrorCode - if err := json.Unmarshal(p, &ecUnmarshaled); err != nil { - t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) - } - - if ecUnmarshaled != desc.Code { - t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, desc.Code) - } - } -} - -// TestErrorsManagement does a quick check of the Errors type to ensure that -// members are properly pushed and marshaled. -func TestErrorsManagement(t *testing.T) { - var errs Errors - - errs.Push(ErrorCodeDigestInvalid) - errs.Push(ErrorCodeBlobUnknown, - map[string]string{"digest": "sometestblobsumdoesntmatter"}) - - p, err := json.Marshal(errs) - - if err != nil { - t.Fatalf("error marashaling errors: %v", err) - } - - expectedJSON := "{\"errors\":[{\"code\":\"DIGEST_INVALID\",\"message\":\"provided digest did not match uploaded content\"},{\"code\":\"BLOB_UNKNOWN\",\"message\":\"blob unknown to registry\",\"detail\":{\"digest\":\"sometestblobsumdoesntmatter\"}}]}" - - if string(p) != expectedJSON { - t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) - } - - errs.Clear() - errs.Push(ErrorCodeUnknown) - expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}" - p, err = json.Marshal(errs) - - if err != nil { - t.Fatalf("error marashaling errors: %v", err) - } - - if string(p) != expectedJSON { - t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) - } -} - -// TestMarshalUnmarshal ensures that api errors can round trip through json -// without losing information. -func TestMarshalUnmarshal(t *testing.T) { - - var errors Errors - - for _, testcase := range []struct { - description string - err Error - }{ - { - description: "unknown error", - err: Error{ - - Code: ErrorCodeUnknown, - Message: ErrorCodeUnknown.Descriptor().Message, - }, - }, - { - description: "unknown manifest", - err: Error{ - Code: ErrorCodeManifestUnknown, - Message: ErrorCodeManifestUnknown.Descriptor().Message, - }, - }, - { - description: "unknown manifest", - err: Error{ - Code: ErrorCodeBlobUnknown, - Message: ErrorCodeBlobUnknown.Descriptor().Message, - Detail: map[string]interface{}{"digest": "asdfqwerqwerqwerqwer"}, - }, - }, - } { - fatalf := func(format string, args ...interface{}) { - t.Fatalf(testcase.description+": "+format, args...) - } - - unexpectedErr := func(err error) { - fatalf("unexpected error: %v", err) - } - - p, err := json.Marshal(testcase.err) - if err != nil { - unexpectedErr(err) - } - - var unmarshaled Error - if err := json.Unmarshal(p, &unmarshaled); err != nil { - unexpectedErr(err) - } - - if !reflect.DeepEqual(unmarshaled, testcase.err) { - fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, testcase.err) - } - - // Roll everything up into an error response envelope. - errors.PushErr(testcase.err) - } - - p, err := json.Marshal(errors) - if err != nil { - t.Fatalf("unexpected error marshaling error envelope: %v", err) - } - - var unmarshaled Errors - if err := json.Unmarshal(p, &unmarshaled); err != nil { - t.Fatalf("unexpected error unmarshaling error envelope: %v", err) - } - - if !reflect.DeepEqual(unmarshaled, errors) { - t.Fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, errors) - } -} diff --git a/registry/v2/regexp.go b/registry/v2/regexp.go deleted file mode 100644 index 07484dcd69538..0000000000000 --- a/registry/v2/regexp.go +++ /dev/null @@ -1,22 +0,0 @@ -package v2 - -import "regexp" - -// This file defines regular expressions for use in route definition. These -// are also defined in the registry code base. Until they are in a common, -// shared location, and exported, they must be repeated here. - -// RepositoryNameComponentRegexp restricts registtry path components names to -// start with at least two letters or numbers, with following parts able to -// separated by one period, dash or underscore. -var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9]+)*`) - -// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to -// 5 path components, separated by a forward slash. -var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String()) - -// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go. -var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`) - -// DigestRegexp matches valid digest types. -var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+`) diff --git a/registry/v2/routes.go b/registry/v2/routes.go deleted file mode 100644 index de0a38fb815a5..0000000000000 --- a/registry/v2/routes.go +++ /dev/null @@ -1,66 +0,0 @@ -package v2 - -import "github.com/gorilla/mux" - -// The following are definitions of the name under which all V2 routes are -// registered. These symbols can be used to look up a route based on the name. -const ( - RouteNameBase = "base" - RouteNameManifest = "manifest" - RouteNameTags = "tags" - RouteNameBlob = "blob" - RouteNameBlobUpload = "blob-upload" - RouteNameBlobUploadChunk = "blob-upload-chunk" -) - -var allEndpoints = []string{ - RouteNameManifest, - RouteNameTags, - RouteNameBlob, - RouteNameBlobUpload, - RouteNameBlobUploadChunk, -} - -// Router builds a gorilla router with named routes for the various API -// methods. This can be used directly by both server implementations and -// clients. -func Router() *mux.Router { - router := mux.NewRouter(). - StrictSlash(true) - - // GET /v2/ Check Check that the registry implements API version 2(.1) - router. - Path("/v2/"). - Name(RouteNameBase) - - // GET /v2//manifest/ Image Manifest Fetch the image manifest identified by name and reference where reference can be a tag or digest. - // PUT /v2//manifest/ Image Manifest Upload the image manifest identified by name and reference where reference can be a tag or digest. - // DELETE /v2//manifest/ Image Manifest Delete the image identified by name and reference where reference can be a tag or digest. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + DigestRegexp.String() + "}"). - Name(RouteNameManifest) - - // GET /v2//tags/list Tags Fetch the tags under the repository identified by name. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list"). - Name(RouteNameTags) - - // GET /v2//blob/ Layer Fetch the blob identified by digest. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}"). - Name(RouteNameBlob) - - // POST /v2//blob/upload/ Layer Upload Initiate an upload of the layer identified by tarsum. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/"). - Name(RouteNameBlobUpload) - - // GET /v2//blob/upload/ Layer Upload Get the status of the upload identified by tarsum and uuid. - // PUT /v2//blob/upload/ Layer Upload Upload all or a chunk of the upload identified by tarsum and uuid. - // DELETE /v2//blob/upload/ Layer Upload Cancel the upload identified by layer and uuid - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}"). - Name(RouteNameBlobUploadChunk) - - return router -} diff --git a/registry/v2/routes_test.go b/registry/v2/routes_test.go deleted file mode 100644 index 0191feed00189..0000000000000 --- a/registry/v2/routes_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package v2 - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "reflect" - "testing" - - "github.com/gorilla/mux" -) - -type routeTestCase struct { - RequestURI string - Vars map[string]string - RouteName string - StatusCode int -} - -// TestRouter registers a test handler with all the routes and ensures that -// each route returns the expected path variables. Not method verification is -// present. This not meant to be exhaustive but as check to ensure that the -// expected variables are extracted. -// -// This may go away as the application structure comes together. -func TestRouter(t *testing.T) { - - router := Router() - - testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - testCase := routeTestCase{ - RequestURI: r.RequestURI, - Vars: mux.Vars(r), - RouteName: mux.CurrentRoute(r).GetName(), - } - - enc := json.NewEncoder(w) - - if err := enc.Encode(testCase); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - // Startup test server - server := httptest.NewServer(router) - - for _, testcase := range []routeTestCase{ - { - RouteName: RouteNameBase, - RequestURI: "/v2/", - Vars: map[string]string{}, - }, - { - RouteName: RouteNameManifest, - RequestURI: "/v2/foo/manifests/bar", - Vars: map[string]string{ - "name": "foo", - "reference": "bar", - }, - }, - { - RouteName: RouteNameManifest, - RequestURI: "/v2/foo/bar/manifests/tag", - Vars: map[string]string{ - "name": "foo/bar", - "reference": "tag", - }, - }, - { - RouteName: RouteNameTags, - RequestURI: "/v2/foo/bar/tags/list", - Vars: map[string]string{ - "name": "foo/bar", - }, - }, - { - RouteName: RouteNameBlob, - RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234", - Vars: map[string]string{ - "name": "foo/bar", - "digest": "tarsum.dev+foo:abcdef0919234", - }, - }, - { - RouteName: RouteNameBlob, - RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234", - Vars: map[string]string{ - "name": "foo/bar", - "digest": "sha256:abcdef0919234", - }, - }, - { - RouteName: RouteNameBlobUpload, - RequestURI: "/v2/foo/bar/blobs/uploads/", - Vars: map[string]string{ - "name": "foo/bar", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/bar/blobs/uploads/uuid", - Vars: map[string]string{ - "name": "foo/bar", - "uuid": "uuid", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", - Vars: map[string]string{ - "name": "foo/bar", - "uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", - Vars: map[string]string{ - "name": "foo/bar", - "uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", - }, - }, - { - // Check ambiguity: ensure we can distinguish between tags for - // "foo/bar/image/image" and image for "foo/bar/image" with tag - // "tags" - RouteName: RouteNameManifest, - RequestURI: "/v2/foo/bar/manifests/manifests/tags", - Vars: map[string]string{ - "name": "foo/bar/manifests", - "reference": "tags", - }, - }, - { - // This case presents an ambiguity between foo/bar with tag="tags" - // and list tags for "foo/bar/manifest" - RouteName: RouteNameTags, - RequestURI: "/v2/foo/bar/manifests/tags/list", - Vars: map[string]string{ - "name": "foo/bar/manifests", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", - StatusCode: http.StatusNotFound, - }, - } { - // Register the endpoint - router.GetRoute(testcase.RouteName).Handler(testHandler) - u := server.URL + testcase.RequestURI - - resp, err := http.Get(u) - - if err != nil { - t.Fatalf("error issuing get request: %v", err) - } - - if testcase.StatusCode == 0 { - // Override default, zero-value - testcase.StatusCode = http.StatusOK - } - - if resp.StatusCode != testcase.StatusCode { - t.Fatalf("unexpected status for %s: %v %v", u, resp.Status, resp.StatusCode) - } - - if testcase.StatusCode != http.StatusOK { - // We don't care about json response. - continue - } - - dec := json.NewDecoder(resp.Body) - - var actualRouteInfo routeTestCase - if err := dec.Decode(&actualRouteInfo); err != nil { - t.Fatalf("error reading json response: %v", err) - } - // Needs to be set out of band - actualRouteInfo.StatusCode = resp.StatusCode - - if actualRouteInfo.RouteName != testcase.RouteName { - t.Fatalf("incorrect route %q matched, expected %q", actualRouteInfo.RouteName, testcase.RouteName) - } - - if !reflect.DeepEqual(actualRouteInfo, testcase) { - t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase) - } - } - -} diff --git a/registry/v2/urls.go b/registry/v2/urls.go deleted file mode 100644 index 38fa98af01d33..0000000000000 --- a/registry/v2/urls.go +++ /dev/null @@ -1,179 +0,0 @@ -package v2 - -import ( - "net/http" - "net/url" - - "github.com/gorilla/mux" -) - -// URLBuilder creates registry API urls from a single base endpoint. It can be -// used to create urls for use in a registry client or server. -// -// All urls will be created from the given base, including the api version. -// For example, if a root of "/foo/" is provided, urls generated will be fall -// under "/foo/v2/...". Most application will only provide a schema, host and -// port, such as "https://localhost:5000/". -type URLBuilder struct { - root *url.URL // url root (ie http://localhost/) - router *mux.Router -} - -// NewURLBuilder creates a URLBuilder with provided root url object. -func NewURLBuilder(root *url.URL) *URLBuilder { - return &URLBuilder{ - root: root, - router: Router(), - } -} - -// NewURLBuilderFromString workes identically to NewURLBuilder except it takes -// a string argument for the root, returning an error if it is not a valid -// url. -func NewURLBuilderFromString(root string) (*URLBuilder, error) { - u, err := url.Parse(root) - if err != nil { - return nil, err - } - - return NewURLBuilder(u), nil -} - -// NewURLBuilderFromRequest uses information from an *http.Request to -// construct the root url. -func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { - u := &url.URL{ - Scheme: r.URL.Scheme, - Host: r.Host, - } - - return NewURLBuilder(u) -} - -// BuildBaseURL constructs a base url for the API, typically just "/v2/". -func (ub *URLBuilder) BuildBaseURL() (string, error) { - route := ub.cloneRoute(RouteNameBase) - - baseURL, err := route.URL() - if err != nil { - return "", err - } - - return baseURL.String(), nil -} - -// BuildTagsURL constructs a url to list the tags in the named repository. -func (ub *URLBuilder) BuildTagsURL(name string) (string, error) { - route := ub.cloneRoute(RouteNameTags) - - tagsURL, err := route.URL("name", name) - if err != nil { - return "", err - } - - return tagsURL.String(), nil -} - -// BuildManifestURL constructs a url for the manifest identified by name and reference. -func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) { - route := ub.cloneRoute(RouteNameManifest) - - manifestURL, err := route.URL("name", name, "reference", reference) - if err != nil { - return "", err - } - - return manifestURL.String(), nil -} - -// BuildBlobURL constructs the url for the blob identified by name and dgst. -func (ub *URLBuilder) BuildBlobURL(name string, dgst string) (string, error) { - route := ub.cloneRoute(RouteNameBlob) - - layerURL, err := route.URL("name", name, "digest", dgst) - if err != nil { - return "", err - } - - return layerURL.String(), nil -} - -// BuildBlobUploadURL constructs a url to begin a blob upload in the -// repository identified by name. -func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) { - route := ub.cloneRoute(RouteNameBlobUpload) - - uploadURL, err := route.URL("name", name) - if err != nil { - return "", err - } - - return appendValuesURL(uploadURL, values...).String(), nil -} - -// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid, -// including any url values. This should generally not be used by clients, as -// this url is provided by server implementations during the blob upload -// process. -func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) { - route := ub.cloneRoute(RouteNameBlobUploadChunk) - - uploadURL, err := route.URL("name", name, "uuid", uuid) - if err != nil { - return "", err - } - - return appendValuesURL(uploadURL, values...).String(), nil -} - -// clondedRoute returns a clone of the named route from the router. Routes -// must be cloned to avoid modifying them during url generation. -func (ub *URLBuilder) cloneRoute(name string) clonedRoute { - route := new(mux.Route) - root := new(url.URL) - - *route = *ub.router.GetRoute(name) // clone the route - *root = *ub.root - - return clonedRoute{Route: route, root: root} -} - -type clonedRoute struct { - *mux.Route - root *url.URL -} - -func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { - routeURL, err := cr.Route.URL(pairs...) - if err != nil { - return nil, err - } - - return cr.root.ResolveReference(routeURL), nil -} - -// appendValuesURL appends the parameters to the url. -func appendValuesURL(u *url.URL, values ...url.Values) *url.URL { - merged := u.Query() - - for _, v := range values { - for k, vv := range v { - merged[k] = append(merged[k], vv...) - } - } - - u.RawQuery = merged.Encode() - return u -} - -// appendValues appends the parameters to the url. Panics if the string is not -// a url. -func appendValues(u string, values ...url.Values) string { - up, err := url.Parse(u) - - if err != nil { - panic(err) // should never happen - } - - return appendValuesURL(up, values...).String() -} diff --git a/registry/v2/urls_test.go b/registry/v2/urls_test.go deleted file mode 100644 index f30c96c0affdf..0000000000000 --- a/registry/v2/urls_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package v2 - -import ( - "net/url" - "testing" -) - -type urlBuilderTestCase struct { - description string - expectedPath string - build func() (string, error) -} - -// TestURLBuilder tests the various url building functions, ensuring they are -// returning the expected values. -func TestURLBuilder(t *testing.T) { - var ( - urlBuilder *URLBuilder - err error - ) - - testCases := []urlBuilderTestCase{ - { - description: "test base url", - expectedPath: "/v2/", - build: func() (string, error) { - return urlBuilder.BuildBaseURL() - }, - }, - { - description: "test tags url", - expectedPath: "/v2/foo/bar/tags/list", - build: func() (string, error) { - return urlBuilder.BuildTagsURL("foo/bar") - }, - }, - { - description: "test manifest url", - expectedPath: "/v2/foo/bar/manifests/tag", - build: func() (string, error) { - return urlBuilder.BuildManifestURL("foo/bar", "tag") - }, - }, - { - description: "build blob url", - expectedPath: "/v2/foo/bar/blobs/tarsum.v1+sha256:abcdef0123456789", - build: func() (string, error) { - return urlBuilder.BuildBlobURL("foo/bar", "tarsum.v1+sha256:abcdef0123456789") - }, - }, - { - description: "build blob upload url", - expectedPath: "/v2/foo/bar/blobs/uploads/", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadURL("foo/bar") - }, - }, - { - description: "build blob upload url with digest and size", - expectedPath: "/v2/foo/bar/blobs/uploads/?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{ - "size": []string{"10000"}, - "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, - }) - }, - }, - { - description: "build blob upload chunk url", - expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part") - }, - }, - { - description: "build blob upload chunk url with digest and size", - expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{ - "size": []string{"10000"}, - "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, - }) - }, - }, - } - - roots := []string{ - "http://example.com", - "https://example.com", - "http://localhost:5000", - "https://localhost:5443", - } - - for _, root := range roots { - urlBuilder, err = NewURLBuilderFromString(root) - if err != nil { - t.Fatalf("unexpected error creating urlbuilder: %v", err) - } - - for _, testCase := range testCases { - url, err := testCase.build() - if err != nil { - t.Fatalf("%s: error building url: %v", testCase.description, err) - } - - expectedURL := root + testCase.expectedPath - - if url != expectedURL { - t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) - } - } - } -} From 11287f77144954fe82f75ade671da739559c664f Mon Sep 17 00:00:00 2001 From: Peter Esbensen Date: Tue, 31 Mar 2015 22:38:23 -0700 Subject: [PATCH 004/332] Added unit tests for stringutils GenerateRandomAlphaOnlyString and GenerateRandomAsciiString Signed-off-by: Peter Esbensen --- pkg/stringutils/stringutils_test.go | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go index 60b848ff5a09f..e9da58b822490 100644 --- a/pkg/stringutils/stringutils_test.go +++ b/pkg/stringutils/stringutils_test.go @@ -23,3 +23,58 @@ func TestRandomStringUniqueness(t *testing.T) { set[str] = struct{}{} } } + +func testLengthHelper(generator func(int) string, t *testing.T) { + expectedLength := 20 + s := generator(expectedLength) + if len(s) != expectedLength { + t.Fatalf("Length of %s was %d but expected length %d", s, len(s), expectedLength) + } +} + +func testUniquenessHelper(generator func(int) string, t *testing.T) { + repeats := 25 + set := make(map[string]struct{}, repeats) + for i := 0; i < repeats; i = i + 1 { + str := generator(64) + if len(str) != 64 { + t.Fatalf("Id returned is incorrect: %s", str) + } + if _, ok := set[str]; ok { + t.Fatalf("Random number is repeated") + } + set[str] = struct{}{} + } +} + +func isASCII(s string) bool { + for _, c := range s { + if c > 127 { + return false + } + } + return true +} + +func TestGenerateRandomAlphaOnlyStringLength(t *testing.T) { + testLengthHelper(GenerateRandomAlphaOnlyString, t) +} + +func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) { + testUniquenessHelper(GenerateRandomAlphaOnlyString, t) +} + +func TestGenerateRandomAsciiStringLength(t *testing.T) { + testLengthHelper(GenerateRandomAsciiString, t) +} + +func TestGenerateRandomAsciiStringUniqueness(t *testing.T) { + testUniquenessHelper(GenerateRandomAsciiString, t) +} + +func TestGenerateRandomAsciiStringIsAscii(t *testing.T) { + str := GenerateRandomAsciiString(64) + if !isASCII(str) { + t.Fatalf("%s contained non-ascii characters", str) + } +} From 6896016b7c7a95ac33c77a222c359cf35a471eb9 Mon Sep 17 00:00:00 2001 From: Peter Esbensen Date: Wed, 1 Apr 2015 07:21:07 -0700 Subject: [PATCH 005/332] Fixes #11721 removed GenerateRandomString Signed-off-by: Peter Esbensen gofmt Signed-off-by: Peter Esbensen --- engine/engine.go | 4 ++-- pkg/stringutils/stringutils.go | 13 ------------- pkg/stringutils/stringutils_test.go | 26 ++------------------------ utils/utils.go | 4 ++-- 4 files changed, 6 insertions(+), 41 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 1090675dfa557..79fae51cc3848 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -11,7 +11,7 @@ import ( "time" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/stringutils" + "github.com/docker/docker/pkg/stringid" ) // Installer is a standard interface for objects which can "install" themselves @@ -78,7 +78,7 @@ func (eng *Engine) RegisterCatchall(catchall Handler) { func New() *Engine { eng := &Engine{ handlers: make(map[string]Handler), - id: stringutils.GenerateRandomString(), + id: stringid.GenerateRandomID(), Stdout: os.Stdout, Stderr: os.Stderr, Stdin: os.Stdin, diff --git a/pkg/stringutils/stringutils.go b/pkg/stringutils/stringutils.go index bcb0ece57cd2c..f5f07dd18efa8 100644 --- a/pkg/stringutils/stringutils.go +++ b/pkg/stringutils/stringutils.go @@ -1,23 +1,10 @@ package stringutils import ( - "crypto/rand" - "encoding/hex" - "io" mathrand "math/rand" "time" ) -// Generate 32 chars random string -func GenerateRandomString() string { - id := make([]byte, 32) - - if _, err := io.ReadFull(rand.Reader, id); err != nil { - panic(err) // This shouldn't happen - } - return hex.EncodeToString(id) -} - // Generate alpha only random stirng with length n func GenerateRandomAlphaOnlyString(n int) string { // make a really long string diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go index e9da58b822490..a5a01b4a0340b 100644 --- a/pkg/stringutils/stringutils_test.go +++ b/pkg/stringutils/stringutils_test.go @@ -2,34 +2,12 @@ package stringutils import "testing" -func TestRandomString(t *testing.T) { - str := GenerateRandomString() - if len(str) != 64 { - t.Fatalf("Id returned is incorrect: %s", str) - } -} - -func TestRandomStringUniqueness(t *testing.T) { - repeats := 25 - set := make(map[string]struct{}, repeats) - for i := 0; i < repeats; i = i + 1 { - str := GenerateRandomString() - if len(str) != 64 { - t.Fatalf("Id returned is incorrect: %s", str) - } - if _, ok := set[str]; ok { - t.Fatalf("Random number is repeated") - } - set[str] = struct{}{} - } -} - func testLengthHelper(generator func(int) string, t *testing.T) { expectedLength := 20 s := generator(expectedLength) if len(s) != expectedLength { t.Fatalf("Length of %s was %d but expected length %d", s, len(s), expectedLength) - } + } } func testUniquenessHelper(generator func(int) string, t *testing.T) { @@ -65,7 +43,7 @@ func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) { } func TestGenerateRandomAsciiStringLength(t *testing.T) { - testLengthHelper(GenerateRandomAsciiString, t) + testLengthHelper(GenerateRandomAsciiString, t) } func TestGenerateRandomAsciiStringUniqueness(t *testing.T) { diff --git a/utils/utils.go b/utils/utils.go index d0e76bf237aee..5084639dbc66f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -24,7 +24,7 @@ import ( "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonmessage" - "github.com/docker/docker/pkg/stringutils" + "github.com/docker/docker/pkg/stringid" ) type KeyValuePair struct { @@ -313,7 +313,7 @@ var globalTestID string // new directory. func TestDirectory(templateDir string) (dir string, err error) { if globalTestID == "" { - globalTestID = stringutils.GenerateRandomString()[:4] + globalTestID = stringid.GenerateRandomID()[:4] } prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2)) if prefix == "" { From eee1efcfd6c46dbdc5da02ca12722e399a56bb12 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 3 Apr 2015 01:38:46 -0600 Subject: [PATCH 006/332] Add "builder-deb" base images for building ".deb" packages properly Signed-off-by: Andrew "Tianon" Page --- contrib/builder/deb/README.md | 5 ++ contrib/builder/deb/build.sh | 10 +++ contrib/builder/deb/debian-jessie/Dockerfile | 14 ++++ contrib/builder/deb/debian-wheezy/Dockerfile | 15 ++++ contrib/builder/deb/generate.sh | 69 ++++++++++++++++ .../deb/ubuntu-debootstrap-trusty/Dockerfile | 14 ++++ .../deb/ubuntu-debootstrap-utopic/Dockerfile | 14 ++++ .../deb/ubuntu-debootstrap-vivid/Dockerfile | 14 ++++ hack/make/.build-deb/compat | 1 + hack/make/.build-deb/control | 27 ++++++ .../.build-deb/docker-core.bash-completion | 1 + .../.build-deb/docker-core.docker.default | 1 + hack/make/.build-deb/docker-core.docker.init | 1 + .../.build-deb/docker-core.docker.upstart | 1 + hack/make/.build-deb/docker-core.install | 10 +++ hack/make/.build-deb/docker-core.manpages | 1 + hack/make/.build-deb/docker-core.postinst | 20 +++++ hack/make/.build-deb/docker-core.udev | 1 + hack/make/.build-deb/docs | 1 + hack/make/.build-deb/rules | 36 ++++++++ hack/make/build-deb | 82 +++++++++++++++++++ 21 files changed, 338 insertions(+) create mode 100644 contrib/builder/deb/README.md create mode 100755 contrib/builder/deb/build.sh create mode 100644 contrib/builder/deb/debian-jessie/Dockerfile create mode 100644 contrib/builder/deb/debian-wheezy/Dockerfile create mode 100755 contrib/builder/deb/generate.sh create mode 100644 contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile create mode 100644 contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile create mode 100644 contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile create mode 100644 hack/make/.build-deb/compat create mode 100644 hack/make/.build-deb/control create mode 100644 hack/make/.build-deb/docker-core.bash-completion create mode 120000 hack/make/.build-deb/docker-core.docker.default create mode 120000 hack/make/.build-deb/docker-core.docker.init create mode 120000 hack/make/.build-deb/docker-core.docker.upstart create mode 100644 hack/make/.build-deb/docker-core.install create mode 100644 hack/make/.build-deb/docker-core.manpages create mode 100644 hack/make/.build-deb/docker-core.postinst create mode 120000 hack/make/.build-deb/docker-core.udev create mode 100644 hack/make/.build-deb/docs create mode 100755 hack/make/.build-deb/rules create mode 100644 hack/make/build-deb diff --git a/contrib/builder/deb/README.md b/contrib/builder/deb/README.md new file mode 100644 index 0000000000000..a6fd70dca72c3 --- /dev/null +++ b/contrib/builder/deb/README.md @@ -0,0 +1,5 @@ +# `dockercore/builder-deb` + +This image's tags contain the dependencies for building Docker `.deb`s for each of the Debian-based platforms Docker targets. + +To add new tags, see [`contrib/builder/deb` in https://github.com/docker/docker](https://github.com/docker/docker/tree/master/contrib/builder/deb), specifically the `generate.sh` script, whose usage is described in a comment at the top of the file. diff --git a/contrib/builder/deb/build.sh b/contrib/builder/deb/build.sh new file mode 100755 index 0000000000000..8271d9dc4740d --- /dev/null +++ b/contrib/builder/deb/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" + +set -x +./generate.sh +for d in */; do + docker build -t "dockercore/builder-deb:$(basename "$d")" "$d" +done diff --git a/contrib/builder/deb/debian-jessie/Dockerfile b/contrib/builder/deb/debian-jessie/Dockerfile new file mode 100644 index 0000000000000..ad90a21183e14 --- /dev/null +++ b/contrib/builder/deb/debian-jessie/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM debian:jessie + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/debian-wheezy/Dockerfile b/contrib/builder/deb/debian-wheezy/Dockerfile new file mode 100644 index 0000000000000..87274d4096878 --- /dev/null +++ b/contrib/builder/deb/debian-wheezy/Dockerfile @@ -0,0 +1,15 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM debian:wheezy +RUN echo deb http://http.debian.net/debian wheezy-backports main > /etc/apt/sources.list.d/wheezy-backports.list + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/generate.sh b/contrib/builder/deb/generate.sh new file mode 100755 index 0000000000000..cd187c7ce8ea1 --- /dev/null +++ b/contrib/builder/deb/generate.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +# usage: ./generate.sh [versions] +# ie: ./generate.sh +# to update all Dockerfiles in this directory +# or: ./generate.sh debian-jessie +# to only update debian-jessie/Dockerfile +# or: ./generate.sh debian-newversion +# to create a new folder and a Dockerfile within it + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" + +versions=( "$@" ) +if [ ${#versions[@]} -eq 0 ]; then + versions=( */ ) +fi +versions=( "${versions[@]%/}" ) + +for version in "${versions[@]}"; do + distro="${version%-*}" + suite="${version##*-}" + from="${distro}:${suite}" + + mkdir -p "$version" + echo "$version -> FROM $from" + cat > "$version/Dockerfile" <<-EOF + # + # THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! + # + + FROM $from + EOF + + case "$from" in + debian:wheezy) + # add -backports, like our users have to + echo "RUN echo deb http://http.debian.net/debian $suite-backports main > /etc/apt/sources.list.d/$suite-backports.list" >> "$version/Dockerfile" + ;; + esac + + echo >> "$version/Dockerfile" + + # this list is sorted alphabetically; please keep it that way + packages=( + bash-completion # for bash-completion debhelper integration + btrfs-tools # for "btrfs/ioctl.h" (and "version.h" if possible) + build-essential # "essential for building Debian packages" + curl ca-certificates # for downloading Go + debhelper # for easy ".deb" building + dh-systemd # for systemd debhelper integration + git # for "git commit" info in "docker -v" + libapparmor-dev # for "sys/apparmor.h" + libdevmapper-dev # for "libdevmapper.h" + libsqlite3-dev # for "sqlite3.h" + ) + echo "RUN apt-get update && apt-get install -y ${packages[*]} --no-install-recommends && rm -rf /var/lib/apt/lists/*" >> "$version/Dockerfile" + + echo >> "$version/Dockerfile" + + awk '$1 == "ENV" && $2 == "GO_VERSION" { print; exit }' ../../../Dockerfile >> "$version/Dockerfile" + echo 'RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local' >> "$version/Dockerfile" + echo 'ENV PATH $PATH:/usr/local/go/bin' >> "$version/Dockerfile" + + echo >> "$version/Dockerfile" + + echo 'ENV AUTO_GOPATH 1' >> "$version/Dockerfile" + awk '$1 == "ENV" && $2 == "DOCKER_BUILDTAGS" { print; exit }' ../../../Dockerfile >> "$version/Dockerfile" +done diff --git a/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile new file mode 100644 index 0000000000000..5715b2698b80b --- /dev/null +++ b/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM ubuntu-debootstrap:trusty + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile new file mode 100644 index 0000000000000..3862b83707b52 --- /dev/null +++ b/contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM ubuntu-debootstrap:utopic + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile new file mode 100644 index 0000000000000..15911b268d39a --- /dev/null +++ b/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM ubuntu-debootstrap:vivid + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/hack/make/.build-deb/compat b/hack/make/.build-deb/compat new file mode 100644 index 0000000000000..ec635144f6004 --- /dev/null +++ b/hack/make/.build-deb/compat @@ -0,0 +1 @@ +9 diff --git a/hack/make/.build-deb/control b/hack/make/.build-deb/control new file mode 100644 index 0000000000000..03caae8342a9e --- /dev/null +++ b/hack/make/.build-deb/control @@ -0,0 +1,27 @@ +Source: docker-core +Maintainer: Docker +Homepage: https://dockerproject.com +Vcs-Browser: https://github.com/docker/docker +Vcs-Git: git://github.com/docker/docker.git + +Package: docker-core +Architecture: linux-any +Depends: iptables, ${misc:Depends}, ${perl:Depends}, ${shlibs:Depends} +Recommends: aufs-tools, + ca-certificates, + cgroupfs-mount | cgroup-lite, + git, + xz-utils, + ${apparmor:Recommends} +Conflicts: docker (<< 1.5~), docker.io, lxc-docker, lxc-docker-virtual-package +Description: Docker: the open-source application container engine + Docker is an open source project to pack, ship and run any application as a + lightweight container + . + Docker containers are both hardware-agnostic and platform-agnostic. This means + they can run anywhere, from your laptop to the largest EC2 compute instance and + they can run anywhere, from your laptop to the largest EC2 compute instance and + everything in between - and they don't require you to use a particular + language, framework or packaging system. That makes them great building blocks + for deploying and scaling web apps, databases, and backend services without + depending on a particular stack or provider. diff --git a/hack/make/.build-deb/docker-core.bash-completion b/hack/make/.build-deb/docker-core.bash-completion new file mode 100644 index 0000000000000..6ea111930886d --- /dev/null +++ b/hack/make/.build-deb/docker-core.bash-completion @@ -0,0 +1 @@ +contrib/completion/bash/docker diff --git a/hack/make/.build-deb/docker-core.docker.default b/hack/make/.build-deb/docker-core.docker.default new file mode 120000 index 0000000000000..4278533d65967 --- /dev/null +++ b/hack/make/.build-deb/docker-core.docker.default @@ -0,0 +1 @@ +../../../contrib/init/sysvinit-debian/docker.default \ No newline at end of file diff --git a/hack/make/.build-deb/docker-core.docker.init b/hack/make/.build-deb/docker-core.docker.init new file mode 120000 index 0000000000000..8cb89d30dde93 --- /dev/null +++ b/hack/make/.build-deb/docker-core.docker.init @@ -0,0 +1 @@ +../../../contrib/init/sysvinit-debian/docker \ No newline at end of file diff --git a/hack/make/.build-deb/docker-core.docker.upstart b/hack/make/.build-deb/docker-core.docker.upstart new file mode 120000 index 0000000000000..7e1b64a3e640a --- /dev/null +++ b/hack/make/.build-deb/docker-core.docker.upstart @@ -0,0 +1 @@ +../../../contrib/init/upstart/docker.conf \ No newline at end of file diff --git a/hack/make/.build-deb/docker-core.install b/hack/make/.build-deb/docker-core.install new file mode 100644 index 0000000000000..c3f4eb146574d --- /dev/null +++ b/hack/make/.build-deb/docker-core.install @@ -0,0 +1,10 @@ +#contrib/syntax/vim/doc/* /usr/share/vim/vimfiles/doc/ +#contrib/syntax/vim/ftdetect/* /usr/share/vim/vimfiles/ftdetect/ +#contrib/syntax/vim/syntax/* /usr/share/vim/vimfiles/syntax/ +contrib/*-integration usr/share/docker-core/contrib/ +contrib/check-config.sh usr/share/docker-core/contrib/ +contrib/completion/zsh/_docker usr/share/zsh/vendor-completions/ +contrib/init/systemd/docker.service lib/systemd/system/ +contrib/init/systemd/docker.socket lib/systemd/system/ +contrib/mk* usr/share/docker-core/contrib/ +contrib/nuke-graph-directory.sh usr/share/docker-core/contrib/ diff --git a/hack/make/.build-deb/docker-core.manpages b/hack/make/.build-deb/docker-core.manpages new file mode 100644 index 0000000000000..d5cff8a479fa3 --- /dev/null +++ b/hack/make/.build-deb/docker-core.manpages @@ -0,0 +1 @@ +docs/man/man*/* diff --git a/hack/make/.build-deb/docker-core.postinst b/hack/make/.build-deb/docker-core.postinst new file mode 100644 index 0000000000000..eeef6ca801605 --- /dev/null +++ b/hack/make/.build-deb/docker-core.postinst @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +case "$1" in + configure) + if [ -z "$2" ]; then + if ! getent group docker > /dev/null; then + groupadd --system docker + fi + fi + ;; + abort-*) + # How'd we get here?? + exit 1 + ;; + *) + ;; +esac + +#DEBHELPER# diff --git a/hack/make/.build-deb/docker-core.udev b/hack/make/.build-deb/docker-core.udev new file mode 120000 index 0000000000000..914a361959de3 --- /dev/null +++ b/hack/make/.build-deb/docker-core.udev @@ -0,0 +1 @@ +../../../contrib/udev/80-docker.rules \ No newline at end of file diff --git a/hack/make/.build-deb/docs b/hack/make/.build-deb/docs new file mode 100644 index 0000000000000..b43bf86b50fd8 --- /dev/null +++ b/hack/make/.build-deb/docs @@ -0,0 +1 @@ +README.md diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules new file mode 100755 index 0000000000000..3369f4fc54286 --- /dev/null +++ b/hack/make/.build-deb/rules @@ -0,0 +1,36 @@ +#!/usr/bin/make -f + +VERSION = $(shell cat VERSION) + +override_dh_gencontrol: + # if we're on Ubuntu, we need to Recommends: apparmor + echo 'apparmor:Recommends=$(shell dpkg-vendor --is Ubuntu && echo apparmor)' >> debian/docker-core.substvars + dh_gencontrol + +override_dh_auto_build: + ./hack/make.sh dynbinary + # ./docs/man/md2man-all.sh runs outside the build container (if at all), since we don't have go-md2man here + +override_dh_auto_test: + ./bundles/$(VERSION)/dynbinary/docker -v + +override_dh_strip: + # the SHA1 of dockerinit is important: don't strip it + # also, Go has lots of problems with stripping, so just don't + +override_dh_auto_install: + mkdir -p debian/docker-core/usr/bin + cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/docker)" debian/docker-core/usr/bin/docker + mkdir -p debian/docker-core/usr/libexec/docker + cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/dockerinit)" debian/docker-core/usr/libexec/docker/dockerinit + +override_dh_installinit: + # use "docker" as our service name, not "docker-core" + dh_installinit --name=docker + +override_dh_installudev: + # match our existing priority + dh_installudev --priority=z80 + +%: + dh $@ --with=systemd,bash-completion diff --git a/hack/make/build-deb b/hack/make/build-deb new file mode 100644 index 0000000000000..657aa04ccbf65 --- /dev/null +++ b/hack/make/build-deb @@ -0,0 +1,82 @@ +#!/bin/bash +set -e + +DEST=$1 + +# subshell so that we can export PATH without breaking other things +( + source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + + # we need to wrap up everything in between integration-daemon-start and + # integration-daemon-stop to make sure we kill the daemon and don't hang, + # even and especially on test failures + didFail= + if ! { + set -e + + # TODO consider using frozen images for the dockercore/builder-deb tags + + debVersion="${VERSION//-/'~'}" + # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better + if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then + gitUnix="$(git log -1 --pretty='%at')" + gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')" + gitCommit="$(git log -1 --pretty='%h')" + gitVersion="git${gitDate}.0.${gitCommit}" + # gitVersion is now something like 'git20150128.112847.0.17e840a' + debVersion="$debVersion~$gitVersion" + + # $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false + # true + + # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a + fi + + debSource="$(awk -F ': ' '$1 == "Source" { print $2; exit }' hack/make/.build-deb/control)" + debMaintainer="$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' hack/make/.build-deb/control)" + debDate="$(date --rfc-2822)" + + # if go-md2man is available, pre-generate the man pages + ./docs/man/md2man-all.sh -q || true + # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this + + # TODO add a configurable knob for _which_ debs to build so we don't have to modify the file or build all of them every time we need to test + for dir in contrib/builder/deb/*/; do + version="$(basename "$dir")" + suite="${version##*-}" + + image="dockercore/builder-deb:$version" + if ! docker inspect "$image" &> /dev/null; then + ( set -x && docker build -t "$image" "$dir" ) + fi + + mkdir -p "$DEST/$version" + cat > "$DEST/$version/Dockerfile.build" <<-EOF + FROM $image + WORKDIR /usr/src/docker + COPY . /usr/src/docker + RUN ln -sfv hack/make/.build-deb debian + RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog + RUN dpkg-buildpackage -uc -us + EOF + cp -a "$DEST/$version/Dockerfile.build" . # can't use $DEST because it's in .dockerignore... + tempImage="docker-temp/build-deb:$version" + ( set -x && docker build -t "$tempImage" -f Dockerfile.build . ) + docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version" + docker rmi "$tempImage" + done + }; then + didFail=1 + fi + + # clean up after ourselves + rm -f Dockerfile.build + + source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + + [ -z "$didFail" ] # "set -e" ftw +) 2>&1 | tee -a $DEST/test.log From ca628c6216ade64790dfbfe388fe4769433ec4aa Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 16 Mar 2015 13:18:40 -0400 Subject: [PATCH 007/332] devmapper: udev sync is a requirement closes #10664 closes #4036 Signed-off-by: Vincent Batts --- daemon/graphdriver/devmapper/deviceset.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 4d35adabc04f1..7e0a13a952469 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -963,9 +963,9 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // https://github.com/docker/docker/issues/4036 if supported := devicemapper.UdevSetSyncSupport(true); !supported { - logrus.Warnf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") + logrus.Errorf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") + return graphdriver.ErrNotSupported } - logrus.Debugf("devicemapper: udev sync support: %v", devicemapper.UdevSyncSupported()) if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { return err From 7bb4b055abab5f5b561a970f7235c2d113a4d85f Mon Sep 17 00:00:00 2001 From: Yestin Sun Date: Wed, 8 Apr 2015 13:58:08 -0700 Subject: [PATCH 008/332] Improve test accuracy for pkg/chrootarchive (part 1) Check test correctness of untar by comparing destination with source. For part one, it only compares the directories. This is a supplement to the #11601 fix. Signed-off-by: Yestin Sun --- pkg/chrootarchive/archive_test.go | 60 ++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go index 45397d38fe7da..183091933c07e 100644 --- a/pkg/chrootarchive/archive_test.go +++ b/pkg/chrootarchive/archive_test.go @@ -59,15 +59,15 @@ func TestChrootUntarEmptyArchive(t *testing.T) { } } -func prepareSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { +func prepareSourceDirectory(numberOfFiles int, targetPath string, makeSymLinks bool) (int, error) { fileData := []byte("fooo") for n := 0; n < numberOfFiles; n++ { fileName := fmt.Sprintf("file-%d", n) if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil { return 0, err } - if makeLinks { - if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { + if makeSymLinks { + if err := os.Symlink(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { return 0, err } } @@ -76,8 +76,19 @@ func prepareSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool return totalSize, nil } -func TestChrootTarUntarWithSoftLink(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSoftLink") +func compareDirectories(src string, dest string) error { + changes, err := archive.ChangesDirs(dest, src) + if err != nil { + return err + } + if len(changes) > 0 { + return fmt.Errorf("Unexpected differences after untar: %v", changes) + } + return nil +} + +func TestChrootTarUntarWithSymlink(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSymlink") if err != nil { t.Fatal(err) } @@ -93,6 +104,9 @@ func TestChrootTarUntarWithSoftLink(t *testing.T) { if err := TarUntar(src, dest); err != nil { t.Fatal(err) } + if err := compareDirectories(src, dest); err != nil { + t.Fatal(err) + } } func TestChrootCopyWithTar(t *testing.T) { @@ -108,19 +122,29 @@ func TestChrootCopyWithTar(t *testing.T) { if _, err := prepareSourceDirectory(10, src, true); err != nil { t.Fatal(err) } - dest := filepath.Join(tmpdir, "dest") + // Copy directory + dest := filepath.Join(tmpdir, "dest") if err := CopyWithTar(src, dest); err != nil { t.Fatal(err) } + if err := compareDirectories(src, dest); err != nil { + t.Fatal(err) + } + // Copy file srcfile := filepath.Join(src, "file-1") - if err := CopyWithTar(srcfile, dest); err != nil { + dest = filepath.Join(tmpdir, "destFile") + destfile := filepath.Join(dest, "file-1") + if err := CopyWithTar(srcfile, destfile); err != nil { t.Fatal(err) } + // Copy symbolic link - linkfile := filepath.Join(src, "file-1-link") - if err := CopyWithTar(linkfile, dest); err != nil { + srcLinkfile := filepath.Join(src, "file-1-link") + dest = filepath.Join(tmpdir, "destSymlink") + destLinkfile := filepath.Join(dest, "file-1-link") + if err := CopyWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } } @@ -138,19 +162,26 @@ func TestChrootCopyFileWithTar(t *testing.T) { if _, err := prepareSourceDirectory(10, src, true); err != nil { t.Fatal(err) } - dest := filepath.Join(tmpdir, "dest") + // Copy directory + dest := filepath.Join(tmpdir, "dest") if err := CopyFileWithTar(src, dest); err == nil { t.Fatal("Expected error on copying directory") } + // Copy file srcfile := filepath.Join(src, "file-1") - if err := CopyFileWithTar(srcfile, dest); err != nil { + dest = filepath.Join(tmpdir, "destFile") + destfile := filepath.Join(dest, "file-1") + if err := CopyFileWithTar(srcfile, destfile); err != nil { t.Fatal(err) } + // Copy symbolic link - linkfile := filepath.Join(src, "file-1-link") - if err := CopyFileWithTar(linkfile, dest); err != nil { + srcLinkfile := filepath.Join(src, "file-1-link") + dest = filepath.Join(tmpdir, "destSymlink") + destLinkfile := filepath.Join(dest, "file-1-link") + if err := CopyFileWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } } @@ -188,6 +219,9 @@ func TestChrootUntarPath(t *testing.T) { if err := UntarPath(tarfile, dest); err != nil { t.Fatal(err) } + if err := compareDirectories(src, dest); err != nil { + t.Fatal(err) + } } type slowEmptyTarReader struct { From a4b7a9e1e5505983aea3f6d7e246c57a6f4f6170 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 8 Apr 2015 16:37:39 -0700 Subject: [PATCH 009/332] cli: Better wording for daemon --log-driver This flag is passed to the daemon CLI. In my opinion, "Container's logging driver" is not accurate and refers to 'one container'. Also the `syslog` driver was missing from the list. Having the list of all logging drivers won't scale here (should be <80 chars per line) and we have `rotation` driver coming up in the pipeline as well (gh11485). Signed-off-by: Ahmet Alp Balkan --- daemon/config.go | 2 +- docs/man/docker.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 9b38fde4edd3c..10608b769d989 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -83,7 +83,7 @@ func (config *Config) InstallFlags() { opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon") config.Ulimits = make(map[string]*ulimit.Ulimit) opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers") - flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver") + flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs") } func getDefaultNetworkMtu() int { diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index bcb9d25414297..ec79850de96f0 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -90,7 +90,7 @@ unix://[/path/to/socket] to use. Set key=value labels to the daemon (displayed in `docker info`) **--log-driver**="*json-file*|*syslog*|*none*" - Container's logging driver. Default is `default`. + Default driver for container logs. Default is `json-file`. **Warning**: `docker logs` command works only for `json-file` logging driver. **--mtu**=VALUE diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 9f8daa03f5978..4658a97ed3f6f 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -134,7 +134,7 @@ expect an integer, and they can only be specified once. --ipv6=false Enable IPv6 networking -l, --log-level="info" Set the logging level --label=[] Set key=value labels to the daemon - --log-driver="json-file" Container's logging driver (json-file/none) + --log-driver="json-file" Default driver for container logs --mtu=0 Set the containers network MTU -p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file --registry-mirror=[] Preferred Docker registry mirror From a264e1e83debfaefcb99f5554b8eda9c088a09d1 Mon Sep 17 00:00:00 2001 From: Brendan Dixon Date: Wed, 8 Apr 2015 16:35:37 -0700 Subject: [PATCH 010/332] Corrected int16 overflow and buffer sizes Signed-off-by: Brendan Dixon --- pkg/term/winconsole/console_windows.go | 51 +++++++++++++------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/pkg/term/winconsole/console_windows.go b/pkg/term/winconsole/console_windows.go index bebf6d7c11cac..e6e3f9bcbabc0 100644 --- a/pkg/term/winconsole/console_windows.go +++ b/pkg/term/winconsole/console_windows.go @@ -410,25 +410,25 @@ func getNumberOfChars(fromCoord COORD, toCoord COORD, screenSize COORD) uint32 { var buffer []CHAR_INFO -func clearDisplayRect(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) { +func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) { var writeRegion SMALL_RECT - writeRegion.Top = fromCoord.Y writeRegion.Left = fromCoord.X + writeRegion.Top = fromCoord.Y writeRegion.Right = toCoord.X writeRegion.Bottom = toCoord.Y // allocate and initialize buffer width := toCoord.X - fromCoord.X + 1 height := toCoord.Y - fromCoord.Y + 1 - size := width * height + size := uint32(width) * uint32(height) if size > 0 { - for i := 0; i < int(size); i++ { - buffer[i].UnicodeChar = WCHAR(fillChar) - buffer[i].Attributes = attributes + buffer := make([]CHAR_INFO, size) + for i := range buffer { + buffer[i] = CHAR_INFO{WCHAR(' '), attributes} } // Write to buffer - r, err := writeConsoleOutput(handle, buffer[:size], windowSize, COORD{X: 0, Y: 0}, &writeRegion) + r, err := writeConsoleOutput(handle, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &writeRegion) if !r { if err != nil { return 0, err @@ -439,18 +439,18 @@ func clearDisplayRect(handle uintptr, fillChar rune, attributes WORD, fromCoord return uint32(size), nil } -func clearDisplayRange(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) { +func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) { nw := uint32(0) // start and end on same line if fromCoord.Y == toCoord.Y { - return clearDisplayRect(handle, fillChar, attributes, fromCoord, toCoord, windowSize) + return clearDisplayRect(handle, attributes, fromCoord, toCoord) } // TODO(azlinux): if full screen, optimize // spans more than one line if fromCoord.Y < toCoord.Y { // from start position till end of line for first line - n, err := clearDisplayRect(handle, fillChar, attributes, fromCoord, COORD{X: windowSize.X - 1, Y: fromCoord.Y}, windowSize) + n, err := clearDisplayRect(handle, attributes, fromCoord, COORD{X: toCoord.X, Y: fromCoord.Y}) if err != nil { return nw, err } @@ -458,14 +458,14 @@ func clearDisplayRange(handle uintptr, fillChar rune, attributes WORD, fromCoord // lines between linesBetween := toCoord.Y - fromCoord.Y - 1 if linesBetween > 0 { - n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: windowSize.X - 1, Y: toCoord.Y - 1}, windowSize) + n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: toCoord.X, Y: toCoord.Y - 1}) if err != nil { return nw, err } nw += n } // lines at end - n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord, windowSize) + n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord) if err != nil { return nw, err } @@ -715,9 +715,9 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) switch value { case 0: start = screenBufferInfo.CursorPosition - // end of the screen - end.X = screenBufferInfo.MaximumWindowSize.X - 1 - end.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + // end of the buffer + end.X = screenBufferInfo.Size.X - 1 + end.Y = screenBufferInfo.Size.Y - 1 // cursor cursor = screenBufferInfo.CursorPosition case 1: @@ -733,20 +733,21 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) // start of the screen start.X = 0 start.Y = 0 - // end of the screen - end.X = screenBufferInfo.MaximumWindowSize.X - 1 - end.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + // end of the buffer + end.X = screenBufferInfo.Size.X - 1 + end.Y = screenBufferInfo.Size.Y - 1 // cursor cursor.X = 0 cursor.Y = 0 } - if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil { + if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil { return n, err } // remember the the cursor position is 1 based if err := setConsoleCursorPosition(handle, false, int16(cursor.X), int16(cursor.Y)); err != nil { return n, err } + case "K": // [K // Clears all characters from the cursor position to the end of the line (including the character at the cursor position). @@ -766,7 +767,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) // start is where cursor is start = screenBufferInfo.CursorPosition // end of line - end.X = screenBufferInfo.MaximumWindowSize.X - 1 + end.X = screenBufferInfo.Size.X - 1 end.Y = screenBufferInfo.CursorPosition.Y // cursor remains the same cursor = screenBufferInfo.CursorPosition @@ -782,15 +783,15 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) case 2: // start of the line start.X = 0 - start.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + start.Y = screenBufferInfo.CursorPosition.Y - 1 // end of the line - end.X = screenBufferInfo.MaximumWindowSize.X - 1 - end.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + end.X = screenBufferInfo.Size.X - 1 + end.Y = screenBufferInfo.CursorPosition.Y - 1 // cursor cursor.X = 0 - cursor.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + cursor.Y = screenBufferInfo.CursorPosition.Y - 1 } - if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil { + if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil { return n, err } // remember the the cursor position is 1 based From fa961ce0463ebd738da875c3a6da8171373d7723 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Thu, 9 Apr 2015 13:55:26 -0400 Subject: [PATCH 011/332] Wrap installer in a function This will assure that the install script will not begin executing until after it has been downloaded should it be utilized in a 'curl | bash' workflow. Signed-off-by: Eric Windisch --- hack/install.sh | 387 ++++++++++++++++++++++++------------------------ 1 file changed, 195 insertions(+), 192 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index fcea11d01e71d..305d8fb0f2158 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -20,213 +20,216 @@ command_exists() { command -v "$@" > /dev/null 2>&1 } -case "$(uname -m)" in - *64) - ;; - *) - echo >&2 'Error: you are not using a 64bit platform.' - echo >&2 'Docker currently only supports 64bit platforms.' - exit 1 - ;; -esac - -if command_exists docker || command_exists lxc-docker; then - echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.' - echo >&2 'Please ensure that you do not already have docker installed.' - echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.' - ( set -x; sleep 20 ) -fi - -user="$(id -un 2>/dev/null || true)" - -sh_c='sh -c' -if [ "$user" != 'root' ]; then - if command_exists sudo; then - sh_c='sudo -E sh -c' - elif command_exists su; then - sh_c='su -c' - else - echo >&2 'Error: this installer needs the ability to run commands as root.' - echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.' - exit 1 +do_docker_install() { + case "$(uname -m)" in + *64) + ;; + *) + echo >&2 'Error: you are not using a 64bit platform.' + echo >&2 'Docker currently only supports 64bit platforms.' + exit 1 + ;; + esac + + if command_exists docker || command_exists lxc-docker; then + echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.' + echo >&2 'Please ensure that you do not already have docker installed.' + echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.' + ( set -x; sleep 20 ) fi -fi - -curl='' -if command_exists curl; then - curl='curl -sSL' -elif command_exists wget; then - curl='wget -qO-' -elif command_exists busybox && busybox --list-modules | grep -q wget; then - curl='busybox wget -qO-' -fi - -# perform some very rudimentary platform detection -lsb_dist='' -if command_exists lsb_release; then - lsb_dist="$(lsb_release -si)" -fi -if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then - lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")" -fi -if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then - lsb_dist='debian' -fi -if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then - lsb_dist='fedora' -fi -if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then - lsb_dist="$(. /etc/os-release && echo "$ID")" -fi - -lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" -case "$lsb_dist" in - amzn|fedora|centos) - if [ "$lsb_dist" = 'amzn' ]; then - ( - set -x - $sh_c 'sleep 3; yum -y -q install docker' - ) + + user="$(id -un 2>/dev/null || true)" + + sh_c='sh -c' + if [ "$user" != 'root' ]; then + if command_exists sudo; then + sh_c='sudo -E sh -c' + elif command_exists su; then + sh_c='su -c' else - ( - set -x - $sh_c 'sleep 3; yum -y -q install docker-io' - ) - fi - if command_exists docker && [ -e /var/run/docker.sock ]; then - ( - set -x - $sh_c 'docker version' - ) || true + echo >&2 'Error: this installer needs the ability to run commands as root.' + echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.' + exit 1 fi - your_user=your-user - [ "$user" != 'root' ] && your_user="$user" - echo - echo 'If you would like to use Docker as a non-root user, you should now consider' - echo 'adding your user to the "docker" group with something like:' - echo - echo ' sudo usermod -aG docker' $your_user - echo - echo 'Remember that you will have to log out and back in for this to take effect!' - echo - exit 0 - ;; - - ubuntu|debian|linuxmint) - export DEBIAN_FRONTEND=noninteractive - - did_apt_get_update= - apt_get_update() { - if [ -z "$did_apt_get_update" ]; then - ( set -x; $sh_c 'sleep 3; apt-get update' ) - did_apt_get_update=1 - fi - } + fi - # aufs is preferred over devicemapper; try to ensure the driver is available. - if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then - if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then - kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual" + curl='' + if command_exists curl; then + curl='curl -sSL' + elif command_exists wget; then + curl='wget -qO-' + elif command_exists busybox && busybox --list-modules | grep -q wget; then + curl='busybox wget -qO-' + fi - apt_get_update - ( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true + # perform some very rudimentary platform detection + lsb_dist='' + if command_exists lsb_release; then + lsb_dist="$(lsb_release -si)" + fi + if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then + lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")" + fi + if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then + lsb_dist='debian' + fi + if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then + lsb_dist='fedora' + fi + if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then + lsb_dist="$(. /etc/os-release && echo "$ID")" + fi - if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then - echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)' - echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!' + lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" + case "$lsb_dist" in + amzn|fedora|centos) + if [ "$lsb_dist" = 'amzn' ]; then + ( + set -x + $sh_c 'sleep 3; yum -y -q install docker' + ) + else + ( + set -x + $sh_c 'sleep 3; yum -y -q install docker-io' + ) + fi + if command_exists docker && [ -e /var/run/docker.sock ]; then + ( + set -x + $sh_c 'docker version' + ) || true + fi + your_user=your-user + [ "$user" != 'root' ] && your_user="$user" + echo + echo 'If you would like to use Docker as a non-root user, you should now consider' + echo 'adding your user to the "docker" group with something like:' + echo + echo ' sudo usermod -aG docker' $your_user + echo + echo 'Remember that you will have to log out and back in for this to take effect!' + echo + exit 0 + ;; + + ubuntu|debian|linuxmint) + export DEBIAN_FRONTEND=noninteractive + + did_apt_get_update= + apt_get_update() { + if [ -z "$did_apt_get_update" ]; then + ( set -x; $sh_c 'sleep 3; apt-get update' ) + did_apt_get_update=1 + fi + } + + # aufs is preferred over devicemapper; try to ensure the driver is available. + if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then + if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then + kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual" + + apt_get_update + ( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true + + if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then + echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)' + echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!' + ( set -x; sleep 10 ) + fi + else + echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual' + echo >&2 ' package. We have no AUFS support. Consider installing the packages' + echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.' ( set -x; sleep 10 ) fi - else - echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual' - echo >&2 ' package. We have no AUFS support. Consider installing the packages' - echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.' - ( set -x; sleep 10 ) fi - fi - # install apparmor utils if they're missing and apparmor is enabled in the kernel - # otherwise Docker will fail to start - if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then - if command -v apparmor_parser &> /dev/null; then - echo 'apparmor is enabled in the kernel and apparmor utils were already installed' - else - echo 'apparmor is enabled in the kernel, but apparmor_parser missing' - apt_get_update - ( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' ) + # install apparmor utils if they're missing and apparmor is enabled in the kernel + # otherwise Docker will fail to start + if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then + if command -v apparmor_parser &> /dev/null; then + echo 'apparmor is enabled in the kernel and apparmor utils were already installed' + else + echo 'apparmor is enabled in the kernel, but apparmor_parser missing' + apt_get_update + ( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' ) + fi fi - fi - if [ ! -e /usr/lib/apt/methods/https ]; then - apt_get_update - ( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https ca-certificates' ) - fi - if [ -z "$curl" ]; then - apt_get_update - ( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' ) - curl='curl -sSL' - fi - ( - set -x - if [ "https://get.docker.com/" = "$url" ]; then - $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9" - elif [ "https://test.docker.com/" = "$url" ]; then - $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6" - else - $sh_c "$curl ${url}gpg | apt-key add -" + if [ ! -e /usr/lib/apt/methods/https ]; then + apt_get_update + ( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https ca-certificates' ) + fi + if [ -z "$curl" ]; then + apt_get_update + ( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' ) + curl='curl -sSL' fi - $sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list" - $sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker' - ) - if command_exists docker && [ -e /var/run/docker.sock ]; then ( set -x - $sh_c 'docker version' - ) || true - fi - your_user=your-user - [ "$user" != 'root' ] && your_user="$user" - echo - echo 'If you would like to use Docker as a non-root user, you should now consider' - echo 'adding your user to the "docker" group with something like:' - echo - echo ' sudo usermod -aG docker' $your_user - echo - echo 'Remember that you will have to log out and back in for this to take effect!' - echo - exit 0 - ;; - - gentoo) - if [ "$url" = "https://test.docker.com/" ]; then - echo >&2 - echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.' - echo >&2 ' The portage tree should contain the latest stable release of Docker, but' - echo >&2 ' if you want something more recent, you can always use the live ebuild' - echo >&2 ' provided in the "docker" overlay available via layman. For more' - echo >&2 ' instructions, please see the following URL:' - echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay' - echo >&2 ' After adding the "docker" overlay, you should be able to:' - echo >&2 ' emerge -av =app-emulation/docker-9999' - echo >&2 - exit 1 - fi - - ( - set -x - $sh_c 'sleep 3; emerge app-emulation/docker' - ) - exit 0 - ;; -esac - -cat >&2 <<'EOF' - - Either your platform is not easily detectable, is not supported by this - installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have - a package for Docker. Please visit the following URL for more detailed - installation instructions: + if [ "https://get.docker.com/" = "$url" ]; then + $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9" + elif [ "https://test.docker.com/" = "$url" ]; then + $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6" + else + $sh_c "$curl ${url}gpg | apt-key add -" + fi + $sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list" + $sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker' + ) + if command_exists docker && [ -e /var/run/docker.sock ]; then + ( + set -x + $sh_c 'docker version' + ) || true + fi + your_user=your-user + [ "$user" != 'root' ] && your_user="$user" + echo + echo 'If you would like to use Docker as a non-root user, you should now consider' + echo 'adding your user to the "docker" group with something like:' + echo + echo ' sudo usermod -aG docker' $your_user + echo + echo 'Remember that you will have to log out and back in for this to take effect!' + echo + exit 0 + ;; + + gentoo) + if [ "$url" = "https://test.docker.com/" ]; then + echo >&2 + echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.' + echo >&2 ' The portage tree should contain the latest stable release of Docker, but' + echo >&2 ' if you want something more recent, you can always use the live ebuild' + echo >&2 ' provided in the "docker" overlay available via layman. For more' + echo >&2 ' instructions, please see the following URL:' + echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay' + echo >&2 ' After adding the "docker" overlay, you should be able to:' + echo >&2 ' emerge -av =app-emulation/docker-9999' + echo >&2 + exit 1 + fi - https://docs.docker.com/en/latest/installation/ + ( + set -x + $sh_c 'sleep 3; emerge app-emulation/docker' + ) + exit 0 + ;; + esac + + echo >&2 + echo >&2 'Either your platform is not easily detectable, is not supported by this' + echo >&2 'installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have' + echo >&2 'a package for Docker. Please visit the following URL for more detailed' + echo >&2 'installation instructions:' + echo >&2 + echo >&2 ' https://docs.docker.com/en/latest/installation/' + echo >&2 + exit 1 +} -EOF +do_docker_install exit 1 From e4addf1c016dd4b6510e17e3f0d23032f880ba6f Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Thu, 9 Apr 2015 14:20:03 -0700 Subject: [PATCH 012/332] Docs cleanup - Contributor Guide Signed-off-by: Megan Kostick --- docs/sources/project/find-an-issue.md | 2 +- docs/sources/project/work-issue.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/find-an-issue.md b/docs/sources/project/find-an-issue.md index 0cfe7d7b5937f..a5a3c8bfe1513 100644 --- a/docs/sources/project/find-an-issue.md +++ b/docs/sources/project/find-an-issue.md @@ -158,7 +158,7 @@ To sync your repository: origin https://github.com/moxiegirl/docker.git (fetch) origin https://github.com/moxiegirl/docker.git (push) upstream https://github.com/docker/docker.git (fetch) - upstream https://github.com/docker/docker.git ( + upstream https://github.com/docker/docker.git (push) If the `upstream` is missing, add it. diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 5e70bc32cc47e..1719195cd4a4c 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -149,7 +149,7 @@ You should pull and rebase frequently as you work. 2. Make sure you are in your branch. - $ git branch 11038-fix-rhel-link + $ git checkout 11038-fix-rhel-link 3. Fetch all the changes from the `upstream master` branch. From e290a22dc935c2472e08be7362b7d3b0f6303615 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 9 Apr 2015 23:51:22 +0200 Subject: [PATCH 013/332] Remove job from resize&execResize Signed-off-by: Antonio Murdaca --- api/server/server.go | 33 +++++++++++++++++++++++++++++++-- daemon/daemon.go | 2 -- daemon/resize.go | 44 +------------------------------------------- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 97bf08bb0680a..912af638ce10b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1005,9 +1005,26 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re if vars == nil { return fmt.Errorf("Missing parameter") } - if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { + + height, err := strconv.Atoi(r.Form.Get("h")) + if err != nil { + return nil + } + width, err := strconv.Atoi(r.Form.Get("w")) + if err != nil { + return nil + } + + d := getDaemon(eng) + cont, err := d.Get(vars["name"]) + if err != nil { return err } + + if err := cont.Resize(height, width); err != nil { + return err + } + return nil } @@ -1363,9 +1380,21 @@ func postContainerExecResize(eng *engine.Engine, version version.Version, w http if vars == nil { return fmt.Errorf("Missing parameter") } - if err := eng.Job("execResize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { + + height, err := strconv.Atoi(r.Form.Get("h")) + if err != nil { + return nil + } + width, err := strconv.Atoi(r.Form.Get("w")) + if err != nil { + return nil + } + + d := getDaemon(eng) + if err := d.ContainerExecResize(vars["name"], height, width); err != nil { return err } + return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 36d05cd92c12e..06e9bb440afec 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -125,7 +125,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "info": daemon.CmdInfo, "kill": daemon.ContainerKill, "logs": daemon.ContainerLogs, - "resize": daemon.ContainerResize, "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, @@ -133,7 +132,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, - "execResize": daemon.ContainerExecResize, "execInspect": daemon.ContainerExecInspect, } { if err := eng.Register(name, method); err != nil { diff --git a/daemon/resize.go b/daemon/resize.go index fce06753e0f89..060634b13bfc6 100644 --- a/daemon/resize.go +++ b/daemon/resize.go @@ -1,48 +1,6 @@ package daemon -import ( - "fmt" - "strconv" - - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerResize(job *engine.Job) error { - if len(job.Args) != 3 { - return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) - } - name := job.Args[0] - height, err := strconv.Atoi(job.Args[1]) - if err != nil { - return err - } - width, err := strconv.Atoi(job.Args[2]) - if err != nil { - return err - } - container, err := daemon.Get(name) - if err != nil { - return err - } - if err := container.Resize(height, width); err != nil { - return err - } - return nil -} - -func (daemon *Daemon) ContainerExecResize(job *engine.Job) error { - if len(job.Args) != 3 { - return fmt.Errorf("Not enough arguments. Usage: %s EXEC HEIGHT WIDTH\n", job.Name) - } - name := job.Args[0] - height, err := strconv.Atoi(job.Args[1]) - if err != nil { - return err - } - width, err := strconv.Atoi(job.Args[2]) - if err != nil { - return err - } +func (daemon *Daemon) ContainerExecResize(name string, height, width int) error { execConfig, err := daemon.getExecConfig(name) if err != nil { return err From 3cb751906a8a0397dcf57d8fca97c0e9c0c418e8 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 9 Apr 2015 11:56:47 -0700 Subject: [PATCH 014/332] Remove Job from `docker kill` Signed-off-by: Doug Davis --- api/server/server.go | 29 ++++++++++++++++++++++++----- daemon/daemon.go | 1 - daemon/kill.go | 34 ++-------------------------------- integration/server_test.go | 4 ++-- integration/utils_test.go | 2 +- 5 files changed, 29 insertions(+), 41 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d8b40f02fa61a..f8eba047eefea 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -28,6 +28,7 @@ import ( "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" @@ -193,16 +194,34 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - if err := parseForm(r); err != nil { + err := parseForm(r) + if err != nil { return err } - job := eng.Job("kill", vars["name"]) - if sig := r.Form.Get("signal"); sig != "" { - job.Args = append(job.Args, sig) + + var sig uint64 + name := vars["name"] + + // If we have a signal, look at it. Otherwise, do nothing + if sigStr := vars["signal"]; sigStr != "" { + // Check if we passed the signal as a number: + // The largest legal signal is 31, so let's parse on 5 bits + sig, err = strconv.ParseUint(sigStr, 10, 5) + if err != nil { + // The signal is not a number, treat it as a string (either like + // "KILL" or like "SIGKILL") + sig = uint64(signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")]) + } + + if sig == 0 { + return fmt.Errorf("Invalid signal: %s", sigStr) + } } - if err := job.Run(); err != nil { + + if err = getDaemon(eng).ContainerKill(name, sig); err != nil { return err } + w.WriteHeader(http.StatusNoContent) return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index c9b5285f0a6aa..b5d11f04cfa83 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -123,7 +123,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "create": daemon.ContainerCreate, "export": daemon.ContainerExport, "info": daemon.CmdInfo, - "kill": daemon.ContainerKill, "logs": daemon.ContainerLogs, "pause": daemon.ContainerPause, "resize": daemon.ContainerResize, diff --git a/daemon/kill.go b/daemon/kill.go index 56bcad900e70a..5d828f16b2321 100644 --- a/daemon/kill.go +++ b/daemon/kill.go @@ -2,43 +2,14 @@ package daemon import ( "fmt" - "strconv" - "strings" "syscall" - - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/signal" ) // ContainerKill send signal to the container // If no signal is given (sig 0), then Kill with SIGKILL and wait // for the container to exit. // If a signal is given, then just send it to the container and return. -func (daemon *Daemon) ContainerKill(job *engine.Job) error { - if n := len(job.Args); n < 1 || n > 2 { - return fmt.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) - } - var ( - name = job.Args[0] - sig uint64 - err error - ) - - // If we have a signal, look at it. Otherwise, do nothing - if len(job.Args) == 2 && job.Args[1] != "" { - // Check if we passed the signal as a number: - // The largest legal signal is 31, so let's parse on 5 bits - sig, err = strconv.ParseUint(job.Args[1], 10, 5) - if err != nil { - // The signal is not a number, treat it as a string (either like "KILL" or like "SIGKILL") - sig = uint64(signal.SignalMap[strings.TrimPrefix(job.Args[1], "SIG")]) - } - - if sig == 0 { - return fmt.Errorf("Invalid signal: %s", job.Args[1]) - } - } - +func (daemon *Daemon) ContainerKill(name string, sig uint64) error { container, err := daemon.Get(name) if err != nil { return err @@ -49,13 +20,12 @@ func (daemon *Daemon) ContainerKill(job *engine.Job) error { if err := container.Kill(); err != nil { return fmt.Errorf("Cannot kill container %s: %s", name, err) } - container.LogEvent("kill") } else { // Otherwise, just send the requested signal if err := container.KillSig(int(sig)); err != nil { return fmt.Errorf("Cannot kill container %s: %s", name, err) } - // FIXME: Add event for signals } + container.LogEvent("kill") return nil } diff --git a/integration/server_test.go b/integration/server_test.go index b2c4dd80a4004..5dc4f1aa4a59b 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -132,8 +132,8 @@ func TestRestartKillWait(t *testing.T) { if err := job.Run(); err != nil { t.Fatal(err) } - job = eng.Job("kill", id) - if err := job.Run(); err != nil { + + if err := runtime.ContainerKill(id, 0); err != nil { t.Fatal(err) } diff --git a/integration/utils_test.go b/integration/utils_test.go index f8afe62b6aa91..3e16165db0d82 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -104,7 +104,7 @@ func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error { } func containerKill(eng *engine.Engine, id string, t Fataler) { - if err := eng.Job("kill", id).Run(); err != nil { + if err := getDaemon(eng).ContainerKill(id, 0); err != nil { t.Fatal(err) } } From f01d755cd0af7b17596cc084871f7f032995cac3 Mon Sep 17 00:00:00 2001 From: WiseTrem Date: Thu, 9 Apr 2015 23:26:36 +0300 Subject: [PATCH 015/332] Remove pools_nopool.go & build tag from pools.go Fix #11576 Signed-off-by: Gleb Shepelev --- pkg/pools/pools.go | 2 -- pkg/pools/pools_nopool.go | 73 --------------------------------------- 2 files changed, 75 deletions(-) delete mode 100644 pkg/pools/pools_nopool.go diff --git a/pkg/pools/pools.go b/pkg/pools/pools.go index 5338a0cfb2561..f366fa67a773c 100644 --- a/pkg/pools/pools.go +++ b/pkg/pools/pools.go @@ -1,5 +1,3 @@ -// +build go1.3 - // Package pools provides a collection of pools which provide various // data types with buffers. These can be used to lower the number of // memory allocations and reuse buffers. diff --git a/pkg/pools/pools_nopool.go b/pkg/pools/pools_nopool.go deleted file mode 100644 index 48903c2396a27..0000000000000 --- a/pkg/pools/pools_nopool.go +++ /dev/null @@ -1,73 +0,0 @@ -// +build !go1.3 - -package pools - -import ( - "bufio" - "io" - - "github.com/docker/docker/pkg/ioutils" -) - -var ( - BufioReader32KPool *BufioReaderPool - BufioWriter32KPool *BufioWriterPool -) - -const buffer32K = 32 * 1024 - -type BufioReaderPool struct { - size int -} - -func init() { - BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K) - BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K) -} - -func newBufioReaderPoolWithSize(size int) *BufioReaderPool { - return &BufioReaderPool{size: size} -} - -func (bufPool *BufioReaderPool) Get(r io.Reader) *bufio.Reader { - return bufio.NewReaderSize(r, bufPool.size) -} - -func (bufPool *BufioReaderPool) Put(b *bufio.Reader) { - b.Reset(nil) -} - -func (bufPool *BufioReaderPool) NewReadCloserWrapper(buf *bufio.Reader, r io.Reader) io.ReadCloser { - return ioutils.NewReadCloserWrapper(r, func() error { - if readCloser, ok := r.(io.ReadCloser); ok { - return readCloser.Close() - } - return nil - }) -} - -type BufioWriterPool struct { - size int -} - -func newBufioWriterPoolWithSize(size int) *BufioWriterPool { - return &BufioWriterPool{size: size} -} - -func (bufPool *BufioWriterPool) Get(w io.Writer) *bufio.Writer { - return bufio.NewWriterSize(w, bufPool.size) -} - -func (bufPool *BufioWriterPool) Put(b *bufio.Writer) { - b.Reset(nil) -} - -func (bufPool *BufioWriterPool) NewWriteCloserWrapper(buf *bufio.Writer, w io.Writer) io.WriteCloser { - return ioutils.NewWriteCloserWrapper(w, func() error { - buf.Flush() - if writeCloser, ok := w.(io.WriteCloser); ok { - return writeCloser.Close() - } - return nil - }) -} From 8636a219911536123decb547dab9bf50ebb2c8f8 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Fri, 10 Apr 2015 09:14:01 +0800 Subject: [PATCH 016/332] add TestContainerApiPause case Signed-off-by: Yuan Sun --- integration-cli/docker_api_containers_test.go | 48 +++++++++++++++++-- integration-cli/docker_utils.go | 3 ++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 02d069f5987d1..07793a8fca89d 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -3,14 +3,14 @@ package main import ( "bytes" "encoding/json" + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "os/exec" "strings" "testing" "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestContainerApiGetAll(t *testing.T) { @@ -553,3 +553,45 @@ func TestPostContainerBindNormalVolume(t *testing.T) { logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume") } + +func TestContainerApiPause(t *testing.T) { + defer deleteAllContainers() + defer unpauseAllContainers() + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "30") + out, _, err := runCommandWithOutput(runCmd) + + if err != nil { + t.Fatalf("failed to create a container: %s, %v", out, err) + } + ContainerID := strings.TrimSpace(out) + + if _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + t.Fatalf("POST a container pause: sockRequest failed: %v", err) + } + + pausedContainers, err := getSliceOfPausedContainers() + + if err != nil { + t.Fatalf("error thrown while checking if containers were paused: %v", err) + } + + if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] { + t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) + } + + if _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + t.Fatalf("POST a container pause: sockRequest failed: %v", err) + } + + pausedContainers, err = getSliceOfPausedContainers() + + if err != nil { + t.Fatalf("error thrown while checking if containers were paused: %v", err) + } + + if pausedContainers != nil { + t.Fatalf("There should be no paused container.") + } + + logDone("container REST API - check POST containers/pause nad unpause") +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 843a07a20ef7f..943c1e02a4a35 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -389,6 +389,9 @@ func getPausedContainers() (string, error) { func getSliceOfPausedContainers() ([]string, error) { out, err := getPausedContainers() if err == nil { + if len(out) == 0 { + return nil, err + } slice := strings.Split(strings.TrimSpace(out), "\n") return slice, err } From 3e096cb9c9e9d708df7982be5694daaa62bb4849 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 9 Apr 2015 15:13:01 -0700 Subject: [PATCH 017/332] Remove Job from `docker top` Signed-off-by: Doug Davis --- api/client/top.go | 19 +++++++++--------- api/server/server.go | 11 +++++++--- api/types/types.go | 6 ++++++ daemon/daemon.go | 1 - daemon/top.go | 48 ++++++++++++++++++-------------------------- 5 files changed, 44 insertions(+), 41 deletions(-) diff --git a/api/client/top.go b/api/client/top.go index 9de04cac68ef3..4975f47597f9e 100644 --- a/api/client/top.go +++ b/api/client/top.go @@ -1,12 +1,13 @@ package client import ( + "encoding/json" "fmt" "net/url" "strings" "text/tabwriter" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" ) @@ -28,17 +29,17 @@ func (cli *DockerCli) CmdTop(args ...string) error { if err != nil { return err } - var procs engine.Env - if err := procs.Decode(stream); err != nil { + + procList := types.ContainerProcessList{} + err = json.NewDecoder(stream).Decode(&procList) + if err != nil { return err } + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t")) - processes := [][]string{} - if err := procs.GetJson("Processes", &processes); err != nil { - return err - } - for _, proc := range processes { + fmt.Fprintln(w, strings.Join(procList.Titles, "\t")) + + for _, proc := range procList.Processes { fmt.Fprintln(w, strings.Join(proc, "\t")) } w.Flush() diff --git a/api/server/server.go b/api/server/server.go index 912af638ce10b..7b38ea30cc905 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -471,16 +471,21 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon if version.LessThan("1.4") { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } + if vars == nil { return fmt.Errorf("Missing parameter") } + if err := parseForm(r); err != nil { return err } - job := eng.Job("top", vars["name"], r.Form.Get("ps_args")) - streamJSON(job, w, false) - return job.Run() + procList, err := getDaemon(eng).ContainerTop(vars["name"], r.Form.Get("ps_args")) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, procList) } func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/types/types.go b/api/types/types.go index 77b211705dc2d..48c9265a5040b 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -103,3 +103,9 @@ type Container struct { type CopyConfig struct { Resource string } + +// GET "/containers/{name:.*}/top" +type ContainerProcessList struct { + Processes [][]string + Titles []string +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 06e9bb440afec..ea49a8b87c4fc 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -128,7 +128,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, - "top": daemon.ContainerTop, "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, diff --git a/daemon/top.go b/daemon/top.go index 1e8c39987c9b4..14b252370588f 100644 --- a/daemon/top.go +++ b/daemon/top.go @@ -6,54 +6,48 @@ import ( "strconv" "strings" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" ) -func (daemon *Daemon) ContainerTop(job *engine.Job) error { - if len(job.Args) != 1 && len(job.Args) != 2 { - return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) - } - var ( - name = job.Args[0] +func (daemon *Daemon) ContainerTop(name string, psArgs string) (*types.ContainerProcessList, error) { + if psArgs == "" { psArgs = "-ef" - ) - - if len(job.Args) == 2 && job.Args[1] != "" { - psArgs = job.Args[1] } container, err := daemon.Get(name) if err != nil { - return err + return nil, err } + if !container.IsRunning() { - return fmt.Errorf("Container %s is not running", name) + return nil, fmt.Errorf("Container %s is not running", name) } + pids, err := daemon.ExecutionDriver().GetPidsForContainer(container.ID) if err != nil { - return err + return nil, err } + output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output() if err != nil { - return fmt.Errorf("Error running ps: %s", err) + return nil, fmt.Errorf("Error running ps: %s", err) } + procList := &types.ContainerProcessList{} + lines := strings.Split(string(output), "\n") - header := strings.Fields(lines[0]) - out := &engine.Env{} - out.SetList("Titles", header) + procList.Titles = strings.Fields(lines[0]) pidIndex := -1 - for i, name := range header { + for i, name := range procList.Titles { if name == "PID" { pidIndex = i } } if pidIndex == -1 { - return fmt.Errorf("Couldn't find PID field in ps output") + return nil, fmt.Errorf("Couldn't find PID field in ps output") } - processes := [][]string{} for _, line := range lines[1:] { if len(line) == 0 { continue @@ -61,20 +55,18 @@ func (daemon *Daemon) ContainerTop(job *engine.Job) error { fields := strings.Fields(line) p, err := strconv.Atoi(fields[pidIndex]) if err != nil { - return fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) + return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) } for _, pid := range pids { if pid == p { // Make sure number of fields equals number of header titles // merging "overhanging" fields - process := fields[:len(header)-1] - process = append(process, strings.Join(fields[len(header)-1:], " ")) - processes = append(processes, process) + process := fields[:len(procList.Titles)-1] + process = append(process, strings.Join(fields[len(procList.Titles)-1:], " ")) + procList.Processes = append(procList.Processes, process) } } } - out.SetJson("Processes", processes) - out.WriteTo(job.Stdout) - return nil + return procList, nil } From 6842bba163de753a5ff3ddbaf9b408c89235022b Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 9 Apr 2015 12:14:53 -0600 Subject: [PATCH 018/332] Commonalize more bits of install.sh (especially standardizing around "cat <<-EOF") Signed-off-by: Andrew "Tianon" Page --- hack/install.sh | 112 +++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index 305d8fb0f2158..b0177e6708909 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -20,21 +20,41 @@ command_exists() { command -v "$@" > /dev/null 2>&1 } -do_docker_install() { +echo_docker_as_nonroot() { + your_user=your-user + [ "$user" != 'root' ] && your_user="$user" + # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output + cat <<-EOF + + If you would like to use Docker as a non-root user, you should now consider + adding your user to the "docker" group with something like: + + sudo usermod -aG docker $your_user + + Remember that you will have to log out and back in for this to take effect! + + EOF +} + +do_install() { case "$(uname -m)" in *64) ;; *) - echo >&2 'Error: you are not using a 64bit platform.' - echo >&2 'Docker currently only supports 64bit platforms.' + cat >&2 <<-'EOF' + Error: you are not using a 64bit platform. + Docker currently only supports 64bit platforms. + EOF exit 1 ;; esac if command_exists docker || command_exists lxc-docker; then - echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.' - echo >&2 'Please ensure that you do not already have docker installed.' - echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.' + cat >&2 <<-'EOF' + Warning: "docker" or "lxc-docker" command appears to already exist. + Please ensure that you do not already have docker installed. + You may press Ctrl+C now to abort this process and rectify this situation. + EOF ( set -x; sleep 20 ) fi @@ -47,8 +67,10 @@ do_docker_install() { elif command_exists su; then sh_c='su -c' else - echo >&2 'Error: this installer needs the ability to run commands as root.' - echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.' + cat >&2 <<-'EOF' + Error: this installer needs the ability to run commands as root. + We are unable to find either "sudo" or "su" available to make this happen. + EOF exit 1 fi fi @@ -100,16 +122,7 @@ do_docker_install() { $sh_c 'docker version' ) || true fi - your_user=your-user - [ "$user" != 'root' ] && your_user="$user" - echo - echo 'If you would like to use Docker as a non-root user, you should now consider' - echo 'adding your user to the "docker" group with something like:' - echo - echo ' sudo usermod -aG docker' $your_user - echo - echo 'Remember that you will have to log out and back in for this to take effect!' - echo + echo_docker_as_nonroot exit 0 ;; @@ -184,31 +197,28 @@ do_docker_install() { $sh_c 'docker version' ) || true fi - your_user=your-user - [ "$user" != 'root' ] && your_user="$user" - echo - echo 'If you would like to use Docker as a non-root user, you should now consider' - echo 'adding your user to the "docker" group with something like:' - echo - echo ' sudo usermod -aG docker' $your_user - echo - echo 'Remember that you will have to log out and back in for this to take effect!' - echo + echo_docker_as_nonroot exit 0 ;; gentoo) if [ "$url" = "https://test.docker.com/" ]; then - echo >&2 - echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.' - echo >&2 ' The portage tree should contain the latest stable release of Docker, but' - echo >&2 ' if you want something more recent, you can always use the live ebuild' - echo >&2 ' provided in the "docker" overlay available via layman. For more' - echo >&2 ' instructions, please see the following URL:' - echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay' - echo >&2 ' After adding the "docker" overlay, you should be able to:' - echo >&2 ' emerge -av =app-emulation/docker-9999' - echo >&2 + # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output + cat >&2 <<-'EOF' + + You appear to be trying to install the latest nightly build in Gentoo.' + The portage tree should contain the latest stable release of Docker, but' + if you want something more recent, you can always use the live ebuild' + provided in the "docker" overlay available via layman. For more' + instructions, please see the following URL:' + + https://github.com/tianon/docker-overlay#using-this-overlay' + + After adding the "docker" overlay, you should be able to:' + + emerge -av =app-emulation/docker-9999' + + EOF exit 1 fi @@ -220,16 +230,20 @@ do_docker_install() { ;; esac - echo >&2 - echo >&2 'Either your platform is not easily detectable, is not supported by this' - echo >&2 'installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have' - echo >&2 'a package for Docker. Please visit the following URL for more detailed' - echo >&2 'installation instructions:' - echo >&2 - echo >&2 ' https://docs.docker.com/en/latest/installation/' - echo >&2 - exit 1 + # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output + cat >&2 <<-'EOF' + + Either your platform is not easily detectable, is not supported by this + installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have + a package for Docker. Please visit the following URL for more detailed + installation instructions: + + https://docs.docker.com/en/latest/installation/ + + EOF + exit 1 } -do_docker_install -exit 1 +# wrapped up in a function so that we have some protection against only getting +# half the file during "curl | sh" +do_install From bf57339527f153b502a6443a495824a40768e39f Mon Sep 17 00:00:00 2001 From: David Young Date: Sun, 4 Jan 2015 14:47:01 +0800 Subject: [PATCH 019/332] Add comment column in docker history command output Signed-off-by: David Young --- api/client/history.go | 5 ++- api/types/types.go | 1 + docs/man/docker-history.1.md | 20 ++++++++- docs/sources/reference/commandline/cli.md | 21 ++++++++++ graph/history.go | 1 + integration-cli/docker_cli_history_test.go | 47 ++++++++++++++++++++++ 6 files changed, 92 insertions(+), 3 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 6e0cdb24cde4a..8736b057b75e7 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -36,7 +36,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") + fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\tCOMMENT") } for _, entry := range history { @@ -53,7 +53,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error { } else { fmt.Fprintf(w, "%s\t", utils.Trunc(entry.CreatedBy, 45)) } - fmt.Fprintf(w, "%s", units.HumanSize(float64(entry.Size))) + fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) + fmt.Fprintf(w, "%s\n", entry.Comment) } fmt.Fprintf(w, "\n") } diff --git a/api/types/types.go b/api/types/types.go index 77b211705dc2d..774203d023fa8 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -49,6 +49,7 @@ type ImageHistory struct { CreatedBy string Tags []string Size int64 + Comment string } // DELETE "/images/{name:.*}" diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 24f928c291899..0c322d795f7a2 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -26,11 +26,29 @@ Show the history of when and how an image was created. Only show numeric IDs. The default is *false*. # EXAMPLES +<<<<<<< HEAD $ docker history fedora IMAGE CREATED CREATED BY SIZE +======= + +## Show the history of images created through docker build command + + $ sudo docker history fedora + IMAGE CREATED CREATED BY SIZE COMMENT +>>>>>>> Add comment column in docker history command output 105182bb5e8b 5 days ago /bin/sh -c #(nop) ADD file:71356d2ad59aa3119d 372.7 MB 73bd853d2ea5 13 days ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B - 511136ea3c5a 10 months ago 0 B + 511136ea3c5a 10 months ago 0 B Imported from - + +## Show the history of images created through docker commit command +`docker commit` command accepts a **-m** parameter to provide comment messages to the image. You can see these messages in image history. + + $ sudo docker history docker:scm + IMAGE CREATED CREATED BY SIZE COMMENT + 2ac9d1098bf1 3 months ago /bin/bash 241.4 MB Added Apache to Fedora base image + 88b42ffd1f7c 5 months ago /bin/sh -c #(nop) ADD file:1fd8d7f9f6557cafc7 373.7 MB + c69cab00d6ef 5 months ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B + 511136ea3c5a 19 months ago 0 B Imported from - # HISTORY April 2014, Originally compiled by William Henry (whenry at redhat dot com) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 9f8daa03f5978..f7d47edb04653 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1151,6 +1151,7 @@ This will create a new Bash session in the container `ubuntu_bash`. To see how the `docker:latest` image was built: +<<<<<<< HEAD $ docker history docker IMAGE CREATED CREATED BY SIZE 3e23a5875458790b7a806f95f7ec0d0b2a5c1659bfc899c89f939f6d5b8f7094 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B @@ -1159,6 +1160,26 @@ To see how the `docker:latest` image was built: 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 +======= + $ sudo docker history docker + IMAGE CREATED CREATED BY SIZE COMMENT + 3e23a5875458 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B + 8578938dd170 8 days ago /bin/sh -c dpkg-reconfigure locales && loc 1.245 MB + be51b77efb42 8 days ago /bin/sh -c apt-get update && apt-get install 338.3 MB + 4b137612be55 6 weeks ago /bin/sh -c #(nop) ADD jessie.tar.xz in / 121 MB + 750d58736b4b 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi >>>>>> Add comment column in docker history command output ## images diff --git a/graph/history.go b/graph/history.go index 1290de9a30781..9c3efabaf5478 100644 --- a/graph/history.go +++ b/graph/history.go @@ -41,6 +41,7 @@ func (s *TagStore) CmdHistory(job *engine.Job) error { CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), Tags: lookupMap[img.ID], Size: img.Size, + Comment: img.Comment, }) return nil }) diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index ecb0a3a07ec06..89bb53e71e9b8 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os/exec" + "regexp" "strings" "testing" ) @@ -82,3 +83,49 @@ func TestHistoryNonExistentImage(t *testing.T) { } logDone("history - history on non-existent image must pass") } + +func TestHistoryImageWithComment(t *testing.T) { + + // make a image through docker commit [ -m messages ] + runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + t.Fatalf("failed to run container: %s, %v", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + + waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) + if _, _, err = runCommandWithOutput(waitCmd); err != nil { + t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + } + + commitCmd := exec.Command(dockerBinary, "commit", "-m=This is a comment", cleanedContainerID) + out, _, err = runCommandWithOutput(commitCmd) + if err != nil { + t.Fatalf("failed to commit container to image: %s, %v", out, err) + } + + cleanedImageID := stripTrailingCharacters(out) + deleteContainer(cleanedContainerID) + defer deleteImages(cleanedImageID) + + // test docker history to check comment messages + historyCmd := exec.Command(dockerBinary, "history", cleanedImageID) + out, exitCode, err := runCommandWithOutput(historyCmd) + if err != nil || exitCode != 0 { + t.Fatalf("failed to get image history: %s, %v", out, err) + } + + expectedValue := "This is a comment" + + outputLine := strings.Split(out, "\n")[1] + outputTabs := regexp.MustCompile(" +").Split(outputLine, -1) + actualValue := outputTabs[len(outputTabs)-1] + + if !strings.Contains(actualValue, expectedValue) { + t.Fatalf("Expected comments \"%s\", but found \"%s\"", expectedValue, actualValue) + } + + logDone("history - history on image with comment") +} From 8d682bf734539ade2d618349f82b8b5e83a87167 Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 26 Mar 2015 12:39:50 +0800 Subject: [PATCH 020/332] Refine document by review comments Signed-off-by: David Young --- docs/man/docker-history.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 0c322d795f7a2..cd8fb6e6dc5d6 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -41,7 +41,7 @@ Show the history of when and how an image was created. 511136ea3c5a 10 months ago 0 B Imported from - ## Show the history of images created through docker commit command -`docker commit` command accepts a **-m** parameter to provide comment messages to the image. You can see these messages in image history. +The `docker commit` command has a **-m** flag for adding comments to the image. These comments will be displayed in the image history. $ sudo docker history docker:scm IMAGE CREATED CREATED BY SIZE COMMENT diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index f7d47edb04653..ec26d0418c57a 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1170,7 +1170,7 @@ To see how the `docker:latest` image was built: 750d58736b4b 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi Date: Wed, 8 Apr 2015 19:43:25 -0400 Subject: [PATCH 021/332] Rebase + some fixes Signed-off-by: Tibor Vass --- api/client/history.go | 2 +- docs/man/docker-history.1.md | 10 +----- docs/sources/reference/commandline/cli.md | 13 +------- integration-cli/docker_cli_history_test.go | 38 ++++++++++------------ 4 files changed, 20 insertions(+), 43 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 8736b057b75e7..844a6fb770e2c 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -54,7 +54,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintf(w, "%s\t", utils.Trunc(entry.CreatedBy, 45)) } fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) - fmt.Fprintf(w, "%s\n", entry.Comment) + fmt.Fprintf(w, "%s", entry.Comment) } fmt.Fprintf(w, "\n") } diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index cd8fb6e6dc5d6..2b38d83e6732d 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -26,21 +26,13 @@ Show the history of when and how an image was created. Only show numeric IDs. The default is *false*. # EXAMPLES -<<<<<<< HEAD $ docker history fedora - IMAGE CREATED CREATED BY SIZE -======= - -## Show the history of images created through docker build command - - $ sudo docker history fedora IMAGE CREATED CREATED BY SIZE COMMENT ->>>>>>> Add comment column in docker history command output 105182bb5e8b 5 days ago /bin/sh -c #(nop) ADD file:71356d2ad59aa3119d 372.7 MB 73bd853d2ea5 13 days ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B 511136ea3c5a 10 months ago 0 B Imported from - -## Show the history of images created through docker commit command +## Display comments in the image history The `docker commit` command has a **-m** flag for adding comments to the image. These comments will be displayed in the image history. $ sudo docker history docker:scm diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index ec26d0418c57a..507f2990b98e7 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1151,17 +1151,7 @@ This will create a new Bash session in the container `ubuntu_bash`. To see how the `docker:latest` image was built: -<<<<<<< HEAD $ 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 -======= - $ sudo docker history docker IMAGE CREATED CREATED BY SIZE COMMENT 3e23a5875458 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B 8578938dd170 8 days ago /bin/sh -c dpkg-reconfigure locales && loc 1.245 MB @@ -1172,14 +1162,13 @@ To see how the `docker:latest` image was built: To see how the `docker:apache` image was added to a container's base image: - $ sudo docker history docker:scm + $ docker history docker:scm IMAGE CREATED CREATED BY SIZE COMMENT 2ac9d1098bf1 3 months ago /bin/bash 241.4 MB Added Apache to Fedora base image 88b42ffd1f7c 5 months ago /bin/sh -c #(nop) ADD file:1fd8d7f9f6557cafc7 373.7 MB c69cab00d6ef 5 months ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B 511136ea3c5a 19 months ago 0 B Imported from - ->>>>>>> Add comment column in docker history command output ## images diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 89bb53e71e9b8..9fd2180d3e303 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os/exec" - "regexp" "strings" "testing" ) @@ -85,46 +84,43 @@ func TestHistoryNonExistentImage(t *testing.T) { } func TestHistoryImageWithComment(t *testing.T) { + name := "testhistoryimagewithcomment" + defer deleteContainer(name) + defer deleteImages(name) // make a image through docker commit [ -m messages ] - runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") - out, _, _, err := runCommandWithStdoutStderr(runCmd) + //runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true") + out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatalf("failed to run container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) - - waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) - if _, _, err = runCommandWithOutput(waitCmd); err != nil { + waitCmd := exec.Command(dockerBinary, "wait", name) + if out, _, err := runCommandWithOutput(waitCmd); err != nil { t.Fatalf("error thrown while waiting for container: %s, %v", out, err) } - commitCmd := exec.Command(dockerBinary, "commit", "-m=This is a comment", cleanedContainerID) - out, _, err = runCommandWithOutput(commitCmd) - if err != nil { + comment := "This_is_a_comment" + + commitCmd := exec.Command(dockerBinary, "commit", "-m="+comment, name, name) + if out, _, err := runCommandWithOutput(commitCmd); err != nil { t.Fatalf("failed to commit container to image: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) - deleteContainer(cleanedContainerID) - defer deleteImages(cleanedImageID) - // test docker history to check comment messages - historyCmd := exec.Command(dockerBinary, "history", cleanedImageID) + historyCmd := exec.Command(dockerBinary, "history", name) out, exitCode, err := runCommandWithOutput(historyCmd) if err != nil || exitCode != 0 { t.Fatalf("failed to get image history: %s, %v", out, err) } - expectedValue := "This is a comment" - - outputLine := strings.Split(out, "\n")[1] - outputTabs := regexp.MustCompile(" +").Split(outputLine, -1) + outputTabs := strings.Fields(strings.Split(out, "\n")[1]) + //outputTabs := regexp.MustCompile(" +").Split(outputLine, -1) actualValue := outputTabs[len(outputTabs)-1] - if !strings.Contains(actualValue, expectedValue) { - t.Fatalf("Expected comments \"%s\", but found \"%s\"", expectedValue, actualValue) + if !strings.Contains(actualValue, comment) { + t.Fatalf("Expected comments %q, but found %q", comment, actualValue) } logDone("history - history on image with comment") From 50372973884d96ee115094336ed1952b1e71250a Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Fri, 10 Apr 2015 00:08:05 -0400 Subject: [PATCH 022/332] cp: add support for copy filename with ":" We use ":" as separator CONTAINER:PATH. This patch enables copy filename with ":" to host. Signed-off-by: Chen Hanxiao --- api/client/cp.go | 3 ++- integration-cli/docker_cli_cp_test.go | 32 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/api/client/cp.go b/api/client/cp.go index f32e55187fa49..392e362929e70 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -21,7 +21,8 @@ func (cli *DockerCli) CmdCp(args ...string) error { cmd.ParseFlags(args, true) - info := strings.Split(cmd.Arg(0), ":") + // deal with path name with `:` + info := strings.SplitN(cmd.Arg(0), ":", 2) if len(info) != 2 { return fmt.Errorf("Error: Path not specified") diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 37e4659e917ac..12da76abcaa5a 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -620,3 +620,35 @@ func TestCpToStdout(t *testing.T) { } logDone("cp - to stdout") } + +func TestCpNameHasColon(t *testing.T) { + testRequires(t, SameHostDaemon) + + out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := strings.TrimSpace(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = dockerCmd(t, "wait", cleanedContainerID) + if err != nil || strings.TrimSpace(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) + _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) + if err != nil { + t.Fatalf("couldn't docker cp to %s: %s", tmpdir, err) + } + content, err := ioutil.ReadFile(tmpdir + "/te:s:t") + if string(content) != "lololol\n" { + t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") + } + logDone("cp - copy filename has ':'") +} From 4ddc721f234ebb721b9540af3a9358da2f3e6e58 Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Fri, 10 Apr 2015 03:09:26 -0400 Subject: [PATCH 023/332] api_resize_test: fix a typo s/cintainer/container Signed-off-by: Chen Hanxiao --- integration-cli/docker_api_resize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 2e7677d100c4d..be36be8c1d2f7 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -33,7 +33,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) - // make sure the exited cintainer is not running + // make sure the exited container is not running runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { From 5c7c3fea6caf43d51344faaf190b869db1b44f46 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 10 Apr 2015 18:55:07 +0800 Subject: [PATCH 024/332] Remove Job from History API a part of issue #12151 Signed-off-by: Hu Keping --- api/server/server.go | 10 +++++----- api/server/server_unit_test.go | 30 ------------------------------ graph/history.go | 21 +++++---------------- graph/service.go | 1 - 4 files changed, 10 insertions(+), 52 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 912af638ce10b..0c92b4b2a4a0b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -434,13 +434,13 @@ func getImagesHistory(eng *engine.Engine, version version.Version, w http.Respon return fmt.Errorf("Missing parameter") } - var job = eng.Job("history", vars["name"]) - streamJSON(job, w, false) - - if err := job.Run(); err != nil { + name := vars["name"] + history, err := getDaemon(eng).Repositories().History(name) + if err != nil { return err } - return nil + + return writeJSON(w, http.StatusOK, history) } func getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 88dadab6f544b..b5dd984b0c3f0 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -219,36 +219,6 @@ func TestLogsNoStreams(t *testing.T) { } } -func TestGetImagesHistory(t *testing.T) { - eng := engine.New() - imageName := "docker-test-image" - var called bool - eng.Register("history", func(job *engine.Job) error { - called = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != imageName { - t.Fatalf("name != '%s': %#v", imageName, job.Args[0]) - } - v := &engine.Env{} - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/images/"+imageName+"/history", nil, eng, t) - if !called { - t.Fatalf("handler was not called") - } - if r.Code != http.StatusOK { - t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK) - } - if r.HeaderMap.Get("Content-Type") != "application/json" { - t.Fatalf("%#v\n", r) - } -} - func TestGetImagesByName(t *testing.T) { eng := engine.New() name := "image_name" diff --git a/graph/history.go b/graph/history.go index 1290de9a30781..5c27dbd921f4d 100644 --- a/graph/history.go +++ b/graph/history.go @@ -1,24 +1,17 @@ package graph import ( - "encoding/json" - "fmt" "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/utils" ) -func (s *TagStore) CmdHistory(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s IMAGE", job.Name) - } - name := job.Args[0] +func (s *TagStore) History(name string) ([]*types.ImageHistory, error) { foundImage, err := s.LookupImage(name) if err != nil { - return err + return nil, err } lookupMap := make(map[string][]string) @@ -32,10 +25,10 @@ func (s *TagStore) CmdHistory(job *engine.Job) error { } } - history := []types.ImageHistory{} + history := []*types.ImageHistory{} err = foundImage.WalkHistory(func(img *image.Image) error { - history = append(history, types.ImageHistory{ + history = append(history, &types.ImageHistory{ ID: img.ID, Created: img.Created.Unix(), CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), @@ -45,9 +38,5 @@ func (s *TagStore) CmdHistory(job *engine.Job) error { return nil }) - if err = json.NewEncoder(job.Stdout).Encode(history); err != nil { - return err - } - - return nil + return history, err } diff --git a/graph/service.go b/graph/service.go index a51d106e1389d..46f83103dbf42 100644 --- a/graph/service.go +++ b/graph/service.go @@ -17,7 +17,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_inspect": s.CmdLookup, "image_tarlayer": s.CmdTarLayer, "image_export": s.CmdImageExport, - "history": s.CmdHistory, "viz": s.CmdViz, "load": s.CmdLoad, "import": s.CmdImport, From bfc68d10ed778bc0c2e2caedb80b65bd961c76b0 Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Fri, 10 Apr 2015 11:10:26 -0400 Subject: [PATCH 025/332] A wrong key.json would remain if the TestDaemonwithwrongkey case fails. The issue would lead to failure of other cases. Signed-off-by: Yan Feng --- integration-cli/docker_cli_daemon_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 3a10fb004cb21..c4746bf5e399d 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -884,8 +884,10 @@ func TestDaemonwithwrongkey(t *testing.T) { if err := d1.Start(); err == nil { d1.Stop() + os.Remove("/etc/docker/key.json") t.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) } + os.Remove("/etc/docker/key.json") content, _ := ioutil.ReadFile(d1.logFile.Name()) @@ -893,6 +895,5 @@ func TestDaemonwithwrongkey(t *testing.T) { t.Fatal("Missing KeyID message from daemon logs") } - os.Remove("/etc/docker/key.json") logDone("daemon - it should be failed to start daemon with wrong key") } From 202e0380f36be93ace139cb52e6c4752d195a034 Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Wed, 8 Apr 2015 23:07:03 -0400 Subject: [PATCH 026/332] Adds example request and Json parameter information for container start API. Closes #10304. Signed-off-by: Raghuram Devarakonda --- .../reference/api/docker_remote_api_v1.18.md | 177 +++++++++++++----- .../reference/api/docker_remote_api_v1.19.md | 177 +++++++++++++----- 2 files changed, 256 insertions(+), 98 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index 75eed99dad4eb..c4cb15718738f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -212,55 +212,56 @@ Json Parameters: - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` - **HostConfig** - - **Binds** – A list of volume bindings for this container. Each volume - binding is a string of the form `container_path` (to create a new - volume for the container), `host_path:container_path` (to bind-mount - a host path into the container), or `host_path:container_path:ro` - (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form "container_name:alias". - - **LxcConf** - LXC specific configurations. These configurations will only - work when using the `lxc` execution driver. - - **PortBindings** - A map of exposed container ports and the host port they - should map to. It should be specified in the form - `{ /: [{ "HostPort": "" }] }` - Take note that `port` is specified as a string and not an integer value. - - **PublishAllPorts** - Allocates a random host port for all of a container's - exposed ports. Specified as a boolean value. - - **Privileged** - Gives the container full access to the host. Specified as - a boolean value. - - **ReadonlyRootfs** - Mount the container's root filesystem as read only. - Specified as a boolean value. - - **Dns** - A list of dns servers for the container to use. - - **DnsSearch** - A list of DNS search domains - - **ExtraHosts** - A list of hostnames/IP mappings to be added to the - container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - - **VolumesFrom** - A list of volumes to inherit from another container. - Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. - - **RestartPolicy** – The behavior to apply when the container exits. The - value is an object with a `Name` property of either `"always"` to - always restart or `"on-failure"` to restart only when the container - exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` - controls the number of times to retry before giving up. - The default is not to restart. (optional) - An ever increasing delay (double the previous delay, starting at 100mS) - is added before each restart to prevent flooding the server. - - **NetworkMode** - Sets the networking mode for the container. Supported - values are: `bridge`, `host`, and `container:` - - **Devices** - A list of devices to add to the container specified in the - form - `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` - - **Ulimits** - A list of ulimits to be set in the container, specified as - `{ "Name": , "Soft": , "Hard": }`, for example: - `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` - - **SecurityOpt**: A list of string values to customize labels for MLS - systems, such as SELinux. - - **LogConfig** - Logging configuration to container, format: - `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. - - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + - **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). + - **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. + - **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. + - **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. + - **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. + - **Privileged** - Gives the container full access to the host. Specified as + a boolean value. + - **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. + - **Dns** - A list of dns servers for the container to use. + - **DnsSearch** - A list of DNS search domains + - **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. + - **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` + - **CapAdd** - A list of kernel capabilties to add to the container. + - **Capdrop** - A list of kernel capabilties to drop from the container. + - **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. + - **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` + - **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + - **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` + - **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. + - **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. + - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. Query Parameters: @@ -675,12 +676,90 @@ Start the container `id` POST /containers/(id)/start HTTP/1.1 Content-Type: application/json + { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 512, + "CpusetCpus": "0,1", + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [], + "Ulimits": [{}], + "LogConfig": { "Type": "json-file", Config: {} }, + "SecurityOpt": [""], + "CgroupParent": "" + } + **Example response**: HTTP/1.1 204 No Content Json Parameters: +- **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). +- **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. +- **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. +- **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. +- **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. +- **Privileged** - Gives the container full access to the host. Specified as + a boolean value. +- **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. +- **Dns** - A list of dns servers for the container to use. +- **DnsSearch** - A list of DNS search domains +- **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. +- **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` +- **CapAdd** - A list of kernel capabilties to add to the container. +- **Capdrop** - A list of kernel capabilties to drop from the container. +- **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. +- **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` +- **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` +- **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` +- **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. +- **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. +- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + Status Codes: - **204** – no error diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index b643d449042f4..3543f74309d00 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -212,55 +212,56 @@ Json Parameters: - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` - **HostConfig** - - **Binds** – A list of volume bindings for this container. Each volume - binding is a string of the form `container_path` (to create a new - volume for the container), `host_path:container_path` (to bind-mount - a host path into the container), or `host_path:container_path:ro` - (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form "container_name:alias". - - **LxcConf** - LXC specific configurations. These configurations will only - work when using the `lxc` execution driver. - - **PortBindings** - A map of exposed container ports and the host port they - should map to. It should be specified in the form - `{ /: [{ "HostPort": "" }] }` - Take note that `port` is specified as a string and not an integer value. - - **PublishAllPorts** - Allocates a random host port for all of a container's - exposed ports. Specified as a boolean value. - - **Privileged** - Gives the container full access to the host. Specified as - a boolean value. - - **ReadonlyRootfs** - Mount the container's root filesystem as read only. - Specified as a boolean value. - - **Dns** - A list of dns servers for the container to use. - - **DnsSearch** - A list of DNS search domains - - **ExtraHosts** - A list of hostnames/IP mappings to be added to the - container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - - **VolumesFrom** - A list of volumes to inherit from another container. - Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. - - **RestartPolicy** – The behavior to apply when the container exits. The - value is an object with a `Name` property of either `"always"` to - always restart or `"on-failure"` to restart only when the container - exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` - controls the number of times to retry before giving up. - The default is not to restart. (optional) - An ever increasing delay (double the previous delay, starting at 100mS) - is added before each restart to prevent flooding the server. - - **NetworkMode** - Sets the networking mode for the container. Supported - values are: `bridge`, `host`, and `container:` - - **Devices** - A list of devices to add to the container specified in the - form - `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` - - **Ulimits** - A list of ulimits to be set in the container, specified as - `{ "Name": , "Soft": , "Hard": }`, for example: - `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` - - **SecurityOpt**: A list of string values to customize labels for MLS - systems, such as SELinux. - - **LogConfig** - Logging configuration to container, format: - `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. - - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + - **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). + - **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. + - **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. + - **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. + - **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. + - **Privileged** - Gives the container full access to the host. Specified as + a boolean value. + - **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. + - **Dns** - A list of dns servers for the container to use. + - **DnsSearch** - A list of DNS search domains + - **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. + - **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` + - **CapAdd** - A list of kernel capabilties to add to the container. + - **Capdrop** - A list of kernel capabilties to drop from the container. + - **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. + - **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` + - **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + - **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` + - **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. + - **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. + - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. Query Parameters: @@ -675,12 +676,90 @@ Start the container `id` POST /containers/(id)/start HTTP/1.1 Content-Type: application/json + { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 512, + "CpusetCpus": "0,1", + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [], + "Ulimits": [{}], + "LogConfig": { "Type": "json-file", Config: {} }, + "SecurityOpt": [""], + "CgroupParent": "" + } + **Example response**: HTTP/1.1 204 No Content Json Parameters: +- **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). +- **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. +- **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. +- **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. +- **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. +- **Privileged** - Gives the container full access to the host. Specified as + a boolean value. +- **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. +- **Dns** - A list of dns servers for the container to use. +- **DnsSearch** - A list of DNS search domains +- **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. +- **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` +- **CapAdd** - A list of kernel capabilties to add to the container. +- **Capdrop** - A list of kernel capabilties to drop from the container. +- **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. +- **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` +- **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` +- **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` +- **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. +- **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. +- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + Status Codes: - **204** – no error From e7a2e2bf7e5adbbed0443cc99ab728d5118b3b7b Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Fri, 10 Apr 2015 10:45:38 -0700 Subject: [PATCH 027/332] Rename TestStartSilentAttach to TestStartAttachSilent Signed-off-by: Megan Kostick --- integration-cli/docker_cli_start_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 25b23e888f4b1..c703e434c1237 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -69,7 +69,7 @@ func TestStartAttachCorrectExitCode(t *testing.T) { logDone("start - correct exit code returned with -a") } -func TestStartSilentAttach(t *testing.T) { +func TestStartAttachSilent(t *testing.T) { defer deleteAllContainers() name := "teststartattachcorrectexitcode" From db0ffba3b92aeda667501aaa10926943a7738f82 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 9 Apr 2015 23:11:11 +0200 Subject: [PATCH 028/332] Remove job from wait Signed-off-by: Antonio Murdaca --- api/server/server.go | 18 ++++---- daemon/daemon.go | 1 - daemon/wait.go | 22 --------- integration-cli/docker_cli_daemon_test.go | 41 +++++++++++++++++ integration/server_test.go | 56 ----------------------- 5 files changed, 49 insertions(+), 89 deletions(-) delete mode 100644 daemon/wait.go diff --git a/api/server/server.go b/api/server/server.go index de3e86181039b..ffbef8cee9037 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1005,20 +1005,18 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - stdoutBuffer = bytes.NewBuffer(nil) - job = eng.Job("wait", vars["name"]) - ) - job.Stdout.Add(stdoutBuffer) - if err := job.Run(); err != nil { - return err - } - statusCode, err := strconv.Atoi(engine.Tail(stdoutBuffer, 1)) + + name := vars["name"] + d := getDaemon(eng) + cont, err := d.Get(name) if err != nil { return err } + + status, _ := cont.WaitStop(-1 * time.Second) + return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{ - StatusCode: statusCode, + StatusCode: status, }) } diff --git a/daemon/daemon.go b/daemon/daemon.go index e22790e93231f..86ed71e231ead 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -127,7 +127,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, - "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, "execInspect": daemon.ContainerExecInspect, diff --git a/daemon/wait.go b/daemon/wait.go deleted file mode 100644 index 5c1f44beb34f6..0000000000000 --- a/daemon/wait.go +++ /dev/null @@ -1,22 +0,0 @@ -package daemon - -import ( - "fmt" - "time" - - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerWait(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s", job.Name) - } - name := job.Args[0] - container, err := daemon.Get(name) - if err != nil { - return fmt.Errorf("%s: %v", job.Name, err) - } - status, _ := container.WaitStop(-1 * time.Second) - job.Printf("%d\n", status) - return nil -} diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 3a10fb004cb21..684f1978e3bc7 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -896,3 +896,44 @@ func TestDaemonwithwrongkey(t *testing.T) { os.Remove("/etc/docker/key.json") logDone("daemon - it should be failed to start daemon with wrong key") } + +func TestDaemonRestartKillWait(t *testing.T) { + d := NewDaemon(t) + if err := d.StartWithBusybox(); err != nil { + t.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + out, err := d.Cmd("run", "-d", "busybox", "/bin/cat") + if err != nil { + t.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) + } + containerID := strings.TrimSpace(out) + + if out, err := d.Cmd("kill", containerID); err != nil { + t.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) + } + + if err := d.Restart(); err != nil { + t.Fatalf("Could not restart daemon: %v", err) + } + + errchan := make(chan error) + go func() { + if out, err := d.Cmd("wait", containerID); err != nil { + errchan <- fmt.Errorf("%v:\n%s", err, out) + } + close(errchan) + }() + + select { + case <-time.After(5 * time.Second): + t.Fatal("Waiting on a stopped (killed) container timed out") + case err := <-errchan: + if err != nil { + t.Fatal(err) + } + } + + logDone("wait - wait on a stopped container doesn't timeout") +} diff --git a/integration/server_test.go b/integration/server_test.go index 5dc4f1aa4a59b..34c56f4a8b56a 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -3,10 +3,8 @@ package docker import ( "bytes" "testing" - "time" "github.com/docker/docker/builder" - "github.com/docker/docker/daemon" "github.com/docker/docker/engine" ) @@ -103,60 +101,6 @@ func TestMergeConfigOnCommit(t *testing.T) { } } -func TestRestartKillWait(t *testing.T) { - eng := NewTestEngine(t) - runtime := mkDaemonFromEngine(eng, t) - defer runtime.Nuke() - - config, hostConfig, _, err := parseRun([]string{"-i", unitTestImageID, "/bin/cat"}) - if err != nil { - t.Fatal(err) - } - - id := createTestContainer(eng, config, t) - - containers, err := runtime.Containers(&daemon.ContainersConfig{All: true}) - - if err != nil { - t.Errorf("Error getting containers1: %q", err) - } - - if len(containers) != 1 { - t.Errorf("Expected 1 container, %v found", len(containers)) - } - - job := eng.Job("start", id) - if err := job.ImportEnv(hostConfig); err != nil { - t.Fatal(err) - } - if err := job.Run(); err != nil { - t.Fatal(err) - } - - if err := runtime.ContainerKill(id, 0); err != nil { - t.Fatal(err) - } - - eng = newTestEngine(t, false, runtime.Config().Root) - runtime = mkDaemonFromEngine(eng, t) - - containers, err = runtime.Containers(&daemon.ContainersConfig{All: true}) - - if err != nil { - t.Errorf("Error getting containers1: %q", err) - } - if len(containers) != 1 { - t.Errorf("Expected 1 container, %v found", len(containers)) - } - - setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() { - job = eng.Job("wait", containers[0].ID) - if err := job.Run(); err != nil { - t.Fatal(err) - } - }) -} - func TestRunWithTooLowMemoryLimit(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From 0e21782de5c038dfa3cfdfc7655b9e6b143baa7b Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 17 Mar 2015 10:44:42 -0400 Subject: [PATCH 029/332] devmapper: storage-opt override for udev sync This provides an override for forcing the daemon to still attempt running the devicemapper driver even when udev sync is not supported. Intended to be a very clear impairment for those choosing to use it. If udev sync is false, there will still be an error in the daemon logs, even when the override is in place. The docs have an explicit WARNING. Including link to the docs for users that encounter this daemon error during an upgrade. Signed-off-by: Vincent Batts --- daemon/graphdriver/devmapper/README.md | 38 +++++++++++- daemon/graphdriver/devmapper/deviceset.go | 62 +++++++++++-------- .../graphdriver/devmapper/devmapper_test.go | 1 + docs/sources/reference/commandline/cli.md | 35 +++++++++++ 4 files changed, 109 insertions(+), 27 deletions(-) diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md index 1dc918016d523..a090b731faf8c 100644 --- a/daemon/graphdriver/devmapper/README.md +++ b/daemon/graphdriver/devmapper/README.md @@ -186,7 +186,7 @@ Here is the list of supported options: 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``` + ``dd if=/dev/zero of=$metadata_dev bs=4096 count=1`` Example use: @@ -216,3 +216,39 @@ Here is the list of supported options: Example use: ``docker -d --storage-opt dm.blkdiscard=false`` + + * `dm.override_udev_sync_check` + + Overrides the `udev` synchronization checks between `devicemapper` and `udev`. + `udev` is the device manager for the Linux kernel. + + To view the `udev` sync support of a Docker daemon that is using the + `devicemapper` driver, run: + + $ docker info + [...] + Udev Sync Supported: true + [...] + + When `udev` sync support is `true`, then `devicemapper` and udev can + coordinate the activation and deactivation of devices for containers. + + When `udev` sync support is `false`, a race condition occurs between + the`devicemapper` and `udev` during create and cleanup. The race condition + results in errors and failures. (For information on these failures, see + [docker#4036](https://github.com/docker/docker/issues/4036)) + + To allow the `docker` daemon to start, regardless of `udev` sync not being + supported, set `dm.override_udev_sync_check` to true: + + $ docker -d --storage-opt dm.override_udev_sync_check=true + + When this value is `true`, the `devicemapper` continues and simply warns + you the errors are happening. + + > **Note**: The ideal is to pursue a `docker` daemon and environment that + > does support synchronizing with `udev`. For further discussion on this + > topic, see [docker#4036](https://github.com/docker/docker/issues/4036). + > Otherwise, set this flag for migrating existing Docker daemons to a + > daemon with a supported environment. + diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 7e0a13a952469..4d6ce5a2ae1e3 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -30,7 +30,8 @@ var ( DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 - DefaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors + DefaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors + DefaultUdevSyncOverride bool = false MaxDeviceId int = 0xffffff // 24 bit, pool limit DeviceIdMapSz int = (MaxDeviceId + 1) / 8 ) @@ -83,20 +84,21 @@ type DeviceSet struct { deviceIdMap []byte // Options - dataLoopbackSize int64 - metaDataLoopbackSize int64 - baseFsSize uint64 - filesystem string - mountOptions string - mkfsArgs []string - dataDevice string // block or loop dev - dataLoopFile string // loopback file, if used - metadataDevice string // block or loop dev - metadataLoopFile string // loopback file, if used - doBlkDiscard bool - thinpBlockSize uint32 - thinPoolDevice string - Transaction `json:"-"` + dataLoopbackSize int64 + metaDataLoopbackSize int64 + baseFsSize uint64 + filesystem string + mountOptions string + mkfsArgs []string + dataDevice string // block or loop dev + dataLoopFile string // loopback file, if used + metadataDevice string // block or loop dev + metadataLoopFile string // loopback file, if used + doBlkDiscard bool + thinpBlockSize uint32 + thinPoolDevice string + Transaction `json:"-"` + overrideUdevSyncCheck bool } type DiskUsage struct { @@ -963,8 +965,10 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // https://github.com/docker/docker/issues/4036 if supported := devicemapper.UdevSetSyncSupport(true); !supported { - logrus.Errorf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") - return graphdriver.ErrNotSupported + logrus.Errorf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors. For more information, see https://docs.docker.com/reference/commandline/cli/#daemon-storage-driver-option") + if !devices.overrideUdevSyncCheck { + return graphdriver.ErrNotSupported + } } if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { @@ -1656,15 +1660,16 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error devicemapper.SetDevDir("/dev") devices := &DeviceSet{ - root: root, - MetaData: MetaData{Devices: make(map[string]*DevInfo)}, - dataLoopbackSize: DefaultDataLoopbackSize, - metaDataLoopbackSize: DefaultMetaDataLoopbackSize, - baseFsSize: DefaultBaseFsSize, - filesystem: "ext4", - doBlkDiscard: true, - thinpBlockSize: DefaultThinpBlockSize, - deviceIdMap: make([]byte, DeviceIdMapSz), + root: root, + MetaData: MetaData{Devices: make(map[string]*DevInfo)}, + dataLoopbackSize: DefaultDataLoopbackSize, + metaDataLoopbackSize: DefaultMetaDataLoopbackSize, + baseFsSize: DefaultBaseFsSize, + overrideUdevSyncCheck: DefaultUdevSyncOverride, + filesystem: "ext4", + doBlkDiscard: true, + thinpBlockSize: DefaultThinpBlockSize, + deviceIdMap: make([]byte, DeviceIdMapSz), } foundBlkDiscard := false @@ -1721,6 +1726,11 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error } // convert to 512b sectors devices.thinpBlockSize = uint32(size) >> 9 + case "dm.override_udev_sync_check": + devices.overrideUdevSyncCheck, err = strconv.ParseBool(val) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("Unknown option %s\n", key) } diff --git a/daemon/graphdriver/devmapper/devmapper_test.go b/daemon/graphdriver/devmapper/devmapper_test.go index 6cb7572384fb3..60006af5a5d1c 100644 --- a/daemon/graphdriver/devmapper/devmapper_test.go +++ b/daemon/graphdriver/devmapper/devmapper_test.go @@ -13,6 +13,7 @@ func init() { DefaultDataLoopbackSize = 300 * 1024 * 1024 DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 DefaultBaseFsSize = 300 * 1024 * 1024 + DefaultUdevSyncOverride = true if err := graphtest.InitLoopbacks(); err != nil { panic(err) } diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 2d59a64bb1d84..b71360a9fa207 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -370,6 +370,41 @@ Currently supported options are: $ docker -d --storage-opt dm.blkdiscard=false + * `dm.override_udev_sync_check` + + Overrides the `udev` synchronization checks between `devicemapper` and `udev`. + `udev` is the device manager for the Linux kernel. + + To view the `udev` sync support of a Docker daemon that is using the + `devicemapper` driver, run: + + $ docker info + [...] + Udev Sync Supported: true + [...] + + When `udev` sync support is `true`, then `devicemapper` and udev can + coordinate the activation and deactivation of devices for containers. + + When `udev` sync support is `false`, a race condition occurs between + the`devicemapper` and `udev` during create and cleanup. The race condition + results in errors and failures. (For information on these failures, see + [docker#4036](https://github.com/docker/docker/issues/4036)) + + To allow the `docker` daemon to start, regardless of `udev` sync not being + supported, set `dm.override_udev_sync_check` to true: + + $ docker -d --storage-opt dm.override_udev_sync_check=true + + When this value is `true`, the `devicemapper` continues and simply warns + you the errors are happening. + + > **Note**: The ideal is to pursue a `docker` daemon and environment that + > does support synchronizing with `udev`. For further discussion on this + > topic, see [docker#4036](https://github.com/docker/docker/issues/4036). + > Otherwise, set this flag for migrating existing Docker daemons to a + > daemon with a supported environment. + ### Docker exec-driver option The Docker daemon uses a specifically built `libcontainer` execution driver as its From 73d08528ea9b043cbe941b1219b1c55625c636a8 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 10 Apr 2015 18:00:33 +0000 Subject: [PATCH 030/332] Unwanted declaration causing compilation issues with gccgo Signed-off-by: Srini Brahmaroutu --- engine/shutdown_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/shutdown_test.go b/engine/shutdown_test.go index cde177e398d6f..d2ef0339de399 100644 --- a/engine/shutdown_test.go +++ b/engine/shutdown_test.go @@ -18,9 +18,7 @@ func TestShutdownEmpty(t *testing.T) { func TestShutdownAfterRun(t *testing.T) { eng := New() - var called bool eng.Register("foo", func(job *Job) error { - called = true return nil }) if err := eng.Job("foo").Run(); err != nil { From c8529fde5f6f2e4b62f9c1b3382fd814c11a7639 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 10 Apr 2015 22:41:43 +0200 Subject: [PATCH 031/332] Remove job from commit Signed-off-by: Antonio Murdaca --- api/server/server.go | 38 ++++++++-------- daemon/commit.go | 43 +++++++++++------- daemon/daemon.go | 1 - integration/server_test.go | 89 +------------------------------------- 4 files changed, 46 insertions(+), 125 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee9037..8a913209229cb 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -618,39 +618,37 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit if err := parseForm(r); err != nil { return err } - var ( - config engine.Env - job = eng.Job("commit", r.Form.Get("container")) - stdoutBuffer = bytes.NewBuffer(nil) - ) if err := checkForJson(r); err != nil { return err } - if err := config.Decode(r.Body); err != nil { - logrus.Errorf("%s", err) - } + cont := r.Form.Get("container") + pause := toBool(r.Form.Get("pause")) if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { - job.Setenv("pause", "1") - } else { - job.Setenv("pause", r.FormValue("pause")) + pause = true } - job.Setenv("repo", r.Form.Get("repo")) - job.Setenv("tag", r.Form.Get("tag")) - job.Setenv("author", r.Form.Get("author")) - job.Setenv("comment", r.Form.Get("comment")) - job.SetenvList("changes", r.Form["changes"]) - job.SetenvSubEnv("config", &config) + containerCommitConfig := &daemon.ContainerCommitConfig{ + Pause: pause, + Repo: r.Form.Get("repo"), + Tag: r.Form.Get("tag"), + Author: r.Form.Get("author"), + Comment: r.Form.Get("comment"), + Changes: r.Form["changes"], + Config: r.Body, + } - job.Stdout.Add(stdoutBuffer) - if err := job.Run(); err != nil { + d := getDaemon(eng) + + imgID, err := d.ContainerCommit(cont, containerCommitConfig) + if err != nil { return err } + return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ - ID: engine.Tail(stdoutBuffer, 1), + ID: imgID, }) } diff --git a/daemon/commit.go b/daemon/commit.go index 1daf57a4fe6c5..1e534cf6288cc 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -3,53 +3,64 @@ package daemon import ( "bytes" "encoding/json" - "fmt" + "io" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerCommit(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) - } - name := job.Args[0] +type ContainerCommitConfig struct { + Pause bool + Repo string + Tag string + Author string + Comment string + Changes []string + Config io.ReadCloser +} +func (daemon *Daemon) ContainerCommit(name string, c *ContainerCommitConfig) (string, error) { container, err := daemon.Get(name) if err != nil { - return err + return "", err } var ( + subenv engine.Env config = container.Config stdoutBuffer = bytes.NewBuffer(nil) newConfig runconfig.Config ) + if err := subenv.Decode(c.Config); err != nil { + logrus.Errorf("%s", err) + } + buildConfigJob := daemon.eng.Job("build_config") buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.Setenv("changes", job.Getenv("changes")) + buildConfigJob.SetenvList("changes", c.Changes) // FIXME this should be remove when we remove deprecated config param - buildConfigJob.Setenv("config", job.Getenv("config")) + buildConfigJob.SetenvSubEnv("config", &subenv) if err := buildConfigJob.Run(); err != nil { - return err + return "", err } if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return err + return "", err } if err := runconfig.Merge(&newConfig, config); err != nil { - return err + return "", err } - img, err := daemon.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), job.GetenvBool("pause"), &newConfig) + img, err := daemon.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, &newConfig) if err != nil { - return err + return "", err } - job.Printf("%s\n", img.ID) - return nil + + return img.ID, nil } // Commit creates a new filesystem image from the current state of a container. diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e231ead..f24d7dc0429f1 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -117,7 +117,6 @@ type Daemon struct { // Install installs daemon capabilities to eng. func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ - "commit": daemon.ContainerCommit, "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, diff --git a/integration/server_test.go b/integration/server_test.go index 34c56f4a8b56a..9745d9ce0ff12 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,12 +1,6 @@ package docker -import ( - "bytes" - "testing" - - "github.com/docker/docker/builder" - "github.com/docker/docker/engine" -) +import "testing" func TestCreateNumberHostname(t *testing.T) { eng := NewTestEngine(t) @@ -20,87 +14,6 @@ func TestCreateNumberHostname(t *testing.T) { createTestContainer(eng, config, t) } -func TestCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"}) - if err != nil { - t.Fatal(err) - } - - id := createTestContainer(eng, config, t) - - job := eng.Job("commit", id) - job.Setenv("repo", "testrepo") - job.Setenv("tag", "testtag") - job.SetenvJson("config", config) - if err := job.Run(); err != nil { - t.Fatal(err) - } -} - -func TestMergeConfigOnCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - runtime := mkDaemonFromEngine(eng, t) - defer runtime.Nuke() - - container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) - defer runtime.Rm(container1) - - config, _, _, err := parseRun([]string{container1.ID, "cat /tmp/foo"}) - if err != nil { - t.Error(err) - } - - job := eng.Job("commit", container1.ID) - job.Setenv("repo", "testrepo") - job.Setenv("tag", "testtag") - job.SetenvJson("config", config) - var outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { - t.Error(err) - } - - container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) - defer runtime.Rm(container2) - - job = eng.Job("container_inspect", container1.Name) - baseContainer, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { - t.Error(err) - } - - job = eng.Job("container_inspect", container2.Name) - commitContainer, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { - t.Error(err) - } - - baseConfig := baseContainer.GetSubEnv("Config") - commitConfig := commitContainer.GetSubEnv("Config") - - if commitConfig.Get("Env") != baseConfig.Get("Env") { - t.Fatalf("Env config in committed container should be %v, was %v", - baseConfig.Get("Env"), commitConfig.Get("Env")) - } - - if baseConfig.Get("Cmd") != "[\"echo test \\u003e /tmp/foo\"]" { - t.Fatalf("Cmd in base container should be [\"echo test \\u003e /tmp/foo\"], was %s", - baseConfig.Get("Cmd")) - } - - if commitConfig.Get("Cmd") != "[\"cat /tmp/foo\"]" { - t.Fatalf("Cmd in committed container should be [\"cat /tmp/foo\"], was %s", - commitConfig.Get("Cmd")) - } -} - func TestRunWithTooLowMemoryLimit(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From de03f4797b614fb192a72c83812c2a04a1939c87 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 10 Apr 2015 10:57:43 -0700 Subject: [PATCH 032/332] Allow SEO crawling from docs site Signed-off-by: Nathan LeClaire Docker-DCO-1.1-Signed-off-by: Nathan LeClaire (github: nathanleclaire) --- docs/release.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/release.sh b/docs/release.sh index 09a85016c145e..d01bc0293c396 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -22,10 +22,17 @@ EOF } create_robots_txt() { - cat > ./sources/robots.txt <<'EOF' -User-agent: * -Disallow: / -EOF + if [ "$AWS_S3_BUCKET" == "docs.docker.com" ]; then + cat > ./sources/robots.txt <<-'EOF' + User-agent: * + Allow: / + EOF + else + cat > ./sources/robots.txt <<-'EOF' + User-agent: * + Disallow: / + EOF + fi } setup_s3() { From c337bfd2e0b0293dbca478a13c29a469493782eb Mon Sep 17 00:00:00 2001 From: Brendan Dixon Date: Fri, 10 Apr 2015 15:43:35 -0700 Subject: [PATCH 033/332] Turned off Ctrl+C processing by Windows shell Signed-off-by: Brendan Dixon --- pkg/term/term_windows.go | 7 ++++--- pkg/term/winconsole/console_windows.go | 7 +++++-- pkg/term/winconsole/term_emulator.go | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pkg/term/term_windows.go b/pkg/term/term_windows.go index 5b637928faece..f46c9c8acf01b 100644 --- a/pkg/term/term_windows.go +++ b/pkg/term/term_windows.go @@ -5,6 +5,7 @@ import ( "io" "os" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/term/winconsole" ) @@ -57,6 +58,7 @@ func GetWinsize(fd uintptr) (*Winsize, error) { // SetWinsize sets the size of the given terminal connected to the passed file descriptor. func SetWinsize(fd uintptr, ws *Winsize) error { // TODO(azlinux): Implement SetWinsize + logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked") return nil } @@ -120,11 +122,10 @@ func MakeRaw(fd uintptr) (*State, error) { mode &^= winconsole.ENABLE_ECHO_INPUT mode &^= winconsole.ENABLE_LINE_INPUT mode &^= winconsole.ENABLE_MOUSE_INPUT - // TODO(azlinux): Enable window input to handle window resizing - mode |= winconsole.ENABLE_WINDOW_INPUT + mode &^= winconsole.ENABLE_WINDOW_INPUT + mode &^= winconsole.ENABLE_PROCESSED_INPUT // Enable these modes - mode |= winconsole.ENABLE_PROCESSED_INPUT mode |= winconsole.ENABLE_EXTENDED_FLAGS mode |= winconsole.ENABLE_INSERT_MODE mode |= winconsole.ENABLE_QUICK_EDIT_MODE diff --git a/pkg/term/winconsole/console_windows.go b/pkg/term/winconsole/console_windows.go index e6e3f9bcbabc0..ce40a93167f96 100644 --- a/pkg/term/winconsole/console_windows.go +++ b/pkg/term/winconsole/console_windows.go @@ -12,6 +12,8 @@ import ( "sync" "syscall" "unsafe" + + "github.com/Sirupsen/logrus" ) const ( @@ -593,6 +595,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) n = len(command) parsedCommand := parseAnsiCommand(command) + logrus.Debugf("[windows] HandleOutputCommand: %v", parsedCommand) // console settings changes need to happen in atomic way term.outMutex.Lock() @@ -648,6 +651,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) column = int16(screenBufferInfo.Window.Right) + 1 } // The numbers are not 0 based, but 1 based + logrus.Debugf("[windows] HandleOutputCommmand: Moving cursor to (%v,%v)", column-1, line-1) if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil { return n, err } @@ -1038,8 +1042,7 @@ func (term *WindowsTerminal) HandleInputSequence(fd uintptr, command []byte) (n } func marshal(c COORD) uintptr { - // works only on intel-endian machines - return uintptr(uint32(uint32(uint16(c.Y))<<16 | uint32(uint16(c.X)))) + return uintptr(*((*DWORD)(unsafe.Pointer(&c)))) } // IsConsole returns true if the given file descriptor is a terminal. diff --git a/pkg/term/winconsole/term_emulator.go b/pkg/term/winconsole/term_emulator.go index 8c9f34284d885..2d5edc0390e5f 100644 --- a/pkg/term/winconsole/term_emulator.go +++ b/pkg/term/winconsole/term_emulator.go @@ -1,6 +1,7 @@ package winconsole import ( + "fmt" "io" "strconv" "strings" @@ -206,6 +207,21 @@ func (c *ansiCommand) getParam(index int) string { return "" } +func (ac *ansiCommand) String() string { + return fmt.Sprintf("0x%v \"%v\" (\"%v\")", + bytesToHex(ac.CommandBytes), + ac.Command, + strings.Join(ac.Parameters, "\",\"")) +} + +func bytesToHex(b []byte) string { + hex := make([]string, len(b)) + for i, ch := range b { + hex[i] = fmt.Sprintf("%X", ch) + } + return strings.Join(hex, "") +} + func parseInt16OrDefault(s string, defaultValue int16) (n int16, err error) { if s == "" { return defaultValue, nil From ac8bd12b39d39a9361adc174bdff7837e771460d Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 10 Apr 2015 11:04:30 -0700 Subject: [PATCH 034/332] Get process list after PID 1 dead Fix #11087 Signed-off-by: Alexander Morozov --- daemon/execdriver/native/driver.go | 44 +++++++++++++++++--------- integration-cli/docker_cli_run_test.go | 25 +++++++++++++++ 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index e5811bb852a37..6caa78390fcc8 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -188,6 +188,34 @@ func notifyOnOOM(container libcontainer.Container) <-chan struct{} { return oom } +func killCgroupProcs(c libcontainer.Container) { + var procs []*os.Process + if err := c.Pause(); err != nil { + logrus.Warn(err) + } + pids, err := c.Processes() + if err != nil { + // don't care about childs if we can't get them, this is mostly because cgroup already deleted + logrus.Warnf("Failed to get processes from container %s: %v", c.ID(), err) + } + for _, pid := range pids { + if p, err := os.FindProcess(pid); err == nil { + procs = append(procs, p) + if err := p.Kill(); err != nil { + logrus.Warn(err) + } + } + } + if err := c.Resume(); err != nil { + logrus.Warn(err) + } + for _, p := range procs { + if _, err := p.Wait(); err != nil { + logrus.Warn(err) + } + } +} + func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) { return func() (*os.ProcessState, error) { pid, err := p.Pid() @@ -195,8 +223,6 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o return nil, err } - processes, err := c.Processes() - process, err := os.FindProcess(pid) s, err := process.Wait() if err != nil { @@ -206,19 +232,7 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o } s = execErr.ProcessState } - if err != nil { - return s, err - } - - for _, pid := range processes { - process, err := os.FindProcess(pid) - if err != nil { - logrus.Errorf("Failed to kill process: %d", pid) - continue - } - process.Kill() - } - + killCgroupProcs(c) p.Wait() return s, err } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index a5d1e3e07024b..302286146c510 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3468,3 +3468,28 @@ func TestRunContainerWithRmFlagCannotStartContainer(t *testing.T) { logDone("run - container is removed if run with --rm and cannot start") } + +func TestRunPidHostWithChildIsKillable(t *testing.T) { + defer deleteAllContainers() + name := "ibuildthecloud" + if out, err := exec.Command(dockerBinary, "run", "-d", "--pid=host", "--name", name, "busybox", "sh", "-c", "sleep 30; echo hi").CombinedOutput(); err != nil { + t.Fatal(err, out) + } + time.Sleep(1 * time.Second) + errchan := make(chan error) + go func() { + if out, err := exec.Command(dockerBinary, "kill", name).CombinedOutput(); err != nil { + errchan <- fmt.Errorf("%v:\n%s", err, out) + } + close(errchan) + }() + select { + case err := <-errchan: + if err != nil { + t.Fatal(err) + } + case <-time.After(5 * time.Second): + t.Fatal("Kill container timed out") + } + logDone("run - can kill container with pid-host and some childs of pid 1") +} From a8fddbdeae6dfb8f6366cc476b37c84ed49f2732 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Sat, 11 Apr 2015 08:58:23 +0800 Subject: [PATCH 035/332] update ubuntulinux.md Signed-off-by: Yuan Sun --- docs/sources/installation/ubuntulinux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index 6400fdb59a3f3..dbf86f310b196 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -94,7 +94,7 @@ prerequisite installed, Docker's installation process adds it. ##Installing Docker on Ubuntu -Make sure you have intalled the prerequisites for your Ubuntu version. Then, +Make sure you have installed the prerequisites for your Ubuntu version. Then, install Docker using the following: 1. Log into your Ubuntu installation as a user with `sudo` privileges. @@ -253,7 +253,7 @@ The warning occurs because Docker containers can't use the local DNS nameserver. Instead, Docker defaults to using an external nameserver. To avoid this warning, you can specify a DNS server for use by Docker -containers. Or, you can disable `dnsmasq` in NetworkManager. Though, disabiling +containers. Or, you can disable `dnsmasq` in NetworkManager. Though, disabling `dnsmasq` might make DNS resolution slower on some networks. To specify a DNS server for use by Docker: From 795a58fb44a2bd18ec37d78c82d75c025f786c50 Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Sat, 11 Apr 2015 09:24:21 +0800 Subject: [PATCH 036/332] 'docker rmi -f IMAGE_ID' untag all names and delete the image If an image has been tagged to multiple repos and tags, 'docker rmi -f IMAGE_ID' will just untag one random repo instead of untagging all and deleting the image. This patch implement this. This commit is composed of: *untag all names and delete the image *add test to this feature *modify commandline/cli.md to explain this Signed-off-by: Deng Guangxing --- daemon/image_delete.go | 35 +++++++++++++-------- docs/sources/reference/commandline/cli.md | 15 +++++++++ integration-cli/docker_cli_rmi_test.go | 37 +++++++++++++++++++++++ 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/daemon/image_delete.go b/daemon/image_delete.go index a44eb1bfa6771..ece33a3c78526 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -30,6 +30,7 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi repoName, tag string tags = []string{} ) + repoAndTags := make(map[string][]string) // FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes repoName, tag = parsers.ParseRepositoryTag(name) @@ -68,19 +69,25 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi if repoName == "" || repoName == parsedRepo { repoName = parsedRepo if parsedTag != "" { - tags = append(tags, parsedTag) + repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag) } } else if repoName != parsedRepo && !force && first { // the id belongs to multiple repos, like base:latest and user:test, // in that case return conflict return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name) + } else { + //the id belongs to multiple repos, with -f just delete all + repoName = parsedRepo + if parsedTag != "" { + repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag) + } } } } else { - tags = append(tags, tag) + repoAndTags[repoName] = append(repoAndTags[repoName], tag) } - if !first && len(tags) > 0 { + if !first && len(repoAndTags) > 0 { return nil } @@ -91,16 +98,18 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi } // Untag the current image - for _, tag := range tags { - tagDeleted, err := daemon.Repositories().Delete(repoName, tag) - if err != nil { - return err - } - if tagDeleted { - *list = append(*list, types.ImageDelete{ - Untagged: utils.ImageReference(repoName, tag), - }) - daemon.EventsService.Log("untag", img.ID, "") + for repoName, tags := range repoAndTags { + for _, tag := range tags { + tagDeleted, err := daemon.Repositories().Delete(repoName, tag) + if err != nil { + return err + } + if tagDeleted { + *list = append(*list, types.ImageDelete{ + Untagged: utils.ImageReference(repoName, tag), + }) + daemon.EventsService.Log("untag", img.ID, "") + } } } tags = daemon.Repositories().ByID()[img.ID] diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 79f59f5f2d293..b9f506a6c858e 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1794,6 +1794,21 @@ before the image is removed. Untagged: test:latest Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 +If you use the `-f` flag and specify the image's short or long ID, then this +command untags and removes all images that match the specified ID. + + $ 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) + + $ docker rmi -f fd484f19954f + Untagged: test1:latest + Untagged: test:latest + Untagged: test2:latest + Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 + An image pulled by digest has no tag associated with it: $ docker images --digests diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 277004d2ecca7..7161a09228c71 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -77,6 +77,43 @@ func TestRmiTag(t *testing.T) { logDone("rmi - tag,rmi - tagging the same images multiple times then removing tags") } +func TestRmiImgIDForce(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf("failed to create a container:%s, %v", out, err) + } + containerID := strings.TrimSpace(out) + runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-test") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) + } + + imagesBefore, _, _ := dockerCmd(t, "images", "-a") + dockerCmd(t, "tag", "busybox-test", "utest:tag1") + dockerCmd(t, "tag", "busybox-test", "utest:tag2") + dockerCmd(t, "tag", "busybox-test", "utest/docker:tag3") + dockerCmd(t, "tag", "busybox-test", "utest:5000/docker:tag4") + { + imagesAfter, _, _ := dockerCmd(t, "images", "-a") + if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 { + t.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) + } + } + out, _, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") + imgID := strings.TrimSpace(out) + dockerCmd(t, "rmi", "-f", imgID) + { + imagesAfter, _, _ := dockerCmd(t, "images", "-a") + if strings.Contains(imagesAfter, imgID[:12]) { + t.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) + } + + } + logDone("rmi - imgID,rmi -f imgID delete all tagged repos of specific imgID") +} + func TestRmiTagWithExistingContainers(t *testing.T) { defer deleteAllContainers() From 24dd8a4698315a4aafee5071f4fe4d74fa06c377 Mon Sep 17 00:00:00 2001 From: y00277921 Date: Sat, 11 Apr 2015 10:34:21 +0800 Subject: [PATCH 037/332] Fix a typo in comment of parseMaybeJSONToList Signed-off-by: Yu Changchun --- builder/parser/line_parsers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/parser/line_parsers.go b/builder/parser/line_parsers.go index 6e284d6fc3b85..5f65a8762fa2f 100644 --- a/builder/parser/line_parsers.go +++ b/builder/parser/line_parsers.go @@ -279,7 +279,7 @@ func parseMaybeJSON(rest string) (*Node, map[string]bool, error) { } // parseMaybeJSONToList determines if the argument appears to be a JSON array. If -// so, passes to parseJSON; if not, attmpts to parse it as a whitespace +// so, passes to parseJSON; if not, attempts to parse it as a whitespace // delimited string. func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) { node, attrs, err := parseJSON(rest) From 2cce4791b0e75201cb65daad07d4203d1c4c2996 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sat, 11 Apr 2015 11:04:24 +0800 Subject: [PATCH 038/332] Add `-u|--user` flag to docker exec for running command as a different user Signed-off-by: Lei Jitang --- contrib/completion/bash/docker | 2 +- daemon/exec.go | 1 + daemon/execdriver/native/exec.go | 4 ++-- docs/man/docker-exec.1.md | 9 +++++++ docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_exec_test.go | 29 +++++++++++++++++++++++ runconfig/exec.go | 7 +++--- 7 files changed, 46 insertions(+), 7 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index ad48f2886cdb7..ef7f0f3356363 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -407,7 +407,7 @@ _docker_events() { _docker_exec() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty -u --user" -- "$cur" ) ) ;; *) __docker_containers_running diff --git a/daemon/exec.go b/daemon/exec.go index f91600da7aa48..fa26dca7d561b 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -138,6 +138,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { Tty: config.Tty, Entrypoint: entrypoint, Arguments: args, + User: config.User, } execConfig := &execConfig{ diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index 2edd3313b158b..ed89269e27b58 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -14,7 +14,7 @@ import ( "github.com/docker/libcontainer/utils" ) -// TODO(vishh): Add support for running in privileged mode and running as a different user. +// TODO(vishh): Add support for running in privileged mode. func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { active := d.activeContainers[c.ID] if active == nil { @@ -28,7 +28,7 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...), Env: c.ProcessConfig.Env, Cwd: c.WorkingDir, - User: c.ProcessConfig.User, + User: processConfig.User, } if processConfig.Tty { diff --git a/docs/man/docker-exec.1.md b/docs/man/docker-exec.1.md index e7554419ecfe0..c1de7b59ed9fe 100644 --- a/docs/man/docker-exec.1.md +++ b/docs/man/docker-exec.1.md @@ -10,6 +10,7 @@ docker-exec - Run a command in a running container [**--help**] [**-i**|**--interactive**[=*false*]] [**-t**|**--tty**[=*false*]] +[**-u**|**--user**[=*USER*]] CONTAINER COMMAND [ARG...] # DESCRIPTION @@ -35,6 +36,14 @@ container is unpaused, and then run **-t**, **--tty**=*true*|*false* Allocate a pseudo-TTY. The default is *false*. +**-u**, **--user**="" + Sets the username or UID used and optionally the groupname or GID for the specified command. + + The followings examples are all valid: + --user [user | user:group | uid | uid:gid | user:gid | uid:group ] + + Without this argument the command will be run as root in the container. + The **-t** option is incompatible with a redirection of the docker client standard input. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index b9f506a6c858e..855e40d08bf42 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1115,6 +1115,7 @@ You'll need two shells for this example. -d, --detach=false Detached mode: run command in the background -i, --interactive=false Keep STDIN open even if not attached -t, --tty=false Allocate a pseudo-TTY + -u, --user= Username or UID (format: [:]) The `docker exec` command runs a new command in a running container. diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 9fcee32a7a4cb..505690a11b797 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -665,3 +665,32 @@ func TestRunMutableNetworkFiles(t *testing.T) { } logDone("run - mutable network files") } + +func TestExecWithUser(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + cmd := exec.Command(dockerBinary, "exec", "-u", "1", "parent", "id") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { + t.Fatalf("exec with user by id expected daemon user got %s", out) + } + + cmd = exec.Command(dockerBinary, "exec", "-u", "root", "parent", "id") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("exec with user by root expected root user got %s", out) + } + + logDone("exec - with user") +} diff --git a/runconfig/exec.go b/runconfig/exec.go index 1bcdad1599b94..01ddeaf548e51 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -21,8 +21,7 @@ type ExecConfig struct { func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { execConfig := &ExecConfig{ - // TODO(vishh): Expose 'User' once it is supported. - //User: job.Getenv("User"), + User: job.Getenv("User"), // TODO(vishh): Expose 'Privileged' once it is supported. //Privileged: job.GetenvBool("Privileged"), Tty: job.GetenvBool("Tty"), @@ -45,6 +44,7 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") + flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])") execCmd []string container string ) @@ -57,8 +57,7 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { execCmd = parsedArgs[1:] execConfig := &ExecConfig{ - // TODO(vishh): Expose '-u' flag once it is supported. - User: "", + User: *flUser, // TODO(vishh): Expose '-p' flag once it is supported. Privileged: false, Tty: *flTty, From 72a500e9e5929b038816d8bd18d462a19e571c99 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sat, 11 Apr 2015 11:26:37 +0800 Subject: [PATCH 039/332] Add docker exec run a command in privileged mode Signed-off-by: Lei Jitang --- contrib/completion/bash/docker | 2 +- daemon/exec.go | 1 + daemon/execdriver/native/exec.go | 5 +++- docs/man/docker-exec.1.md | 8 +++++++ docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_exec_test.go | 28 +++++++++++++++++++++++ runconfig/exec.go | 21 ++++++++--------- 7 files changed, 53 insertions(+), 13 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index ef7f0f3356363..f669352119bd0 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -407,7 +407,7 @@ _docker_events() { _docker_exec() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty -u --user" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i --privileged -t --tty -u --user" -- "$cur" ) ) ;; *) __docker_containers_running diff --git a/daemon/exec.go b/daemon/exec.go index fa26dca7d561b..46c255a7cf0f0 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -139,6 +139,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { Entrypoint: entrypoint, Arguments: args, User: config.User, + Privileged: config.Privileged, } execConfig := &execConfig{ diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index ed89269e27b58..04239fdac724b 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -14,7 +14,6 @@ import ( "github.com/docker/libcontainer/utils" ) -// TODO(vishh): Add support for running in privileged mode. func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { active := d.activeContainers[c.ID] if active == nil { @@ -31,6 +30,10 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo User: processConfig.User, } + if processConfig.Privileged { + p.Capabilities = execdriver.GetAllCapabilities() + } + if processConfig.Tty { config := active.Config() rootuid, err := config.HostUID() diff --git a/docs/man/docker-exec.1.md b/docs/man/docker-exec.1.md index c1de7b59ed9fe..312fa397f5be9 100644 --- a/docs/man/docker-exec.1.md +++ b/docs/man/docker-exec.1.md @@ -9,6 +9,7 @@ docker-exec - Run a command in a running container [**-d**|**--detach**[=*false*]] [**--help**] [**-i**|**--interactive**[=*false*]] +[**--privileged**[=*false*]] [**-t**|**--tty**[=*false*]] [**-u**|**--user**[=*USER*]] CONTAINER COMMAND [ARG...] @@ -33,6 +34,13 @@ container is unpaused, and then run **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. +**--privileged**=*true*|*false* + Give extended privileges to the process to run in a running container. The default is *false*. + + By default, the process run by docker exec in a running container +have the same capabilities of the container. By setting --privileged will give +all the capabilities to the process. + **-t**, **--tty**=*true*|*false* Allocate a pseudo-TTY. The default is *false*. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 855e40d08bf42..cfbd3398ee99d 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1114,6 +1114,7 @@ You'll need two shells for this example. -d, --detach=false Detached mode: run command in the background -i, --interactive=false Keep STDIN open even if not attached + --privileged=false Give extended privileges to the command -t, --tty=false Allocate a pseudo-TTY -u, --user= Username or UID (format: [:]) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 505690a11b797..8906da2526387 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -694,3 +694,31 @@ func TestExecWithUser(t *testing.T) { logDone("exec - with user") } + +func TestExecWithPrivileged(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "top") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sda b 8 0") + out, _, err := runCommandWithOutput(cmd) + fmt.Printf("%s", out) + if err == nil || !strings.Contains(out, "Operation not permitted") { + t.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") + } + + cmd = exec.Command(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.TrimSpace(out); actual != "ok" { + t.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) + } + + logDone("exec - exec command in a container with privileged") +} diff --git a/runconfig/exec.go b/runconfig/exec.go index 01ddeaf548e51..e634d3081317b 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -22,8 +22,7 @@ type ExecConfig struct { func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { execConfig := &ExecConfig{ User: job.Getenv("User"), - // TODO(vishh): Expose 'Privileged' once it is supported. - //Privileged: job.GetenvBool("Privileged"), + Privileged: job.GetenvBool("Privileged"), Tty: job.GetenvBool("Tty"), AttachStdin: job.GetenvBool("AttachStdin"), AttachStderr: job.GetenvBool("AttachStderr"), @@ -41,12 +40,13 @@ func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { var ( - flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") - flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") - flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") - flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])") - execCmd []string - container string + flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") + flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") + flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") + flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])") + flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to the command") + execCmd []string + container string ) cmd.Require(flag.Min, 2) if err := cmd.ParseFlags(args, true); err != nil { @@ -57,9 +57,8 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { execCmd = parsedArgs[1:] execConfig := &ExecConfig{ - User: *flUser, - // TODO(vishh): Expose '-p' flag once it is supported. - Privileged: false, + User: *flUser, + Privileged: *flPrivileged, Tty: *flTty, Cmd: execCmd, Container: container, From d2d583c53b56a3ce069bf57ede9be9574c58a687 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sat, 11 Apr 2015 11:39:47 +0800 Subject: [PATCH 040/332] Add CFS_BANDWIDTH to check-config Signed-off-by: Lei Jitang --- contrib/check-config.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index 59649d6c66bc3..54b1b7eacdeb8 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -161,6 +161,7 @@ echo 'Optional Features:' flags=( RESOURCE_COUNTERS CGROUP_PERF + CFS_BANDWIDTH ) check_flags "${flags[@]}" From 39932511c134938233e8bfe4796cec9a1b30d11e Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Sat, 11 Apr 2015 16:37:28 +0800 Subject: [PATCH 041/332] use hostConfig in verifyDaemonSettings We have moved resource configs to hostConfig. Signed-off-by: Qiang Huang --- daemon/container.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 46defe9683298..016ee1fdd1b51 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1282,13 +1282,13 @@ func (container *Container) initializeNetworking() error { // Make sure the config is compatible with the current kernel func (container *Container) verifyDaemonSettings() { - if container.Config.Memory > 0 && !container.daemon.sysInfo.MemoryLimit { + if container.hostConfig.Memory > 0 && !container.daemon.sysInfo.MemoryLimit { logrus.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.") - container.Config.Memory = 0 + container.hostConfig.Memory = 0 } - if container.Config.Memory > 0 && !container.daemon.sysInfo.SwapLimit { + if container.hostConfig.Memory > 0 && !container.daemon.sysInfo.SwapLimit { logrus.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.") - container.Config.MemorySwap = -1 + container.hostConfig.MemorySwap = -1 } if container.daemon.sysInfo.IPv4ForwardingDisabled { logrus.Warnf("IPv4 forwarding is disabled. Networking will not work") From 4f492e794ae9c9a51edae4bb5873187eadab828e Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Sat, 11 Apr 2015 18:11:49 +0800 Subject: [PATCH 042/332] update docker_remote_api_v1.* Signed-off-by: Yuan Sun --- .../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.13.md | 2 +- .../reference/api/docker_remote_api_v1.14.md | 2 +- .../reference/api/docker_remote_api_v1.15.md | 18 ++++++++--------- .../reference/api/docker_remote_api_v1.16.md | 18 ++++++++--------- .../reference/api/docker_remote_api_v1.17.md | 18 ++++++++--------- .../reference/api/docker_remote_api_v1.18.md | 20 +++++++++---------- .../reference/api/docker_remote_api_v1.19.md | 20 +++++++++---------- .../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 +- 14 files changed, 56 insertions(+), 56 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index 7837b82edd845..cd1a248091c7f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -535,7 +535,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index 6bcabfc791c5c..6aa29456d4565 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -570,7 +570,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index 58f3bc3a30f1d..439b2a219b2b5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -618,7 +618,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.13.md b/docs/sources/reference/api/docker_remote_api_v1.13.md index 1590978f0cf40..f3de203eba31c 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.13.md +++ b/docs/sources/reference/api/docker_remote_api_v1.13.md @@ -611,7 +611,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.14.md b/docs/sources/reference/api/docker_remote_api_v1.14.md index f4e1b3edc5a1e..e3c559d6c281f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.14.md +++ b/docs/sources/reference/api/docker_remote_api_v1.14.md @@ -621,7 +621,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.15.md b/docs/sources/reference/api/docker_remote_api_v1.15.md index a956d454ac6f3..d83275112af70 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.15.md +++ b/docs/sources/reference/api/docker_remote_api_v1.15.md @@ -174,12 +174,12 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory usage (memory + swap); set `-1` to disable swap. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). - **CpuSet** - String value containg the cgroups Cpuset to use. + (ie. the relative weight vs other containers). + **CpuSet** - String value containing the cgroups Cpuset to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -195,7 +195,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -225,8 +225,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -553,8 +553,8 @@ Json Parameters: - **DnsSearch** - A list of DNS search domains - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -766,7 +766,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.16.md b/docs/sources/reference/api/docker_remote_api_v1.16.md index a0c875889e352..3110ccbff4372 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.16.md +++ b/docs/sources/reference/api/docker_remote_api_v1.16.md @@ -174,12 +174,12 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory usage (memory + swap); set `-1` to disable swap. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). - **CpuSet** - String value containg the cgroups Cpuset to use. + (ie. the relative weight vs other containers). + **CpuSet** - String value containing the cgroups Cpuset to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -195,7 +195,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -225,8 +225,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -553,8 +553,8 @@ Json Parameters: - **DnsSearch** - A list of DNS search domains - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -766,7 +766,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md index d0abaffd0c043..1551ab18647d5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.17.md +++ b/docs/sources/reference/api/docker_remote_api_v1.17.md @@ -175,13 +175,13 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap, always use this with `memory`, and make the value larger than `memory`. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). - **CpuSet** - String value containg the cgroups Cpuset to use. + (ie. the relative weight vs other containers). + **CpuSet** - String value containing the cgroups Cpuset to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -197,7 +197,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -227,8 +227,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -686,8 +686,8 @@ Json Parameters: - **DnsSearch** - A list of DNS search domains - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -927,7 +927,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index c4cb15718738f..c69ade36a49da 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -183,14 +183,14 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap, always use this with `memory`, and make the value larger than `memory`. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). + (ie. the relative weight vs other containers). - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. -- **CpusetCpus** - String value containg the cgroups CpusetCpus to use. +- **CpusetCpus** - String value containing the cgroups CpusetCpus to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -207,7 +207,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -237,8 +237,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -734,8 +734,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -985,7 +985,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 @@ -1242,7 +1242,7 @@ Query Parameters: - **memory** - set memory limit for build - **memswap** - Total memory (memory + swap), `-1` to disable swap - **cpushares** - CPU shares (relative weight) -- **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1` +- **cpusetcpus** - CPUs in which to allow execution, e.g., `0-3`, `0,1` Request Headers: diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 3543f74309d00..0f63ec310f5a0 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -183,14 +183,14 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap, always use this with `memory`, and make the value larger than `memory`. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). + (ie. the relative weight vs other containers). - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. -- **CpusetCpus** - String value containg the cgroups CpusetCpus to use. +- **CpusetCpus** - String value containing the cgroups CpusetCpus to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -207,7 +207,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -237,8 +237,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -734,8 +734,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -985,7 +985,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 @@ -1242,7 +1242,7 @@ Query Parameters: - **memory** - set memory limit for build - **memswap** - Total memory (memory + swap), `-1` to disable swap - **cpushares** - CPU shares (relative weight) -- **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1` +- **cpusetcpus** - CPUs in which to allow execution, e.g., `0-3`, `0,1` Request Headers: diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.md b/docs/sources/reference/api/docker_remote_api_v1.6.md index d0f9661e50913..cd8a7308841dc 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/docs/sources/reference/api/docker_remote_api_v1.6.md @@ -560,7 +560,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.md b/docs/sources/reference/api/docker_remote_api_v1.7.md index 6cdd60374fb14..dade45fbc9d23 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/docs/sources/reference/api/docker_remote_api_v1.7.md @@ -505,7 +505,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.md b/docs/sources/reference/api/docker_remote_api_v1.8.md index 409e63a163499..56260db867c02 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/docs/sources/reference/api/docker_remote_api_v1.8.md @@ -553,7 +553,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index 7ea3fc9ab1a94..b6675dc4e4c8d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -557,7 +557,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) From d6d8f45b04a6e6d0b616c85d1885342d3e676ec1 Mon Sep 17 00:00:00 2001 From: jianbosun Date: Wed, 8 Apr 2015 12:25:41 +0800 Subject: [PATCH 043/332] change memory usage display using standard unix postfixes add unit test for display also change doc for memory usage display change for example GiB will be GB Signed-off-by: Sun Jianbo --- api/client/stats.go | 4 ++-- api/client/stats_unit_test.go | 29 +++++++++++++++++++++++ docs/man/docker-stats.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 api/client/stats_unit_test.go diff --git a/api/client/stats.go b/api/client/stats.go index bf9d3a814538e..317a4fd84ad47 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -99,9 +99,9 @@ func (s *containerStats) Display(w io.Writer) error { fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", s.Name, s.CPUPercentage, - units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), + units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit), s.MemoryPercentage, - units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx)) + units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx)) return nil } diff --git a/api/client/stats_unit_test.go b/api/client/stats_unit_test.go new file mode 100644 index 0000000000000..0831dbcbbe9dc --- /dev/null +++ b/api/client/stats_unit_test.go @@ -0,0 +1,29 @@ +package client + +import ( + "bytes" + "sync" + "testing" +) + +func TestDisplay(t *testing.T) { + c := &containerStats{ + Name: "app", + CPUPercentage: 30.0, + Memory: 100 * 1024 * 1024.0, + MemoryLimit: 2048 * 1024 * 1024.0, + MemoryPercentage: 100.0 / 2048.0 * 100.0, + NetworkRx: 100 * 1024 * 1024, + NetworkTx: 800 * 1024 * 1024, + mu: sync.RWMutex{}, + } + var b bytes.Buffer + if err := c.Display(&b); err != nil { + t.Fatalf("c.Display() gave error: %s", err) + } + got := b.String() + want := "app\t30.00%\t104.9 MB/2.147 GB\t4.88%\t104.9 MB/838.9 MB\n" + if got != want { + t.Fatalf("c.Display() = %q, want %q", got, want) + } +} diff --git a/docs/man/docker-stats.1.md b/docs/man/docker-stats.1.md index a1adc7ecbaa51..4cf7a66df3ecf 100644 --- a/docs/man/docker-stats.1.md +++ b/docs/man/docker-stats.1.md @@ -24,5 +24,5 @@ Run **docker stats** with multiple containers. $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B - redis2 0.07% 2.746 MiB/64 MiB 4.29% 1.266 KiB/648 B + redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index b9f506a6c858e..c284a9435ada4 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2336,7 +2336,7 @@ Running `docker stats` on multiple containers $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B - redis2 0.07% 2.746 MiB/64 MiB 4.29% 1.266 KiB/648 B + redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B The `docker stats` command will only return a live stream of data for running From 787d774af0eb26aed16cbf24896e4ba051ba4538 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:18:57 -0400 Subject: [PATCH 044/332] Link to HTTPS URLs Link to HTTPS URLs in top-level documentation / project files. Signed-off-by: Eric Windisch --- CONTRIBUTING.md | 2 +- LICENSE | 4 ++-- MAINTAINERS | 2 +- NOTICE | 6 +++--- README.md | 20 ++++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6bf6ad5f3cab..395f25923409d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,7 @@ However, there might be a way to implement that feature *on top of* Docker. Stack Overflow Stack Overflow has over 7000K Docker questions listed. We regularly - monitor Docker questions + monitor Docker questions and so do many other knowledgeable Docker users. diff --git a/LICENSE b/LICENSE index 508036ef4f318..c7a3f0cfd4562 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -182,7 +182,7 @@ 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 + https://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, diff --git a/MAINTAINERS b/MAINTAINERS index e0ddc9f1a5d90..6c79e8b4b3ad4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -37,7 +37,7 @@ project from a great one. text = """ Docker follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for -life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with +life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with yours truly, Solomon Hykes, in the role of BDFL. This means that all decisions are made, by default, by Solomon. Since making every decision myself would be highly un-scalable, in practice decisions are spread diff --git a/NOTICE b/NOTICE index 8e84d0f3b2337..6e6f469ab9b28 100644 --- a/NOTICE +++ b/NOTICE @@ -1,7 +1,7 @@ Docker Copyright 2012-2015 Docker, Inc. -This product includes software developed at Docker, Inc. (http://www.docker.com). +This product includes software developed at Docker, Inc. (https://www.docker.com). This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License. @@ -14,6 +14,6 @@ United States and other governments. 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 +For more information, please see https://www.bis.doc.gov -See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. +See also https://www.apache.org/dev/crypto.html and/or seek legal counsel. diff --git a/README.md b/README.md index 6d259c5edbcce..03aa0139c5fd4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ databases, and backend services without depending on a particular stack or provider. Docker began as an open-source implementation of the deployment engine which -powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service. +powers [dotCloud](https://dotcloud.com), a popular Platform-as-a-Service. It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands of applications and databases. @@ -56,12 +56,12 @@ By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization, containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary for containerization, including -Linux with [openvz](http://openvz.org), +Linux with [openvz](https://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net), Solaris with -[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc), +[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc), and FreeBSD with -[Jails](http://www.freebsd.org/doc/handbook/jails.html). +[Jails](https://www.freebsd.org/doc/handbook/jails.html). Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves all four problems. @@ -115,7 +115,7 @@ This is usually difficult for several reasons: Docker solves the problem of dependency hell by giving the developer a simple way to express *all* their application's dependencies in one place, while streamlining the process of assembling them. If this makes you think of -[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't +[XKCD 927](https://xkcd.com/927/), don't worry. Docker doesn't *replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers. @@ -147,10 +147,10 @@ Docker can be installed on your local machine as well as servers - both bare metal and virtualized. It is available as a binary on most modern Linux systems, or as a VM on Windows, Mac and other systems. -We also offer an [interactive tutorial](http://www.docker.com/tryit/) +We also offer an [interactive tutorial](https://www.docker.com/tryit/) for quickly learning the basics of using Docker. -For up-to-date install instructions, see the [Docs](http://docs.docker.com). +For up-to-date install instructions, see the [Docs](https://docs.docker.com). Usage examples ============== @@ -159,7 +159,7 @@ Docker can be used to run short-lived commands, long-running daemons (app servers, databases, etc.), interactive shell sessions, etc. You can find a [list of real-world -examples](http://docs.docker.com/examples/) in the +examples](https://docs.docker.com/examples/) in the documentation. Under the hood @@ -172,7 +172,7 @@ Under the hood, Docker is built on the following components: and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel -* The [Go](http://golang.org) programming language +* The [Go](https://golang.org) programming language * The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md) * The [Libcontainer Specification](https://github.com/docker/libcontainer/blob/master/SPEC.md) @@ -218,7 +218,7 @@ United States and other governments. 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 +For more information, please see https://www.bis.doc.gov Licensing From 67a983fc372e7b5fd1c75d1ceafe9b79b84d7e92 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:21:16 -0400 Subject: [PATCH 045/332] Use HTTPS for package URL Signed-off-by: Eric Windisch --- hack/make/ubuntu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/make/ubuntu b/hack/make/ubuntu index e34369eb16383..5bbca8a89e93f 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -23,7 +23,7 @@ fi # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a PACKAGE_ARCHITECTURE="$(dpkg-architecture -qDEB_HOST_ARCH)" -PACKAGE_URL="http://www.docker.com/" +PACKAGE_URL="https://www.docker.com/" PACKAGE_MAINTAINER="support@docker.com" PACKAGE_DESCRIPTION="Linux container runtime Docker complements LXC with a high-level API which operates at the process From 723d43387a5c04ef8588c7e1557aa163e268581c Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:22:16 -0400 Subject: [PATCH 046/332] HTTPS urls for ./hacking Signed-off-by: Eric Windisch --- hack/dind | 2 +- hack/make.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hack/dind b/hack/dind index 1242cbffe1ed0..dfd463731e441 100755 --- a/hack/dind +++ b/hack/dind @@ -3,7 +3,7 @@ set -e # DinD: a wrapper script which allows docker to be run inside a docker container. # Original version by Jerome Petazzoni -# See the blog post: http://blog.docker.com/2013/09/docker-can-now-run-within-docker/ +# See the blog post: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/ # # This script should be executed inside a docker container in privilieged mode # ('docker run --privileged', introduced in docker 0.6). diff --git a/hack/make.sh b/hack/make.sh index 4117469d6640f..c3d4623af46e1 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -6,7 +6,7 @@ set -e # # Requirements: # - The current directory should be a checkout of the docker source code -# (http://github.com/docker/docker). Whatever version is checked out +# (https://github.com/docker/docker). Whatever version is checked out # will be built. # - The VERSION file, at the root of the repository, should exist, and # will be used as Docker binary version and package version. @@ -85,7 +85,7 @@ if [ "$AUTO_GOPATH" ]; then fi if [ ! "$GOPATH" ]; then - echo >&2 'error: missing GOPATH; please see http://golang.org/doc/code.html#GOPATH' + echo >&2 'error: missing GOPATH; please see https://golang.org/doc/code.html#GOPATH' echo >&2 ' alternatively, set AUTO_GOPATH=1' exit 1 fi From ca37301d54e1525d4522dea266180072d4fd892b Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:31:34 -0400 Subject: [PATCH 047/332] Link to HTTPS URLs in engine comments Updates most of the instances of HTTP urls in the engine's comments. Does not account for any use in the code itself, documentation, contrib, or project files. Signed-off-by: Eric Windisch --- api/server/server.go | 2 +- daemon/daemon.go | 4 ++-- daemon/execdriver/lxc/lxc_template.go | 2 +- engine/env.go | 4 ++-- integration-cli/docker_cli_proxy_test.go | 2 +- integration/api_test.go | 2 +- utils/utils.go | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee9037..4074ebb53e74b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -956,7 +956,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res // If contentLength is -1, we can assumed chunked encoding // or more technically that the length is unknown - // http://golang.org/src/pkg/net/http/request.go#L139 + // https://golang.org/src/pkg/net/http/request.go#L139 // net/http otherwise seems to swallow any headers related to chunked encoding // including r.TransferEncoding // allow a nil body for backwards compatibility diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e231ead..4408260510005 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -957,7 +957,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy) if sysInitPath == "" { - return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.com/contributing/devenvironment for official build instructions.") + return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See https://docs.docker.com/contributing/devenvironment for official build instructions.") } if sysInitPath != localCopy { @@ -1227,7 +1227,7 @@ func checkKernel() error { // Unfortunately we can't test for the feature "does not cause a kernel panic" // without actually causing a kernel panic, so we need this workaround until // the circumstances of pre-3.8 crashes are clearer. - // For details see http://github.com/docker/docker/issues/407 + // For details see https://github.com/docker/docker/issues/407 if k, err := kernel.GetKernelVersion(); err != nil { logrus.Warnf("%s", err) } else { diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 02313d465ad59..6d6decb79f9f4 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -62,7 +62,7 @@ lxc.pivotdir = lxc_putold # NOTICE: These mounts must be applied within the namespace {{if .ProcessConfig.Privileged}} # WARNING: mounting procfs and/or sysfs read-write is a known attack vector. -# See e.g. http://blog.zx2c4.com/749 and http://bit.ly/T9CkqJ +# See e.g. http://blog.zx2c4.com/749 and https://bit.ly/T9CkqJ # We mount them read-write here, but later, dockerinit will call the Restrict() function to remount them read-only. # 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 diff --git a/engine/env.go b/engine/env.go index c6c673271e9f0..089bc162c062b 100644 --- a/engine/env.go +++ b/engine/env.go @@ -210,7 +210,7 @@ func (env *Env) SetAuto(k string, v interface{}) { // FIXME: we fix-convert float values to int, because // encoding/json decodes integers to float64, but cannot encode them back. - // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) + // (See https://golang.org/src/pkg/encoding/json/decode.go#L46) if fval, ok := v.(float64); ok { env.SetInt64(k, int64(fval)) } else if sval, ok := v.(string); ok { @@ -245,7 +245,7 @@ func (env *Env) Encode(dst io.Writer) error { if err := json.Unmarshal([]byte(v), &val); err == nil { // FIXME: we fix-convert float values to int, because // encoding/json decodes integers to float64, but cannot encode them back. - // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) + // (See https://golang.org/src/pkg/encoding/json/decode.go#L46) m[k] = changeFloats(val) } else { m[k] = v diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go index b39dd5634d37e..55c54400324fb 100644 --- a/integration-cli/docker_cli_proxy_test.go +++ b/integration-cli/docker_cli_proxy_test.go @@ -21,7 +21,7 @@ func TestCliProxyDisableProxyUnixSock(t *testing.T) { } // Can't use localhost here since go has a special case to not use proxy if connecting to localhost -// See http://golang.org/pkg/net/http/#ProxyFromEnvironment +// See https://golang.org/pkg/net/http/#ProxyFromEnvironment func TestCliProxyProxyTCPSock(t *testing.T) { testRequires(t, SameHostDaemon) // get the IP to use to connect since we can't use localhost diff --git a/integration/api_test.go b/integration/api_test.go index 98e683d0040ac..c527bcb927d59 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -932,7 +932,7 @@ func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) { req.Header.Add("Content-Type", "application/json") // This is a cheat to make the http request do chunked encoding // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite - // http://golang.org/src/pkg/net/http/request.go?s=11980:12172 + // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 req.ContentLength = -1 server.ServeRequest(eng, api.APIVERSION, r, req) assertHttpNotError(r, t) diff --git a/utils/utils.go b/utils/utils.go index d0e76bf237aee..df93eabbd1c5e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -127,12 +127,12 @@ func DockerInitPath(localCopy string) string { filepath.Join(filepath.Dir(selfPath), "dockerinit"), // FHS 3.0 Draft: "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec." - // http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec + // https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec "/usr/libexec/docker/dockerinit", "/usr/local/libexec/docker/dockerinit", // FHS 2.3: "/usr/lib includes object files, libraries, and internal binaries that are not intended to be executed directly by users or shell scripts." - // http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA + // https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA "/usr/lib/docker/dockerinit", "/usr/local/lib/docker/dockerinit", } From df9ee6d6563ace6e382a3bdd4a45b38756a76afb Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:35:08 -0400 Subject: [PATCH 048/332] Link to HTTPS urls in contrib comments/maintainers Updates comments and dockerfile maintainer lines to use HTTPS urls where applicable. Signed-off-by: Eric Windisch --- contrib/check-config.sh | 2 +- contrib/project-stats.sh | 2 +- contrib/syntax/vim/doc/dockerfile.txt | 2 +- contrib/syntax/vim/syntax/dockerfile.vim | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index 59649d6c66bc3..b3ae169bc0ac3 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -27,7 +27,7 @@ is_set() { zgrep "CONFIG_$1=[y|m]" "$CONFIG" > /dev/null } -# see http://en.wikipedia.org/wiki/ANSI_escape_code#Colors +# see https://en.wikipedia.org/wiki/ANSI_escape_code#Colors declare -A colors=( [black]=30 [red]=31 diff --git a/contrib/project-stats.sh b/contrib/project-stats.sh index 985a77f22dcbf..2691c72ffbd6a 100755 --- a/contrib/project-stats.sh +++ b/contrib/project-stats.sh @@ -3,7 +3,7 @@ ## Run this script from the root of the docker repository ## to query project stats useful to the maintainers. ## You will need to install `pulls` and `issues` from -## http://github.com/crosbymichael/pulls +## https://github.com/crosbymichael/pulls set -e diff --git a/contrib/syntax/vim/doc/dockerfile.txt b/contrib/syntax/vim/doc/dockerfile.txt index 37cc7be9154f8..e69e2b7b30fca 100644 --- a/contrib/syntax/vim/doc/dockerfile.txt +++ b/contrib/syntax/vim/doc/dockerfile.txt @@ -1,6 +1,6 @@ *dockerfile.txt* Syntax highlighting for Dockerfiles -Author: Honza Pokorny +Author: Honza Pokorny License: BSD INSTALLATION *installation* diff --git a/contrib/syntax/vim/syntax/dockerfile.vim b/contrib/syntax/vim/syntax/dockerfile.vim index 36691e2504162..bd092686642e9 100644 --- a/contrib/syntax/vim/syntax/dockerfile.vim +++ b/contrib/syntax/vim/syntax/dockerfile.vim @@ -1,5 +1,5 @@ " dockerfile.vim - Syntax highlighting for Dockerfiles -" Maintainer: Honza Pokorny +" Maintainer: Honza Pokorny " Version: 0.5 From ac65c8c3801385c8278e0740ef2738b0aa56689a Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:42:17 -0400 Subject: [PATCH 049/332] HTTPS URLs for docs top-level & man pages This updates all of docs outside of sources. Signed-off-by: Eric Windisch --- docs/README.md | 4 ++-- docs/man/README.md | 2 +- docs/man/docker-run.1.md | 2 +- docs/mkdocs.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index 15fee1d364699..8ff25adab75e8 100755 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ The source for Docker documentation is in this directory under `sources/`. Our documentation uses extended Markdown, as implemented by [MkDocs](http://mkdocs.org). The current release of the Docker documentation -resides on [http://docs.docker.com](http://docs.docker.com). +resides on [https://docs.docker.com](https://docs.docker.com). ## Understanding the documentation branches and processes @@ -11,7 +11,7 @@ Docker has two primary branches for documentation: | Branch | Description | URL (published via commit-hook) | |----------|--------------------------------|------------------------------------------------------------------------------| -| `docs` | Official release documentation | [http://docs.docker.com](http://docs.docker.com) | +| `docs` | Official release documentation | [https://docs.docker.com](https://docs.docker.com) | | `master` | Merged but unreleased development work | [http://docs.master.dockerproject.com](http://docs.master.dockerproject.com) | Additions and updates to upcoming releases are made in a feature branch off of diff --git a/docs/man/README.md b/docs/man/README.md index 402178a9c2cda..e25a925adb716 100644 --- a/docs/man/README.md +++ b/docs/man/README.md @@ -30,4 +30,4 @@ The `md2man` Docker container will process the Markdown files and generate the man pages inside the `docker/docs/man/man1` directory using Docker volumes. For more information on Docker volumes see the man page for `docker run` and also look at the article [Sharing Directories via Volumes] -(http://docs.docker.com/use/working_with_volumes/). +(https://docs.docker.com/use/working_with_volumes/). diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 53d762cf61578..8a856ca608a9e 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -416,7 +416,7 @@ you’d like to connect instead, as in: ## Sharing IPC between containers -Using shm_server.c available here: http://www.cs.cf.ac.uk/Dave/C/node27.html +Using shm_server.c available here: https://www.cs.cf.ac.uk/Dave/C/node27.html Testing `--ipc=host` mode: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b5b30d72d32be..fd56ad15c36e9 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Docker Documentation -#site_url: http://docs.docker.com/ +#site_url: https://docs.docker.com/ site_url: / site_description: Documentation for fast and lightweight Docker container based virtualization framework. site_favicon: img/favicon.png From 5dc83233bc41fa4d8189aec6ce0327b06038c9b5 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:58:09 -0400 Subject: [PATCH 050/332] HTTPS urls for ./project Signed-off-by: Eric Windisch --- project/GOVERNANCE.md | 2 +- project/PACKAGERS.md | 6 +++--- project/RELEASE-CHECKLIST.md | 4 ++-- project/TOOLS.md | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/project/GOVERNANCE.md b/project/GOVERNANCE.md index 52a8bf05d6cb8..6ae7baf743017 100644 --- a/project/GOVERNANCE.md +++ b/project/GOVERNANCE.md @@ -4,7 +4,7 @@ In the spirit of openness, Docker created a Governance Advisory Board, and commi All output from the meetings should be considered proposals only, and are subject to the review and approval of the community and the project leadership. The materials from the first Docker Governance Advisory Board meeting, held on October 28, 2014, are available at -[Google Docs Folder](http://goo.gl/Alfj8r) +[Google Docs Folder](https://goo.gl/Alfj8r) These include: diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md index 5704b0a2b29c4..6acd4aef35fae 100644 --- a/project/PACKAGERS.md +++ b/project/PACKAGERS.md @@ -47,7 +47,7 @@ To build Docker, you will need the following: * A recent version of Git and Mercurial * Go version 1.3 or later * A clean checkout of the source added to a valid [Go - workspace](http://golang.org/doc/code.html#Workspaces) under the path + workspace](https://golang.org/doc/code.html#Workspaces) under the path *src/github.com/docker/docker* (unless you plan to use `AUTO_GOPATH`, explained in more detail below) @@ -237,9 +237,9 @@ are as follows (in order): installed at "/usr/bin/docker", then "/usr/bin/dockerinit" will be the first place this file is searched for) * "/usr/libexec/docker/dockerinit" or "/usr/local/libexec/docker/dockerinit" - ([FHS 3.0 Draft](http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec)) + ([FHS 3.0 Draft](https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec)) * "/usr/lib/docker/dockerinit" or "/usr/local/lib/docker/dockerinit" ([FHS - 2.3](http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA)) + 2.3](https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA)) If (and please, only if) one of the paths above is insufficient due to distro policy or similar issues, you may use the `DOCKER_INITPATH` environment variable diff --git a/project/RELEASE-CHECKLIST.md b/project/RELEASE-CHECKLIST.md index 10af71c81d2ec..8a2070e58d8c9 100644 --- a/project/RELEASE-CHECKLIST.md +++ b/project/RELEASE-CHECKLIST.md @@ -145,7 +145,7 @@ To test locally: make docs ``` -To make a shared test at http://beta-docs.docker.io: +To make a shared test at https://beta-docs.docker.io: (You will need the `awsconfig` file added to the `docs/` dir) @@ -341,7 +341,7 @@ git push -f origin docs make AWS_S3_BUCKET=docs.docker.com BUILD_ROOT=yes DISTRIBUTION_ID=C2K6......FL2F docs-release ``` -The docs will appear on http://docs.docker.com/ (though there may be cached +The docs will appear on https://docs.docker.com/ (though there may be cached versions, so its worth checking http://docs.docker.com.s3-website-us-east-1.amazonaws.com/). For more information about documentation releases, see `docs/README.md`. diff --git a/project/TOOLS.md b/project/TOOLS.md index f057ccd2befd9..79bd28374d244 100644 --- a/project/TOOLS.md +++ b/project/TOOLS.md @@ -14,11 +14,11 @@ we run Docker in Docker to test. Leeroy is a Go application which integrates Jenkins with GitHub pull requests. Leeroy uses -[GitHub hooks](http://developer.github.com/v3/repos/hooks/) +[GitHub hooks](https://developer.github.com/v3/repos/hooks/) to listen for pull request notifications and starts jobs on your Jenkins server. Using the Jenkins [notification plugin][jnp], Leeroy updates the pull request using GitHub's -[status API](http://developer.github.com/v3/repos/statuses/) +[status API](https://developer.github.com/v3/repos/statuses/) with pending, success, failure, or error statuses. The leeroy repository is maintained at From a9843cb739bd30a9e6eeb8841f645008e1fc905f Mon Sep 17 00:00:00 2001 From: John Gossman Date: Sat, 11 Apr 2015 10:40:37 -0700 Subject: [PATCH 051/332] Added some error messages and tracing to bridge network initialization Signed-off-by: John Gossman --- daemon/daemon.go | 1 + daemon/networkdriver/bridge/driver.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e231ead..1acf96a023000 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -938,6 +938,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if !config.DisableNetwork { if err := bridge.InitDriver(&config.Bridge); err != nil { + logrus.Errorf("Error initializing Bridge: %s", err) return nil, err } } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index dabb1165e76bd..d9698382e02de 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -135,8 +135,11 @@ func InitDriver(config *Config) error { return err } + logrus.Infof("Bridge interface not found, trying to create it") + // If the iface is not found, try to create it if err := configureBridge(config.IP, bridgeIPv6, config.EnableIPv6); err != nil { + logrus.Errorf("Could not configure Bridge: %s", err) return err } @@ -214,6 +217,7 @@ func InitDriver(config *Config) error { // Configure iptables for link support if config.EnableIptables { if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { + logrus.Errorf("Error configuing iptables: %s", err) return err } @@ -261,6 +265,7 @@ func InitDriver(config *Config) error { } logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil { + logrus.Errorf("Error registering subnet for IPv4 bridge network: %s", err) return err } } @@ -272,6 +277,7 @@ func InitDriver(config *Config) error { } logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil { + logrus.Errorf("Error registering subnet for IPv4 bridge network: %s", err) return err } globalIPv6Network = subnet From c4fe5dad1deb45ecde460d7627523dbf032dc205 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Wed, 8 Apr 2015 13:29:32 +0200 Subject: [PATCH 052/332] Add test on archive.go (#11603) - Trying to add or complete unit test to each ``func`` - Removing dead code (``escapeName``) Signed-off-by: Vincent Demeester --- pkg/archive/archive.go | 16 --- pkg/archive/archive_test.go | 243 +++++++++++++++++++++++++++++++++++- 2 files changed, 242 insertions(+), 17 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 7082cd9088277..7f188975036dd 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -388,22 +388,6 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) { return TarWithOptions(path, &TarOptions{Compression: compression}) } -func escapeName(name string) string { - escaped := make([]byte, 0) - for i, c := range []byte(name) { - if i == 0 && c == '/' { - continue - } - // all printable chars except "-" which is 0x2d - if (0x20 <= c && c <= 0x7E) && c != 0x2d { - escaped = append(escaped, c) - } else { - escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) - } - } - return string(escaped) -} - // TarWithOptions creates an archive from the directory at `path`, only including files whose relative // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index c127b307e2bc0..065e4e47f67b7 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -14,9 +14,150 @@ import ( "testing" "time" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) +func TestIsArchiveNilHeader(t *testing.T) { + out := IsArchive(nil) + if out { + t.Fatalf("isArchive should return false as nil is not a valid archive header") + } +} + +func TestIsArchiveInvalidHeader(t *testing.T) { + header := []byte{0x00, 0x01, 0x02} + out := IsArchive(header) + if out { + t.Fatalf("isArchive should return false as %s is not a valid archive header", header) + } +} + +func TestIsArchiveBzip2(t *testing.T) { + header := []byte{0x42, 0x5A, 0x68} + out := IsArchive(header) + if !out { + t.Fatalf("isArchive should return true as %s is a bz2 header", header) + } +} + +func TestIsArchive7zip(t *testing.T) { + header := []byte{0x50, 0x4b, 0x03, 0x04} + out := IsArchive(header) + if out { + t.Fatalf("isArchive should return false as %s is a 7z header and it is not supported", header) + } +} + +func TestDecompressStreamGzip(t *testing.T) { + cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && gzip -f /tmp/archive") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Fail to create an archive file for test : %s.", output) + } + archive, err := os.Open("/tmp/archive.gz") + _, err = DecompressStream(archive) + if err != nil { + t.Fatalf("Failed to decompress a gzip file.") + } +} + +func TestDecompressStreamBzip2(t *testing.T) { + cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && bzip2 -f /tmp/archive") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Fail to create an archive file for test : %s.", output) + } + archive, err := os.Open("/tmp/archive.bz2") + _, err = DecompressStream(archive) + if err != nil { + t.Fatalf("Failed to decompress a bzip2 file.") + } +} + +func TestDecompressStreamXz(t *testing.T) { + cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && xz -f /tmp/archive") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Fail to create an archive file for test : %s.", output) + } + archive, err := os.Open("/tmp/archive.xz") + _, err = DecompressStream(archive) + if err != nil { + t.Fatalf("Failed to decompress a xz file.") + } +} + +func TestCompressStreamXzUnsuported(t *testing.T) { + dest, err := os.Create("/tmp/dest") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + _, err = CompressStream(dest, Xz) + if err == nil { + t.Fatalf("Should fail as xz is unsupported for compression format.") + } +} + +func TestCompressStreamBzip2Unsupported(t *testing.T) { + dest, err := os.Create("/tmp/dest") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + _, err = CompressStream(dest, Xz) + if err == nil { + t.Fatalf("Should fail as xz is unsupported for compression format.") + } +} + +func TestCompressStreamInvalid(t *testing.T) { + dest, err := os.Create("/tmp/dest") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + _, err = CompressStream(dest, -1) + if err == nil { + t.Fatalf("Should fail as xz is unsupported for compression format.") + } +} + +func TestExtensionInvalid(t *testing.T) { + compression := Compression(-1) + output := compression.Extension() + if output != "" { + t.Fatalf("The extension of an invalid compression should be an empty string.") + } +} + +func TestExtensionUncompressed(t *testing.T) { + compression := Uncompressed + output := compression.Extension() + if output != "tar" { + t.Fatalf("The extension of a uncompressed archive should be 'tar'.") + } +} +func TestExtensionBzip2(t *testing.T) { + compression := Bzip2 + output := compression.Extension() + if output != "tar.bz2" { + t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'") + } +} +func TestExtensionGzip(t *testing.T) { + compression := Gzip + output := compression.Extension() + if output != "tar.gz" { + t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'") + } +} +func TestExtensionXz(t *testing.T) { + compression := Xz + output := compression.Extension() + if output != "tar.xz" { + t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'") + } +} + func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") out, err := CmdStream(cmd, nil) @@ -179,11 +320,56 @@ func TestTarUntar(t *testing.T) { } } +func TestTarUntarWithXattr(t *testing.T) { + origin, err := ioutil.TempDir("", "docker-test-untar-origin") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(origin) + if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil { + t.Fatal(err) + } + if err := system.Lsetxattr(path.Join(origin, "2"), "security.capability", []byte{0x00}, 0); err != nil { + t.Fatal(err) + } + + for _, c := range []Compression{ + Uncompressed, + Gzip, + } { + changes, err := tarUntar(t, origin, &TarOptions{ + Compression: c, + ExcludePatterns: []string{"3"}, + }) + + if err != nil { + t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err) + } + + if len(changes) != 1 || changes[0].Path != "/3" { + t.Fatalf("Unexpected differences after tarUntar: %v", changes) + } + capability, _ := system.Lgetxattr(path.Join(origin, "2"), "security.capability") + if capability == nil && capability[0] != 0x00 { + t.Fatalf("Untar should have kept the 'security.capability' xattr.") + } + } +} + func TestTarWithOptions(t *testing.T) { origin, err := ioutil.TempDir("", "docker-test-untar-origin") if err != nil { t.Fatal(err) } + if _, err := ioutil.TempDir(origin, "folder"); err != nil { + t.Fatal(err) + } defer os.RemoveAll(origin) if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { t.Fatal(err) @@ -196,8 +382,11 @@ func TestTarWithOptions(t *testing.T) { opts *TarOptions numChanges int }{ - {&TarOptions{IncludeFiles: []string{"1"}}, 1}, + {&TarOptions{IncludeFiles: []string{"1"}}, 2}, {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, + {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, + {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, + {&TarOptions{Name: "test", IncludeFiles: []string{"1"}}, 4}, } for _, testCase := range cases { changes, err := tarUntar(t, origin, testCase.opts) @@ -256,6 +445,58 @@ func TestUntarUstarGnuConflict(t *testing.T) { } } +func TestTarWithBlockCharFifo(t *testing.T) { + origin, err := ioutil.TempDir("", "docker-test-tar-hardlink") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(origin) + if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { + t.Fatal(err) + } + if err := system.Mknod(path.Join(origin, "2"), syscall.S_IFBLK, int(system.Mkdev(int64(12), int64(5)))); err != nil { + t.Fatal(err) + } + if err := system.Mknod(path.Join(origin, "3"), syscall.S_IFCHR, int(system.Mkdev(int64(12), int64(5)))); err != nil { + t.Fatal(err) + } + if err := system.Mknod(path.Join(origin, "4"), syscall.S_IFIFO, int(system.Mkdev(int64(12), int64(5)))); err != nil { + t.Fatal(err) + } + + dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dest) + + // we'll do this in two steps to separate failure + fh, err := Tar(origin, Uncompressed) + if err != nil { + t.Fatal(err) + } + + // ensure we can read the whole thing with no error, before writing back out + buf, err := ioutil.ReadAll(fh) + if err != nil { + t.Fatal(err) + } + + bRdr := bytes.NewReader(buf) + err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed}) + if err != nil { + t.Fatal(err) + } + + changes, err := ChangesDirs(origin, dest) + if err != nil { + t.Fatal(err) + } + if len(changes) > 0 { + t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %s", changes) + } +} + func TestTarWithHardLink(t *testing.T) { origin, err := ioutil.TempDir("", "docker-test-tar-hardlink") if err != nil { From 621ee1f6a4cbc8ee8758ce77a0cf6215c88f12f7 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 00:15:34 +0200 Subject: [PATCH 053/332] Remove job from execInspect Signed-off-by: Antonio Murdaca --- api/server/server.go | 11 ++++++++--- daemon/daemon.go | 1 - daemon/inspect.go | 15 +++------------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee9037..66a0fa8126fe8 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1151,9 +1151,14 @@ func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWri if vars == nil { return fmt.Errorf("Missing parameter 'id'") } - var job = eng.Job("execInspect", vars["id"]) - streamJSON(job, w, false) - return job.Run() + + d := getDaemon(eng) + eConfig, err := d.ContainerExecInspect(vars["id"]) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, eConfig) } func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e231ead..b7474fdbfd63d 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -129,7 +129,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "stop": daemon.ContainerStop, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, - "execInspect": daemon.ContainerExecInspect, } { if err := eng.Register(name, method); err != nil { return err diff --git a/daemon/inspect.go b/daemon/inspect.go index 73ce2ea8e250a..7e25626ef7a60 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -80,20 +80,11 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) error { return nil } -func (daemon *Daemon) ContainerExecInspect(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s ID", job.Name) - } - id := job.Args[0] +func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) { eConfig, err := daemon.getExecConfig(id) if err != nil { - return err + return nil, err } - b, err := json.Marshal(*eConfig) - if err != nil { - return err - } - job.Stdout.Write(b) - return nil + return eConfig, nil } From 04cc6c6aa4f8ea656d23268b7bff0136e4fc6c8d Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 00:41:16 +0200 Subject: [PATCH 054/332] Remove job from stop Signed-off-by: Antonio Murdaca --- api/server/server.go | 12 +++++++++--- daemon/daemon.go | 1 - daemon/stop.go | 20 +++----------------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee9037..923e6528a0c8a 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -988,9 +988,14 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - job := eng.Job("stop", vars["name"]) - job.Setenv("t", r.Form.Get("t")) - if err := job.Run(); err != nil { + + d := getDaemon(eng) + seconds, err := strconv.Atoi(r.Form.Get("t")) + if err != nil { + return err + } + + if err := d.ContainerStop(vars["name"], seconds); err != nil { if err.Error() == "Container already stopped" { w.WriteHeader(http.StatusNotModified) return nil @@ -998,6 +1003,7 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp return err } w.WriteHeader(http.StatusNoContent) + return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e231ead..f79457ada5b3c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -126,7 +126,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "logs": daemon.ContainerLogs, "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, - "stop": daemon.ContainerStop, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, "execInspect": daemon.ContainerExecInspect, diff --git a/daemon/stop.go b/daemon/stop.go index 871683be91921..b481f87efb692 100644 --- a/daemon/stop.go +++ b/daemon/stop.go @@ -1,22 +1,8 @@ package daemon -import ( - "fmt" +import "fmt" - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerStop(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } - var ( - name = job.Args[0] - t = 10 - ) - if job.EnvExists("t") { - t = job.GetenvInt("t") - } +func (daemon *Daemon) ContainerStop(name string, seconds int) error { container, err := daemon.Get(name) if err != nil { return err @@ -24,7 +10,7 @@ func (daemon *Daemon) ContainerStop(job *engine.Job) error { if !container.IsRunning() { return fmt.Errorf("Container already stopped") } - if err := container.Stop(int(t)); err != nil { + if err := container.Stop(seconds); err != nil { return fmt.Errorf("Cannot stop container %s: %s\n", name, err) } container.LogEvent("stop") From 7560018541192ebdfe16e39515f9a04b44635d84 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 16:25:10 +0200 Subject: [PATCH 055/332] Remove engine from links Signed-off-by: Antonio Murdaca --- daemon/container.go | 2 +- links/links.go | 5 +---- links/links_test.go | 13 +++++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 46defe9683298..fc9ae1ae58fa4 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1328,7 +1328,7 @@ func (container *Container) setupLinkedContainers() ([]string, error) { linkAlias, child.Config.Env, child.Config.ExposedPorts, - daemon.eng) + ) if err != nil { rollback() diff --git a/links/links.go b/links/links.go index 0e5e806e57be3..1ae8f23aeaf59 100644 --- a/links/links.go +++ b/links/links.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/nat" ) @@ -17,10 +16,9 @@ type Link struct { ChildEnvironment []string Ports []nat.Port IsEnabled bool - eng *engine.Engine } -func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.Port]struct{}, eng *engine.Engine) (*Link, error) { +func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.Port]struct{}) (*Link, error) { var ( i int @@ -38,7 +36,6 @@ func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat. ParentIP: parentIP, ChildEnvironment: env, Ports: ports, - eng: eng, } return l, nil diff --git a/links/links_test.go b/links/links_test.go index ba548fc5b3922..e639e2c42e557 100644 --- a/links/links_test.go +++ b/links/links_test.go @@ -2,16 +2,17 @@ package links import ( "fmt" - "github.com/docker/docker/nat" "strings" "testing" + + "github.com/docker/docker/nat" ) func TestLinkNaming(t *testing.T) { ports := make(nat.PortSet) ports[nat.Port("6379/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, ports) if err != nil { t.Fatal(err) } @@ -41,7 +42,7 @@ func TestLinkNew(t *testing.T) { ports := make(nat.PortSet) ports[nat.Port("6379/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, ports) if err != nil { t.Fatal(err) } @@ -72,7 +73,7 @@ func TestLinkEnv(t *testing.T) { ports := make(nat.PortSet) ports[nat.Port("6379/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports) if err != nil { t.Fatal(err) } @@ -115,7 +116,7 @@ func TestLinkMultipleEnv(t *testing.T) { ports[nat.Port("6380/tcp")] = struct{}{} ports[nat.Port("6381/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports) if err != nil { t.Fatal(err) } @@ -164,7 +165,7 @@ func TestLinkPortRangeEnv(t *testing.T) { ports[nat.Port("6380/tcp")] = struct{}{} ports[nat.Port("6381/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports) if err != nil { t.Fatal(err) } From 4ce19da739ccdb8337c59f841e790255d21c6f50 Mon Sep 17 00:00:00 2001 From: John Gossman Date: Sun, 12 Apr 2015 15:49:29 -0700 Subject: [PATCH 056/332] Addressed feedback. Will squash after further review Signed-off-by: John Gossman --- daemon/networkdriver/bridge/driver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index d9698382e02de..11b26128c1b96 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -135,7 +135,7 @@ func InitDriver(config *Config) error { return err } - logrus.Infof("Bridge interface not found, trying to create it") + logrus.Info("Bridge interface not found, trying to create it") // If the iface is not found, try to create it if err := configureBridge(config.IP, bridgeIPv6, config.EnableIPv6); err != nil { @@ -277,7 +277,7 @@ func InitDriver(config *Config) error { } logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil { - logrus.Errorf("Error registering subnet for IPv4 bridge network: %s", err) + logrus.Errorf("Error registering subnet for IPv6 bridge network: %s", err) return err } globalIPv6Network = subnet From 80e9f6f83856b8e762f030d5e562f9e9f8d17233 Mon Sep 17 00:00:00 2001 From: John Gossman Date: Sun, 12 Apr 2015 16:26:37 -0700 Subject: [PATCH 057/332] More review feedback addressed Signed-off-by: John Gossman --- daemon/daemon.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 1acf96a023000..f68363cb6016e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -938,8 +938,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if !config.DisableNetwork { if err := bridge.InitDriver(&config.Bridge); err != nil { - logrus.Errorf("Error initializing Bridge: %s", err) - return nil, err + return nil, fmt.Errorf("Error initializing Bridge: %v", err) } } From 908db51804635ce002e97e4efb867f7352204f8e Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Fri, 10 Apr 2015 14:23:09 -0400 Subject: [PATCH 058/332] Send archive options via pipe in chrootarchive After finding our initial thinking on env. space versus arg list space was wrong, we need to solve this by using a pipe between the caller and child to marshall the (potentially very large) options array to the archiver. Docker-DCO-1.1-Signed-off-by: Phil Estes (github: estesp) --- pkg/chrootarchive/archive.go | 43 ++++++++++++++++++++----------- pkg/chrootarchive/archive_test.go | 37 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go index 17d3739d1ac86..a1454f7b9ff98 100644 --- a/pkg/chrootarchive/archive.go +++ b/pkg/chrootarchive/archive.go @@ -1,6 +1,7 @@ package chrootarchive import ( + "bytes" "encoding/json" "flag" "fmt" @@ -29,7 +30,8 @@ func untar() { var options *archive.TarOptions - if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil { + //read the options from the pipe "ExtraFiles" + if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil { fatal(err) } @@ -62,28 +64,39 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error } } - // We can't pass the exclude list directly via cmd line - // because we easily overrun the shell max argument list length - // when the full image list is passed (e.g. when this is used - // by `docker load`). Instead we will add the JSON marshalled - // and placed in the env, which has significantly larger - // max size - data, err := json.Marshal(options) - if err != nil { - return fmt.Errorf("Untar json encode: %v", err) - } decompressedArchive, err := archive.DecompressStream(tarArchive) if err != nil { return err } defer decompressedArchive.Close() + // We can't pass a potentially large exclude list directly via cmd line + // because we easily overrun the kernel's max argument/environment size + // when the full image list is passed (e.g. when this is used by + // `docker load`). We will marshall the options via a pipe to the + // child + r, w, err := os.Pipe() + if err != nil { + return fmt.Errorf("Untar pipe failure: %v", err) + } cmd := reexec.Command("docker-untar", dest) cmd.Stdin = decompressedArchive - cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data)) - out, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("Untar %s %s", err, out) + cmd.ExtraFiles = append(cmd.ExtraFiles, r) + var output bytes.Buffer + cmd.Stdout = &output + cmd.Stderr = &output + + if err := cmd.Start(); err != nil { + return fmt.Errorf("Untar error on re-exec cmd: %v", err) + } + //write the options to the pipe for the untar exec to read + if err := json.NewEncoder(w).Encode(options); err != nil { + return fmt.Errorf("Untar json encode to pipe failed: %v", err) + } + w.Close() + + if err := cmd.Wait(); err != nil { + return fmt.Errorf("Untar re-exec error: %v: output: %s", err, output) } return nil } diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go index 45397d38fe7da..18f7a93f94f89 100644 --- a/pkg/chrootarchive/archive_test.go +++ b/pkg/chrootarchive/archive_test.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "strings" "testing" "time" @@ -48,6 +49,42 @@ func TestChrootTarUntar(t *testing.T) { } } +// gh#10426: Verify the fix for having a huge excludes list (like on `docker load` with large # of +// local images) +func TestChrootUntarWithHugeExcludesList(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarHugeExcludes") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + src := filepath.Join(tmpdir, "src") + if err := os.MkdirAll(src, 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(src, "toto"), []byte("hello toto"), 0644); err != nil { + t.Fatal(err) + } + stream, err := archive.Tar(src, archive.Uncompressed) + if err != nil { + t.Fatal(err) + } + dest := filepath.Join(tmpdir, "dest") + if err := os.MkdirAll(dest, 0700); err != nil { + t.Fatal(err) + } + options := &archive.TarOptions{} + //65534 entries of 64-byte strings ~= 4MB of environment space which should overflow + //on most systems when passed via environment or command line arguments + excludes := make([]string, 65534, 65534) + for i := 0; i < 65534; i++ { + excludes[i] = strings.Repeat(string(i), 64) + } + options.ExcludePatterns = excludes + if err := Untar(stream, dest, options); err != nil { + t.Fatal(err) + } +} + func TestChrootUntarEmptyArchive(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarEmptyArchive") if err != nil { From f5a07f0c884fc6d4fa7882cd8d07319c9bcfba1c Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Fri, 10 Apr 2015 15:53:05 +0800 Subject: [PATCH 059/332] Add event log for push Signed-off-by: Ma Shimiao --- graph/push.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/push.go b/graph/push.go index 881d24a9b8873..fedd29498eec0 100644 --- a/graph/push.go +++ b/graph/push.go @@ -536,6 +536,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { err := s.pushV2Repository(r, localRepo, job.Stdout, repoInfo, tag, sf) if err == nil { + s.eventsService.Log("push", repoInfo.LocalName, "") return nil } @@ -547,6 +548,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil { return err } + s.eventsService.Log("push", repoInfo.LocalName, "") return nil } From 91bfed604959c591a076c2e330cb3ded7443f504 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 11 Apr 2015 23:49:14 +0200 Subject: [PATCH 060/332] Remove job from logs Signed-off-by: Antonio Murdaca --- api/server/server.go | 41 +++----- api/server/server_unit_test.go | 94 ------------------- daemon/daemon.go | 1 - daemon/logs.go | 74 ++++++++------- integration-cli/docker_api_containers_test.go | 51 +++++----- integration-cli/docker_api_exec_test.go | 2 +- integration-cli/docker_api_images_test.go | 2 +- integration-cli/docker_api_inspect_test.go | 2 +- integration-cli/docker_api_logs_test.go | 53 +++++++++++ integration-cli/docker_api_resize_test.go | 4 +- integration-cli/docker_cli_rm_test.go | 2 +- integration-cli/docker_utils.go | 20 ++-- integration-cli/requirements.go | 2 +- 13 files changed, 151 insertions(+), 197 deletions(-) create mode 100644 integration-cli/docker_api_logs_test.go diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee9037..262a5072ce757 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -557,43 +557,26 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo return fmt.Errorf("Missing parameter") } - var ( - inspectJob = eng.Job("container_inspect", vars["name"]) - logsJob = eng.Job("logs", vars["name"]) - c, err = inspectJob.Stdout.AddEnv() - ) - if err != nil { - return err - } - logsJob.Setenv("follow", r.Form.Get("follow")) - logsJob.Setenv("tail", r.Form.Get("tail")) - logsJob.Setenv("stdout", r.Form.Get("stdout")) - logsJob.Setenv("stderr", r.Form.Get("stderr")) - logsJob.Setenv("timestamps", r.Form.Get("timestamps")) // Validate args here, because we can't return not StatusOK after job.Run() call - stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr") + stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr")) if !(stdout || stderr) { return fmt.Errorf("Bad parameters: you must choose at least one stream") } - if err = inspectJob.Run(); err != nil { - return err - } - var outStream, errStream io.Writer - outStream = utils.NewWriteFlusher(w) - - if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { - errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) - outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) - } else { - errStream = outStream + logsConfig := &daemon.ContainerLogsConfig{ + Follow: toBool(r.Form.Get("follow")), + Timestamps: toBool(r.Form.Get("timestamps")), + Tail: r.Form.Get("tail"), + UseStdout: stdout, + UseStderr: stderr, + OutStream: utils.NewWriteFlusher(w), } - logsJob.Stdout.Add(outStream) - logsJob.Stderr.Set(errStream) - if err := logsJob.Run(); err != nil { - fmt.Fprintf(outStream, "Error running logs job: %s\n", err) + d := getDaemon(eng) + if err := d.ContainerLogs(vars["name"], logsConfig); err != nil { + fmt.Fprintf(w, "Error running logs job: %s\n", err) } + return nil } diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index b5dd984b0c3f0..78b95b26bb6dd 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "net/http/httptest" - "strings" "testing" "github.com/docker/docker/api" @@ -126,99 +125,6 @@ func TestGetContainersByName(t *testing.T) { } } -func TestLogs(t *testing.T) { - eng := engine.New() - var inspect bool - var logs bool - eng.Register("container_inspect", func(job *engine.Job) error { - inspect = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != "test" { - t.Fatalf("Container name %s, must be test", job.Args[0]) - } - return nil - }) - expected := "logs" - eng.Register("logs", func(job *engine.Job) error { - logs = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != "test" { - t.Fatalf("Container name %s, must be test", job.Args[0]) - } - follow := job.Getenv("follow") - if follow != "1" { - t.Fatalf("follow: %s, must be 1", follow) - } - stdout := job.Getenv("stdout") - if stdout != "1" { - t.Fatalf("stdout %s, must be 1", stdout) - } - stderr := job.Getenv("stderr") - if stderr != "" { - t.Fatalf("stderr %s, must be empty", stderr) - } - timestamps := job.Getenv("timestamps") - if timestamps != "1" { - t.Fatalf("timestamps %s, must be 1", timestamps) - } - job.Stdout.Write([]byte(expected)) - return nil - }) - r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1×tamps=1", nil, eng, t) - if r.Code != http.StatusOK { - t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK) - } - if !inspect { - t.Fatal("container_inspect job was not called") - } - if !logs { - t.Fatal("logs job was not called") - } - res := r.Body.String() - if res != expected { - t.Fatalf("Output %s, expected %s", res, expected) - } -} - -func TestLogsNoStreams(t *testing.T) { - eng := engine.New() - var inspect bool - var logs bool - eng.Register("container_inspect", func(job *engine.Job) error { - inspect = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != "test" { - t.Fatalf("Container name %s, must be test", job.Args[0]) - } - return nil - }) - eng.Register("logs", func(job *engine.Job) error { - logs = true - return nil - }) - r := serveRequest("GET", "/containers/test/logs", nil, eng, t) - if r.Code != http.StatusBadRequest { - t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest) - } - if inspect { - t.Fatal("container_inspect job was called, but it shouldn't") - } - if logs { - t.Fatal("logs job was called, but it shouldn't") - } - res := strings.TrimSpace(r.Body.String()) - expected := "Bad parameters: you must choose at least one stream" - if !strings.Contains(res, expected) { - t.Fatalf("Output %s, expected %s in it", res, expected) - } -} - func TestGetImagesByName(t *testing.T) { eng := engine.New() name := "image_name" diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e231ead..505a09e0afd38 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -123,7 +123,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "create": daemon.ContainerCreate, "export": daemon.ContainerExport, "info": daemon.CmdInfo, - "logs": daemon.ContainerLogs, "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, diff --git a/daemon/logs.go b/daemon/logs.go index c991fa1978a46..79d4044bbe20b 100644 --- a/daemon/logs.go +++ b/daemon/logs.go @@ -10,40 +10,50 @@ import ( "sync" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonlog" + "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/tailfile" "github.com/docker/docker/pkg/timeutils" ) -func (daemon *Daemon) ContainerLogs(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } +type ContainerLogsConfig struct { + Follow, Timestamps bool + Tail string + UseStdout, UseStderr bool + OutStream io.Writer +} +func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error { var ( - name = job.Args[0] - stdout = job.GetenvBool("stdout") - stderr = job.GetenvBool("stderr") - tail = job.Getenv("tail") - follow = job.GetenvBool("follow") - times = job.GetenvBool("timestamps") lines = -1 format string ) - if !(stdout || stderr) { + if !(config.UseStdout || config.UseStderr) { return fmt.Errorf("You must choose at least one stream") } - if times { + if config.Timestamps { format = timeutils.RFC3339NanoFixed } - if tail == "" { - tail = "all" + if config.Tail == "" { + config.Tail = "all" } + container, err := daemon.Get(name) if err != nil { return err } + + var ( + outStream = config.OutStream + errStream io.Writer + ) + if !container.Config.Tty { + errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) + outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) + } else { + errStream = outStream + } + if container.LogDriverType() != "json-file" { return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver") } @@ -51,30 +61,30 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error { if err != nil && os.IsNotExist(err) { // Legacy logs logrus.Debugf("Old logs format") - if stdout { + if config.UseStdout { cLog, err := container.ReadLog("stdout") if err != nil { logrus.Errorf("Error reading logs (stdout): %s", err) - } else if _, err := io.Copy(job.Stdout, cLog); err != nil { + } else if _, err := io.Copy(outStream, cLog); err != nil { logrus.Errorf("Error streaming logs (stdout): %s", err) } } - if stderr { + if config.UseStderr { cLog, err := container.ReadLog("stderr") if err != nil { logrus.Errorf("Error reading logs (stderr): %s", err) - } else if _, err := io.Copy(job.Stderr, cLog); err != nil { + } else if _, err := io.Copy(errStream, cLog); err != nil { logrus.Errorf("Error streaming logs (stderr): %s", err) } } } else if err != nil { logrus.Errorf("Error reading logs (json): %s", err) } else { - if tail != "all" { + if config.Tail != "all" { var err error - lines, err = strconv.Atoi(tail) + lines, err = strconv.Atoi(config.Tail) if err != nil { - logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err) + logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err) lines = -1 } } @@ -101,39 +111,39 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error { break } logLine := l.Log - if times { + if config.Timestamps { // format can be "" or time format, so here can't be error logLine, _ = l.Format(format) } - if l.Stream == "stdout" && stdout { - io.WriteString(job.Stdout, logLine) + if l.Stream == "stdout" && config.UseStdout { + io.WriteString(outStream, logLine) } - if l.Stream == "stderr" && stderr { - io.WriteString(job.Stderr, logLine) + if l.Stream == "stderr" && config.UseStderr { + io.WriteString(errStream, logLine) } l.Reset() } } } - if follow && container.IsRunning() { + if config.Follow && container.IsRunning() { errors := make(chan error, 2) wg := sync.WaitGroup{} - if stdout { + if config.UseStdout { wg.Add(1) stdoutPipe := container.StdoutLogPipe() defer stdoutPipe.Close() go func() { - errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format) + errors <- jsonlog.WriteLog(stdoutPipe, outStream, format) wg.Done() }() } - if stderr { + if config.UseStderr { wg.Add(1) stderrPipe := container.StderrLogPipe() defer stderrPipe.Close() go func() { - errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format) + errors <- jsonlog.WriteLog(stderrPipe, errStream, format) wg.Done() }() } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 07793a8fca89d..2771c7c02c9d8 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -3,14 +3,15 @@ package main import ( "bytes" "encoding/json" - "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "os/exec" "strings" "testing" "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestContainerApiGetAll(t *testing.T) { @@ -28,7 +29,7 @@ func TestContainerApiGetAll(t *testing.T) { t.Fatalf("Error on container creation: %v, output: %q", err, out) } - body, err := sockRequest("GET", "/containers/json?all=1", nil) + _, body, err := sockRequest("GET", "/containers/json?all=1", nil) if err != nil { t.Fatalf("GET all containers sockRequest failed: %v", err) } @@ -61,7 +62,7 @@ func TestContainerApiGetExport(t *testing.T) { t.Fatalf("Error on container creation: %v, output: %q", err, out) } - body, err := sockRequest("GET", "/containers/"+name+"/export", nil) + _, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) if err != nil { t.Fatalf("GET containers/export sockRequest failed: %v", err) } @@ -98,7 +99,7 @@ func TestContainerApiGetChanges(t *testing.T) { t.Fatalf("Error on container creation: %v, output: %q", err, out) } - body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) + _, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) if err != nil { t.Fatalf("GET containers/changes sockRequest failed: %v", err) } @@ -133,7 +134,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } @@ -141,7 +142,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { config = map[string]interface{}{ "Binds": []string{bindPath + ":/tmp"}, } - if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -166,7 +167,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } @@ -176,7 +177,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { config = map[string]interface{}{ "Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"}, } - if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { + if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { t.Fatal("expected container start to fail when duplicate volume binds to same container path") } else { if !strings.Contains(string(body), "Duplicate volume") { @@ -201,14 +202,14 @@ func TestContainerApiStartVolumesFrom(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } config = map[string]interface{}{ "VolumesFrom": []string{volName}, } - if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -245,7 +246,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } @@ -254,7 +255,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "VolumesFrom": []string{volName}, "Binds": []string{bindPath + ":/tmp"}, } - if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -290,7 +291,7 @@ func TestGetContainerStats(t *testing.T) { } bc := make(chan b, 1) go func() { - body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + _, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) bc <- b{body, err} }() @@ -334,7 +335,7 @@ func TestGetStoppedContainerStats(t *testing.T) { go func() { // We'll never get return for GET stats from sockRequest as of now, // just send request and see if panic or error would happen on daemon side. - _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + _, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) if err != nil { t.Fatal(err) } @@ -367,7 +368,7 @@ func TestBuildApiDockerfilePath(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + _, out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") if err == nil { t.Fatalf("Build was supposed to fail: %s", out) } @@ -391,7 +392,7 @@ RUN find /tmp/`, } defer server.Close() - buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } @@ -417,7 +418,7 @@ RUN echo from dockerfile`, } defer git.Close() - buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s\n%q", err, buf) } @@ -443,7 +444,7 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s\n%q", err, buf) } @@ -470,7 +471,7 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } @@ -501,7 +502,7 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + _, out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") if err == nil { t.Fatalf("Build was supposed to fail: %s", out) } @@ -537,7 +538,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) { } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} - _, err = sockRequest("POST", "/containers/two/start", bindSpec) + _, _, err = sockRequest("POST", "/containers/two/start", bindSpec) if err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -565,7 +566,7 @@ func TestContainerApiPause(t *testing.T) { } ContainerID := strings.TrimSpace(out) - if _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } @@ -579,7 +580,7 @@ func TestContainerApiPause(t *testing.T) { t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - if _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go index 1ed99a2561e86..f898250a11470 100644 --- a/integration-cli/docker_api_exec_test.go +++ b/integration-cli/docker_api_exec_test.go @@ -18,7 +18,7 @@ func TestExecApiCreateNoCmd(t *testing.T) { t.Fatal(out, err) } - body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) + _, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) if err == nil || !bytes.Contains(body, []byte("No exec command specified")) { t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 38d891fd5ad83..49cfb36da8a63 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -8,7 +8,7 @@ import ( ) func TestLegacyImages(t *testing.T) { - body, err := sockRequest("GET", "/v1.6/images/json", nil) + _, body, err := sockRequest("GET", "/v1.6/images/json", nil) if err != nil { t.Fatalf("Error on GET: %s", err) } diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index e43f10fd6e2bc..43144f9165243 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -27,7 +27,7 @@ func TestInspectApiContainerResponse(t *testing.T) { if testVersion != "latest" { endpoint = "/" + testVersion + endpoint } - body, err := sockRequest("GET", endpoint, nil) + _, body, err := sockRequest("GET", endpoint, nil) if err != nil { t.Fatalf("sockRequest failed for %s version: %v", testVersion, err) } diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go new file mode 100644 index 0000000000000..27eb31c339be8 --- /dev/null +++ b/integration-cli/docker_api_logs_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "bytes" + "fmt" + "net/http" + "os/exec" + "testing" +) + +func TestLogsApiWithStdout(t *testing.T) { + defer deleteAllContainers() + name := "logs_test" + + runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name) + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) + + if err != nil || statusCode != http.StatusOK { + t.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) + } + + if !bytes.Contains(body, []byte(name)) { + t.Fatalf("Expected %s, got %s", name, string(body[:])) + } + + logDone("logs API - with stdout ok") +} + +func TestLogsApiNoStdoutNorStderr(t *testing.T) { + defer deleteAllContainers() + name := "logs_test" + runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) + + if err == nil || statusCode != http.StatusBadRequest { + t.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) + } + + expected := "Bad parameters: you must choose at least one stream" + if !bytes.Contains(body, []byte(expected)) { + t.Fatalf("Expected %s, got %s", expected, string(body[:])) + } + + logDone("logs API - returns error when no stdout nor stderr specified") +} diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index be36be8c1d2f7..27d7d10a0af24 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -16,7 +16,7 @@ func TestResizeApiResponse(t *testing.T) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - _, err = sockRequest("POST", endpoint, nil) + _, _, err = sockRequest("POST", endpoint, nil) if err != nil { t.Fatalf("resize Request failed %v", err) } @@ -41,7 +41,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { } endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - body, err := sockRequest("POST", endpoint, nil) + _, body, err := sockRequest("POST", endpoint, nil) if err == nil { t.Fatalf("resize should fail when container is not started") } diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index d01b36d45d2d4..8f8ea7b6649f6 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -64,7 +64,7 @@ func TestRmRunningContainerCheckError409(t *testing.T) { createRunningContainer(t, "foo") endpoint := "/containers/foo" - _, err := sockRequest("DELETE", endpoint, nil) + _, _, err := sockRequest("DELETE", endpoint, nil) if err == nil { t.Fatalf("Expected error, can't rm a running container") diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 943c1e02a4a35..4984b578bcdfe 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -298,19 +298,19 @@ func sockConn(timeout time.Duration) (net.Conn, error) { } } -func sockRequest(method, endpoint string, data interface{}) ([]byte, error) { +func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) { jsonData := bytes.NewBuffer(nil) if err := json.NewEncoder(jsonData).Encode(data); err != nil { - return nil, err + return -1, nil, err } return sockRequestRaw(method, endpoint, jsonData, "application/json") } -func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, error) { +func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, []byte, error) { c, err := sockConn(time.Duration(10 * time.Second)) if err != nil { - return nil, fmt.Errorf("could not dial docker daemon: %v", err) + return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err) } client := httputil.NewClientConn(c, nil) @@ -318,7 +318,7 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, req, err := http.NewRequest(method, endpoint, data) if err != nil { - return nil, fmt.Errorf("could not create new request: %v", err) + return -1, nil, fmt.Errorf("could not create new request: %v", err) } if ct == "" { @@ -328,15 +328,17 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("could not perform request: %v", err) + return -1, nil, fmt.Errorf("could not perform request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := ioutil.ReadAll(resp.Body) - return body, fmt.Errorf("received status != 200 OK: %s", resp.Status) + return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status) } - return ioutil.ReadAll(resp.Body) + b, err := ioutil.ReadAll(resp.Body) + + return resp.StatusCode, b, err } func deleteContainer(container string) error { @@ -1041,7 +1043,7 @@ func daemonTime(t *testing.T) time.Time { return time.Now() } - body, err := sockRequest("GET", "/info", nil) + _, body, err := sockRequest("GET", "/info", nil) if err != nil { t.Fatalf("daemonTime: failed to get /info: %v", err) } diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index cdd9991873947..9769e2d3a5a1e 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -57,7 +57,7 @@ var ( func() bool { if daemonExecDriver == "" { // get daemon info - body, err := sockRequest("GET", "/info", nil) + _, body, err := sockRequest("GET", "/info", nil) if err != nil { log.Fatalf("sockRequest failed for /info: %v", err) } From 65a056345cec1b85bd41ed70ee814894709ee6c0 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 13 Apr 2015 08:33:53 +0200 Subject: [PATCH 061/332] Remove jobs from stats Signed-off-by: Antonio Murdaca --- api/server/server.go | 8 ++++---- daemon/daemon.go | 1 - daemon/stats.go | 10 +++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d1f9b18946a73..840c656b19f5c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -543,10 +543,10 @@ func getContainersStats(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - job := eng.Job("container_stats", name) - streamJSON(job, w, true) - return job.Run() + + d := getDaemon(eng) + + return d.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) } func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 40d1ecb418ad5..290a9bebec4da 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,7 +119,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "commit": daemon.ContainerCommit, "container_inspect": daemon.ContainerInspect, - "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, "export": daemon.ContainerExport, "info": daemon.CmdInfo, diff --git a/daemon/stats.go b/daemon/stats.go index e40788013ff86..a95168d128784 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -2,20 +2,20 @@ package daemon import ( "encoding/json" + "io" "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/engine" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" ) -func (daemon *Daemon) ContainerStats(job *engine.Job) error { - updates, err := daemon.SubscribeToContainerStats(job.Args[0]) +func (daemon *Daemon) ContainerStats(name string, out io.Writer) error { + updates, err := daemon.SubscribeToContainerStats(name) if err != nil { return err } - enc := json.NewEncoder(job.Stdout) + enc := json.NewEncoder(out) for v := range updates { update := v.(*execdriver.ResourceStats) ss := convertToAPITypes(update.Stats) @@ -24,7 +24,7 @@ func (daemon *Daemon) ContainerStats(job *engine.Job) error { ss.CpuStats.SystemUsage = update.SystemUsage if err := enc.Encode(ss); err != nil { // TODO: handle the specific broken pipe - daemon.UnsubscribeToContainerStats(job.Args[0], updates) + daemon.UnsubscribeToContainerStats(name, updates) return err } } From 3341f3a3554524f0f5e6114dfa0ed988713803ef Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 13 Apr 2015 08:36:04 +0200 Subject: [PATCH 062/332] fix api server resize&execResize Signed-off-by: Antonio Murdaca --- api/server/server.go | 19 +++++--------- .../docker_api_exec_resize_test.go | 25 +++++++++++++++++++ integration-cli/docker_api_resize_test.go | 18 +++++++++++++ 3 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 integration-cli/docker_api_exec_resize_test.go diff --git a/api/server/server.go b/api/server/server.go index d1f9b18946a73..d7a57be301272 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1036,11 +1036,11 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re height, err := strconv.Atoi(r.Form.Get("h")) if err != nil { - return nil + return err } width, err := strconv.Atoi(r.Form.Get("w")) if err != nil { - return nil + return err } d := getDaemon(eng) @@ -1049,11 +1049,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re return err } - if err := cont.Resize(height, width); err != nil { - return err - } - - return nil + return cont.Resize(height, width) } func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1416,19 +1412,16 @@ func postContainerExecResize(eng *engine.Engine, version version.Version, w http height, err := strconv.Atoi(r.Form.Get("h")) if err != nil { - return nil + return err } width, err := strconv.Atoi(r.Form.Get("w")) if err != nil { - return nil + return err } d := getDaemon(eng) - if err := d.ContainerExecResize(vars["name"], height, width); err != nil { - return err - } - return nil + return d.ContainerExecResize(vars["name"], height, width) } func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go new file mode 100644 index 0000000000000..4108e74dcef8d --- /dev/null +++ b/integration-cli/docker_api_exec_resize_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "os/exec" + "strings" + "testing" +) + +func TestExecResizeApiHeightWidthNoInt(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + defer deleteAllContainers() + cleanedContainerID := strings.TrimSpace(out) + + endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" + _, err = sockRequest("POST", endpoint, nil) + if err == nil { + t.Fatal("Expected exec resize Request to fail") + } + + logDone("container exec resize - height, width no int fail") +} diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index be36be8c1d2f7..bf8beb7910ecf 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -24,6 +24,24 @@ func TestResizeApiResponse(t *testing.T) { logDone("container resize - when started") } +func TestResizeApiHeightWidthNoInt(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + defer deleteAllContainers() + cleanedContainerID := strings.TrimSpace(out) + + endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" + _, err = sockRequest("POST", endpoint, nil) + if err == nil { + t.Fatal("Expected resize Request to fail") + } + + logDone("container resize - height, width no int fail") +} + func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) From c49cc1f2fbd3d0256455750c885eadcaa3d17937 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 13 Apr 2015 16:24:49 +0800 Subject: [PATCH 063/332] fix build test by adding --no-cache Testcase TestBuildResourceConstraintsAreUsed run build without --no-cache, so if you run this test twice, it will fail the second time. TESTFLAGS='-v -run ^TestBuildResourceConstraintsAreUsed$' ./hack/make.sh binary test-integration-cli [PASSED] TESTFLAGS='-v -run ^TestBuildResourceConstraintsAreUsed$' ./hack/make.sh binary test-integration-cli [FAIL] Because we'll use cID to inspect field and will get empty cID if we have cache. Signed-off-by: Qiang Huang --- integration-cli/docker_cli_build_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 01e6d1d3d68d6..40e038fc4b799 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5555,7 +5555,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err) } - cmd := exec.Command(dockerBinary, "build", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpu-shares=100", "-t", name, ".") + cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpu-shares=100", "-t", name, ".") cmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(cmd) From a715b0e31beabed9ac5702af2ede619b4232cbdb Mon Sep 17 00:00:00 2001 From: Deshi Xiao Date: Mon, 13 Apr 2015 17:21:27 +0800 Subject: [PATCH 064/332] correct pkg/stdcopy NewStdWriter function comments pkg/stdcopy NewStdWriter function has wrong doc comment, utils is not correct, it should be stdcopy Signed-off-by: Deshi Xiao --- pkg/stdcopy/stdcopy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/stdcopy/stdcopy.go b/pkg/stdcopy/stdcopy.go index ccf1d9dbabad0..4f78f20d1b024 100644 --- a/pkg/stdcopy/stdcopy.go +++ b/pkg/stdcopy/stdcopy.go @@ -52,7 +52,7 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) { // and written to the underlying `w` stream. // This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection. // `t` indicates the id of the stream to encapsulate. -// It can be utils.Stdin, utils.Stdout, utils.Stderr. +// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. func NewStdWriter(w io.Writer, t StdType) *StdWriter { if len(t) != StdWriterPrefixLen { return nil From 8b3548129220a8c79342a12717d87667927df4c9 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Mon, 13 Apr 2015 20:24:10 +0800 Subject: [PATCH 065/332] Fix daemon panic when release a nil network interface Signed-off-by: Lei Jitang --- daemon/networkdriver/bridge/driver.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index dabb1165e76bd..4eebb9d0446f0 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -583,6 +583,7 @@ func Release(id string) { if containerInterface == nil { logrus.Warnf("No network information to release for %s", id) + return } for _, nat := range containerInterface.PortMappings { From 1567cf2cdf07bcbafbb1555fd950f5c5ce7a1c66 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Mon, 13 Apr 2015 23:09:07 +0800 Subject: [PATCH 066/332] Fix typo in testcase Signed-off-by: Hu Keping --- integration-cli/docker_api_containers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 07793a8fca89d..145fef508f807 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -593,5 +593,5 @@ func TestContainerApiPause(t *testing.T) { t.Fatalf("There should be no paused container.") } - logDone("container REST API - check POST containers/pause nad unpause") + logDone("container REST API - check POST containers/pause and unpause") } From 6b737752e342e30dd20417b18c92c9b4e1c4f8da Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 16:04:01 +0200 Subject: [PATCH 067/332] Remove job from export Signed-off-by: Antonio Murdaca --- api/server/server.go | 10 ++++------ daemon/daemon.go | 1 - daemon/export.go | 11 ++--------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d1f9b18946a73..6d9df561cbfac 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -280,12 +280,10 @@ func getContainersExport(eng *engine.Engine, version version.Version, w http.Res if vars == nil { return fmt.Errorf("Missing parameter") } - job := eng.Job("export", vars["name"]) - job.Stdout.Add(w) - if err := job.Run(); err != nil { - return err - } - return nil + + d := getDaemon(eng) + + return d.ContainerExport(vars["name"], w) } func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 40d1ecb418ad5..748dd5cf5ce2b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -121,7 +121,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, - "export": daemon.ContainerExport, "info": daemon.CmdInfo, "logs": daemon.ContainerLogs, "restart": daemon.ContainerRestart, diff --git a/daemon/export.go b/daemon/export.go index b1417b932cc10..b94b6100cb71c 100644 --- a/daemon/export.go +++ b/daemon/export.go @@ -3,16 +3,9 @@ package daemon import ( "fmt" "io" - - "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerExport(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s container_id", job.Name) - } - name := job.Args[0] - +func (daemon *Daemon) ContainerExport(name string, out io.Writer) error { container, err := daemon.Get(name) if err != nil { return err @@ -25,7 +18,7 @@ func (daemon *Daemon) ContainerExport(job *engine.Job) error { defer data.Close() // Stream the entire contents of the container (basically a volatile snapshot) - if _, err := io.Copy(job.Stdout, data); err != nil { + if _, err := io.Copy(out, data); err != nil { return fmt.Errorf("%s: %s", name, err) } // FIXME: factor job-specific LogEvent to engine.Job.Run() From 5ad15479a0f3ce804a44a6931b716b0fae22ac6d Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 13 Apr 2015 17:33:59 +0100 Subject: [PATCH 068/332] Add a note about PID 1 not terminating on SIGINT/SIGTERM. Also re-arranged the description of CTRL-c to make it clearer. Signed-off-by: Bryan Boreham --- docs/sources/reference/commandline/cli.md | 14 ++++++++++---- docs/sources/reference/run.md | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index b9f506a6c858e..b78a169b5f627 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -516,10 +516,16 @@ interactively. You can attach to the same contained process multiple times simultaneously, screen sharing style, or quickly view the progress of your daemonized process. -You can detach from the container (and leave it running) with `CTRL-p CTRL-q` -(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container. -When you are attached to a container, and exit its main process, the process's -exit code will be returned to the client. +You can detach from the container and leave it running with `CTRL-p +CTRL-q` (for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false. + +If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` +to the container. + +>**Note**: A process running as PID 1 inside a container is treated +>specially by Linux: it ignores any signal with the default action. +>So, the process will not terminate on `SIGINT` or `SIGTERM` unless it is +>coded to do so. It is forbidden to redirect the standard input of a `docker attach` command while attaching to a tty-enabled container (i.e.: launched with `-t`). diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index daf26bff8f689..d41b686c4b24f 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -111,6 +111,11 @@ as you'll see in later examples. Specifying `-t` is forbidden when the client standard output is redirected or piped, such as in: `echo test | docker run -i busybox cat`. +>**Note**: A process running as PID 1 inside a container is treated +>specially by Linux: it ignores any signal with the default action. +>So, the process will not terminate on `SIGINT` or `SIGTERM` unless it is +>coded to do so. + ## Container identification ### Name (--name) From d42753485b71f5f26b682a187d1963ef138cd0ab Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 3 Apr 2015 02:07:57 -0600 Subject: [PATCH 069/332] Add "bundles/latest" symlink This is a symlink to the latest "bundle" that was assembled. For example, if `VERSION` is currently `1.5.0-dev`, then `bundles/latest` will be a symlink to `bundles/1.5.0-dev` after an attempted build. One interesting property of this is that after a successful `binary` build, we can `./bundles/latest/binary/docker -v` and get back something like `Docker version 1.5.0-dev, build 3ff6723-dirty`. Signed-off-by: Andrew "Tianon" Page --- hack/make.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hack/make.sh b/hack/make.sh index 118d4327f5953..8a931766381eb 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -255,6 +255,12 @@ main() { rm -fr bundles/$VERSION && mkdir bundles/$VERSION || exit 1 echo fi + + if [ "$(go env GOHOSTOS)" != 'windows' ]; then + # Windows and symlinks don't get along well + ln -sfT $VERSION bundles/latest + fi + SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ $# -lt 1 ]; then bundles=(${DEFAULT_BUNDLES[@]}) From 27fccdbabb277e29488de88f9af2e7b83af93132 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 13 Apr 2015 10:30:07 -0700 Subject: [PATCH 070/332] Fix errors due changed sockRequest signature Signed-off-by: Alexander Morozov --- integration-cli/docker_api_exec_resize_test.go | 6 +++++- integration-cli/docker_api_resize_test.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go index 4108e74dcef8d..d4290408e1a91 100644 --- a/integration-cli/docker_api_exec_resize_test.go +++ b/integration-cli/docker_api_exec_resize_test.go @@ -1,6 +1,7 @@ package main import ( + "net/http" "os/exec" "strings" "testing" @@ -16,10 +17,13 @@ func TestExecResizeApiHeightWidthNoInt(t *testing.T) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" - _, err = sockRequest("POST", endpoint, nil) + status, _, err := sockRequest("POST", endpoint, nil) if err == nil { t.Fatal("Expected exec resize Request to fail") } + if status != http.StatusInternalServerError { + t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + } logDone("container exec resize - height, width no int fail") } diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 481f5d347447a..8c7b87eb4f219 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -1,6 +1,7 @@ package main import ( + "net/http" "os/exec" "strings" "testing" @@ -34,10 +35,13 @@ func TestResizeApiHeightWidthNoInt(t *testing.T) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" - _, err = sockRequest("POST", endpoint, nil) + status, _, err := sockRequest("POST", endpoint, nil) if err == nil { t.Fatal("Expected resize Request to fail") } + if status != http.StatusInternalServerError { + t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + } logDone("container resize - height, width no int fail") } From 4f91a333d5c9d66ce109c36e7261dbfd3382ebbf Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Mon, 13 Apr 2015 17:56:12 +0800 Subject: [PATCH 071/332] move syslog-tag to syslog.New function Signed-off-by: Deng Guangxing Signed-off-by: Michael Crosby --- daemon/logger/syslog/syslog.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index afd3dacbb481f..4de14aacfec3f 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -11,26 +11,23 @@ import ( type Syslog struct { writer *syslog.Writer - tag string } func New(tag string) (logger.Logger, error) { - log, err := syslog.New(syslog.LOG_USER, path.Base(os.Args[0])) + log, err := syslog.New(syslog.LOG_USER, fmt.Sprintf("%s: <%s> ", path.Base(os.Args[0]), tag)) if err != nil { return nil, err } return &Syslog{ writer: log, - tag: tag, }, nil } func (s *Syslog) Log(msg *logger.Message) error { - logMessage := fmt.Sprintf("%s: %s", s.tag, msg.Line) if msg.Source == "stderr" { - return s.writer.Err(logMessage) + return s.writer.Err(string(msg.Line)) } - return s.writer.Info(logMessage) + return s.writer.Info(string(msg.Line)) } func (s *Syslog) Close() error { From 213eab995a3e6dcdb69b587301cc5008911e3dfe Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 13 Apr 2015 11:43:20 -0700 Subject: [PATCH 072/332] Fix vet warning pkg/archive/archive_test.go:496: arg changes for printf verb %s of wrong type: []archive.Change Signed-off-by: Alexander Morozov --- pkg/archive/archive_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index 065e4e47f67b7..dabb0d504bc13 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -493,7 +493,7 @@ func TestTarWithBlockCharFifo(t *testing.T) { t.Fatal(err) } if len(changes) > 0 { - t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %s", changes) + t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %v", changes) } } From 7523beff41eca212794e902afa1a614b2672e245 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 10 Apr 2015 17:07:05 -0700 Subject: [PATCH 073/332] Log memory swap capabilities properly. Check whether the swap limit capabilities are disabled or not only when memory swap is set to greater than 0. Signed-off-by: David Calavera --- daemon/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index a0dd36c8db8ad..e831f07a520a4 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1286,7 +1286,7 @@ func (container *Container) verifyDaemonSettings() { logrus.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.") container.hostConfig.Memory = 0 } - if container.hostConfig.Memory > 0 && !container.daemon.sysInfo.SwapLimit { + if container.hostConfig.Memory > 0 && container.hostConfig.MemorySwap != -1 && !container.daemon.sysInfo.SwapLimit { logrus.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.") container.hostConfig.MemorySwap = -1 } From 3280ce651b13866f93440b60a9182f9a4f9f14b9 Mon Sep 17 00:00:00 2001 From: bobby abbott Date: Tue, 31 Mar 2015 21:48:03 -0700 Subject: [PATCH 074/332] Adds validate-vet script resolves #11970 Signed-off-by: bobby abbott --- Dockerfile | 4 ++++ Makefile | 2 +- hack/make.sh | 1 + hack/make/validate-vet | 22 ++++++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 hack/make/validate-vet diff --git a/Dockerfile b/Dockerfile index b54fda561407e..3cf5eb5ce5523 100644 --- a/Dockerfile +++ b/Dockerfile @@ -105,6 +105,10 @@ RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env # Grab Go's cover tool for dead-simple code coverage testing RUN go get golang.org/x/tools/cmd/cover +# Grab Go's vet tool for examining go code to find suspicious constructs +# and help prevent errors that the compiler might not catch +RUN go get golang.org/x/tools/cmd/vet + # TODO replace FPM with some very minimal debhelper stuff RUN gem install --no-rdoc --no-ri fpm --version 1.3.2 diff --git a/Makefile b/Makefile index 9bf1b16c9456c..7978b632cacd8 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ test-docker-py: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py validate: build - $(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco validate-toml + $(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-toml validate-vet shell: build $(DOCKER_RUN_DOCKER) bash diff --git a/hack/make.sh b/hack/make.sh index 1ab1d8137e6ee..3bcb265b3c3b9 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -45,6 +45,7 @@ DEFAULT_BUNDLES=( validate-dco validate-gofmt validate-toml + validate-vet binary diff --git a/hack/make/validate-vet b/hack/make/validate-vet new file mode 100644 index 0000000000000..994a6ac03d509 --- /dev/null +++ b/hack/make/validate-vet @@ -0,0 +1,22 @@ +#!/bin/bash + +source "$(dirname "$BASH_SOURCE")/.validate" + +IFS=$'\n' +files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) +unset IFS + +for f in "${files[@]}"; do + # we use "git show" here to validate that what's committed is vetted + failedVet=$(git show "$VALIDATE_HEAD:$f" | go vet) + if [ $failedVet ]; then + fails=yes + echo $failedVet + fi +done + +if [ $fails ]; then + echo 'Please review and resolve the above issues and commit the result.' +else + echo 'All Go source files have been vetted.' +fi From f3ba0a6a3505f5c5c690b84a4db2255fea9af18f Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 13 Apr 2015 11:31:17 -0700 Subject: [PATCH 075/332] change tabs to spaces Signed-off-by: Jessica Frazelle --- hack/make/validate-vet | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/hack/make/validate-vet b/hack/make/validate-vet index 994a6ac03d509..e88f7549c36ef 100644 --- a/hack/make/validate-vet +++ b/hack/make/validate-vet @@ -6,17 +6,27 @@ IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) unset IFS +errors=() for f in "${files[@]}"; do - # we use "git show" here to validate that what's committed is vetted - failedVet=$(git show "$VALIDATE_HEAD:$f" | go vet) - if [ $failedVet ]; then - fails=yes - echo $failedVet - fi + # we use "git show" here to validate that what's committed passes go vet + failedVet=$(go vet "$f") + if [ "$failedVet" ]; then + errors+=( "$failedVet" ) + fi done -if [ $fails ]; then - echo 'Please review and resolve the above issues and commit the result.' + +if [ ${#errors[@]} -eq 0 ]; then + echo 'Congratulations! All Go source files have been vetted.' else - echo 'All Go source files have been vetted.' + { + echo "Errors from go vet:" + for err in "${errors[@]}"; do + echo " - $err" + done + echo + echo 'Please fix the above errors. You can test via "go vet" and commit the result.' + echo + } >&2 + false fi From 9b4d9a34218bcc506c7080a35c70788f46dc2e4e Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:00:45 -0400 Subject: [PATCH 076/332] Move TestRunWithTooLowMemory to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_run_test.go | 12 ++++ integration/server_test.go | 92 +++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 10 deletions(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 302286146c510..7c931f8fac3c4 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3493,3 +3493,15 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) { } logDone("run - can kill container with pid-host and some childs of pid 1") } + +func TestRunWithTooSmallMemoryLimit(t *testing.T) { + defer deleteAllContainers() + // this memory limit is 1 byte less than the min, which is 4MB + // https://github.com/docker/docker/blob/v1.5.0/daemon/create.go#L22 + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-m", "4194303", "busybox")) + if err == nil || !strings.Contains(out, "Minimum memory limit allowed is 4MB") { + t.Fatalf("expected run to fail when using too low a memory limit: %q", out) + } + + logDone("run - can't set too low memory limit") +} diff --git a/integration/server_test.go b/integration/server_test.go index 9745d9ce0ff12..42234e3e9d74c 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,6 +1,12 @@ package docker -import "testing" +import ( + "bytes" + "testing" + + "github.com/docker/docker/builder" + "github.com/docker/docker/engine" +) func TestCreateNumberHostname(t *testing.T) { eng := NewTestEngine(t) @@ -14,18 +20,84 @@ func TestCreateNumberHostname(t *testing.T) { createTestContainer(eng, config, t) } -func TestRunWithTooLowMemoryLimit(t *testing.T) { +func TestCommit(t *testing.T) { eng := NewTestEngine(t) + b := &builder.BuilderJob{Engine: eng} + b.Install() defer mkDaemonFromEngine(eng, t).Nuke() - // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. - job := eng.Job("create") - job.Setenv("Image", unitTestImageID) - job.Setenv("Memory", "524287") - job.Setenv("CpuShares", "1000") - job.SetenvList("Cmd", []string{"/bin/cat"}) - if err := job.Run(); err == nil { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"}) + if err != nil { + t.Fatal(err) + } + + id := createTestContainer(eng, config, t) + + job := eng.Job("commit", id) + job.Setenv("repo", "testrepo") + job.Setenv("tag", "testtag") + job.SetenvJson("config", config) + if err := job.Run(); err != nil { + t.Fatal(err) + } +} + +func TestMergeConfigOnCommit(t *testing.T) { + eng := NewTestEngine(t) + b := &builder.BuilderJob{Engine: eng} + b.Install() + runtime := mkDaemonFromEngine(eng, t) + defer runtime.Nuke() + + container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) + defer runtime.Rm(container1) + + config, _, _, err := parseRun([]string{container1.ID, "cat /tmp/foo"}) + if err != nil { + t.Error(err) + } + + job := eng.Job("commit", container1.ID) + job.Setenv("repo", "testrepo") + job.Setenv("tag", "testtag") + job.SetenvJson("config", config) + var outputBuffer = bytes.NewBuffer(nil) + job.Stdout.Add(outputBuffer) + if err := job.Run(); err != nil { + t.Error(err) + } + + container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) + defer runtime.Rm(container2) + + job = eng.Job("container_inspect", container1.Name) + baseContainer, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + t.Error(err) + } + + job = eng.Job("container_inspect", container2.Name) + commitContainer, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + t.Error(err) + } + + baseConfig := baseContainer.GetSubEnv("Config") + commitConfig := commitContainer.GetSubEnv("Config") + + if commitConfig.Get("Env") != baseConfig.Get("Env") { + t.Fatalf("Env config in committed container should be %v, was %v", + baseConfig.Get("Env"), commitConfig.Get("Env")) + } + + if baseConfig.Get("Cmd") != "[\"echo test \\u003e /tmp/foo\"]" { + t.Fatalf("Cmd in base container should be [\"echo test \\u003e /tmp/foo\"], was %s", + baseConfig.Get("Cmd")) + } + + if commitConfig.Get("Cmd") != "[\"cat /tmp/foo\"]" { + t.Fatalf("Cmd in committed container should be [\"cat /tmp/foo\"], was %s", + commitConfig.Get("Cmd")) } } From ed6074ea6be17518a3b21e4dd5038f13ca835194 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:01:40 -0400 Subject: [PATCH 077/332] Move TestMergeOnCommit to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_commit_test.go | 50 +++++++++++++ integration/server_test.go | 89 +---------------------- 2 files changed, 51 insertions(+), 88 deletions(-) diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 3143c21fcdf11..a51360a9b68ae 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -279,3 +279,53 @@ func TestCommitChange(t *testing.T) { logDone("commit - commit --change") } + +// TODO: commit --run is deprecated, remove this once --run is removed +func TestCommitMergeConfigRun(t *testing.T) { + defer deleteAllContainers() + name := "commit-test" + out, _, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") + id := strings.TrimSpace(out) + + dockerCmd(t, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") + defer deleteImages("commit-test") + + out, _, _ = dockerCmd(t, "run", "--name", name, "commit-test") + if strings.TrimSpace(out) != "testing" { + t.Fatal("run config in commited container was not merged") + } + + type cfg struct { + Env []string + Cmd []string + } + config1 := cfg{} + if err := inspectFieldAndMarshall(id, "Config", &config1); err != nil { + t.Fatal(err) + } + config2 := cfg{} + if err := inspectFieldAndMarshall(name, "Config", &config2); err != nil { + t.Fatal(err) + } + + // Env has at least PATH loaded as well here, so let's just grab the FOO one + var env1, env2 string + for _, e := range config1.Env { + if strings.HasPrefix(e, "FOO") { + env1 = e + break + } + } + for _, e := range config2.Env { + if strings.HasPrefix(e, "FOO") { + env2 = e + break + } + } + + if len(config1.Env) != len(config2.Env) || env1 != env2 && env2 != "" { + t.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env) + } + + logDone("commit - configs are merged with --run") +} diff --git a/integration/server_test.go b/integration/server_test.go index 42234e3e9d74c..2a12244a2b7b3 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,12 +1,6 @@ package docker -import ( - "bytes" - "testing" - - "github.com/docker/docker/builder" - "github.com/docker/docker/engine" -) +import "testing" func TestCreateNumberHostname(t *testing.T) { eng := NewTestEngine(t) @@ -20,87 +14,6 @@ func TestCreateNumberHostname(t *testing.T) { createTestContainer(eng, config, t) } -func TestCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"}) - if err != nil { - t.Fatal(err) - } - - id := createTestContainer(eng, config, t) - - job := eng.Job("commit", id) - job.Setenv("repo", "testrepo") - job.Setenv("tag", "testtag") - job.SetenvJson("config", config) - if err := job.Run(); err != nil { - t.Fatal(err) - } -} - -func TestMergeConfigOnCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - runtime := mkDaemonFromEngine(eng, t) - defer runtime.Nuke() - - container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) - defer runtime.Rm(container1) - - config, _, _, err := parseRun([]string{container1.ID, "cat /tmp/foo"}) - if err != nil { - t.Error(err) - } - - job := eng.Job("commit", container1.ID) - job.Setenv("repo", "testrepo") - job.Setenv("tag", "testtag") - job.SetenvJson("config", config) - var outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { - t.Error(err) - } - - container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) - defer runtime.Rm(container2) - - job = eng.Job("container_inspect", container1.Name) - baseContainer, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { - t.Error(err) - } - - job = eng.Job("container_inspect", container2.Name) - commitContainer, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { - t.Error(err) - } - - baseConfig := baseContainer.GetSubEnv("Config") - commitConfig := commitContainer.GetSubEnv("Config") - - if commitConfig.Get("Env") != baseConfig.Get("Env") { - t.Fatalf("Env config in committed container should be %v, was %v", - baseConfig.Get("Env"), commitConfig.Get("Env")) - } - - if baseConfig.Get("Cmd") != "[\"echo test \\u003e /tmp/foo\"]" { - t.Fatalf("Cmd in base container should be [\"echo test \\u003e /tmp/foo\"], was %s", - baseConfig.Get("Cmd")) - } - - if commitConfig.Get("Cmd") != "[\"cat /tmp/foo\"]" { - t.Fatalf("Cmd in committed container should be [\"cat /tmp/foo\"], was %s", - commitConfig.Get("Cmd")) - } -} - func TestImagesFilter(t *testing.T) { eng := NewTestEngine(t) defer nuke(mkDaemonFromEngine(eng, t)) From 2c24a8a4ea7ea0571cd4e125158fa59f584d2464 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:06:43 -0400 Subject: [PATCH 078/332] Move TestCreateNumberHostname to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_create_test.go | 8 ++++++++ integration/server_test.go | 12 ------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 3a3c2f07df249..ac10b264eb7f9 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -305,3 +305,11 @@ func TestCreateLabelFromImage(t *testing.T) { logDone("create - labels from image") } + +func TestCreateHostnameWithNumber(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") + if strings.TrimSpace(out) != "web.0" { + t.Fatalf("hostname not set, expected `web.0`, got: %s", out) + } + logDone("create - use hostname with number") +} diff --git a/integration/server_test.go b/integration/server_test.go index 2a12244a2b7b3..f23d0e675c975 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -2,18 +2,6 @@ package docker import "testing" -func TestCreateNumberHostname(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{"-h", "web.0", unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - createTestContainer(eng, config, t) -} - func TestImagesFilter(t *testing.T) { eng := NewTestEngine(t) defer nuke(mkDaemonFromEngine(eng, t)) From 02706a40bb29c6ab7f5ac36ece6fd1073879d95d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:51:40 -0400 Subject: [PATCH 079/332] move TestImagesFilter to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_images_test.go | 43 ++++++++++++++++++++++ integration/server_test.go | 44 ----------------------- 2 files changed, 43 insertions(+), 44 deletions(-) delete mode 100644 integration/server_test.go diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 49cfb36da8a63..ee403a1880961 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/url" "testing" "github.com/docker/docker/api/types" @@ -24,3 +25,45 @@ func TestLegacyImages(t *testing.T) { logDone("images - checking legacy json") } + +func TestApiImagesFilter(t *testing.T) { + name := "utest:tag1" + name2 := "utest/docker:tag2" + name3 := "utest:5000/docker:tag3" + defer deleteImages(name, name2, name3) + dockerCmd(t, "tag", "busybox", name) + dockerCmd(t, "tag", "busybox", name2) + dockerCmd(t, "tag", "busybox", name3) + + type image struct{ RepoTags []string } + getImages := func(filter string) []image { + v := url.Values{} + v.Set("filter", filter) + _, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) + if err != nil { + t.Fatal(err) + } + var images []image + if err := json.Unmarshal(b, &images); err != nil { + t.Fatal(err) + } + + return images + } + + errMsg := "incorrect number of matches returned" + if images := getImages("utest*/*"); len(images[0].RepoTags) != 2 { + t.Fatal(errMsg) + } + if images := getImages("utest"); len(images[0].RepoTags) != 1 { + t.Fatal(errMsg) + } + if images := getImages("utest*"); len(images[0].RepoTags) != 1 { + t.Fatal(errMsg) + } + if images := getImages("*5000*/*"); len(images[0].RepoTags) != 1 { + t.Fatal(errMsg) + } + + logDone("images - filter param is applied") +} diff --git a/integration/server_test.go b/integration/server_test.go deleted file mode 100644 index f23d0e675c975..0000000000000 --- a/integration/server_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package docker - -import "testing" - -func TestImagesFilter(t *testing.T) { - eng := NewTestEngine(t) - defer nuke(mkDaemonFromEngine(eng, t)) - - if err := eng.Job("tag", unitTestImageName, "utest", "tag1").Run(); err != nil { - t.Fatal(err) - } - - if err := eng.Job("tag", unitTestImageName, "utest/docker", "tag2").Run(); err != nil { - t.Fatal(err) - } - - if err := eng.Job("tag", unitTestImageName, "utest:5000/docker", "tag3").Run(); err != nil { - t.Fatal(err) - } - - images := getImages(eng, t, false, "utest*/*") - - if len(images[0].RepoTags) != 2 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "*5000*/*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } -} From 579c9ec1d07983edf423d8c7564840d2fcc6f303 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 7 Apr 2015 21:10:39 -0400 Subject: [PATCH 080/332] Don't return error when adding existing volume Error wasn't really doing anything except for making a bunch of extra debug logs. Signed-off-by: Brian Goff --- volumes/repository.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/volumes/repository.go b/volumes/repository.go index 08c5849818295..0dac3753dad6a 100644 --- a/volumes/repository.go +++ b/volumes/repository.go @@ -77,7 +77,8 @@ func (r *Repository) newVolume(path string, writable bool) (*Volume, error) { return nil, err } - return v, r.add(v) + r.add(v) + return v, nil } func (r *Repository) restore() error { @@ -103,9 +104,7 @@ func (r *Repository) restore() error { continue } } - if err := r.add(vol); err != nil { - logrus.Debugf("Error restoring volume: %v", err) - } + r.add(vol) } return nil } @@ -125,12 +124,11 @@ func (r *Repository) get(path string) *Volume { return r.volumes[filepath.Clean(path)] } -func (r *Repository) add(volume *Volume) error { +func (r *Repository) add(volume *Volume) { if vol := r.get(volume.Path); vol != nil { - return fmt.Errorf("Volume exists: %s", volume.ID) + return } r.volumes[volume.Path] = volume - return nil } func (r *Repository) Delete(path string) error { From d71c929d081c2fb7c33b36ae5db40014163f9b6c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 13 Apr 2015 16:10:31 -0700 Subject: [PATCH 081/332] Fix JSON format in the remote api configuration examples. Signed-off-by: David Calavera --- docs/sources/reference/api/docker_remote_api_v1.19.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 0f63ec310f5a0..524f77037ffc5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -161,7 +161,7 @@ Create a container "NetworkMode": "bridge", "Devices": [], "Ulimits": [{}], - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": [""], "CgroupParent": "" } @@ -359,7 +359,7 @@ Return low-level information on the container `id` "MaximumRetryCount": 2, "Name": "on-failure" }, - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": null, "VolumesFrom": null, "Ulimits": [{}] @@ -698,7 +698,7 @@ Start the container `id` "NetworkMode": "bridge", "Devices": [], "Ulimits": [{}], - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": [""], "CgroupParent": "" } From c30a55f14dbbe3971ba0ac716ba69a60868f4490 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 29 Mar 2015 23:17:23 +0200 Subject: [PATCH 082/332] Refactor utils/utils, fixes #11923 Signed-off-by: Antonio Murdaca --- api/client/attach.go | 3 +- api/client/build.go | 2 +- api/client/client.go | 12 + api/client/exec.go | 5 +- api/client/history.go | 4 +- api/client/inspect.go | 5 +- api/client/ps.go | 4 +- api/client/run.go | 3 +- api/client/search.go | 4 +- api/client/start.go | 3 +- builder/internals.go | 4 +- builder/job.go | 4 +- daemon/attach.go | 46 +++- daemon/container.go | 6 +- daemon/daemon.go | 9 +- daemon/execdriver/lxc/driver.go | 4 +- daemon/execdriver/lxc/lxc_template.go | 4 +- daemon/execdriver/utils.go | 14 +- daemon/info.go | 3 +- daemon/utils_test.go | 3 +- docker/docker.go | 3 +- engine/env.go | 4 +- graph/graph.go | 3 +- graph/import.go | 3 +- graph/load.go | 3 +- image/image.go | 14 +- integration/graph_test.go | 3 +- integration/runtime_test.go | 3 +- integration/z_final_test.go | 5 +- pkg/fileutils/fileutils.go | 54 +++++ pkg/fileutils/fileutils_test.go | 81 +++++++ pkg/httputils/httputils.go | 26 ++ pkg/ioutils/readers.go | 10 + pkg/ioutils/writers.go | 21 ++ pkg/ioutils/writers_test.go | 41 ++++ pkg/requestdecorator/requestdecorator_test.go | 16 +- pkg/resolvconf/resolvconf.go | 4 +- pkg/stringutils/stringutils.go | 56 +++++ pkg/stringutils/stringutils_test.go | 29 +++ registry/config.go | 4 +- registry/session.go | 29 ++- registry/session_v2.go | 18 +- runconfig/hostconfig.go | 8 +- runconfig/parse.go | 7 +- utils/utils.go | 224 +----------------- utils/utils_test.go | 146 ++++-------- 46 files changed, 530 insertions(+), 427 deletions(-) create mode 100644 pkg/fileutils/fileutils_test.go create mode 100644 pkg/httputils/httputils.go create mode 100644 pkg/ioutils/writers_test.go diff --git a/api/client/attach.go b/api/client/attach.go index 48cb8b4478638..77947a2941a67 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -9,7 +9,6 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" - "github.com/docker/docker/utils" ) // CmdAttach attaches to a running container. @@ -81,7 +80,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil diff --git a/api/client/build.go b/api/client/build.go index f1bceb4a161a3..dc54c22ffadb8 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -302,7 +302,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if jerr.Code == 0 { jerr.Code = 1 } - return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code} + return &StatusError{Status: jerr.Message, StatusCode: jerr.Code} } return err } diff --git a/api/client/client.go b/api/client/client.go index 4cfce5f6842cb..c849fa40f6659 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -3,3 +3,15 @@ // Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand. // See https://docs.docker.com/installation/ for instructions on installing Docker. package client + +import "fmt" + +// An StatusError reports an unsuccessful exit by a command. +type StatusError struct { + Status string + StatusCode int +} + +func (e *StatusError) Error() string { + return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) +} diff --git a/api/client/exec.go b/api/client/exec.go index 27e6878df4302..25b7a85fd23d1 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -9,7 +9,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // CmdExec runs a command in a running container. @@ -21,7 +20,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { execConfig, err := runconfig.ParseExec(cmd, args) // just in case the ParseExec does not exit if execConfig.Container == "" || err != nil { - return &utils.StatusError{StatusCode: 1} + return &StatusError{StatusCode: 1} } stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) @@ -122,7 +121,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil diff --git a/api/client/history.go b/api/client/history.go index 844a6fb770e2c..4ac46d92cef36 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -9,8 +9,8 @@ import ( "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) // CmdHistory shows the history of an image. @@ -51,7 +51,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { if *noTrunc { fmt.Fprintf(w, "%s\t", entry.CreatedBy) } else { - fmt.Fprintf(w, "%s\t", utils.Trunc(entry.CreatedBy, 45)) + fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45)) } fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) fmt.Fprintf(w, "%s", entry.Comment) diff --git a/api/client/inspect.go b/api/client/inspect.go index 0f47480b149db..8514b1ecbc558 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -9,7 +9,6 @@ import ( "text/template" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdInspect displays low-level information on one or more containers or images. @@ -27,7 +26,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { var err error if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) - return &utils.StatusError{StatusCode: 64, + return &StatusError{StatusCode: 64, Status: "Template parsing error: " + err.Error()} } } @@ -86,7 +85,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil } diff --git a/api/client/ps.go b/api/client/ps.go index be20d7a6f6343..44f5ff0d21ea2 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -15,8 +15,8 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) // CmdPs outputs a list of Docker containers. @@ -135,7 +135,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { ) if !*noTrunc { - command = utils.Trunc(command, 20) + command = stringutils.Truncate(command, 20) // only display the default name for the container with notrunc is passed for _, name := range names { diff --git a/api/client/run.go b/api/client/run.go index 474c88f98150e..b37b6bab29b24 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -12,7 +12,6 @@ import ( "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) func (cid *cidFile) Close() error { @@ -242,7 +241,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil } diff --git a/api/client/search.go b/api/client/search.go index 8f4eb0b301223..5e0a22f014deb 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -10,8 +10,8 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) type ByStars []registry.SearchResult @@ -68,7 +68,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { desc := strings.Replace(res.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) if !*noTrunc && len(desc) > 45 { - desc = utils.Trunc(desc, 42) + "..." + desc = stringutils.Truncate(desc, 42) + "..." } fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount) if res.IsOfficial { diff --git a/api/client/start.go b/api/client/start.go index 66aa5150db7de..a03b8c1d2925e 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -11,7 +11,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" - "github.com/docker/docker/utils" ) func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { @@ -156,7 +155,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } } return nil diff --git a/builder/internals.go b/builder/internals.go index e0c7987ca13f8..728ccde8aefdc 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -25,6 +25,7 @@ import ( imagepkg "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" + "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" @@ -35,7 +36,6 @@ import ( "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) func (b *Builder) readContext(context io.Reader) error { @@ -250,7 +250,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri *cInfos = append(*cInfos, &ci) // Initiate the download - resp, err := utils.Download(ci.origPath) + resp, err := httputils.Download(ci.origPath) if err != nil { return err } diff --git a/builder/job.go b/builder/job.go index 89ed52f873d74..b0ce8ddc09061 100644 --- a/builder/job.go +++ b/builder/job.go @@ -16,12 +16,12 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // whitelist of commands allowed for a commit/import @@ -106,7 +106,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } context = c } else if urlutil.IsURL(remoteURL) { - f, err := utils.Download(remoteURL) + f, err := httputils.Download(remoteURL) if err != nil { return err } diff --git a/daemon/attach.go b/daemon/attach.go index f95de41d52ee8..b2b8d0906789b 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -10,7 +10,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/promise" - "github.com/docker/docker/utils" ) func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error { @@ -131,7 +130,7 @@ func attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io var err error if tty { - _, err = utils.CopyEscapable(cStdin, stdin) + _, err = copyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) @@ -185,3 +184,46 @@ func attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io return nil }) } + +// Code c/c from io.Copy() modified to handle escape sequence +func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) { + buf := make([]byte, 32*1024) + for { + nr, er := src.Read(buf) + if nr > 0 { + // ---- Docker addition + // char 16 is C-p + if nr == 1 && buf[0] == 16 { + nr, er = src.Read(buf) + // char 17 is C-q + if nr == 1 && buf[0] == 17 { + if err := src.Close(); err != nil { + return 0, err + } + return 0, nil + } + } + // ---- End of docker + nw, ew := dst.Write(buf[0:nr]) + if nw > 0 { + written += int64(nw) + } + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er == io.EOF { + break + } + if er != nil { + err = er + break + } + } + return written, err +} diff --git a/daemon/container.go b/daemon/container.go index e831f07a520a4..99fe157ad8886 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1063,7 +1063,7 @@ func (container *Container) setupContainerDns() error { updatedResolvConf, modified := resolvconf.FilterResolvDns(latestResolvConf, container.daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during resolv.conf localhost cleanup: generate an updated hash - newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf)) + newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) if err != nil { return err } @@ -1118,7 +1118,7 @@ func (container *Container) setupContainerDns() error { } //get a sha256 hash of the resolv conf at this point so we can check //for changes when the host resolv.conf changes (e.g. network update) - resolvHash, err := utils.HashData(bytes.NewReader(resolvConf)) + resolvHash, err := ioutils.HashData(bytes.NewReader(resolvConf)) if err != nil { return err } @@ -1150,7 +1150,7 @@ func (container *Container) updateResolvConf(updatedResolvConf []byte, newResolv if err != nil { return err } - curHash, err := utils.HashData(bytes.NewReader(resolvBytes)) + curHash, err := ioutils.HashData(bytes.NewReader(resolvBytes)) if err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 789cd2b32b794..e2ca568052fbf 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -32,6 +32,7 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/broadcastwriter" + "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/namesgenerator" @@ -431,7 +432,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error { updatedResolvConf, modified := resolvconf.FilterResolvDns(updatedResolvConf, daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during localhost cleanup: generate an updated hash - newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf)) + newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) if err != nil { logrus.Debugf("Error generating hash of new resolv.conf: %v", err) } else { @@ -830,7 +831,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if err != nil { return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err) } - realTmp, err := utils.ReadSymlinkedDirectory(tmp) + realTmp, err := fileutils.ReadSymlinkedDirectory(tmp) if err != nil { return nil, fmt.Errorf("Unable to get the full path to the TempDir (%s): %s", tmp, err) } @@ -841,7 +842,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) { realRoot = config.Root } else { - realRoot, err = utils.ReadSymlinkedDirectory(config.Root) + realRoot, err = fileutils.ReadSymlinkedDirectory(config.Root) if err != nil { return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err) } @@ -959,7 +960,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if err := os.Mkdir(path.Dir(localCopy), 0700); err != nil && !os.IsExist(err) { return nil, err } - if _, err := utils.CopyFile(sysInitPath, localCopy); err != nil { + if _, err := fileutils.CopyFile(sysInitPath, localCopy); err != nil { return nil, err } if err := os.Chmod(localCopy, 0700); err != nil { diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 97b34bb678586..1637bc2c69b6f 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -18,10 +18,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" + "github.com/docker/docker/pkg/stringutils" sysinfo "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/version" - "github.com/docker/docker/utils" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" @@ -187,7 +187,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba // without exec in go we have to do this horrible shell hack... shellString := "mount --make-rslave /; exec " + - utils.ShellQuoteArguments(params) + stringutils.ShellQuoteArguments(params) params = []string{ "unshare", "-m", "--", "/bin/sh", "-c", shellString, diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 6d6decb79f9f4..6c182ab3948f3 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -9,7 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/libcontainer/label" ) @@ -177,7 +177,7 @@ func keepCapabilities(adds []string, drops []string) ([]string, error) { } func dropList(drops []string) ([]string, error) { - if utils.StringsContainsNoCase(drops, "all") { + if stringutils.InSlice(drops, "all") { var newCaps []string for _, capName := range execdriver.GetAllCapabilities() { cap := execdriver.GetCapability(capName) diff --git a/daemon/execdriver/utils.go b/daemon/execdriver/utils.go index e1fc9b9014b53..407c4f4fa17b5 100644 --- a/daemon/execdriver/utils.go +++ b/daemon/execdriver/utils.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/stringutils" "github.com/syndtr/gocapability/capability" ) @@ -89,17 +89,17 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { if strings.ToLower(cap) == "all" { continue } - if !utils.StringsContainsNoCase(allCaps, cap) { + if !stringutils.InSlice(allCaps, cap) { return nil, fmt.Errorf("Unknown capability drop: %q", cap) } } // handle --cap-add=all - if utils.StringsContainsNoCase(adds, "all") { + if stringutils.InSlice(adds, "all") { basics = allCaps } - if !utils.StringsContainsNoCase(drops, "all") { + if !stringutils.InSlice(drops, "all") { for _, cap := range basics { // skip `all` aready handled above if strings.ToLower(cap) == "all" { @@ -107,7 +107,7 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { } // if we don't drop `all`, add back all the non-dropped caps - if !utils.StringsContainsNoCase(drops, cap) { + if !stringutils.InSlice(drops, cap) { newCaps = append(newCaps, strings.ToUpper(cap)) } } @@ -119,12 +119,12 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { continue } - if !utils.StringsContainsNoCase(allCaps, cap) { + if !stringutils.InSlice(allCaps, cap) { return nil, fmt.Errorf("Unknown capability to add: %q", cap) } // add cap if not already in the list - if !utils.StringsContainsNoCase(newCaps, cap) { + if !stringutils.InSlice(newCaps, cap) { newCaps = append(newCaps, strings.ToUpper(cap)) } } diff --git a/daemon/info.go b/daemon/info.go index 183a9e68bb01b..a77e3efd16320 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -8,6 +8,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/parsers/operatingsystem" "github.com/docker/docker/pkg/system" @@ -61,7 +62,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { v.SetBool("SwapLimit", daemon.SystemConfig().SwapLimit) v.SetBool("IPv4Forwarding", !daemon.SystemConfig().IPv4ForwardingDisabled) v.SetBool("Debug", os.Getenv("DEBUG") != "") - v.SetInt("NFd", utils.GetTotalUsedFds()) + v.SetInt("NFd", fileutils.GetTotalUsedFds()) v.SetInt("NGoroutines", runtime.NumGoroutine()) v.Set("SystemTime", time.Now().Format(time.RFC3339Nano)) v.Set("ExecutionDriver", daemon.ExecutionDriver().Name()) diff --git a/daemon/utils_test.go b/daemon/utils_test.go index ff5b082ba5385..aabbeaf6fa327 100644 --- a/daemon/utils_test.go +++ b/daemon/utils_test.go @@ -4,12 +4,11 @@ import ( "testing" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) func TestMergeLxcConfig(t *testing.T) { hostConfig := &runconfig.HostConfig{ - LxcConf: []utils.KeyValuePair{ + LxcConf: []runconfig.KeyValuePair{ {Key: "lxc.cgroups.cpuset", Value: "1,2"}, }, } diff --git a/docker/docker.go b/docker/docker.go index c9b2c77b02c2f..cf5b715598162 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -15,7 +15,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/utils" ) const ( @@ -136,7 +135,7 @@ func main() { } if err := cli.Cmd(flag.Args()...); err != nil { - if sterr, ok := err.(*utils.StatusError); ok { + if sterr, ok := err.(*client.StatusError); ok { if sterr.Status != "" { logrus.Println(sterr.Status) } diff --git a/engine/env.go b/engine/env.go index 089bc162c062b..107ae4a0d9160 100644 --- a/engine/env.go +++ b/engine/env.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/ioutils" ) type Env []string @@ -258,7 +258,7 @@ func (env *Env) Encode(dst io.Writer) error { } func (env *Env) WriteTo(dst io.Writer) (int64, error) { - wc := utils.NewWriteCounter(dst) + wc := ioutils.NewWriteCounter(dst) err := env.Encode(wc) return wc.Count, err } diff --git a/graph/graph.go b/graph/graph.go index 087a6f093cede..5159a93223dd6 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -25,7 +25,6 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // A Graph is a store for versioned filesystem images and the relationship between them. @@ -154,7 +153,7 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader) graph.driver.Remove(img.ID) } }() - if err := utils.ValidateID(img.ID); err != nil { + if err := image.ValidateID(img.ID); err != nil { return err } // (This is a convenience to save time. Race conditions are taken care of by os.Rename) diff --git a/graph/import.go b/graph/import.go index eb63af0b6046b..0ba03d0f53dd6 100644 --- a/graph/import.go +++ b/graph/import.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/runconfig" @@ -46,7 +47,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { u.Path = "" } job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u)) - resp, err = utils.Download(u.String()) + resp, err = httputils.Download(u.String()) if err != nil { return err } diff --git a/graph/load.go b/graph/load.go index ace222e3fec3f..bf2cc6d700625 100644 --- a/graph/load.go +++ b/graph/load.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" - "github.com/docker/docker/utils" ) // Loads a set of images into the repository. This is the complementary of ImageExport. @@ -100,7 +99,7 @@ func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string logrus.Debugf("Error unmarshalling json", err) return err } - if err := utils.ValidateID(img.ID); err != nil { + if err := image.ValidateID(img.ID); err != nil { logrus.Debugf("Error validating ID: %s", err) return err } diff --git a/image/image.go b/image/image.go index a661e3ce70b86..90714d6dbe773 100644 --- a/image/image.go +++ b/image/image.go @@ -6,12 +6,12 @@ import ( "io/ioutil" "os" "path" + "regexp" "strconv" "time" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // Set the max depth to the aufs default that most @@ -51,7 +51,7 @@ func LoadImage(root string) (*Image, error) { if err := dec.Decode(img); err != nil { return nil, err } - if err := utils.ValidateID(img.ID); err != nil { + if err := ValidateID(img.ID); err != nil { return nil, err } @@ -263,3 +263,13 @@ func NewImgJSON(src []byte) (*Image, error) { } return ret, nil } + +// Check wheather id is a valid image ID or not +func ValidateID(id string) error { + validHex := regexp.MustCompile(`^([a-f0-9]{64})$`) + if ok := validHex.MatchString(id); !ok { + err := fmt.Errorf("image ID '%s' is invalid", id) + return err + } + return nil +} diff --git a/integration/graph_test.go b/integration/graph_test.go index a481154551f19..8b6d7626f666e 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -15,7 +15,6 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/utils" ) func TestMount(t *testing.T) { @@ -103,7 +102,7 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - if err := utils.ValidateID(img.ID); err != nil { + if err := image.ValidateID(img.ID); err != nil { t.Fatal(err) } if img.Comment != "Testing" { diff --git a/integration/runtime_test.go b/integration/runtime_test.go index b5e404d59dfe2..6881fbea60ad3 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/nat" + "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/stringid" @@ -121,7 +122,7 @@ func init() { spawnGlobalDaemon() spawnLegitHttpsDaemon() spawnRogueHttpsDaemon() - startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() + startFds, startGoroutines = fileutils.GetTotalUsedFds(), runtime.NumGoroutine() } func setupBaseImage() { diff --git a/integration/z_final_test.go b/integration/z_final_test.go index 13cd0c3fd4f0a..d6ef2884f2518 100644 --- a/integration/z_final_test.go +++ b/integration/z_final_test.go @@ -1,13 +1,14 @@ package docker import ( - "github.com/docker/docker/utils" "runtime" "testing" + + "github.com/docker/docker/pkg/fileutils" ) func displayFdGoroutines(t *testing.T) { - t.Logf("File Descriptors: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) + t.Logf("File Descriptors: %d, Goroutines: %d", fileutils.GetTotalUsedFds(), runtime.NumGoroutine()) } func TestFinal(t *testing.T) { diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index 432529765104a..ef2a6523dc398 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -1,6 +1,10 @@ package fileutils import ( + "fmt" + "io" + "io/ioutil" + "os" "path/filepath" "github.com/Sirupsen/logrus" @@ -25,3 +29,53 @@ func Matches(relFilePath string, patterns []string) (bool, error) { } return false, nil } + +func CopyFile(src, dst string) (int64, error) { + if src == dst { + return 0, nil + } + sf, err := os.Open(src) + if err != nil { + return 0, err + } + defer sf.Close() + if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { + return 0, err + } + df, err := os.Create(dst) + if err != nil { + return 0, err + } + defer df.Close() + return io.Copy(df, sf) +} + +func GetTotalUsedFds() int { + if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { + logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) + } else { + return len(fds) + } + return -1 +} + +// ReadSymlinkedDirectory returns the target directory of a symlink. +// The target of the symbolic link may not be a file. +func ReadSymlinkedDirectory(path string) (string, error) { + var realPath string + var err error + if realPath, err = filepath.Abs(path); err != nil { + return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) + } + if realPath, err = filepath.EvalSymlinks(realPath); err != nil { + return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) + } + realPathInfo, err := os.Stat(realPath) + if err != nil { + return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) + } + if !realPathInfo.Mode().IsDir() { + return "", fmt.Errorf("canonical path points to a file '%s'", realPath) + } + return realPath, nil +} diff --git a/pkg/fileutils/fileutils_test.go b/pkg/fileutils/fileutils_test.go new file mode 100644 index 0000000000000..16d00d7b95d97 --- /dev/null +++ b/pkg/fileutils/fileutils_test.go @@ -0,0 +1,81 @@ +package fileutils + +import ( + "os" + "testing" +) + +// Reading a symlink to a directory must return the directory +func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { + var err error + if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil { + t.Errorf("failed to create directory: %s", err) + } + + if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil { + t.Errorf("failed to create symlink: %s", err) + } + + var path string + if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil { + t.Fatalf("failed to read symlink to directory: %s", err) + } + + if path != "/tmp/testReadSymlinkToExistingDirectory" { + t.Fatalf("symlink returned unexpected directory: %s", path) + } + + if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil { + t.Errorf("failed to remove temporary directory: %s", err) + } + + if err = os.Remove("/tmp/dirLinkTest"); err != nil { + t.Errorf("failed to remove symlink: %s", err) + } +} + +// Reading a non-existing symlink must fail +func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) { + var path string + var err error + if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil { + t.Fatalf("error expected for non-existing symlink") + } + + if path != "" { + t.Fatalf("expected empty path, but '%s' was returned", path) + } +} + +// Reading a symlink to a file must fail +func TestReadSymlinkedDirectoryToFile(t *testing.T) { + var err error + var file *os.File + + if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + file.Close() + + if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil { + t.Errorf("failed to create symlink: %s", err) + } + + var path string + if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil { + t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed") + } + + if path != "" { + t.Fatalf("path should've been empty: %s", path) + } + + if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil { + t.Errorf("failed to remove file: %s", err) + } + + if err = os.Remove("/tmp/fileLinkTest"); err != nil { + t.Errorf("failed to remove symlink: %s", err) + } +} diff --git a/pkg/httputils/httputils.go b/pkg/httputils/httputils.go new file mode 100644 index 0000000000000..1c922240e6f45 --- /dev/null +++ b/pkg/httputils/httputils.go @@ -0,0 +1,26 @@ +package httputils + +import ( + "fmt" + "net/http" + + "github.com/docker/docker/pkg/jsonmessage" +) + +// Request a given URL and return an io.Reader +func Download(url string) (resp *http.Response, err error) { + if resp, err = http.Get(url); err != nil { + return nil, err + } + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status) + } + return resp, nil +} + +func NewHTTPRequestError(msg string, res *http.Response) error { + return &jsonmessage.JSONError{ + Message: msg, + Code: res.StatusCode, + } +} diff --git a/pkg/ioutils/readers.go b/pkg/ioutils/readers.go index 58ff1af639cca..0e542cbad35ae 100644 --- a/pkg/ioutils/readers.go +++ b/pkg/ioutils/readers.go @@ -3,6 +3,8 @@ package ioutils import ( "bytes" "crypto/rand" + "crypto/sha256" + "encoding/hex" "io" "math/big" "sync" @@ -215,3 +217,11 @@ func (r *bufReader) Close() error { } return closer.Close() } + +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 +} diff --git a/pkg/ioutils/writers.go b/pkg/ioutils/writers.go index c0b3608fe6f36..43fdc44ea9686 100644 --- a/pkg/ioutils/writers.go +++ b/pkg/ioutils/writers.go @@ -37,3 +37,24 @@ func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser { closer: closer, } } + +// Wrap a concrete io.Writer and hold a count of the number +// of bytes written to the writer during a "session". +// This can be convenient when write return is masked +// (e.g., json.Encoder.Encode()) +type WriteCounter struct { + Count int64 + Writer io.Writer +} + +func NewWriteCounter(w io.Writer) *WriteCounter { + return &WriteCounter{ + Writer: w, + } +} + +func (wc *WriteCounter) Write(p []byte) (count int, err error) { + count, err = wc.Writer.Write(p) + wc.Count += int64(count) + return +} diff --git a/pkg/ioutils/writers_test.go b/pkg/ioutils/writers_test.go new file mode 100644 index 0000000000000..80d7f7f795452 --- /dev/null +++ b/pkg/ioutils/writers_test.go @@ -0,0 +1,41 @@ +package ioutils + +import ( + "bytes" + "strings" + "testing" +) + +func TestNopWriter(t *testing.T) { + nw := &NopWriter{} + l, err := nw.Write([]byte{'c'}) + if err != nil { + t.Fatal(err) + } + if l != 1 { + t.Fatalf("Expected 1 got %d", l) + } +} + +func TestWriteCounter(t *testing.T) { + dummy1 := "This is a dummy string." + dummy2 := "This is another dummy string." + totalLength := int64(len(dummy1) + len(dummy2)) + + reader1 := strings.NewReader(dummy1) + reader2 := strings.NewReader(dummy2) + + var buffer bytes.Buffer + wc := NewWriteCounter(&buffer) + + reader1.WriteTo(wc) + reader2.WriteTo(wc) + + if wc.Count != totalLength { + t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength) + } + + if buffer.String() != dummy1+dummy2 { + t.Error("Wrong message written") + } +} diff --git a/pkg/requestdecorator/requestdecorator_test.go b/pkg/requestdecorator/requestdecorator_test.go index b2c1fb3b97215..f1f9ef756b845 100644 --- a/pkg/requestdecorator/requestdecorator_test.go +++ b/pkg/requestdecorator/requestdecorator_test.go @@ -180,8 +180,8 @@ func TestRequestFactory(t *testing.T) { requestFactory := NewRequestFactory(ad, uad) - if dlen := len(requestFactory.GetDecorators()); dlen != 2 { - t.Fatalf("Expected to have two decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 2 { + t.Fatalf("Expected to have two decorators, got %d", l) } req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test")) @@ -209,8 +209,8 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { requestFactory := NewRequestFactory(ad) - if dlen := len(requestFactory.GetDecorators()); dlen != 1 { - t.Fatalf("Expected to have one decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 1 { + t.Fatalf("Expected to have one decorators, got %d", l) } ad2 := NewAuthDecorator("test2", "password2") @@ -235,15 +235,15 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { func TestRequestFactoryAddDecorator(t *testing.T) { requestFactory := NewRequestFactory() - if dlen := len(requestFactory.GetDecorators()); dlen != 0 { - t.Fatalf("Expected to have zero decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 0 { + t.Fatalf("Expected to have zero decorators, got %d", l) } ad := NewAuthDecorator("test", "password") requestFactory.AddDecorator(ad) - if dlen := len(requestFactory.GetDecorators()); dlen != 1 { - t.Fatalf("Expected to have one decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 1 { + t.Fatalf("Expected to have one decorators, got %d", l) } } diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go index d7d53e16d010f..5707b16b7fbf5 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/pkg/resolvconf/resolvconf.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/Sirupsen/logrus" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/ioutils" ) var ( @@ -59,7 +59,7 @@ func GetIfChanged() ([]byte, string, error) { if err != nil { return nil, "", err } - newHash, err := utils.HashData(bytes.NewReader(resolv)) + newHash, err := ioutils.HashData(bytes.NewReader(resolv)) if err != nil { return nil, "", err } diff --git a/pkg/stringutils/stringutils.go b/pkg/stringutils/stringutils.go index f5f07dd18efa8..e3ebf5d1ed82b 100644 --- a/pkg/stringutils/stringutils.go +++ b/pkg/stringutils/stringutils.go @@ -1,7 +1,9 @@ package stringutils import ( + "bytes" mathrand "math/rand" + "strings" "time" ) @@ -28,3 +30,57 @@ func GenerateRandomAsciiString(n int) string { } return string(res) } + +// Truncate a string to maxlen +func Truncate(s string, maxlen int) string { + if len(s) <= maxlen { + return s + } + return s[:maxlen] +} + +// Test wheather a string is contained in a slice of strings or not. +// Comparison is case insensitive +func InSlice(slice []string, s string) bool { + for _, ss := range slice { + if strings.ToLower(s) == strings.ToLower(ss) { + return true + } + } + return false +} + +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("'") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("'") +} + +// Take a list of strings and escape them so they will be handled right +// when passed as arguments to an program via a shell +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go index a5a01b4a0340b..8dcb4696bb749 100644 --- a/pkg/stringutils/stringutils_test.go +++ b/pkg/stringutils/stringutils_test.go @@ -56,3 +56,32 @@ func TestGenerateRandomAsciiStringIsAscii(t *testing.T) { t.Fatalf("%s contained non-ascii characters", str) } } + +func TestTruncate(t *testing.T) { + str := "teststring" + newstr := Truncate(str, 4) + if newstr != "test" { + t.Fatalf("Expected test, got %s", newstr) + } + newstr = Truncate(str, 20) + if newstr != "teststring" { + t.Fatalf("Expected teststring, got %s", newstr) + } +} + +func TestInSlice(t *testing.T) { + slice := []string{"test", "in", "slice"} + + test := InSlice(slice, "test") + if !test { + t.Fatalf("Expected string test to be in slice") + } + test = InSlice(slice, "SLICE") + if !test { + t.Fatalf("Expected string SLICE to be in slice") + } + test = InSlice(slice, "notinslice") + if test { + t.Fatalf("Expected string notinslice not to be in slice") + } +} diff --git a/registry/config.go b/registry/config.go index 3515836d187af..a0a978cc72832 100644 --- a/registry/config.go +++ b/registry/config.go @@ -9,9 +9,9 @@ import ( "regexp" "strings" + "github.com/docker/docker/image" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // Options holds command line options. @@ -213,7 +213,7 @@ func validateRemoteName(remoteName string) error { name = nameParts[0] // the repository name must not be a valid image ID - if err := utils.ValidateID(name); err == nil { + if err := image.ValidateID(name); err == nil { return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) } } else { diff --git a/registry/session.go b/registry/session.go index 4682a5074cd15..c62745b5bc675 100644 --- a/registry/session.go +++ b/registry/session.go @@ -21,7 +21,6 @@ import ( "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/requestdecorator" "github.com/docker/docker/pkg/tarsum" - "github.com/docker/docker/utils" ) type Session struct { @@ -86,7 +85,7 @@ func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]st if res.StatusCode == 401 { return nil, errLoginRequired } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) } jsonString, err := ioutil.ReadAll(res.Body) @@ -115,7 +114,7 @@ func (r *Session) LookupRemoteImage(imgID, registry string, token []string) erro } res.Body.Close() if res.StatusCode != 200 { - return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } return nil } @@ -134,7 +133,7 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([] } defer res.Body.Close() if res.StatusCode != 200 { - return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) + return nil, -1, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } // if the size header is not present, then set it to '-1' imageSize := -1 @@ -282,13 +281,13 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { // 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 == 404 { - return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) } else if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res) } var tokens []string @@ -379,12 +378,12 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist } defer res.Body.Close() if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") { - return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res) + return httputils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res) } if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } var jsonBody map[string]string if err := json.Unmarshal(errBody, &jsonBody); err != nil { @@ -392,7 +391,7 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist } else if jsonBody["error"] == "Image already exists" { return ErrAlreadyExists } - return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) } return nil } @@ -432,9 +431,9 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return "", "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } - return "", "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) + return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) } checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil)) @@ -461,7 +460,7 @@ func (r *Session) PushRegistryTag(remote, revision, tag, registry string, token } res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 201 { - return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res) } return nil } @@ -523,7 +522,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res) } if res.Header.Get("X-Docker-Token") != "" { tokens = res.Header["X-Docker-Token"] @@ -547,7 +546,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res) } } @@ -595,7 +594,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { } defer res.Body.Close() if res.StatusCode != 200 { - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) } result := new(SearchResults) err = json.NewDecoder(res.Body).Decode(result) diff --git a/registry/session_v2.go b/registry/session_v2.go index fb1d18e8e7d12..a14e434acf43e 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -12,7 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/distribution/registry/api/v2" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/httputils" ) const DockerDigestHeader = "Docker-Content-Digest" @@ -95,7 +95,7 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au } else if res.StatusCode == 404 { return nil, "", ErrDoesNotExist } - return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) + return nil, "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) } manifestBytes, err := ioutil.ReadAll(res.Body) @@ -141,7 +141,7 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Di return false, nil } - return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res) + return false, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res) } func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error { @@ -168,7 +168,7 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig if res.StatusCode == 401 { return errLoginRequired } - return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res) } _, err = io.Copy(blobWrtr, res.Body) @@ -198,7 +198,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst dige if res.StatusCode == 401 { return nil, 0, errLoginRequired } - return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res) + return nil, 0, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res) } lenStr := res.Header.Get("Content-Length") l, err := strconv.ParseInt(lenStr, 10, 64) @@ -245,7 +245,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig return err } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res) } return nil @@ -286,7 +286,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res) + return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res) } if location = res.Header.Get("Location"); location == "" { @@ -328,7 +328,7 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si return "", err } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) + return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) } hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader)) @@ -384,7 +384,7 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA } else if res.StatusCode == 404 { return nil, ErrDoesNotExist } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) } decoder := json.NewDecoder(res.Body) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 84d636b5c4beb..9d4eb264140be 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -6,9 +6,13 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/ulimit" - "github.com/docker/docker/utils" ) +type KeyValuePair struct { + Key string + Value string +} + type NetworkMode string // IsPrivate indicates whether container use it's private network stack @@ -107,7 +111,7 @@ type LogConfig struct { type HostConfig struct { Binds []string ContainerIDFile string - LxcConf []utils.KeyValuePair + LxcConf []KeyValuePair 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) diff --git a/runconfig/parse.go b/runconfig/parse.go index 1fb36e4ace539..d302330c82755 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -12,7 +12,6 @@ import ( "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) var ( @@ -430,14 +429,14 @@ func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) { return out, nil } -func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) { - out := make([]utils.KeyValuePair, opts.Len()) +func parseKeyValueOpts(opts opts.ListOpts) ([]KeyValuePair, error) { + out := make([]KeyValuePair, opts.Len()) for i, o := range opts.GetAll() { k, v, err := parsers.ParseKeyValueOpt(o) if err != nil { return nil, err } - out[i] = utils.KeyValuePair{Key: k, Value: v} + out[i] = KeyValuePair{Key: k, Value: v} } return out, nil } diff --git a/utils/utils.go b/utils/utils.go index 92ecb9b6c2b4a..a151fc3f0ea07 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,9 +2,7 @@ package utils import ( "bufio" - "bytes" "crypto/sha1" - "crypto/sha256" "encoding/hex" "fmt" "io" @@ -13,47 +11,17 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "runtime" "strings" "sync" - "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stringid" ) -type KeyValuePair struct { - Key string - Value string -} - -var ( - validHex = regexp.MustCompile(`^([a-f0-9]{64})$`) -) - -// Request a given URL and return an io.Reader -func Download(url string) (resp *http.Response, err error) { - if resp, err = http.Get(url); err != nil { - return nil, err - } - if resp.StatusCode >= 400 { - return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status) - } - return resp, nil -} - -func Trunc(s string, maxlen int) string { - if len(s) <= maxlen { - return s - } - return s[:maxlen] -} - // Figure out the absolute path of our own binary (if it's still around). func SelfPath() string { path, err := exec.LookPath(os.Args[0]) @@ -155,74 +123,7 @@ func DockerInitPath(localCopy string) string { return "" } -func GetTotalUsedFds() int { - if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { - logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) - } else { - return len(fds) - } - return -1 -} - -func ValidateID(id string) error { - if ok := validHex.MatchString(id); !ok { - err := fmt.Errorf("image ID '%s' is invalid", id) - return err - } - return nil -} - -// Code c/c from io.Copy() modified to handle escape sequence -func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) { - buf := make([]byte, 32*1024) - for { - nr, er := src.Read(buf) - if nr > 0 { - // ---- Docker addition - // char 16 is C-p - if nr == 1 && buf[0] == 16 { - nr, er = src.Read(buf) - // char 17 is C-q - if nr == 1 && buf[0] == 17 { - if err := src.Close(); err != nil { - return 0, err - } - return 0, nil - } - } - // ---- End of docker - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er == io.EOF { - break - } - if er != nil { - err = er - break - } - } - 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 -} - +// FIXME: move to httputils? ioutils? type WriteFlusher struct { sync.Mutex w io.Writer @@ -254,58 +155,6 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher { return &WriteFlusher{w: w, flusher: flusher} } -func NewHTTPRequestError(msg string, res *http.Response) error { - return &jsonmessage.JSONError{ - Message: msg, - Code: res.StatusCode, - } -} - -// An StatusError reports an unsuccessful exit by a command. -type StatusError struct { - Status string - StatusCode int -} - -func (e *StatusError) Error() string { - return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) -} - -func quote(word string, buf *bytes.Buffer) { - // Bail out early for "simple" strings - if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { - buf.WriteString(word) - return - } - - buf.WriteString("'") - - for i := 0; i < len(word); i++ { - b := word[i] - if b == '\'' { - // Replace literal ' with a close ', a \', and a open ' - buf.WriteString("'\\''") - } else { - buf.WriteByte(b) - } - } - - buf.WriteString("'") -} - -// Take a list of strings and escape them so they will be handled right -// when passed as arguments to an program via a shell -func ShellQuoteArguments(args []string) string { - var buf bytes.Buffer - for i, arg := range args { - if i != 0 { - buf.WriteByte(' ') - } - quote(arg, &buf) - } - return buf.String() -} - var globalTestID string // TestDirectory creates a new temporary directory and returns its path. @@ -343,26 +192,6 @@ func GetCallerName(depth int) string { return callerShortName } -func CopyFile(src, dst string) (int64, error) { - if src == dst { - return 0, nil - } - sf, err := os.Open(src) - if err != nil { - return 0, err - } - defer sf.Close() - if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { - return 0, err - } - df, err := os.Create(dst) - if err != nil { - return 0, err - } - defer df.Close() - return io.Copy(df, sf) -} - // ReplaceOrAppendValues returns the defaults with the overrides either // replaced by env key or appended to the list func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { @@ -411,27 +240,6 @@ func DoesEnvExist(name string) bool { return false } -// ReadSymlinkedDirectory returns the target directory of a symlink. -// The target of the symbolic link may not be a file. -func ReadSymlinkedDirectory(path string) (string, error) { - var realPath string - var err error - if realPath, err = filepath.Abs(path); err != nil { - return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) - } - if realPath, err = filepath.EvalSymlinks(realPath); err != nil { - return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) - } - realPathInfo, err := os.Stat(realPath) - if err != nil { - return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) - } - if !realPathInfo.Mode().IsDir() { - return "", fmt.Errorf("canonical path points to a file '%s'", realPath) - } - return realPath, nil -} - // 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 @@ -476,15 +284,6 @@ func ValidateContextDirectory(srcPath string, excludes []string) error { }) } -func StringsContainsNoCase(slice []string, s string) bool { - for _, ss := range slice { - if strings.ToLower(s) == strings.ToLower(ss) { - return true - } - } - return false -} - // Reads a .dockerignore file and returns the list of file patterns // to ignore. Note this will trim whitespace from each line as well // as use GO's "clean" func to get the shortest/cleanest path for each. @@ -516,27 +315,6 @@ func ReadDockerIgnore(path string) ([]string, error) { return excludes, nil } -// Wrap a concrete io.Writer and hold a count of the number -// of bytes written to the writer during a "session". -// This can be convenient when write return is masked -// (e.g., json.Encoder.Encode()) -type WriteCounter struct { - Count int64 - Writer io.Writer -} - -func NewWriteCounter(w io.Writer) *WriteCounter { - return &WriteCounter{ - Writer: w, - } -} - -func (wc *WriteCounter) Write(p []byte) (count int, err error) { - count, err = wc.Writer.Write(p) - wc.Count += int64(count) - return -} - // ImageReference combines `repo` and `ref` and returns a string representing // the combination. If `ref` is a digest (meaning it's of the form // :, the returned string is @. Otherwise, diff --git a/utils/utils_test.go b/utils/utils_test.go index 94303a0e96819..2863009423dd5 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -1,9 +1,10 @@ package utils import ( - "bytes" + "fmt" + "io/ioutil" "os" - "strings" + "path/filepath" "testing" ) @@ -25,104 +26,6 @@ func TestReplaceAndAppendEnvVars(t *testing.T) { } } -// Reading a symlink to a directory must return the directory -func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { - var err error - if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil { - t.Errorf("failed to create directory: %s", err) - } - - if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil { - t.Errorf("failed to create symlink: %s", err) - } - - var path string - if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil { - t.Fatalf("failed to read symlink to directory: %s", err) - } - - if path != "/tmp/testReadSymlinkToExistingDirectory" { - t.Fatalf("symlink returned unexpected directory: %s", path) - } - - if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil { - t.Errorf("failed to remove temporary directory: %s", err) - } - - if err = os.Remove("/tmp/dirLinkTest"); err != nil { - t.Errorf("failed to remove symlink: %s", err) - } -} - -// Reading a non-existing symlink must fail -func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) { - var path string - var err error - if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil { - t.Fatalf("error expected for non-existing symlink") - } - - if path != "" { - t.Fatalf("expected empty path, but '%s' was returned", path) - } -} - -// Reading a symlink to a file must fail -func TestReadSymlinkedDirectoryToFile(t *testing.T) { - var err error - var file *os.File - - if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil { - t.Fatalf("failed to create file: %s", err) - } - - file.Close() - - if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil { - t.Errorf("failed to create symlink: %s", err) - } - - var path string - if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil { - t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed") - } - - if path != "" { - t.Fatalf("path should've been empty: %s", path) - } - - if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil { - t.Errorf("failed to remove file: %s", err) - } - - if err = os.Remove("/tmp/fileLinkTest"); err != nil { - t.Errorf("failed to remove symlink: %s", err) - } -} - -func TestWriteCounter(t *testing.T) { - dummy1 := "This is a dummy string." - dummy2 := "This is another dummy string." - totalLength := int64(len(dummy1) + len(dummy2)) - - reader1 := strings.NewReader(dummy1) - reader2 := strings.NewReader(dummy2) - - var buffer bytes.Buffer - wc := NewWriteCounter(&buffer) - - reader1.WriteTo(wc) - reader2.WriteTo(wc) - - if wc.Count != totalLength { - t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength) - } - - if buffer.String() != dummy1+dummy2 { - t.Error("Wrong message written") - } -} - func TestImageReference(t *testing.T) { tests := []struct { repo string @@ -152,3 +55,46 @@ func TestDigestReference(t *testing.T) { t.Errorf("Unexpected DigestReference=true for input %q", input) } } + +func TestReadDockerIgnore(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "dockerignore-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + diName := filepath.Join(tmpDir, ".dockerignore") + + di, err := ReadDockerIgnore(diName) + if err != nil { + t.Fatalf("Expected not to have error, got %s", err) + } + + if diLen := len(di); diLen != 0 { + t.Fatalf("Expected to have zero dockerignore entry, got %d", diLen) + } + + content := fmt.Sprintf("test1\n/test2\n/a/file/here\n\nlastfile") + err = ioutil.WriteFile(diName, []byte(content), 0777) + if err != nil { + t.Fatal(err) + } + + di, err = ReadDockerIgnore(diName) + if err != nil { + t.Fatal(err) + } + + if di[0] != "test1" { + t.Fatalf("First element is not test1") + } + if di[1] != "/test2" { + t.Fatalf("Second element is not /test2") + } + if di[2] != "/a/file/here" { + t.Fatalf("Third element is not /a/file/here") + } + if di[3] != "lastfile" { + t.Fatalf("Fourth element is not lastfile") + } +} From 9a87553e4fc6c0abdd298893deefc44050208dda Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 13 Apr 2015 16:31:20 -0700 Subject: [PATCH 083/332] cleanup test wrong key.json leading to other failures wait for container to be running before trying to kill it in daemon tests Signed-off-by: Jessica Frazelle --- integration-cli/docker_cli_daemon_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index adaccc24ccee8..384c96803c273 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -881,13 +881,12 @@ func TestDaemonwithwrongkey(t *testing.T) { } d1 := NewDaemon(t) + defer os.Remove("/etc/docker/key.json") if err := d1.Start(); err == nil { d1.Stop() - os.Remove("/etc/docker/key.json") t.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) } - os.Remove("/etc/docker/key.json") content, _ := ioutil.ReadFile(d1.logFile.Name()) @@ -905,7 +904,7 @@ func TestDaemonRestartKillWait(t *testing.T) { } defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "/bin/cat") + out, err := d.Cmd("run", "-id", "busybox", "/bin/cat") if err != nil { t.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) } From 7e2d05b4938c010bf15224bd2857e2dca92ec9b3 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Mon, 13 Apr 2015 14:14:37 -0700 Subject: [PATCH 084/332] Add detection for F2Fs and JFS Signed-off-by: Megan Kostick Alphabetize FSMagic list to make more human-readable. Signed-off-by: Megan Kostick --- daemon/graphdriver/driver.go | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 26095b05c4747..79e6b72deae3f 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -14,20 +14,22 @@ import ( type FsMagic uint32 const ( - FsMagicBtrfs = FsMagic(0x9123683E) FsMagicAufs = FsMagic(0x61756673) - FsMagicExtfs = FsMagic(0x0000EF53) + FsMagicBtrfs = FsMagic(0x9123683E) FsMagicCramfs = FsMagic(0x28cd3d45) - FsMagicRamFs = FsMagic(0x858458f6) - FsMagicTmpFs = FsMagic(0x01021994) - FsMagicSquashFs = FsMagic(0x73717368) + FsMagicExtfs = FsMagic(0x0000EF53) + FsMagicF2fs = FsMagic(0xF2F52010) + FsMagicJffs2Fs = FsMagic(0x000072b6) + FsMagicJfs = FsMagic(0x3153464a) FsMagicNfsFs = FsMagic(0x00006969) + FsMagicRamFs = FsMagic(0x858458f6) FsMagicReiserFs = FsMagic(0x52654973) FsMagicSmbFs = FsMagic(0x0000517B) - FsMagicJffs2Fs = FsMagic(0x000072b6) - FsMagicZfs = FsMagic(0x2fc12fc1) - FsMagicXfs = FsMagic(0x58465342) + FsMagicSquashFs = FsMagic(0x73717368) + FsMagicTmpFs = FsMagic(0x01021994) FsMagicUnsupported = FsMagic(0x00000000) + FsMagicXfs = FsMagic(0x58465342) + FsMagicZfs = FsMagic(0x2fc12fc1) ) var ( @@ -50,18 +52,20 @@ var ( FsNames = map[FsMagic]string{ FsMagicAufs: "aufs", FsMagicBtrfs: "btrfs", - FsMagicExtfs: "extfs", FsMagicCramfs: "cramfs", - FsMagicRamFs: "ramfs", - FsMagicTmpFs: "tmpfs", - FsMagicSquashFs: "squashfs", + FsMagicExtfs: "extfs", + FsMagicF2fs: "f2fs", + FsMagicJffs2Fs: "jffs2", + FsMagicJfs: "jfs", FsMagicNfsFs: "nfs", + FsMagicRamFs: "ramfs", FsMagicReiserFs: "reiserfs", FsMagicSmbFs: "smb", - FsMagicJffs2Fs: "jffs2", - FsMagicZfs: "zfs", - FsMagicXfs: "xfs", + FsMagicSquashFs: "squashfs", + FsMagicTmpFs: "tmpfs", FsMagicUnsupported: "unsupported", + FsMagicXfs: "xfs", + FsMagicZfs: "zfs", } ) From a55f8e1ce734035a77cc13d1e2f42dbef41d418e Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 30 Mar 2015 16:32:04 +1000 Subject: [PATCH 085/332] Researching Docker Hub account linking and automated builds details Signed-off-by: Sven Dowideit --- docs/sources/docker-hub/builds.md | 149 ++++++++++++++---- .../gh-check-admin-org-dh-app-access.png | Bin 0 -> 31529 bytes .../gh-check-user-org-dh-app-access.png | Bin 0 -> 38325 bytes 3 files changed, 119 insertions(+), 30 deletions(-) create mode 100644 docs/sources/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png create mode 100644 docs/sources/docker-hub/hub-images/gh-check-user-org-dh-app-access.png diff --git a/docs/sources/docker-hub/builds.md b/docs/sources/docker-hub/builds.md index 1613ad1d4b0e7..bd3e3d2cb94b5 100644 --- a/docs/sources/docker-hub/builds.md +++ b/docs/sources/docker-hub/builds.md @@ -8,20 +8,18 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub *Automated Builds* are a special feature of Docker Hub which allow you to use [Docker Hub's](https://hub.docker.com) build clusters to automatically -create images from a specified `Dockerfile` and a GitHub or Bitbucket repository -(or "context"). The system will clone your repository and build the image -described by the `Dockerfile` using the repository as the context. The -resulting automated image will then be uploaded to the Docker Hub registry -and marked as an *Automated Build*. +create images from a GitHub or Bitbucket repository containing a `Dockerfile` +The system will clone your repository and build the image described by the +`Dockerfile` using the directory the `Dockerfile` is in (and subdirectories) +as the build context. The resulting automated image will then be uploaded +to the Docker Hub registry and marked as an *Automated Build*. Automated Builds have several advantages: * Users of *your* Automated Build can trust that the resulting image was built exactly as specified. - * The `Dockerfile` will be available to anyone with access to -your repository on the Docker Hub registry. - +your repository on the Docker Hub registry. * Because the process is automated, Automated Builds help to make sure that your repository is always up to date. @@ -33,16 +31,26 @@ http://docs.docker.com/userguide/dockerhub/#creating-a-docker-hub-account) and on GitHub and/or Bitbucket. In either case, the account needs to be properly validated and activated before you can link to it. -## Setting up Automated Builds with GitHub - -In order to set up an Automated Build, you need to first link your -[Docker Hub](https://hub.docker.com) account with a GitHub account. +The first time you to set up an Automated Build, your +[Docker Hub](https://hub.docker.com) account will need to be linked to +a GitHub or Bitbucket account. This will allow the registry to see your repositories. -> *Note:* +If you have previously linked your Docker Hub account, and want to view or modify +that link, click on the "Manage - Settings" link in the sidebar, and then +"Linked Accounts" in your Settings sidebar. + +## Automated Builds from GitHub + +If you've previously linked your Docker Hub account to your GitHub account, +you'll be able to skip to the [Creating an Automated Build](#creating-an-automated-build). + +### Linking your Docker Hub account to a GitHub account + +> *Note:* > Automated Builds currently require *read* and *write* access since > [Docker Hub](https://hub.docker.com) needs to setup a GitHub service -> hook. We have no choice here, this is how GitHub manages permissions, sorry! +> hook. We have no choice here, this is how GitHub manages permissions, sorry! > We do guarantee nothing else will be touched in your account. To get started, log into your Docker Hub account and click the @@ -51,17 +59,99 @@ To get started, log into your Docker Hub account and click the Select the [GitHub service](https://registry.hub.docker.com/associate/github/). -Then follow the onscreen instructions to authorize and link your +When linking to GitHub, you'll need to select either "Public and Private", +or "Limited" linking. + +The "Public and Private" option is the easiest to use, +as it grants the Docker Hub full access to all of your repositories. GitHub +also allows you to grant access to repositories belonging to your GitHub +organizations. + +By choosing the "Limited" linking, your Docker Hub account only gets permission +to access your public data and public repositories. + +Follow the onscreen instructions to authorize and link your GitHub account to Docker Hub. Once it is linked, you'll be able to -choose a repo from which to create the Automatic Build. +choose a source repository from which to create the Automatic Build. + +You will be able to review and revoke Docker Hub's access by visiting the +[GitHub User's Applications settings](https://github.com/settings/applications). + +> **Note**: If you delete the GitHub account linkage that is used for one of your +> automated build repositories, the previously built images will still be available. +> If you re-link to that GitHub account later, the automated build can be started +> using the "Start Build" button on the Hub, or if the webhook on the GitHub repository +> still exists, will be triggered by any subsequent commits. + +### Auto builds and Limited linked GitHub accounts. + +If you selected to link your GitHub account with only a "Limited" link, then +after creating your automated build, you will need to either manually trigger a +Docker Hub build using the "Start a Build" button, or add the GitHub webhook +manually, as described in [GitHub Service Hooks](#github-service-hooks). + +### Changing the GitHub user link + +If you want to remove, or change the level of linking between your GitHub account +and the Docker Hub, you need to do this in two places. + +First, remove the "Linked Account" from your Docker Hub "Settings". +Then go to your GitHub account's Personal settings, and in the "Applications" +section, "Revoke access". + +You can now re-link your account at any time. + +### GitHub Organizations + +GitHub organizations and private repositories forked from organizations will be +made available to auto build using the "Docker Hub Registry" application, which +needs to be added to the organization - and then will apply to all users. + +To check, or request access, go to your GitHub user's "Setting" page, select the +"Applications" section from the left side bar, then click the "View" button for +"Docker Hub Registry". + +![Check User access to GitHub](/docker-hub/hub-images/gh-check-user-org-dh-app-access.png) + +The organization's administrators may need to go to the Organization's "Third +party access" screen in "Settings" to Grant or Deny access to the Docker Hub +Registry application. This change will apply to all organization members. + +![Check Docker Hub application access to Organization](/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png) + +More detailed access controls to specific users and GitHub repositories would be +managed using the GitHub People and Teams interfaces. ### Creating an Automated Build 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`. +public or private GitHub repositories that have a `Dockerfile`. + +Once you've selected the source repository, you can then configure: -### GitHub Submodules +- The Hub user/org the repository is built to - either your Hub account name, +or the name of any Hub organizations your account is in +- The Docker repository name the image is built to +- If the Docker repository should be "Public" or "Private" + You can change the accessibility options after the repository has been created. + If you add a Private repository to a Hub user, then you can only add other users + as collaborators, and those users will be able to view and pull all images in that + repository. To configure more granular access permissions, such as using groups of + users or allow different users access to different image tags, then you need + to add the Private repository to a Hub organization that your user has Administrator + privilege on. +- If you want the GitHub to notify the Docker Hub when a commit is made, and thus trigger + a rebuild of all the images in this automated build. + +You can also select one or more +- The git branch/tag, which repository sub-directory to use as the context +- The Docker image tag name + +You can set a description for the repository by clicking "Description" link in the righthand side bar after the automated build - note that the "Full Description" will be over-written next build from the README.md file. +has been created. + +### GitHub private submodules If your GitHub repository contains links to private submodules, you'll get an error message in your build. @@ -114,17 +204,14 @@ can be limited to read-only access to just the repositories required to build. - -### GitHub Organizations -GitHub organizations will 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 +The GitHub Service hook allows GitHub to notify the Docker Hub when something has +been committed to that git repository. You will need to add the Service Hook manually +if your GitHub account is "Limited" linked to the Docker Hub. -Follow the steps below to configure the GitHub service -hooks for your Automated Build: +Follow the steps below to configure the GitHub Service hooks for your Automated Build: @@ -146,14 +233,16 @@ hooks for your Automated Build: - - + + + +
Webhooks & Services Click on "Webhooks & Services" on the left side of the page.
3.Find the service labeled DockerFind the service labeled "Docker" and click on it.
4.Activate Service HooksFind the service labeled DockerFind the service labeled "Docker" (or click on "Add service") and click on it.
4.Activate Service Hooks Make sure the "Active" checkbox is selected and click the "Update service" button to save your changes.
-## Setting up Automated Builds with Bitbucket +## 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 account. @@ -249,7 +338,7 @@ $ curl --data "build=true" -X POST https://registry.hub.docker.com/u/svendowidei OK ``` -> **Note:** +> **Note:** > You can only trigger one build at a time and no more than one > every five minutes. If you already have a build pending, or if you > recently submitted a build request, those requests *will be ignored*. diff --git a/docs/sources/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png b/docs/sources/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png new file mode 100644 index 0000000000000000000000000000000000000000..0df38c69465ddf106921a8531faff3d596b0a2e8 GIT binary patch literal 31529 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

bzNr=?cDdHSIwRhH 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 kkGH7nzi<{9 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub/hub-images/gh-check-user-org-dh-app-access.png b/docs/sources/docker-hub/hub-images/gh-check-user-org-dh-app-access.png new file mode 100644 index 0000000000000000000000000000000000000000..13ad6468f6604d50115c88ee3bed0908fbfc679c GIT binary patch literal 38325 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 z%0D@`o6+FJ; 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 literal 0 HcmV?d00001 From b85ade79d5a2cceb926fcae13997a6dfa28be4bc Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Tue, 14 Apr 2015 08:32:25 +0800 Subject: [PATCH 086/332] duplicate logDone in TestRmRunningContainerCheckError409 and TestRmRunningContainer Signed-off-by: Yuan Sun --- integration-cli/docker_cli_rm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 8f8ea7b6649f6..56eb2e525223d 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -73,7 +73,7 @@ func TestRmRunningContainerCheckError409(t *testing.T) { t.Fatalf("Expected error to contain '409 Conflict' but found %s", err) } - logDone("rm - running container") + logDone("rm - running container with Error 409") } func TestRmForceRemoveRunningContainer(t *testing.T) { From f7538c77efaf47fa5b82ae844b5c01887239ce65 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:16:42 -0400 Subject: [PATCH 087/332] Move TestRunDetach to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_run_unix_test.go | 71 +++++++++++++++++++++ integration-cli/utils.go | 11 +++- integration/commands_test.go | 50 --------------- 3 files changed, 81 insertions(+), 51 deletions(-) diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 026f8279efba8..211e6c1f553fd 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -3,6 +3,7 @@ package main import ( + "bufio" "fmt" "io/ioutil" "os" @@ -201,3 +202,73 @@ func TestRunDeviceDirectory(t *testing.T) { logDone("run - test --device directory mounts all internal devices") } + +// TestRunDetach checks attaching and detaching with the escape sequence. +func TestRunAttachDetach(t *testing.T) { + defer deleteAllContainers() + name := "attach-detach" + cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat") + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + cpty, tty, err := pty.Open() + if err != nil { + t.Fatal(err) + } + defer cpty.Close() + cmd.Stdin = tty + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + if err := waitRun(name); err != nil { + t.Fatal(err) + } + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + + out, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write([]byte{16}); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write([]byte{17}); err != nil { + t.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + running, err := inspectField(name, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + go func() { + dockerCmd(t, "kill", name) + }() + + select { + case <-ch: + case <-time.After(10 * time.Millisecond): + t.Fatal("timed out waiting for container to exit") + } + + logDone("run - attach detach") +} diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 536f6984e22ab..1fcf44535eeb2 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -213,7 +213,16 @@ func waitInspect(name, expr, expected string, timeout int) error { cmd := exec.Command(dockerBinary, "inspect", "-f", expr, name) out, _, err := runCommandWithOutput(cmd) if err != nil { - return fmt.Errorf("error executing docker inspect: %v", err) + if !strings.Contains(out, "No such") { + return fmt.Errorf("error executing docker inspect: %v\n%s", err, out) + } + select { + case <-after: + return err + default: + time.Sleep(10 * time.Millisecond) + continue + } } out = strings.TrimSpace(out) diff --git a/integration/commands_test.go b/integration/commands_test.go index 97a927b8bf3b6..7847605cb2063 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -113,56 +113,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// TestRunDetach checks attaching and detaching with the escape sequence. -func TestRunDetach(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - ch := make(chan struct{}) - go func() { - defer close(ch) - cli.CmdRun("-i", "-t", unitTestImageID, "cat") - }() - - container := waitContainerStart(t, 10*time.Second) - - state := setRaw(t, container) - defer unsetRaw(t, container, state) - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - t.Fatal(err) - } - }) - - setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { - cpty.Write([]byte{16}) - time.Sleep(100 * time.Millisecond) - cpty.Write([]byte{17}) - }) - - // wait for CmdRun to return - setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() { - <-ch - }) - closeWrap(cpty, stdout, stdoutPipe) - - time.Sleep(500 * time.Millisecond) - if !container.IsRunning() { - t.Fatal("The detached container should be still running") - } - - setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() { - container.Kill() - }) -} - // TestAttachDetach checks that attach in tty mode can be detached using the long container ID func TestAttachDetach(t *testing.T) { stdout, stdoutPipe := io.Pipe() From ae0883ce009daa2b94e67264cfa5a13b4cae76bf Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:17:55 -0400 Subject: [PATCH 088/332] Move TestAttachDetach to integration-cli Signed-off-by: Brian Goff --- .../docker_cli_attach_unix_test.go | 76 ++++++++++++++++ integration/commands_test.go | 88 ------------------- 2 files changed, 76 insertions(+), 88 deletions(-) diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index 8d15735120cb8..bea73d770d77d 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -3,6 +3,7 @@ package main import ( + "bufio" "os/exec" "strings" "testing" @@ -137,3 +138,78 @@ func TestAttachAfterDetach(t *testing.T) { logDone("attach - reconnect after detaching") } + +// TestAttachDetach checks that attach in tty mode can be detached using the long container ID +func TestAttachDetach(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + t.Fatal(err) + } + + cpty, tty, err := pty.Open() + if err != nil { + t.Fatal(err) + } + defer cpty.Close() + + cmd := exec.Command(dockerBinary, "attach", id) + cmd.Stdin = tty + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + defer stdout.Close() + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + if err := waitRun(id); err != nil { + t.Fatalf("error waiting for container to start: %v", err) + } + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + out, err = bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write([]byte{16}); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write([]byte{17}); err != nil { + t.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + running, err := inspectField(id, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + go func() { + dockerCmd(t, "kill", id) + }() + + select { + case <-ch: + case <-time.After(10 * time.Millisecond): + t.Fatal("timed out waiting for container to exit") + } + + logDone("attach - detach") +} diff --git a/integration/commands_test.go b/integration/commands_test.go index 7847605cb2063..88a7bb87b34ff 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -113,94 +113,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// TestAttachDetach checks that attach in tty mode can be detached using the long container ID -func TestAttachDetach(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - ch := make(chan struct{}) - go func() { - defer close(ch) - if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { - t.Fatal(err) - } - }() - - container := waitContainerStart(t, 10*time.Second) - - setTimeout(t, "Reading container's id timed out", 10*time.Second, func() { - buf := make([]byte, 1024) - n, err := stdout.Read(buf) - if err != nil { - t.Fatal(err) - } - - if strings.Trim(string(buf[:n]), " \r\n") != container.ID { - t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n]) - } - }) - setTimeout(t, "Starting container timed out", 10*time.Second, func() { - <-ch - }) - - state := setRaw(t, container) - defer unsetRaw(t, container, state) - - stdout, stdoutPipe = io.Pipe() - cpty, tty, err = pty.Open() - if err != nil { - t.Fatal(err) - } - - cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - - ch = make(chan struct{}) - go func() { - defer close(ch) - if err := cli.CmdAttach(container.ID); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }() - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }) - - setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { - cpty.Write([]byte{16}) - time.Sleep(100 * time.Millisecond) - cpty.Write([]byte{17}) - }) - - // wait for CmdRun to return - setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { - <-ch - }) - - closeWrap(cpty, stdout, stdoutPipe) - - time.Sleep(500 * time.Millisecond) - if !container.IsRunning() { - t.Fatal("The detached container should be still running") - } - - setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { - container.Kill() - }) -} - // TestAttachDetachTruncatedID checks that attach in tty mode can be detached func TestAttachDetachTruncatedID(t *testing.T) { stdout, stdoutPipe := io.Pipe() From 28cda048384ac8913f4912758fe35a673f4f8465 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:21:45 -0400 Subject: [PATCH 089/332] Move TestAttachDetachTruncatedID to integration-cli Signed-off-by: Brian Goff --- .../docker_cli_attach_unix_test.go | 73 +++++++++++++++++++ integration/commands_test.go | 73 ------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index bea73d770d77d..ebc3804e3e8a7 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/docker/docker/pkg/stringid" "github.com/kr/pty" ) @@ -213,3 +214,75 @@ func TestAttachDetach(t *testing.T) { logDone("attach - detach") } + +// TestAttachDetachTruncatedID checks that attach in tty mode can be detached +func TestAttachDetachTruncatedID(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + id := stringid.TruncateID(strings.TrimSpace(out)) + if err := waitRun(id); err != nil { + t.Fatal(err) + } + + cpty, tty, err := pty.Open() + if err != nil { + t.Fatal(err) + } + defer cpty.Close() + + cmd := exec.Command(dockerBinary, "attach", id) + cmd.Stdin = tty + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + defer stdout.Close() + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + out, err = bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write([]byte{16}); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write([]byte{17}); err != nil { + t.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + running, err := inspectField(id, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + go func() { + dockerCmd(t, "kill", id) + }() + + select { + case <-ch: + case <-time.After(10 * time.Millisecond): + t.Fatal("timed out waiting for container to exit") + } + + logDone("attach - detach truncated ID") +} diff --git a/integration/commands_test.go b/integration/commands_test.go index 88a7bb87b34ff..900bdace22249 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -12,7 +12,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" - "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/term" "github.com/kr/pty" ) @@ -113,78 +112,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// TestAttachDetachTruncatedID checks that attach in tty mode can be detached -func TestAttachDetachTruncatedID(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - // Discard the CmdRun output - go stdout.Read(make([]byte, 1024)) - setTimeout(t, "Starting container timed out", 2*time.Second, func() { - if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { - t.Fatal(err) - } - }) - - container := waitContainerStart(t, 10*time.Second) - - state := setRaw(t, container) - defer unsetRaw(t, container, state) - - stdout, stdoutPipe = io.Pipe() - cpty, tty, err = pty.Open() - if err != nil { - t.Fatal(err) - } - - cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - - ch := make(chan struct{}) - go func() { - defer close(ch) - if err := cli.CmdAttach(stringid.TruncateID(container.ID)); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }() - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }) - - setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { - cpty.Write([]byte{16}) - time.Sleep(100 * time.Millisecond) - cpty.Write([]byte{17}) - }) - - // wait for CmdRun to return - setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { - <-ch - }) - closeWrap(cpty, stdout, stdoutPipe) - - time.Sleep(500 * time.Millisecond) - if !container.IsRunning() { - t.Fatal("The detached container should be still running") - } - - setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { - container.Kill() - }) -} - // Expected behaviour, the process stays alive when the client disconnects func TestAttachDisconnect(t *testing.T) { stdout, stdoutPipe := io.Pipe() From e4cfd9b3924fae0369956b4f0e7f73a7e3b0cbf7 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:35:54 -0400 Subject: [PATCH 090/332] MovetAttachDisconnect to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_attach_test.go | 49 +++++++++++++++ integration/commands_test.go | 75 ----------------------- 2 files changed, 49 insertions(+), 75 deletions(-) diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index cf21cda5883f6..04cb593989866 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "io" "os/exec" "strings" @@ -134,3 +135,51 @@ func TestAttachTtyWithoutStdin(t *testing.T) { logDone("attach - forbid piped stdin to tty enabled container") } + +func TestAttachDisconnect(t *testing.T) { + defer deleteAllContainers() + out, _, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") + id := strings.TrimSpace(out) + + cmd := exec.Command(dockerBinary, "attach", id) + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatal(err) + } + defer stdin.Close() + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + defer stdout.Close() + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + defer cmd.Process.Kill() + + if _, err := stdin.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + out, err = bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + if err := stdin.Close(); err != nil { + t.Fatal(err) + } + + // Expect container to still be running after stdin is closed + running, err := inspectField(id, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + logDone("attach - disconnect") +} diff --git a/integration/commands_test.go b/integration/commands_test.go index 900bdace22249..02efdbe9a7a7c 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -9,11 +9,9 @@ import ( "testing" "time" - "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/term" - "github.com/kr/pty" ) func closeWrap(args ...io.Closer) error { @@ -112,79 +110,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// Expected behaviour, the process stays alive when the client disconnects -func TestAttachDisconnect(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - go func() { - // Start a process in daemon mode - if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil { - logrus.Debugf("Error CmdRun: %s", err) - } - }() - - setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() { - if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil { - t.Fatal(err) - } - }) - - setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { - for { - l := globalDaemon.List() - if len(l) == 1 && l[0].IsRunning() { - break - } - time.Sleep(10 * time.Millisecond) - } - }) - - container := globalDaemon.List()[0] - - // Attach to it - c1 := make(chan struct{}) - go func() { - // We're simulating a disconnect so the return value doesn't matter. What matters is the - // fact that CmdAttach returns. - cli.CmdAttach(container.ID) - close(c1) - }() - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - t.Fatal(err) - } - }) - // Close pipes (client disconnects) - if err := closeWrap(cpty, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - // Wait for attach to finish, the client disconnected, therefore, Attach finished his job - setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { - <-c1 - }) - - // We closed stdin, expect /bin/cat to still be running - // Wait a little bit to make sure container.monitor() did his thing - _, err = container.WaitStop(500 * time.Millisecond) - if err == nil || !container.IsRunning() { - t.Fatalf("/bin/cat is not running after closing stdin") - } - - // Try to avoid the timeout in destroy. Best effort, don't check error - cStdin := container.StdinPipe() - cStdin.Close() - container.WaitStop(-1 * time.Second) -} - // Expected behaviour: container gets deleted automatically after exit func TestRunAutoRemove(t *testing.T) { t.Skip("Fixme. Skipping test for now, race condition") From 125747e967e82788df8799622f28a7bcd97a03ad Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:37:46 -0400 Subject: [PATCH 091/332] Remove TestRunAutoremove This test is already being skipped, and is also fully tested by `TestRunContainerWithRmFlagExitCodeNotEqualToZero` Signed-off-by: Brian Goff --- integration/commands_test.go | 41 ------------------------------------ 1 file changed, 41 deletions(-) diff --git a/integration/commands_test.go b/integration/commands_test.go index 02efdbe9a7a7c..20b88611c47c8 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -4,12 +4,10 @@ import ( "bufio" "fmt" "io" - "io/ioutil" "strings" "testing" "time" - "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/term" ) @@ -109,42 +107,3 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error } return nil } - -// Expected behaviour: container gets deleted automatically after exit -func TestRunAutoRemove(t *testing.T) { - t.Skip("Fixme. Skipping test for now, race condition") - stdout, stdoutPipe := io.Pipe() - - cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - c := make(chan struct{}) - go func() { - defer close(c) - if err := cli.CmdRun("--rm", unitTestImageID, "hostname"); err != nil { - t.Fatal(err) - } - }() - - var temporaryContainerID string - setTimeout(t, "Reading command output time out", 2*time.Second, func() { - cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') - if err != nil { - t.Fatal(err) - } - temporaryContainerID = cmdOutput - if err := closeWrap(stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - }) - - setTimeout(t, "CmdRun timed out", 10*time.Second, func() { - <-c - }) - - time.Sleep(500 * time.Millisecond) - - if len(globalDaemon.List()) > 0 { - t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID) - } -} From 7d738e0b8c650817b8596e8c12fbe0baa3f045ed Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:42:02 -0400 Subject: [PATCH 092/332] Move utils from commands_test.go into utils.go Everything else was gone from this file except these utils which are being used in other files and can't yet be removed. Signed-off-by: Brian Goff --- integration/{commands_test.go => utils.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename integration/{commands_test.go => utils.go} (100%) diff --git a/integration/commands_test.go b/integration/utils.go similarity index 100% rename from integration/commands_test.go rename to integration/utils.go From 77f2a4a0e373225c145966c96d076d6d9f25b3b3 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Tue, 14 Apr 2015 09:23:26 +0800 Subject: [PATCH 093/332] add TestSearchCmdOptions case Signed-off-by: Yuan Sun --- integration-cli/docker_cli_search_test.go | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go index a3546103f39e5..fcfd9eceb9b5c 100644 --- a/integration-cli/docker_cli_search_test.go +++ b/integration-cli/docker_cli_search_test.go @@ -45,3 +45,53 @@ func TestSearchStarsOptionWithWrongParameter(t *testing.T) { logDone("search - Verify search with wrong parameter.") } + +func TestSearchCmdOptions(t *testing.T) { + testRequires(t, Network) + searchCmdhelp := exec.Command(dockerBinary, "search", "--help") + out, exitCode, err := runCommandWithOutput(searchCmdhelp) + if err != nil || exitCode != 0 { + t.Fatalf("failed to get search help information: %s, %v", out, err) + } + + if !strings.Contains(out, "Usage: docker search [OPTIONS] TERM") { + t.Fatalf("failed to show docker search usage: %s, %v", out, err) + } + + searchCmd := exec.Command(dockerBinary, "search", "busybox") + outSearchCmd, exitCode, err := runCommandWithOutput(searchCmd) + if err != nil || exitCode != 0 { + t.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err) + } + + searchCmdautomated := exec.Command(dockerBinary, "search", "--automated=true", "busybox") + outSearchCmdautomated, exitCode, err := runCommandWithOutput(searchCmdautomated) //The busybox is a busybox base image, not an AUTOMATED image. + if err != nil || exitCode != 0 { + t.Fatalf("failed to search with automated=true on the central registry: %s, %v", outSearchCmdautomated, err) + } + + outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n") + for i := range outSearchCmdautomatedSlice { + if strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox ") { + t.Fatalf("The busybox is not an AUTOMATED image: %s, %v", out, err) + } + } + + searchCmdStars := exec.Command(dockerBinary, "search", "-s=2", "busybox") + outSearchCmdStars, exitCode, err := runCommandWithOutput(searchCmdStars) + if err != nil || exitCode != 0 { + t.Fatalf("failed to search with stars=2 on the central registry: %s, %v", outSearchCmdStars, err) + } + + if strings.Count(outSearchCmdStars, "[OK]") > strings.Count(outSearchCmd, "[OK]") { + t.Fatalf("The quantity of images with stars should be less than that of all images: %s, %v", outSearchCmdStars, err) + } + + searchCmdOptions := exec.Command(dockerBinary, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox") + out, exitCode, err = runCommandWithOutput(searchCmdOptions) + if err != nil || exitCode != 0 { + t.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err) + } + + logDone("search - have a try for search options.") +} From bcd5e20a094e63093a95840f4f3342d981752708 Mon Sep 17 00:00:00 2001 From: Tatsushi Inagaki Date: Wed, 8 Apr 2015 04:41:03 -0400 Subject: [PATCH 094/332] Enable "netgo" library when we build a static binary with gccgo Signed-off-by: Tatsushi Inagaki --- hack/make/.dockerinit-gccgo | 1 + hack/make/gccgo | 3 +++ 2 files changed, 4 insertions(+) diff --git a/hack/make/.dockerinit-gccgo b/hack/make/.dockerinit-gccgo index 592a4152c857e..50854b40119ff 100644 --- a/hack/make/.dockerinit-gccgo +++ b/hack/make/.dockerinit-gccgo @@ -12,6 +12,7 @@ go build --compiler=gccgo \ -g -Wl,--no-export-dynamic $EXTLDFLAGS_STATIC_DOCKER + -lnetgo " \ ./dockerinit diff --git a/hack/make/gccgo b/hack/make/gccgo index c85d2fbda55d2..c3e9a228b866f 100644 --- a/hack/make/gccgo +++ b/hack/make/gccgo @@ -8,6 +8,9 @@ BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION" source "$(dirname "$BASH_SOURCE")/.go-autogen" +if [[ "${BUILDFLAGS[@]}" =~ 'netgo ' ]]; then + EXTLDFLAGS_STATIC_DOCKER+=' -lnetgo' +fi go build -compiler=gccgo \ -o "$DEST/$BINARY_FULLNAME" \ "${BUILDFLAGS[@]}" \ From b68e161e5b76b5f622cf4fc226df46cbe314ea1e Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 1 Apr 2015 14:12:15 -0400 Subject: [PATCH 095/332] graphdriver: prefer prior driver state Before this, a storage driver would be defaulted to based on the priority list, and only print a warning if there is state from other drivers. This meant a reordering of priority list would "break" users in an upgrade of docker, such that there images in the prior driver's state were now invisible. With this change, prior state is scanned, and if present that driver is preferred. As such, we can reorder the priority list, and after an upgrade, existing installs with prior drivers can have a contiguous experience, while fresh installs may default to a driver in the new priority list. Ref: https://github.com/docker/docker/pull/11962#issuecomment-88274858 Signed-off-by: Vincent Batts --- daemon/graphdriver/driver.go | 55 ++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 79e6b72deae3f..0260e532d942a 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -146,10 +146,40 @@ func GetDriver(name, home string, options []string) (Driver, error) { func New(root string, options []string) (driver Driver, err error) { for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { + logrus.Infof("[graphdriver] trying provided driver %q", name) // so the logs show specified driver return GetDriver(name, root, options) } } + // Guess for prior driver + priorDrivers := scanPriorDrivers(root) + for _, name := range priority { + if name == "vfs" { + // don't use vfs even if there is state present. + continue + } + for _, prior := range priorDrivers { + // of the state found from prior drivers, check in order of our priority + // which we would prefer + if prior == name { + driver, err = GetDriver(name, root, options) + if err != nil { + // unlike below, we will return error here, because there is prior + // state, and now it is no longer supported/prereq/compatible, so + // something changed and needs attention. Otherwise the daemon's + // images would just "disappear". + logrus.Errorf("[graphdriver] prior storage driver %q failed: %s", name, err) + return nil, err + } + if err := checkPriorDriver(name, root); err != nil { + return nil, err + } + logrus.Infof("[graphdriver] using prior storage driver %q", name) + return driver, nil + } + } + } + // Check for priority drivers first for _, name := range priority { driver, err = GetDriver(name, root, options) @@ -159,34 +189,47 @@ func New(root string, options []string) (driver Driver, err error) { } return nil, err } - checkPriorDriver(name, root) return driver, nil } // Check all registered drivers if no priority driver is found - for name, initFunc := range drivers { + for _, initFunc := range drivers { if driver, err = initFunc(root, options); err != nil { if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { continue } return nil, err } - checkPriorDriver(name, root) return driver, nil } return nil, fmt.Errorf("No supported storage backend found") } -func checkPriorDriver(name, root string) { +// scanPriorDrivers returns an un-ordered scan of directories of prior storage drivers +func scanPriorDrivers(root string) []string { + priorDrivers := []string{} + for driver := range drivers { + p := path.Join(root, driver) + if _, err := os.Stat(p); err == nil { + priorDrivers = append(priorDrivers, driver) + } + } + return priorDrivers +} + +func checkPriorDriver(name, root string) error { priorDrivers := []string{} - for prior := range drivers { + for _, prior := range scanPriorDrivers(root) { if prior != name && prior != "vfs" { if _, err := os.Stat(path.Join(root, prior)); err == nil { priorDrivers = append(priorDrivers, prior) } } } + if len(priorDrivers) > 0 { - logrus.Warnf("Graphdriver %s selected. Your graphdriver directory %s already contains data managed by other graphdrivers: %s", name, root, strings.Join(priorDrivers, ",")) + + return errors.New(fmt.Sprintf("%q contains other graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s )", root, strings.Join(priorDrivers, ","))) } + return nil } From 25fab69f7dcf72dabfeb60f61f50f175692f0b03 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 8 Apr 2015 18:09:55 -0700 Subject: [PATCH 096/332] names-generator: use local random instance Instead of seeding/polluting the global random instance, creating a local `rand.Random` instance which provides the same level of randomness. Signed-off-by: Ahmet Alp Balkan --- pkg/namesgenerator/names-generator.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index 38dced39ffa78..b33e8c21b04de 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -302,19 +302,19 @@ var ( // Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath "yonath", } + + rnd = rand.New(rand.NewSource(time.Now().UnixNano())) ) 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))]) + name := fmt.Sprintf("%s_%s", left[rnd.Intn(len(left))], right[rnd.Intn(len(right))]) if name == "boring_wozniak" /* Steve Wozniak is not boring */ { goto begin } if retry > 0 { - name = fmt.Sprintf("%s%d", name, rand.Intn(10)) + name = fmt.Sprintf("%s%d", name, rnd.Intn(10)) } return name } From 67df8e4257c165bc6abe77bb8f4ab55c10b2fbff Mon Sep 17 00:00:00 2001 From: Yestin Sun Date: Sat, 11 Apr 2015 22:12:40 -0700 Subject: [PATCH 097/332] Improve test accuracy for pkg/chrootarchive (part 2) Check test correctness of untar by comparing destination with source. For part 2, it checkes hashes of source and destination files or the target files of symbolic links. This is a supplement to the #11601 fix. Signed-off-by: Yestin Sun --- pkg/chrootarchive/archive_test.go | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go index b381d4301e42f..f9b5b09707ae6 100644 --- a/pkg/chrootarchive/archive_test.go +++ b/pkg/chrootarchive/archive_test.go @@ -3,6 +3,7 @@ package chrootarchive import ( "bytes" "fmt" + "hash/crc32" "io" "io/ioutil" "os" @@ -113,6 +114,16 @@ func prepareSourceDirectory(numberOfFiles int, targetPath string, makeSymLinks b return totalSize, nil } +func getHash(filename string) (uint32, error) { + stream, err := ioutil.ReadFile(filename) + if err != nil { + return 0, err + } + hash := crc32.NewIEEE() + hash.Write(stream) + return hash.Sum32(), nil +} + func compareDirectories(src string, dest string) error { changes, err := archive.ChangesDirs(dest, src) if err != nil { @@ -124,6 +135,21 @@ func compareDirectories(src string, dest string) error { return nil } +func compareFiles(src string, dest string) error { + srcHash, err := getHash(src) + if err != nil { + return err + } + destHash, err := getHash(dest) + if err != nil { + return err + } + if srcHash != destHash { + return fmt.Errorf("%s is different from %s", src, dest) + } + return nil +} + func TestChrootTarUntarWithSymlink(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSymlink") if err != nil { @@ -176,6 +202,9 @@ func TestChrootCopyWithTar(t *testing.T) { if err := CopyWithTar(srcfile, destfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcfile, destfile); err != nil { + t.Fatal(err) + } // Copy symbolic link srcLinkfile := filepath.Join(src, "file-1-link") @@ -184,6 +213,9 @@ func TestChrootCopyWithTar(t *testing.T) { if err := CopyWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcLinkfile, destLinkfile); err != nil { + t.Fatal(err) + } } func TestChrootCopyFileWithTar(t *testing.T) { @@ -213,6 +245,9 @@ func TestChrootCopyFileWithTar(t *testing.T) { if err := CopyFileWithTar(srcfile, destfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcfile, destfile); err != nil { + t.Fatal(err) + } // Copy symbolic link srcLinkfile := filepath.Join(src, "file-1-link") @@ -221,6 +256,9 @@ func TestChrootCopyFileWithTar(t *testing.T) { if err := CopyFileWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcLinkfile, destLinkfile); err != nil { + t.Fatal(err) + } } func TestChrootUntarPath(t *testing.T) { From 531433e7650c5d33ff6580d7de28a093a504ac6c Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Tue, 14 Apr 2015 08:00:46 +0000 Subject: [PATCH 098/332] integ-cli: Use status code from sockRequest (fix #12335) sockRequest now makes the status code available in the returned values. This helps avoid string checking for non-HttpStatusOK(=200) yet successful error messages. Signed-off-by: Ahmet Alp Balkan --- integration-cli/docker_api_containers_test.go | 22 +++++++++---------- integration-cli/docker_cli_rm_test.go | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 9ca2ebc86b2b5..d48b945724f91 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "io" + "net/http" "os/exec" "strings" "testing" @@ -134,7 +135,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } @@ -142,7 +143,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { config = map[string]interface{}{ "Binds": []string{bindPath + ":/tmp"}, } - if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -167,7 +168,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } @@ -202,14 +203,14 @@ func TestContainerApiStartVolumesFrom(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } config = map[string]interface{}{ "VolumesFrom": []string{volName}, } - if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -246,7 +247,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } @@ -255,7 +256,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "VolumesFrom": []string{volName}, "Binds": []string{bindPath + ":/tmp"}, } - if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -538,8 +539,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) { } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} - _, _, err = sockRequest("POST", "/containers/two/start", bindSpec) - if err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/two/start", bindSpec); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -566,7 +566,7 @@ func TestContainerApiPause(t *testing.T) { } ContainerID := strings.TrimSpace(out) - if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && status != http.StatusNoContent { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } @@ -580,7 +580,7 @@ func TestContainerApiPause(t *testing.T) { t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && status != http.StatusNoContent { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 56eb2e525223d..5f9a5dda5733d 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -1,6 +1,7 @@ package main import ( + "net/http" "os" "os/exec" "strings" @@ -64,12 +65,11 @@ func TestRmRunningContainerCheckError409(t *testing.T) { createRunningContainer(t, "foo") endpoint := "/containers/foo" - _, _, err := sockRequest("DELETE", endpoint, nil) + status, _, err := sockRequest("DELETE", endpoint, nil) if err == nil { t.Fatalf("Expected error, can't rm a running container") - } - if !strings.Contains(err.Error(), "409 Conflict") { + } else if status != http.StatusConflict { t.Fatalf("Expected error to contain '409 Conflict' but found %s", err) } From c89ddb6d015692e38a9b1a3223c406ddb5409758 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 14 Apr 2015 10:53:37 +0100 Subject: [PATCH 099/332] Add -f option and explanation to push after rebase Signed-off-by: Bryan Boreham --- docs/sources/project/work-issue.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 1719195cd4a4c..32e21d5e8a6f8 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -191,9 +191,10 @@ You should pull and rebase frequently as you work. Make sure you include your signature. -8. Push any changes to your fork on GitHub. +8. Push any changes to your fork on GitHub, using the `-f` option to +force the previous change to be overwritten. - $ git push origin 11038-fix-rhel-link + $ git push -f origin 11038-fix-rhel-link ## Where to go next From d791f7e9f81010299130ae8816ebe58667ea6b09 Mon Sep 17 00:00:00 2001 From: wonderflow Date: Tue, 14 Apr 2015 18:34:20 +0800 Subject: [PATCH 100/332] fix memory stats display document Signed-off-by: Sun Jianbo --- docs/man/docker-stats.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/docker-stats.1.md b/docs/man/docker-stats.1.md index 4cf7a66df3ecf..f6fc3f7f23f1f 100644 --- a/docs/man/docker-stats.1.md +++ b/docs/man/docker-stats.1.md @@ -23,6 +23,6 @@ Run **docker stats** with multiple containers. $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O - redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B + redis1 0.07% 796 KB/64 MB 1.21% 788 B/648 B redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e607120c87698..7375261230c07 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2343,7 +2343,7 @@ Running `docker stats` on multiple containers $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O - redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B + redis1 0.07% 796 KB/64 MB 1.21% 788 B/648 B redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B From fc20658a01e362a5bb484b439a0a1004c51f9ff5 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Tue, 14 Apr 2015 09:13:50 -0700 Subject: [PATCH 101/332] Fix vet warning in archive.go Signed-off-by: Megan Kostick --- pkg/chrootarchive/archive.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go index a1454f7b9ff98..49d19175d7d12 100644 --- a/pkg/chrootarchive/archive.go +++ b/pkg/chrootarchive/archive.go @@ -82,9 +82,9 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error cmd := reexec.Command("docker-untar", dest) cmd.Stdin = decompressedArchive cmd.ExtraFiles = append(cmd.ExtraFiles, r) - var output bytes.Buffer - cmd.Stdout = &output - cmd.Stderr = &output + output := bytes.NewBuffer(nil) + cmd.Stdout = output + cmd.Stderr = output if err := cmd.Start(); err != nil { return fmt.Errorf("Untar error on re-exec cmd: %v", err) From a0804e8e118510dc49e7e74273a323e99c097a3d Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Wed, 15 Apr 2015 00:29:53 +0800 Subject: [PATCH 102/332] Use local variable err instead of a outer one Signed-off-by: Hu Keping --- api/server/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 7ed3abc889fc9..b8ac0e9371567 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -506,8 +506,7 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon } func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var err error - if err = parseForm(r); err != nil { + if err := parseForm(r); err != nil { return err } @@ -520,10 +519,11 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo } if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { - config.Limit, err = strconv.Atoi(tmpLimit) + limit, err := strconv.Atoi(tmpLimit) if err != nil { return err } + config.Limit = limit } containers, err := getDaemon(eng).Containers(config) From 736824ccc134a90740c2ae95034b4c5c506594c7 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 13 Apr 2015 19:26:04 -0700 Subject: [PATCH 103/332] change go tools to use certain commit Signed-off-by: Jessica Frazelle --- Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3cf5eb5ce5523..b1c7c4a6f0fe8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -102,12 +102,15 @@ RUN cd /usr/local/go/src \ ENV GOFMT_VERSION 1.3.3 RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt +# Update this sha when we upgrade to go 1.5.0 +ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9 # Grab Go's cover tool for dead-simple code coverage testing -RUN go get golang.org/x/tools/cmd/cover - # Grab Go's vet tool for examining go code to find suspicious constructs # and help prevent errors that the compiler might not catch -RUN go get golang.org/x/tools/cmd/vet +RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \ + && (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \ + && go install -v golang.org/x/tools/cmd/cover \ + && go install -v golang.org/x/tools/cmd/vet # TODO replace FPM with some very minimal debhelper stuff RUN gem install --no-rdoc --no-ri fpm --version 1.3.2 From 4dd4bd00aa8285b3fcfca78322d4d867c65f7a50 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 14 Apr 2015 10:09:00 -0700 Subject: [PATCH 104/332] Fixing changed pushed without edit Signed-off-by: Mary Anthony --- docs/sources/project/work-issue.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 32e21d5e8a6f8..6433c42a67929 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -191,8 +191,10 @@ You should pull and rebase frequently as you work. Make sure you include your signature. -8. Push any changes to your fork on GitHub, using the `-f` option to -force the previous change to be overwritten. +8. Push any changes to your fork on GitHub. + + The rebase rewrote history, so you'll need to use the `-f` or `--force` flag + to push your change. $ git push -f origin 11038-fix-rhel-link From 69747b3c1e254d78619b2d66340f5d5172280d90 Mon Sep 17 00:00:00 2001 From: buddhamagnet Date: Tue, 14 Apr 2015 14:14:33 +0100 Subject: [PATCH 105/332] add docs for DockerCli and NewDockerCli Signed-off-by: buddhamagnet --- api/client/cli.go | 49 +++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/api/client/cli.go b/api/client/cli.go index 01b5f2e63f26a..dfa5fe520f3dd 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -20,25 +20,38 @@ import ( "github.com/docker/docker/registry" ) +// DockerCli represents the docker command line client. +// Instances of the client can be returned from NewDockerCli. type DockerCli struct { - proto string - addr string + // proto holds the client protocol i.e. unix. + proto string + // addr holds the client address. + addr string + // configFile holds the configuration file (instance of registry.ConfigFile). configFile *registry.ConfigFile - in io.ReadCloser - out io.Writer - err io.Writer - keyFile string - tlsConfig *tls.Config - scheme string - // inFd holds file descriptor of the client's STDIN, if it's a valid file + // in holds the input stream and closer (io.ReadCloser) for the client. + in io.ReadCloser + // out holds the output stream (io.Writer) for the client. + out io.Writer + // err holds the error stream (io.Writer) for the client. + err io.Writer + // keyFile holds the key file as a string. + keyFile string + // tlsConfig holds the TLS configuration for the client, and will + // set the scheme to https in NewDockerCli if present. + tlsConfig *tls.Config + // scheme holds the scheme of the client i.e. https. + scheme string + // inFd holds the file descriptor of the client's STDIN (if valid). inFd uintptr - // outFd holds file descriptor of the client's STDOUT, if it's a valid file + // outFd holds file descriptor of the client's STDOUT (if valid). outFd uintptr - // isTerminalIn describes if client's STDIN is a TTY + // isTerminalIn indicates whether the client's STDIN is a TTY isTerminalIn bool - // isTerminalOut describes if client's STDOUT is a TTY + // isTerminalOut dindicates whether the client's STDOUT is a TTY isTerminalOut bool - transport *http.Transport + // transport holds the client transport instance. + transport *http.Transport } var funcMap = template.FuncMap{ @@ -125,6 +138,10 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { return nil } +// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. +// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config +// is set the client scheme will be set to https. +// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( inFd uintptr @@ -149,15 +166,15 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a err = out } - // The transport is created here for reuse during the client session + // The transport is created here for reuse during the client session. tr := &http.Transport{ TLSClientConfig: tlsConfig, } - // Why 32? See issue 8035 + // Why 32? See https://github.com/docker/docker/pull/8035. timeout := 32 * time.Second if proto == "unix" { - // no need in compressing for local communications + // No need for compression in local communications. tr.DisableCompression = true tr.Dial = func(_, _ string) (net.Conn, error) { return net.DialTimeout(proto, addr, timeout) From 98f857772f3be0985ab87c4bc6bd91e80e122c53 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Fri, 10 Apr 2015 09:53:40 -0700 Subject: [PATCH 106/332] Remove container name not empty check Signed-off-by: Megan Kostick --- api/client/diff.go | 4 ++++ api/client/rm.go | 4 ++++ api/server/server.go | 12 ++++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/api/client/diff.go b/api/client/diff.go index 3f6c28384ef73..a22734d046f05 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -21,6 +21,10 @@ func (cli *DockerCli) CmdDiff(args ...string) error { cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) + if cmd.Arg(0) == "" { + return fmt.Errorf("Container name cannot be empty") + } + rdr, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil) if err != nil { return err diff --git a/api/client/rm.go b/api/client/rm.go index 89b11825431df..1d49e98a844ee 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -30,6 +30,10 @@ func (cli *DockerCli) CmdRm(args ...string) error { var encounteredError error for _, name := range cmd.Args() { + if name == "" { + return fmt.Errorf("Container name cannot be empty") + } + _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) diff --git a/api/server/server.go b/api/server/server.go index 7ed3abc889fc9..61eee110e553b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -466,10 +466,6 @@ func getContainersChanges(eng *engine.Engine, version version.Version, w http.Re } name := vars["name"] - if name == "" { - return fmt.Errorf("Container name cannot be empty") - } - d := getDaemon(eng) cont, err := d.Get(name) if err != nil { @@ -883,10 +879,6 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon } name := vars["name"] - if name == "" { - return fmt.Errorf("Container name cannot be empty") - } - d := getDaemon(eng) config := &daemon.ContainerRmConfig{ ForceRemove: toBool(r.Form.Get("force")), @@ -895,6 +887,10 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon } if err := d.ContainerRm(name, config); err != nil { + // Force a 404 for the empty string + if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { + return fmt.Errorf("no such id: \"\"") + } return err } From 18a8bcf0720b394e22e28b1389589f1ebbdc5cfc Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Tue, 14 Apr 2015 15:11:35 -0400 Subject: [PATCH 107/332] Improve the git instructions to update a PR. Signed-off-by: Raghuram Devarakonda --- docs/sources/project/review-pr.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/sources/project/review-pr.md b/docs/sources/project/review-pr.md index e8cb6c7c0432d..3d77ea406c8a2 100644 --- a/docs/sources/project/review-pr.md +++ b/docs/sources/project/review-pr.md @@ -49,15 +49,23 @@ need to update your pull request with additional changes. To update your existing pull request: -1. Change one or more files in your local `docker-fork` repository. +1. Checkout the PR branch in your local `docker-fork` repository. -2. Commit the change with the `git commit --amend` command. + This is the branch associated with your request. + +2. Change one or more files and then stage your changes. + + The command syntax is: + + git add + +3. Commit the change. $ git commit --amend Git opens an editor containing your last commit message. -3. Adjust your last comment to reflect this new change. +4. Adjust your last comment to reflect this new change. Added a new sentence per Anaud's suggestion @@ -72,15 +80,17 @@ To update your existing pull request: # modified: docs/sources/installation/mac.md # modified: docs/sources/installation/rhel.md -4. Push to your origin. +5. Force push the change to your origin. + + The command syntax is: - $ git push origin + git push -f origin -5. Open your browser to your pull request on GitHub. +6. Open your browser to your pull request on GitHub. You should see your pull request now contains your newly pushed code. -6. Add a comment to your pull request. +7. Add a comment to your pull request. GitHub only notifies PR participants when you comment. For example, you can mention that you updated your PR. Your comment alerts the maintainers that From 63331abbcadee3528f3e03f96cff1ca6a506cc9e Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 15:17:17 -0400 Subject: [PATCH 108/332] remove integration/utils setRaw funcs Signed-off-by: Brian Goff --- daemon/container.go | 9 --------- integration/utils.go | 21 --------------------- 2 files changed, 30 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index e831f07a520a4..7cc874f67f517 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -14,7 +14,6 @@ import ( "syscall" "time" - "github.com/docker/libcontainer" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/devices" "github.com/docker/libcontainer/label" @@ -1020,14 +1019,6 @@ func (container *Container) Exposes(p nat.Port) bool { return exists } -func (container *Container) GetPtyMaster() (libcontainer.Console, error) { - ttyConsole, ok := container.command.ProcessConfig.Terminal.(execdriver.TtyTerminal) - if !ok { - return nil, ErrNoTTY - } - return ttyConsole.Master(), nil -} - func (container *Container) HostConfig() *runconfig.HostConfig { container.Lock() res := container.hostConfig diff --git a/integration/utils.go b/integration/utils.go index 20b88611c47c8..1d27cd6e42234 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -9,7 +9,6 @@ import ( "time" "github.com/docker/docker/daemon" - "github.com/docker/docker/pkg/term" ) func closeWrap(args ...io.Closer) error { @@ -27,26 +26,6 @@ func closeWrap(args ...io.Closer) error { return nil } -func setRaw(t *testing.T, c *daemon.Container) *term.State { - pty, err := c.GetPtyMaster() - if err != nil { - t.Fatal(err) - } - state, err := term.MakeRaw(pty.Fd()) - if err != nil { - t.Fatal(err) - } - return state -} - -func unsetRaw(t *testing.T, c *daemon.Container, state *term.State) { - pty, err := c.GetPtyMaster() - if err != nil { - t.Fatal(err) - } - term.RestoreTerminal(pty.Fd(), state) -} - func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container { var container *daemon.Container From 8ce42baaef314a75bb6726891774393d540e9d06 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 24 Feb 2015 17:17:13 -0500 Subject: [PATCH 109/332] Make `docker cp` bind-mount volumes Allows `docker cp` to work seamlessly, and a lot more cleanly. Signed-off-by: Brian Goff --- daemon/container.go | 39 +++++++++---------- daemon/volumes.go | 94 +++++++++++++++++++++++++++++++++------------ volumes/volume.go | 32 --------------- 3 files changed, 89 insertions(+), 76 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 228944d2d47cd..f144934c320af 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -970,37 +970,35 @@ func (container *Container) GetSize() (int64, int64) { } func (container *Container) Copy(resource string) (io.ReadCloser, error) { + container.Lock() + defer container.Unlock() + var err error if err := container.Mount(); err != nil { return nil, err } + defer func() { + if err != nil { + container.Unmount() + } + }() - basePath, err := container.getResourcePath(resource) - if err != nil { - container.Unmount() + if err = container.mountVolumes(); err != nil { + container.unmountVolumes() return nil, err } - - // Check if this is actually in a volume - for _, mnt := range container.VolumeMounts() { - if len(mnt.MountToPath) > 0 && strings.HasPrefix(resource, mnt.MountToPath[1:]) { - return mnt.Export(resource) + defer func() { + if err != nil { + container.unmountVolumes() } - } + }() - // Check if this is a special one (resolv.conf, hostname, ..) - if resource == "etc/resolv.conf" { - basePath = container.ResolvConfPath - } - if resource == "etc/hostname" { - basePath = container.HostnamePath - } - if resource == "etc/hosts" { - basePath = container.HostsPath + basePath, err := container.getResourcePath(resource) + if err != nil { + return nil, err } stat, err := os.Stat(basePath) if err != nil { - container.Unmount() return nil, err } var filter []string @@ -1018,11 +1016,12 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { IncludeFiles: filter, }) if err != nil { - container.Unmount() return nil, err } + return ioutils.NewReadCloserWrapper(archive, func() error { err := archive.Close() + container.unmountVolumes() container.Unmount() return err }), diff --git a/daemon/volumes.go b/daemon/volumes.go index f40fdd3e49df1..7c6696d65fde0 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -2,7 +2,6 @@ package daemon import ( "fmt" - "io" "io/ioutil" "os" "path/filepath" @@ -12,6 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/chrootarchive" + "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" "github.com/docker/docker/volumes" @@ -27,18 +27,6 @@ type Mount struct { isBind bool } -func (mnt *Mount) Export(resource string) (io.ReadCloser, error) { - var name string - if resource == mnt.MountToPath[1:] { - name = filepath.Base(resource) - } - path, err := filepath.Rel(mnt.MountToPath[1:], resource) - if err != nil { - return nil, err - } - return mnt.volume.Export(path, name) -} - func (container *Container) prepareVolumes() error { if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) @@ -320,6 +308,20 @@ func validMountMode(mode string) bool { return validModes[mode] } +func (container *Container) specialMounts() []execdriver.Mount { + var mounts []execdriver.Mount + if container.ResolvConfPath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) + } + if container.HostnamePath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) + } + if container.HostsPath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) + } + return mounts +} + func (container *Container) setupMounts() error { mounts := []execdriver.Mount{} @@ -336,17 +338,7 @@ func (container *Container) setupMounts() error { }) } - if container.ResolvConfPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) - } - - if container.HostnamePath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) - } - - if container.HostsPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) - } + mounts = append(mounts, container.specialMounts()...) container.command.Mounts = mounts return nil @@ -401,3 +393,57 @@ func copyOwnership(source, destination string) error { return os.Chmod(destination, os.FileMode(stat.Mode())) } + +func (container *Container) mountVolumes() error { + for dest, source := range container.Volumes { + v := container.daemon.volumes.Get(source) + if v == nil { + return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest) + } + + destPath, err := container.getResourcePath(dest) + if err != nil { + return err + } + + if err := mount.Mount(source, destPath, "bind", "rbind,rw"); err != nil { + return fmt.Errorf("error while mounting volume %s: %v", source, err) + } + } + + for _, mnt := range container.specialMounts() { + destPath, err := container.getResourcePath(mnt.Destination) + if err != nil { + return err + } + if err := mount.Mount(mnt.Source, destPath, "bind", "bind,rw"); err != nil { + return fmt.Errorf("error while mounting volume %s: %v", mnt.Source, err) + } + } + return nil +} + +func (container *Container) unmountVolumes() { + for dest := range container.Volumes { + destPath, err := container.getResourcePath(dest) + if err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + if err := mount.ForceUnmount(destPath); err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + } + + for _, mnt := range container.specialMounts() { + destPath, err := container.getResourcePath(mnt.Destination) + if err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + if err := mount.ForceUnmount(destPath); err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + } + } +} diff --git a/volumes/volume.go b/volumes/volume.go index c5191c48c5f19..0acc3068f3c0b 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -2,14 +2,11 @@ package volumes import ( "encoding/json" - "io" "io/ioutil" "os" - "path" "path/filepath" "sync" - "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/symlink" ) @@ -24,35 +21,6 @@ type Volume struct { lock sync.Mutex } -func (v *Volume) Export(resource, name string) (io.ReadCloser, error) { - if v.IsBindMount && filepath.Base(resource) == name { - name = "" - } - - basePath, err := v.getResourcePath(resource) - if err != nil { - return nil, err - } - stat, err := os.Stat(basePath) - if err != nil { - return nil, err - } - var filter []string - if !stat.IsDir() { - d, f := path.Split(basePath) - basePath = d - filter = []string{f} - } else { - filter = []string{path.Base(basePath)} - basePath = path.Dir(basePath) - } - return archive.TarWithOptions(basePath, &archive.TarOptions{ - Compression: archive.Uncompressed, - Name: name, - IncludeFiles: filter, - }) -} - func (v *Volume) IsDir() (bool, error) { stat, err := os.Stat(v.Path) if err != nil { From 2ea1febd9aafd94985a2430c35243e539fa29f53 Mon Sep 17 00:00:00 2001 From: Thomas Texier Date: Tue, 14 Apr 2015 16:49:22 -0400 Subject: [PATCH 110/332] Remove unnecessary fmt.Printf Signed-off-by: Thomas Texier --- integration-cli/docker_cli_exec_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 8906da2526387..d4e4839717857 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -705,7 +705,6 @@ func TestExecWithPrivileged(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sda b 8 0") out, _, err := runCommandWithOutput(cmd) - fmt.Printf("%s", out) if err == nil || !strings.Contains(out, "Operation not permitted") { t.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") } From 088e69da35525fdf380ae73046d999332ea977fa Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 14 Apr 2015 23:10:17 +0200 Subject: [PATCH 111/332] Fix wrong graphdb refs paths purging Signed-off-by: Antonio Murdaca --- pkg/graphdb/graphdb.go | 14 ++++++++++-- pkg/graphdb/graphdb_test.go | 44 ++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/pkg/graphdb/graphdb.go b/pkg/graphdb/graphdb.go index c6f13eda27248..b9433dbddcb50 100644 --- a/pkg/graphdb/graphdb.go +++ b/pkg/graphdb/graphdb.go @@ -378,12 +378,22 @@ func (db *Database) Purge(id string) (int, error) { tx.Rollback() return -1, err } - changes, err := rows.RowsAffected() if err != nil { return -1, err } + // Clear who's using this id as parent + refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id) + if err != nil { + tx.Rollback() + return -1, err + } + refsCount, err := refs.RowsAffected() + if err != nil { + return -1, err + } + // Delete entity if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil { tx.Rollback() @@ -394,7 +404,7 @@ func (db *Database) Purge(id string) (int, error) { return -1, err } - return int(changes), nil + return int(changes + refsCount), nil } // Rename an edge for a given path diff --git a/pkg/graphdb/graphdb_test.go b/pkg/graphdb/graphdb_test.go index f22828560c235..12dd524ed5342 100644 --- a/pkg/graphdb/graphdb_test.go +++ b/pkg/graphdb/graphdb_test.go @@ -472,8 +472,8 @@ func TestPurgeId(t *testing.T) { db.Set("/webapp", "1") - if db.Refs("1") != 1 { - t.Fatal("Expect reference count to be 1") + if c := db.Refs("1"); c != 1 { + t.Fatalf("Expect reference count to be 1, got %d", c) } db.Set("/db", "2") @@ -484,7 +484,45 @@ func TestPurgeId(t *testing.T) { t.Fatal(err) } if count != 2 { - t.Fatal("Expected 2 references to be removed") + t.Fatalf("Expected 2 references to be removed, got %d", count) + } +} + +// Regression test https://github.com/docker/docker/issues/12334 +func TestPurgeIdRefPaths(t *testing.T) { + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) + + db.Set("/webapp", "1") + db.Set("/db", "2") + + db.Set("/db/webapp", "1") + + if c := db.Refs("1"); c != 2 { + t.Fatalf("Expected 2 reference for webapp, got %d", c) + } + if c := db.Refs("2"); c != 1 { + t.Fatalf("Expected 1 reference for db, got %d", c) + } + + if rp := db.RefPaths("2"); len(rp) != 1 { + t.Fatalf("Expected 1 reference path for db, got %d", len(rp)) + } + + count, err := db.Purge("2") + if err != nil { + t.Fatal(err) + } + + if count != 2 { + t.Fatalf("Expected 2 rows to be removed, got %d", count) + } + + if c := db.Refs("2"); c != 0 { + t.Fatalf("Expected 0 reference for db, got %d", c) + } + if c := db.Refs("1"); c != 1 { + t.Fatalf("Expected 1 reference for webapp, got %d", c) } } From 610c436e07388f4898020432b25939cc7104b894 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 9 Apr 2015 14:27:12 -0700 Subject: [PATCH 112/332] Remove engine.Job from Start action. Signed-off-by: David Calavera --- api/server/server.go | 6 ++-- daemon/create.go | 2 +- daemon/daemon.go | 3 +- daemon/start.go | 13 ++----- runconfig/hostconfig.go | 80 ++++++++++++++++++++--------------------- 5 files changed, 49 insertions(+), 55 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 7ebeb4b6aaf49..91307a1990c3c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -926,7 +926,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res } var ( name = vars["name"] - job = eng.Job("start", name) + env = new(engine.Env) ) // If contentLength is -1, we can assumed chunked encoding @@ -940,12 +940,12 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res return err } - if err := job.DecodeEnv(r.Body); err != nil { + if err := env.Decode(r.Body); err != nil { return err } } - if err := job.Run(); err != nil { + if err := getDaemon(eng).ContainerStart(name, env); err != nil { if err.Error() == "Container already started" { w.WriteHeader(http.StatusNotModified) return nil diff --git a/daemon/create.go b/daemon/create.go index c820201e42c79..02ef7e0a17fea 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -21,7 +21,7 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error { } config := runconfig.ContainerConfigFromJob(job) - hostConfig := runconfig.ContainerHostConfigFromJob(job) + hostConfig := runconfig.ContainerHostConfigFromJob(job.env) if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { return fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) diff --git a/daemon/daemon.go b/daemon/daemon.go index e2ca568052fbf..d5d420f107211 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -122,7 +122,8 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "create": daemon.ContainerCreate, "info": daemon.CmdInfo, "restart": daemon.ContainerRestart, - "start": daemon.ContainerStart, + "stop": daemon.ContainerStop, + "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { diff --git a/daemon/start.go b/daemon/start.go index 8de67b99670bf..b0b6dc75c3e26 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -7,14 +7,7 @@ import ( "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerStart(job *engine.Job) error { - if len(job.Args) < 1 { - return fmt.Errorf("Usage: %s container_id", job.Name) - } - var ( - name = job.Args[0] - ) - +func (daemon *Daemon) ContainerStart(name string, env *engine.Env) error { container, err := daemon.Get(name) if err != nil { return err @@ -31,8 +24,8 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) error { // If no environment was set, then no hostconfig was passed. // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. - if len(job.Environ()) > 0 { - hostConfig := runconfig.ContainerHostConfigFromJob(job) + if len(env.Map()) > 0 { + hostConfig := runconfig.ContainerHostConfigFromJob(env) if err := daemon.setHostConfig(container, hostConfig); err != nil { return err } diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9d4eb264140be..470588a092643 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -152,80 +152,80 @@ func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig { } } -func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { - if job.EnvExists("HostConfig") { +func ContainerHostConfigFromJob(env *engine.Env) *HostConfig { + if env.Exists("HostConfig") { hostConfig := HostConfig{} - job.GetenvJson("HostConfig", &hostConfig) + env.GetJson("HostConfig", &hostConfig) // FIXME: These are for backward compatibility, if people use these // options with `HostConfig`, we should still make them workable. - if job.EnvExists("Memory") && hostConfig.Memory == 0 { - hostConfig.Memory = job.GetenvInt64("Memory") + if env.Exists("Memory") && hostConfig.Memory == 0 { + hostConfig.Memory = env.GetInt64("Memory") } - if job.EnvExists("MemorySwap") && hostConfig.MemorySwap == 0 { - hostConfig.MemorySwap = job.GetenvInt64("MemorySwap") + if env.Exists("MemorySwap") && hostConfig.MemorySwap == 0 { + hostConfig.MemorySwap = env.GetInt64("MemorySwap") } - if job.EnvExists("CpuShares") && hostConfig.CpuShares == 0 { - hostConfig.CpuShares = job.GetenvInt64("CpuShares") + if env.Exists("CpuShares") && hostConfig.CpuShares == 0 { + hostConfig.CpuShares = env.GetInt64("CpuShares") } - if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = job.Getenv("Cpuset") + if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { + hostConfig.CpusetCpus = env.Get("Cpuset") } return &hostConfig } hostConfig := &HostConfig{ - ContainerIDFile: job.Getenv("ContainerIDFile"), - Memory: job.GetenvInt64("Memory"), - MemorySwap: job.GetenvInt64("MemorySwap"), - CpuShares: job.GetenvInt64("CpuShares"), - CpusetCpus: job.Getenv("CpusetCpus"), - Privileged: job.GetenvBool("Privileged"), - PublishAllPorts: job.GetenvBool("PublishAllPorts"), - NetworkMode: NetworkMode(job.Getenv("NetworkMode")), - IpcMode: IpcMode(job.Getenv("IpcMode")), - PidMode: PidMode(job.Getenv("PidMode")), - ReadonlyRootfs: job.GetenvBool("ReadonlyRootfs"), - CgroupParent: job.Getenv("CgroupParent"), + ContainerIDFile: env.Get("ContainerIDFile"), + Memory: env.GetInt64("Memory"), + MemorySwap: env.GetInt64("MemorySwap"), + CpuShares: env.GetInt64("CpuShares"), + CpusetCpus: env.Get("CpusetCpus"), + Privileged: env.GetBool("Privileged"), + PublishAllPorts: env.GetBool("PublishAllPorts"), + NetworkMode: NetworkMode(env.Get("NetworkMode")), + IpcMode: IpcMode(env.Get("IpcMode")), + PidMode: PidMode(env.Get("PidMode")), + ReadonlyRootfs: env.GetBool("ReadonlyRootfs"), + CgroupParent: env.Get("CgroupParent"), } // FIXME: This is for backward compatibility, if people use `Cpuset` // in json, make it workable, we will only pass hostConfig.CpusetCpus // to execDriver. - if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = job.Getenv("Cpuset") + if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { + hostConfig.CpusetCpus = env.Get("Cpuset") } - job.GetenvJson("LxcConf", &hostConfig.LxcConf) - job.GetenvJson("PortBindings", &hostConfig.PortBindings) - job.GetenvJson("Devices", &hostConfig.Devices) - job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy) - job.GetenvJson("Ulimits", &hostConfig.Ulimits) - job.GetenvJson("LogConfig", &hostConfig.LogConfig) - hostConfig.SecurityOpt = job.GetenvList("SecurityOpt") - if Binds := job.GetenvList("Binds"); Binds != nil { + env.GetJson("LxcConf", &hostConfig.LxcConf) + env.GetJson("PortBindings", &hostConfig.PortBindings) + env.GetJson("Devices", &hostConfig.Devices) + env.GetJson("RestartPolicy", &hostConfig.RestartPolicy) + env.GetJson("Ulimits", &hostConfig.Ulimits) + env.GetJson("LogConfig", &hostConfig.LogConfig) + hostConfig.SecurityOpt = env.GetList("SecurityOpt") + if Binds := env.GetList("Binds"); Binds != nil { hostConfig.Binds = Binds } - if Links := job.GetenvList("Links"); Links != nil { + if Links := env.GetList("Links"); Links != nil { hostConfig.Links = Links } - if Dns := job.GetenvList("Dns"); Dns != nil { + if Dns := env.GetList("Dns"); Dns != nil { hostConfig.Dns = Dns } - if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil { + if DnsSearch := env.GetList("DnsSearch"); DnsSearch != nil { hostConfig.DnsSearch = DnsSearch } - if ExtraHosts := job.GetenvList("ExtraHosts"); ExtraHosts != nil { + if ExtraHosts := env.GetList("ExtraHosts"); ExtraHosts != nil { hostConfig.ExtraHosts = ExtraHosts } - if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil { + if VolumesFrom := env.GetList("VolumesFrom"); VolumesFrom != nil { hostConfig.VolumesFrom = VolumesFrom } - if CapAdd := job.GetenvList("CapAdd"); CapAdd != nil { + if CapAdd := env.GetList("CapAdd"); CapAdd != nil { hostConfig.CapAdd = CapAdd } - if CapDrop := job.GetenvList("CapDrop"); CapDrop != nil { + if CapDrop := env.GetList("CapDrop"); CapDrop != nil { hostConfig.CapDrop = CapDrop } From 98996a432e079d1434182ea1cf84e70c927da4c2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 9 Apr 2015 14:49:22 -0700 Subject: [PATCH 113/332] Remove engine.Job from Create action. Signed-off-by: David Calavera --- api/server/server.go | 27 +++++++++--------------- daemon/create.go | 41 ++++++++++++++---------------------- daemon/daemon.go | 3 ++- runconfig/config.go | 50 ++++++++++++++++++++++---------------------- 4 files changed, 53 insertions(+), 68 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 91307a1990c3c..2cc9662496790 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -809,30 +809,23 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re return err } var ( - job = eng.Job("create", r.Form.Get("name")) - outWarnings []string - stdoutBuffer = bytes.NewBuffer(nil) - warnings = bytes.NewBuffer(nil) + warnings []string + name = r.Form.Get("name") + env = new(engine.Env) ) - if err := job.DecodeEnv(r.Body); err != nil { + if err := env.Decode(r.Body); err != nil { return err } - // Read container ID from the first line of stdout - job.Stdout.Add(stdoutBuffer) - // Read warnings from stderr - job.Stderr.Add(warnings) - if err := job.Run(); err != nil { + + containerId, warnings, err := getDaemon(eng).ContainerCreate(name, env) + if err != nil { return err } - // Parse warnings from stderr - scanner := bufio.NewScanner(warnings) - for scanner.Scan() { - outWarnings = append(outWarnings, scanner.Text()) - } + return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ - ID: engine.Tail(stdoutBuffer, 1), - Warnings: outWarnings, + ID: containerId, + Warnings: warnings, }) } diff --git a/daemon/create.go b/daemon/create.go index 02ef7e0a17fea..da271043f2a37 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -12,36 +12,31 @@ import ( "github.com/docker/libcontainer/label" ) -func (daemon *Daemon) ContainerCreate(job *engine.Job) error { - var name string - if len(job.Args) == 1 { - name = job.Args[0] - } else if len(job.Args) > 1 { - return fmt.Errorf("Usage: %s", job.Name) - } +func (daemon *Daemon) ContainerCreate(name string, env *engine.Env) (string, []string, error) { + var warnings []string - config := runconfig.ContainerConfigFromJob(job) - hostConfig := runconfig.ContainerHostConfigFromJob(job.env) + config := runconfig.ContainerConfigFromJob(env) + hostConfig := runconfig.ContainerHostConfigFromJob(env) if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { - return fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) + return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) } if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { - return fmt.Errorf("Minimum memory limit allowed is 4MB") + return "", warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") } if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") hostConfig.Memory = 0 } if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") hostConfig.MemorySwap = -1 } if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { - return fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") + return "", warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") } if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { - return fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + return "", warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") } container, buildWarnings, err := daemon.Create(config, hostConfig, name) @@ -51,22 +46,18 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error { if tag == "" { tag = graph.DEFAULTTAG } - return fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) + return "", warnings, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) } - return err + return "", warnings, err } if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { - job.Errorf("IPv4 forwarding is disabled.\n") + warnings = append(warnings, "IPv4 forwarding is disabled.\n") } - container.LogEvent("create") - - job.Printf("%s\n", container.ID) - for _, warning := range buildWarnings { - job.Errorf("%s\n", warning) - } + container.LogEvent("create") + warnings = append(warnings, buildWarnings...) - return nil + return container.ID, warnings, nil } // Create creates a new container from the given configuration with a given name. diff --git a/daemon/daemon.go b/daemon/daemon.go index d5d420f107211..76ed5a5ddc354 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,7 +119,8 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "create": daemon.ContainerCreate, + "container_stats": daemon.ContainerStats, + "export": daemon.ContainerExport, "info": daemon.CmdInfo, "restart": daemon.ContainerRestart, "stop": daemon.ContainerStop, diff --git a/runconfig/config.go b/runconfig/config.go index 45255e9b01ebd..30dd1eb4cf768 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -36,41 +36,41 @@ type Config struct { Labels map[string]string } -func ContainerConfigFromJob(job *engine.Job) *Config { +func ContainerConfigFromJob(env *engine.Env) *Config { config := &Config{ - Hostname: job.Getenv("Hostname"), - Domainname: job.Getenv("Domainname"), - User: job.Getenv("User"), - 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"), - Tty: job.GetenvBool("Tty"), - OpenStdin: job.GetenvBool("OpenStdin"), - StdinOnce: job.GetenvBool("StdinOnce"), - Image: job.Getenv("Image"), - WorkingDir: job.Getenv("WorkingDir"), - NetworkDisabled: job.GetenvBool("NetworkDisabled"), - MacAddress: job.Getenv("MacAddress"), + Hostname: env.Get("Hostname"), + Domainname: env.Get("Domainname"), + User: env.Get("User"), + Memory: env.GetInt64("Memory"), + MemorySwap: env.GetInt64("MemorySwap"), + CpuShares: env.GetInt64("CpuShares"), + Cpuset: env.Get("Cpuset"), + AttachStdin: env.GetBool("AttachStdin"), + AttachStdout: env.GetBool("AttachStdout"), + AttachStderr: env.GetBool("AttachStderr"), + Tty: env.GetBool("Tty"), + OpenStdin: env.GetBool("OpenStdin"), + StdinOnce: env.GetBool("StdinOnce"), + Image: env.Get("Image"), + WorkingDir: env.Get("WorkingDir"), + NetworkDisabled: env.GetBool("NetworkDisabled"), + MacAddress: env.Get("MacAddress"), } - job.GetenvJson("ExposedPorts", &config.ExposedPorts) - job.GetenvJson("Volumes", &config.Volumes) - if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil { + env.GetJson("ExposedPorts", &config.ExposedPorts) + env.GetJson("Volumes", &config.Volumes) + if PortSpecs := env.GetList("PortSpecs"); PortSpecs != nil { config.PortSpecs = PortSpecs } - if Env := job.GetenvList("Env"); Env != nil { + if Env := env.GetList("Env"); Env != nil { config.Env = Env } - if Cmd := job.GetenvList("Cmd"); Cmd != nil { + if Cmd := env.GetList("Cmd"); Cmd != nil { config.Cmd = Cmd } - job.GetenvJson("Labels", &config.Labels) + env.GetJson("Labels", &config.Labels) - if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { + if Entrypoint := env.GetList("Entrypoint"); Entrypoint != nil { config.Entrypoint = Entrypoint } return config From 002afbbe77abe722b8d0c6a2d3f11a258509f430 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 9 Apr 2015 16:29:31 -0700 Subject: [PATCH 114/332] Make integration tests to call the new start and create endpoints. Signed-off-by: David Calavera --- integration/runtime_test.go | 52 +++++++++++++++++-------------------- integration/utils_test.go | 15 +++++------ 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 6881fbea60ad3..f3485b2b36b67 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -422,14 +422,13 @@ func TestGet(t *testing.T) { func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) { var ( - err error - id string - outputBuffer = bytes.NewBuffer(nil) - strPort string - eng = NewTestEngine(t) - daemon = mkDaemonFromEngine(eng, t) - port = 5554 - p nat.Port + err error + id string + strPort string + eng = NewTestEngine(t) + daemon = mkDaemonFromEngine(eng, t) + port = 5554 + p nat.Port ) defer func() { if err != nil { @@ -452,16 +451,13 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) ep[p] = struct{}{} - jobCreate := eng.Job("create") - jobCreate.Setenv("Image", unitTestImageID) - jobCreate.SetenvList("Cmd", []string{"sh", "-c", cmd}) - jobCreate.SetenvList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) - jobCreate.SetenvJson("ExposedPorts", ep) - jobCreate.Stdout.Add(outputBuffer) - if err := jobCreate.Run(); err != nil { - t.Fatal(err) - } - id = engine.Tail(outputBuffer, 1) + env := new(engine.Env) + env.Set("Image", unitTestImageID) + env.SetList("Cmd", []string{"sh", "-c", cmd}) + env.SetList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) + env.SetJson("ExposedPorts", ep) + + id, _, err = daemon.ContainerCreate(unitTestImageID, env) // 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! @@ -472,15 +468,16 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem } - jobStart := eng.Job("start", id) portBindings := make(map[nat.Port][]nat.PortBinding) portBindings[p] = []nat.PortBinding{ {}, } - if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil { + + env := new(engine.Env) + if err := env.SetJson("PortsBindings", portBindings); err != nil { t.Fatal(err) } - if err := jobStart.Run(); err != nil { + if err := daemon.ContainerStart(id, env); err != nil { t.Fatal(err) } @@ -731,20 +728,20 @@ func TestContainerNameValidation(t *testing.T) { t.Fatal(err) } - var outputBuffer = bytes.NewBuffer(nil) - job := eng.Job("create", test.Name) - if err := job.ImportEnv(config); err != nil { + env := new(engine.Env) + if err := env.Import(config); err != nil { t.Fatal(err) } - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { + + containerId, _, err := daemon.ContainerCreate(test.Name, env) + if err != nil { if !test.Valid { continue } t.Fatal(err) } - container, err := daemon.Get(engine.Tail(outputBuffer, 1)) + container, err := daemon.Get(containerId) if err != nil { t.Fatal(err) } @@ -759,7 +756,6 @@ func TestContainerNameValidation(t *testing.T) { t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID) } } - } func TestLinkChildContainer(t *testing.T) { diff --git a/integration/utils_test.go b/integration/utils_test.go index cf9104efe130b..86b27fb735946 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -44,16 +44,15 @@ func mkDaemon(f Fataler) *daemon.Daemon { } func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { - job := eng.Job("create", name) - if err := job.ImportEnv(config); err != nil { + env := new(engine.Env) + if err := env.Import(config); err != nil { f.Fatal(err) } - var outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { + containerId, _, err := getDaemon(eng).ContainerCreate(name, env) + if err != nil { f.Fatal(err) } - return engine.Tail(outputBuffer, 1) + return containerId } func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) { @@ -61,8 +60,8 @@ func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler } func startContainer(eng *engine.Engine, id string, t Fataler) { - job := eng.Job("start", id) - if err := job.Run(); err != nil { + env := new(engine.Env) + if err := getDaemon(eng).ContainerStart(id, env); err != nil { t.Fatal(err) } } From d10c5d95a896197b504bae039dc6d9371917cdf1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 14 Apr 2015 16:53:28 -0700 Subject: [PATCH 115/332] Fix @moxiegirl's handler to be consistent with other maintainers. Signed-off-by: David Calavera --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6c79e8b4b3ad4..b708af7745b89 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -408,7 +408,7 @@ made through a pull request. people = [ "fredlf", "james", - "mary", + "moxiegirl", "spf13", "sven" ] @@ -585,7 +585,7 @@ made through a pull request. Email = "lk4d4@docker.com" GitHub = "lk4d4" - [people.mary] + [people.moxiegirl] Name = "Mary Anthony" Email = "mary.anthony@docker.com" GitHub = "moxiegirl" From 25d07511ed63959777ab1d0bee9f8e5f79382318 Mon Sep 17 00:00:00 2001 From: Jason Divock Date: Tue, 14 Apr 2015 16:19:23 -0700 Subject: [PATCH 116/332] Update basic setup instructions Without adding the user to the group you're going to hit nasty TLS errors. Figured I'd save the next guy the hassle. Problem more accurately described here: https://github.com/docker/docker/issues/5314 Signed-off-by: Jason Divock --- docs/sources/installation/amazon.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/amazon.md b/docs/sources/installation/amazon.md index 6a28685dc57c9..60a6653b7bd8e 100644 --- a/docs/sources/installation/amazon.md +++ b/docs/sources/installation/amazon.md @@ -28,8 +28,12 @@ Repository. your Amazon Linux instance should be running! 3. SSH to your instance to install Docker : `ssh -i ec2-user@` -4. Once connected to the instance, type - `sudo yum install -y docker ; sudo service docker start` +4. Add the ec2-user to the docker group : + `sudo usermod -a -G docker ec2-user` +5. Restart the machine and log back in + `sudo shutdown -r now` +6. Once connected to the instance, type + `sudo yum install -y docker ; sudo service docker start` to install and start Docker **If this is your first AWS instance, you may need to set up your Security Group to allow SSH.** By default all incoming ports to your new instance will be blocked by the AWS Security Group, so you might just get timeouts when you try to connect. From 8077b2fb805c78cee642d8350df88227c6414960 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Wed, 15 Apr 2015 09:33:46 +0800 Subject: [PATCH 117/332] add support for cpuset.mems Signed-off-by: Qiang Huang --- daemon/container.go | 1 + daemon/execdriver/driver.go | 2 ++ daemon/execdriver/lxc/lxc_template.go | 3 +++ docs/man/docker-create.1.md | 8 ++++++++ docs/man/docker-run.1.md | 8 ++++++++ .../reference/api/docker_remote_api_v1.19.md | 3 +++ docs/sources/reference/commandline/cli.md | 2 ++ docs/sources/reference/run.md | 16 ++++++++++++++++ integration-cli/docker_cli_run_test.go | 15 +++++++++++++-- runconfig/hostconfig.go | 1 + runconfig/parse.go | 2 ++ 11 files changed, 59 insertions(+), 2 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index f89db5cfcf428..97d1afbb01d99 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -355,6 +355,7 @@ func populateCommand(c *Container, env []string) error { MemorySwap: c.hostConfig.MemorySwap, CpuShares: c.hostConfig.CpuShares, CpusetCpus: c.hostConfig.CpusetCpus, + CpusetMems: c.hostConfig.CpusetMems, Rlimits: rlimits, } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 637f7d779e441..fc3b5caba412e 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -110,6 +110,7 @@ type Resources struct { MemorySwap int64 `json:"memory_swap"` CpuShares int64 `json:"cpu_shares"` CpusetCpus string `json:"cpuset_cpus"` + CpusetMems string `json:"cpuset_mems"` Rlimits []*ulimit.Rlimit `json:"rlimits"` } @@ -204,6 +205,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.MemoryReservation = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap container.Cgroups.CpusetCpus = c.Resources.CpusetCpus + container.Cgroups.CpusetMems = c.Resources.CpusetMems } return nil diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 6c182ab3948f3..ece924d38f0f2 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -110,6 +110,9 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{if .Resources.CpusetCpus}} lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}} {{end}} +{{if .Resources.CpusetMems}} +lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}} +{{end}} {{end}} {{if .LxcConfig}} diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 1a0da1b8f4731..6ce7fe6bdc3b4 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -13,6 +13,7 @@ docker-create - Create a new container [**--cap-drop**[=*[]*]] [**--cidfile**[=*CIDFILE*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] +[**--cpuset-mems**[=*CPUSET-MEMS*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] [**--dns**[=*[]*]] @@ -74,6 +75,13 @@ IMAGE [COMMAND] [ARG...] **--cpuset-cpus**="" CPUs in which to allow execution (0-3, 0,1) +**--cpuset-mems**="" + Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + + If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1` +then processes in your Docker container will only use memory from the first +two memory nodes. + **--device**=[] Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 8a856ca608a9e..42eeeb349dcbd 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -13,6 +13,7 @@ docker-run - Run a command in a new container [**--cap-drop**[=*[]*]] [**--cidfile**[=*CIDFILE*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] +[**--cpuset-mems**[=*CPUSET-MEMS*]] [**-d**|**--detach**[=*false*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] @@ -134,6 +135,13 @@ division of CPU shares: **--cpuset-cpus**="" CPUs in which to allow execution (0-3, 0,1) +**--cpuset-mems**="" + Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + + If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1` +then processes in your Docker container will only use memory from the first +two memory nodes. + **-d**, **--detach**=*true*|*false* Detached mode: run the container in the background and print the new container ID. The default is *false*. diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 524f77037ffc5..18c52ef421188 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -147,6 +147,7 @@ Create a container "MemorySwap": 0, "CpuShares": 512, "CpusetCpus": "0,1", + "CpusetMems": "0,1", "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts": false, "Privileged": false, @@ -191,6 +192,7 @@ Json Parameters: (ie. the relative weight vs other containers). - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. - **CpusetCpus** - String value containing the cgroups CpusetCpus to use. +- **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -340,6 +342,7 @@ Return low-level information on the container `id` "CapDrop": null, "ContainerIDFile": "", "CpusetCpus": "", + "CpusetMems": "", "CpuShares": 0, "Devices": [], "Dns": null, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 7375261230c07..60a604b8bb6fd 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -892,6 +892,7 @@ Creates a new container. --cgroup-parent="" Optional parent cgroup for the container --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) + --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) --device=[] Add a host device to the container --dns=[] Set custom DNS servers --dns-search=[] Set custom DNS search domains @@ -1844,6 +1845,7 @@ To remove an image using its digest: --cap-drop=[] Drop Linux capabilities --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) + --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) -d, --detach=false Run container in background and print container ID --device=[] Add a host device to the container --dns=[] Set custom DNS servers diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index d41b686c4b24f..b5784cba784cb 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -474,6 +474,7 @@ container: -memory-swap="": Total memory limit (memory + swap, format: , where unit = b, k, m or g) -c, --cpu-shares=0: CPU shares (relative weight) --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) + --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. ### Memory constraints @@ -599,6 +600,21 @@ This means processes in container can be executed on cpu 1 and cpu 3. This means processes in container can be executed on cpu 0, cpu 1 and cpu 2. +We can set mems in which to allow execution for containers. Only effective +on NUMA systems. + +Examples: + + $ docker run -ti --cpuset-mems="1,3" ubuntu:14.04 /bin/bash + +This example restricts the processes in the container to only use memory from +memory nodes 1 and 3. + + $ docker run -ti --cpuset-mems="0-2" ubuntu:14.04 /bin/bash + +This example restricts the processes in the container to only use memory from +memory nodes 0, 1 and 2. + ## Runtime privilege, Linux capabilities, and LXC configuration --cap-add: Add Linux capabilities diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7c931f8fac3c4..e990f086ae1dc 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -1367,7 +1367,7 @@ 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) + t.Fatalf("container should run successfully with cpuset of 0: %s", err) } logDone("run - cpuset 0") @@ -1378,12 +1378,23 @@ func TestRunWithCpusetCpus(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--cpuset-cpus", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfuly with cpuset-cpus of 0: %s", err) + t.Fatalf("container should run successfully with cpuset-cpus of 0: %s", err) } logDone("run - cpuset-cpus 0") } +func TestRunWithCpusetMems(t *testing.T) { + defer deleteAllContainers() + + cmd := exec.Command(dockerBinary, "run", "--cpuset-mems", "0", "busybox", "true") + if code, err := runCommand(cmd); err != nil || code != 0 { + t.Fatalf("container should run successfully with cpuset-mems of 0: %s", err) + } + + logDone("run - cpuset-mems 0") +} + func TestRunDeviceNumbers(t *testing.T) { defer deleteAllContainers() diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9d4eb264140be..273634e63f329 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -116,6 +116,7 @@ type HostConfig struct { MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap CpuShares int64 // CPU shares (relative weight vs. other containers) CpusetCpus string // CpusetCpus 0-2, 0,1 + CpusetMems string // CpusetMems 0-2, 0,1 Privileged bool PortBindings nat.PortMap Links []string diff --git a/runconfig/parse.go b/runconfig/parse.go index d302330c82755..d9b21ca2736c0 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -64,6 +64,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") + flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") @@ -313,6 +314,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe MemorySwap: MemorySwap, CpuShares: *flCpuShares, CpusetCpus: *flCpusetCpus, + CpusetMems: *flCpusetMems, Privileged: *flPrivileged, PortBindings: portBindings, Links: flLinks.GetAll(), From f8dc7e8754355c504562021da244c52055ab9204 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 14 Apr 2015 10:00:48 +0800 Subject: [PATCH 118/332] Add cpuset-mems support for docker build Signed-off-by: Qiang Huang --- api/client/build.go | 2 ++ api/server/server.go | 1 + builder/evaluator.go | 1 + builder/internals.go | 1 + builder/job.go | 2 ++ docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_build_test.go | 15 ++++++++------- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index dc54c22ffadb8..b4ee93b2cec3c 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -56,6 +56,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") + flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) @@ -278,6 +279,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } v.Set("cpusetcpus", *flCPUSetCpus) + v.Set("cpusetmems", *flCPUSetMems) v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) v.Set("memory", strconv.FormatInt(memory, 10)) v.Set("memswap", strconv.FormatInt(memorySwap, 10)) diff --git a/api/server/server.go b/api/server/server.go index 7ebeb4b6aaf49..3e8d9bae0f38d 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1213,6 +1213,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite job.Setenv("memswap", r.FormValue("memswap")) job.Setenv("memory", r.FormValue("memory")) job.Setenv("cpusetcpus", r.FormValue("cpusetcpus")) + job.Setenv("cpusetmems", r.FormValue("cpusetmems")) job.Setenv("cpushares", r.FormValue("cpushares")) // Job cancellation. Note: not all job types support this. diff --git a/builder/evaluator.go b/builder/evaluator.go index 6237f2663010a..c159e51bf9774 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -124,6 +124,7 @@ type Builder struct { // Set resource restrictions for build containers cpuSetCpus string + cpuSetMems string cpuShares int64 memory int64 memorySwap int64 diff --git a/builder/internals.go b/builder/internals.go index 728ccde8aefdc..caa94ef2be299 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -541,6 +541,7 @@ func (b *Builder) create() (*daemon.Container, error) { hostConfig := &runconfig.HostConfig{ CpuShares: b.cpuShares, CpusetCpus: b.cpuSetCpus, + CpusetMems: b.cpuSetMems, Memory: b.memory, MemorySwap: b.memorySwap, } diff --git a/builder/job.go b/builder/job.go index b0ce8ddc09061..f9ff6103962e3 100644 --- a/builder/job.go +++ b/builder/job.go @@ -63,6 +63,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { memorySwap = job.GetenvInt64("memswap") cpuShares = job.GetenvInt64("cpushares") cpuSetCpus = job.Getenv("cpusetcpus") + cpuSetMems = job.Getenv("cpusetmems") authConfig = ®istry.AuthConfig{} configFile = ®istry.ConfigFile{} tag string @@ -153,6 +154,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { dockerfileName: dockerfileName, cpuShares: cpuShares, cpuSetCpus: cpuSetCpus, + cpuSetMems: cpuSetMems, memory: memory, memorySwap: memorySwap, cancelled: job.WaitCancelled(), diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 60a604b8bb6fd..607f67076211c 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -597,6 +597,7 @@ is returned by the `docker attach` command to its caller too: --memory-swap="" Total memory (memory + swap), `-1` to disable swap -c, --cpu-shares CPU Shares (relative weight) --cpuset-cpus="" CPUs in which to allow execution, e.g. `0-3`, `0,1` + --cpuset-mems="" MEMs in which to allow execution, e.g. `0-3`, `0,1` Builds Docker images from a Dockerfile and a "context". A build's context is the files located in the specified `PATH` or `URL`. The build process can diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 40e038fc4b799..1ef5088e246f8 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5555,7 +5555,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err) } - cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpu-shares=100", "-t", name, ".") + cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "-t", name, ".") cmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(cmd) @@ -5573,6 +5573,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { Memory float64 // Use float64 here since the json decoder sees it that way MemorySwap int CpusetCpus string + CpusetMems string CpuShares int } @@ -5586,9 +5587,9 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err, cfg) } mem := int64(c1.Memory) - if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpuShares != 100 { - t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d", - mem, c1.MemorySwap, c1.CpusetCpus, c1.CpuShares) + if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { + t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + mem, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) } // Make sure constraints aren't saved to image @@ -5605,9 +5606,9 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err, cfg) } mem = int64(c2.Memory) - if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpuShares == 100 { - t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d", - mem, c2.MemorySwap, c2.CpusetCpus, c2.CpuShares) + if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { + t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + mem, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) } logDone("build - resource constraints applied") From acf025ad1b806fd9b5eb3358a8e1d75c6aae890d Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Wed, 15 Apr 2015 11:15:50 +0800 Subject: [PATCH 119/332] Inspect show right LogPath in json-file driver Signed-off-by: Deng Guangxing --- daemon/container.go | 1 + docs/sources/reference/api/docker_remote_api_v1.18.md | 5 ++++- docs/sources/reference/api/docker_remote_api_v1.19.md | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index f89db5cfcf428..1400e967fbe91 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1405,6 +1405,7 @@ func (container *Container) startLogging() error { if err != nil { return err } + container.LogPath = pth dl, err := jsonfilelog.New(pth) if err != nil { diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index c69ade36a49da..a7e5e55919caf 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -359,7 +359,10 @@ Return low-level information on the container `id` "MaximumRetryCount": 2, "Name": "on-failure" }, - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { + "Config": null, + "Type": "json-file" + }, "SecurityOpt": null, "VolumesFrom": null, "Ulimits": [{}] diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 524f77037ffc5..932ee547163b9 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -359,7 +359,10 @@ Return low-level information on the container `id` "MaximumRetryCount": 2, "Name": "on-failure" }, - "LogConfig": { "Type": "json-file", "Config": {} }, + "LogConfig": { + "Config": null, + "Type": "json-file" + }, "SecurityOpt": null, "VolumesFrom": null, "Ulimits": [{}] From 6f812a4ec18453dd0f46e138f7c05b1ade13f007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 17:21:12 +0200 Subject: [PATCH 120/332] hack/make.sh: use bash internal $PWD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Thalheim --- hack/make.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 3bcb265b3c3b9..b4312b5d5744e 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -27,7 +27,7 @@ export DOCKER_PKG='github.com/docker/docker' # We're a nice, sexy, little shell script, and people might try to run us; # but really, they shouldn't. We want to be in a container! -if [ "$(pwd)" != "/go/src/$DOCKER_PKG" ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then +if [ "$PWD" != "/go/src/$DOCKER_PKG" ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then { echo "# WARNING! I don't seem to be running in the Docker container." echo "# The result of this command might be an incorrect build, and will not be" @@ -82,7 +82,7 @@ if [ "$AUTO_GOPATH" ]; then rm -rf .gopath mkdir -p .gopath/src/"$(dirname "${DOCKER_PKG}")" ln -sf ../../../.. .gopath/src/"${DOCKER_PKG}" - export GOPATH="$(pwd)/.gopath:$(pwd)/vendor" + export GOPATH="${PWD}/.gopath:${PWD}/vendor" fi if [ ! "$GOPATH" ]; then From 23afce5f7fb575ea5fad1afc5f60b38e3e17d8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 17:38:14 +0200 Subject: [PATCH 121/332] hack/make.sh: use SCRIPTDIR wherever possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Thalheim --- hack/make.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index b4312b5d5744e..c8b2da7b8ea5b 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -24,6 +24,7 @@ set -e set -o pipefail export DOCKER_PKG='github.com/docker/docker' +export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # We're a nice, sexy, little shell script, and people might try to run us; # but really, they shouldn't. We want to be in a container! @@ -110,7 +111,7 @@ fi # Use these flags when compiling the tests and final binary IAMSTATIC='true' -source "$(dirname "$BASH_SOURCE")/make/.go-autogen" +source "$SCRIPTDIR/make/.go-autogen" LDFLAGS='-w' LDFLAGS_STATIC='-linkmode external' @@ -270,7 +271,6 @@ main() { ln -sfT $VERSION bundles/latest fi - SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ $# -lt 1 ]; then bundles=(${DEFAULT_BUNDLES[@]}) else From ac20568b0a62c794c0f1190703f051bd1cfac341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 18:08:08 +0200 Subject: [PATCH 122/332] hack: quote all parameters with variable interpolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit better safe then sorry. especially for rm Signed-off-by: Jörg Thalheim --- hack/dind | 2 +- hack/make.sh | 8 ++-- hack/make/.dockerinit | 2 +- hack/make/.dockerinit-gccgo | 2 +- hack/make/.integration-daemon-stop | 4 +- hack/make/test-integration | 2 +- hack/make/test-unit | 2 +- hack/make/ubuntu | 60 +++++++++++++++--------------- hack/release.sh | 52 +++++++++++++------------- 9 files changed, 67 insertions(+), 67 deletions(-) diff --git a/hack/dind b/hack/dind index dfd463731e441..9289ba65561b0 100755 --- a/hack/dind +++ b/hack/dind @@ -60,7 +60,7 @@ for HIER in $(cut -d: -f2 /proc/1/cgroup); do mkdir -p "$CGROUP/$HIER" - if ! mountpoint -q $CGROUP/$HIER; then + if ! mountpoint -q "$CGROUP/$HIER"; then mount -n -t cgroup -o "$OHIER" cgroup "$CGROUP/$HIER" fi diff --git a/hack/make.sh b/hack/make.sh index c8b2da7b8ea5b..0204756d82a60 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -252,7 +252,7 @@ bundle() { bundlescript=$1 bundle=$(basename $bundlescript) echo "---> Making bundle: $bundle (in bundles/$VERSION/$bundle)" - mkdir -p bundles/$VERSION/$bundle + mkdir -p "bundles/$VERSION/$bundle" source "$bundlescript" "$(pwd)/bundles/$VERSION/$bundle" } @@ -262,13 +262,13 @@ main() { mkdir -p bundles if [ -e "bundles/$VERSION" ]; then echo "bundles/$VERSION already exists. Removing." - rm -fr bundles/$VERSION && mkdir bundles/$VERSION || exit 1 + rm -fr "bundles/$VERSION" && mkdir "bundles/$VERSION" || exit 1 echo fi if [ "$(go env GOHOSTOS)" != 'windows' ]; then # Windows and symlinks don't get along well - ln -sfT $VERSION bundles/latest + ln -sfT "$VERSION" bundles/latest fi if [ $# -lt 1 ]; then @@ -277,7 +277,7 @@ main() { bundles=($@) fi for bundle in ${bundles[@]}; do - bundle $SCRIPTDIR/make/$bundle + bundle "$SCRIPTDIR/make/$bundle" echo done } diff --git a/hack/make/.dockerinit b/hack/make/.dockerinit index fceba7db920bb..36be4f822c81c 100644 --- a/hack/make/.dockerinit +++ b/hack/make/.dockerinit @@ -30,4 +30,4 @@ else fi # sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another -export DOCKER_INITSHA1="$($sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)" +export DOCKER_INITSHA1=$($sha1sum "$DEST/dockerinit-$VERSION" | cut -d' ' -f1) diff --git a/hack/make/.dockerinit-gccgo b/hack/make/.dockerinit-gccgo index 592a4152c857e..47611d66a44be 100644 --- a/hack/make/.dockerinit-gccgo +++ b/hack/make/.dockerinit-gccgo @@ -27,4 +27,4 @@ else fi # sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another -export DOCKER_INITSHA1="$($sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)" +export DOCKER_INITSHA1=$($sha1sum "$DEST/dockerinit-$VERSION" | cut -d' ' -f1) diff --git a/hack/make/.integration-daemon-stop b/hack/make/.integration-daemon-stop index 319aaa4a1db54..7e4dc2353afb5 100644 --- a/hack/make/.integration-daemon-stop +++ b/hack/make/.integration-daemon-stop @@ -2,8 +2,8 @@ for pidFile in $(find "$DEST" -name docker.pid); do pid=$(set -x; cat "$pidFile") - ( set -x; kill $pid ) - if ! wait $pid; then + ( set -x; kill "$pid" ) + if ! wait "$pid"; then echo >&2 "warning: PID $pid from $pidFile had a nonzero exit code" fi done diff --git a/hack/make/test-integration b/hack/make/test-integration index 5cb7102bc8e1d..b4cb6debdea6f 100644 --- a/hack/make/test-integration +++ b/hack/make/test-integration @@ -22,4 +22,4 @@ bundle_test_integration() { # spews when it is given packages that aren't used bundle_test_integration 2>&1 \ | grep --line-buffered -v '^warning: no packages being tested depend on ' \ - | tee -a $DEST/test.log + | tee -a "$DEST/test.log" diff --git a/hack/make/test-unit b/hack/make/test-unit index 7a26428201276..15595a89c3e7e 100644 --- a/hack/make/test-unit +++ b/hack/make/test-unit @@ -85,4 +85,4 @@ go_run_test_dir() { fi } -bundle_test_unit 2>&1 | tee -a $DEST/test.log +bundle_test_unit 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 5bbca8a89e93f..7543789a187d9 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -40,26 +40,26 @@ bundle_ubuntu() { DIR=$DEST/build # Include our udev rules - mkdir -p $DIR/etc/udev/rules.d - cp contrib/udev/80-docker.rules $DIR/etc/udev/rules.d/ + mkdir -p "$DIR/etc/udev/rules.d" + cp contrib/udev/80-docker.rules "$DIR/etc/udev/rules.d/" # Include our init scripts - mkdir -p $DIR/etc/init - cp contrib/init/upstart/docker.conf $DIR/etc/init/ - mkdir -p $DIR/etc/init.d - cp contrib/init/sysvinit-debian/docker $DIR/etc/init.d/ - mkdir -p $DIR/etc/default - cp contrib/init/sysvinit-debian/docker.default $DIR/etc/default/docker - mkdir -p $DIR/lib/systemd/system - cp contrib/init/systemd/docker.{service,socket} $DIR/lib/systemd/system/ + mkdir -p "$DIR/etc/init" + cp contrib/init/upstart/docker.conf "$DIR/etc/init/" + mkdir -p "$DIR/etc/init.d" + cp contrib/init/sysvinit-debian/docker "$DIR/etc/init.d/" + mkdir -p "$DIR/etc/default" + cp contrib/init/sysvinit-debian/docker.default "$DIR/etc/default/docker" + mkdir -p "$DIR/lib/systemd/system" + cp contrib/init/systemd/docker.{service,socket} "$DIR/lib/systemd/system/" # Include contributed completions - mkdir -p $DIR/etc/bash_completion.d - cp contrib/completion/bash/docker $DIR/etc/bash_completion.d/ - mkdir -p $DIR/usr/share/zsh/vendor-completions - cp contrib/completion/zsh/_docker $DIR/usr/share/zsh/vendor-completions/ - mkdir -p $DIR/etc/fish/completions - cp contrib/completion/fish/docker.fish $DIR/etc/fish/completions/ + mkdir -p "$DIR/etc/bash_completion.d" + cp contrib/completion/bash/docker "$DIR/etc/bash_completion.d/" + mkdir -p "$DIR/usr/share/zsh/vendor-completions" + cp contrib/completion/zsh/_docker "$DIR/usr/share/zsh/vendor-completions/" + mkdir -p "$DIR/etc/fish/completions" + cp contrib/completion/fish/docker.fish "$DIR/etc/fish/completions/" # Include contributed man pages docs/man/md2man-all.sh -q @@ -76,11 +76,11 @@ bundle_ubuntu() { # Copy the binary # This will fail if the binary bundle hasn't been built - mkdir -p $DIR/usr/bin - cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker + mkdir -p "$DIR/usr/bin" + cp "$DEST/../binary/docker-$VERSION" "$DIR/usr/bin/docker" # Generate postinst/prerm/postrm scripts - cat > $DEST/postinst <<'EOF' + cat > "$DEST/postinst" <<'EOF' #!/bin/sh set -e set -u @@ -104,7 +104,7 @@ service docker $_dh_action 2>/dev/null || true #DEBHELPER# EOF - cat > $DEST/prerm <<'EOF' + cat > "$DEST/prerm" <<'EOF' #!/bin/sh set -e set -u @@ -113,7 +113,7 @@ service docker stop 2>/dev/null || true #DEBHELPER# EOF - cat > $DEST/postrm <<'EOF' + cat > "$DEST/postrm" <<'EOF' #!/bin/sh set -e set -u @@ -131,18 +131,18 @@ fi #DEBHELPER# EOF # TODO swaths of these were borrowed from debhelper's auto-inserted stuff, because we're still using fpm - we need to use debhelper instead, and somehow reconcile Ubuntu that way - chmod +x $DEST/postinst $DEST/prerm $DEST/postrm + chmod +x "$DEST/postinst" "$DEST/prerm" "$DEST/postrm" ( # switch directories so we create *.deb in the right folder - cd $DEST + cd "$DEST" # create lxc-docker-VERSION package - fpm -s dir -C $DIR \ - --name lxc-docker-$VERSION --version "$PKGVERSION" \ - --after-install $DEST/postinst \ - --before-remove $DEST/prerm \ - --after-remove $DEST/postrm \ + fpm -s dir -C "$DIR" \ + --name "lxc-docker-$VERSION" --version "$PKGVERSION" \ + --after-install "$DEST/postinst" \ + --before-remove "$DEST/prerm" \ + --after-remove "$DEST/postrm" \ --architecture "$PACKAGE_ARCHITECTURE" \ --prefix / \ --depends iptables \ @@ -184,8 +184,8 @@ EOF ) # clean up after ourselves so we have a clean output directory - rm $DEST/postinst $DEST/prerm $DEST/postrm - rm -r $DIR + rm "$DEST/postinst" "$DEST/prerm" "$DEST/postrm" + rm -r "$DIR" } bundle_ubuntu diff --git a/hack/release.sh b/hack/release.sh index da95808c5a198..4b0d32c9d2ddd 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -71,23 +71,23 @@ BUCKET=$AWS_S3_BUCKET setup_s3() { # Try creating the bucket. Ignore errors (it might already exist). - s3cmd mb s3://$BUCKET 2>/dev/null || true + s3cmd mb "s3://$BUCKET" 2>/dev/null || true # Check access to the bucket. # s3cmd has no useful exit status, so we cannot check that. # Instead, we check if it outputs anything on standard output. # (When there are problems, it uses standard error instead.) - s3cmd info s3://$BUCKET | grep -q . + s3cmd info "s3://$BUCKET" | grep -q . # Make the bucket accessible through website endpoints. - s3cmd ws-create --ws-index index --ws-error error s3://$BUCKET + s3cmd ws-create --ws-index index --ws-error error "s3://$BUCKET" } # write_to_s3 uploads the contents of standard input to the specified S3 url. write_to_s3() { DEST=$1 F=`mktemp` - cat > $F - s3cmd --acl-public --mime-type='text/plain' put $F $DEST - rm -f $F + cat > "$F" + s3cmd --acl-public --mime-type='text/plain' put "$F" "$DEST" + rm -f "$F" } s3_url() { @@ -246,20 +246,20 @@ release_build() { # 1. A full APT repository is published at $BUCKET/ubuntu/ # 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index release_ubuntu() { - [ -e bundles/$VERSION/ubuntu ] || { + [ -e "bundles/$VERSION/ubuntu" ] || { echo >&2 './hack/make.sh must be run before release_ubuntu' exit 1 } # Sign our packages dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \ - --sign builder bundles/$VERSION/ubuntu/*.deb + --sign builder "bundles/$VERSION/ubuntu/"*.deb # Setup the APT repo APTDIR=bundles/$VERSION/ubuntu/apt - mkdir -p $APTDIR/conf $APTDIR/db - s3cmd sync s3://$BUCKET/ubuntu/db/ $APTDIR/db/ || true - cat > $APTDIR/conf/distributions < "$APTDIR/conf/distributions" < bundles/$VERSION/ubuntu/gpg - s3cmd --acl-public put bundles/$VERSION/ubuntu/gpg s3://$BUCKET/gpg + s3cmd sync "$HOME/.gnupg/" "s3://$BUCKET/ubuntu/.gnupg/" + gpg --armor --export releasedocker > "bundles/$VERSION/ubuntu/gpg" + s3cmd --acl-public put "bundles/$VERSION/ubuntu/gpg" "s3://$BUCKET/gpg" local gpgFingerprint=36A1D7869245C8950F966E92D8576A8BA88D21E9 if [[ $BUCKET == test* ]]; then @@ -287,7 +287,7 @@ EOF fi # Upload repo - s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/ + s3cmd --acl-public sync "$APTDIR/" "s3://$BUCKET/ubuntu/" cat <&2 './hack/make.sh must be run before release_binaries' exit 1 } @@ -341,29 +341,29 @@ EOF # Add redirect at /builds/info for URL-backwards-compatibility rm -rf /tmp/emptyfile && touch /tmp/emptyfile - s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/builds/info + s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile "s3://$BUCKET/builds/info" if [ -z "$NOLATEST" ]; then echo "Advertising $VERSION on $BUCKET as most recent version" - echo $VERSION | write_to_s3 s3://$BUCKET/latest + echo "$VERSION" | write_to_s3 "s3://$BUCKET/latest" fi } # Upload the index script release_index() { - sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 s3://$BUCKET/index + sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 "s3://$BUCKET/index" } release_test() { if [ -e "bundles/$VERSION/test" ]; then - s3cmd --acl-public sync bundles/$VERSION/test/ s3://$BUCKET/test/ + s3cmd --acl-public sync "bundles/$VERSION/test/" "s3://$BUCKET/test/" fi } setup_gpg() { # Make sure that we have our keys - mkdir -p $HOME/.gnupg/ - s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ $HOME/.gnupg/ || true + mkdir -p "$HOME/.gnupg/" + s3cmd sync "s3://$BUCKET/ubuntu/.gnupg/" "$HOME/.gnupg/" || true gpg --list-keys releasedocker >/dev/null || { gpg --gen-key --batch < Date: Tue, 14 Apr 2015 18:31:52 +0200 Subject: [PATCH 123/332] hack: useless use of cat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Thalheim --- hack/make.sh | 2 +- hack/release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 0204756d82a60..d2547af12eaec 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -63,7 +63,7 @@ DEFAULT_BUNDLES=( ubuntu ) -VERSION=$(cat ./VERSION) +VERSION=$(< ./VERSION) if command -v git &> /dev/null && git rev-parse &> /dev/null; then GITCOMMIT=$(git rev-parse --short HEAD) if [ -n "$(git status --porcelain --untracked-files=no)" ]; then diff --git a/hack/release.sh b/hack/release.sh index 4b0d32c9d2ddd..04772546fd658 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -60,7 +60,7 @@ if [ "$1" != '--release-regardless-of-test-failure' ]; then ) fi -VERSION=$(cat VERSION) +VERSION=$(< VERSION) BUCKET=$AWS_S3_BUCKET # These are the 2 keys we've used to sign the deb's From 6533cb973f6bab672018148fd6a67644580cc61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 18:43:33 +0200 Subject: [PATCH 124/332] hack/make/test-integration-cli: introduce MAKEDIR variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - every execution of dirname costs time - less repeating Signed-off-by: Jörg Thalheim --- hack/make.sh | 1 + hack/make/.dockerinit | 2 +- hack/make/.dockerinit-gccgo | 2 +- hack/make/binary | 2 +- hack/make/build-deb | 4 ++-- hack/make/cross | 2 +- hack/make/dynbinary | 4 ++-- hack/make/dyngccgo | 4 ++-- hack/make/gccgo | 2 +- hack/make/test-docker-py | 4 ++-- hack/make/test-integration | 2 +- hack/make/test-integration-cli | 12 ++++++------ hack/make/test-unit | 4 ++-- hack/make/validate-dco | 2 +- hack/make/validate-gofmt | 2 +- hack/make/validate-toml | 2 +- hack/make/validate-vet | 2 +- 17 files changed, 27 insertions(+), 26 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index d2547af12eaec..eeb26cbce0bb7 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -25,6 +25,7 @@ set -o pipefail export DOCKER_PKG='github.com/docker/docker' export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export MAKEDIR="$SCRIPTDIR/make" # We're a nice, sexy, little shell script, and people might try to run us; # but really, they shouldn't. We want to be in a container! diff --git a/hack/make/.dockerinit b/hack/make/.dockerinit index 36be4f822c81c..4a62ee1addc36 100644 --- a/hack/make/.dockerinit +++ b/hack/make/.dockerinit @@ -2,7 +2,7 @@ set -e IAMSTATIC="true" -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" # dockerinit still needs to be a static binary, even if docker is dynamic go build \ diff --git a/hack/make/.dockerinit-gccgo b/hack/make/.dockerinit-gccgo index 47611d66a44be..022f6db009564 100644 --- a/hack/make/.dockerinit-gccgo +++ b/hack/make/.dockerinit-gccgo @@ -2,7 +2,7 @@ set -e IAMSTATIC="true" -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" # dockerinit still needs to be a static binary, even if docker is dynamic go build --compiler=gccgo \ diff --git a/hack/make/binary b/hack/make/binary index 0f57ea0d693e8..d3ec2939c00ca 100644 --- a/hack/make/binary +++ b/hack/make/binary @@ -11,7 +11,7 @@ if [[ "$(uname -s)" == CYGWIN* ]]; then DEST=$(cygpath -mw $DEST) fi -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" go build \ -o "$DEST/$BINARY_FULLNAME" \ diff --git a/hack/make/build-deb b/hack/make/build-deb index 657aa04ccbf65..90c4c16939f72 100644 --- a/hack/make/build-deb +++ b/hack/make/build-deb @@ -5,7 +5,7 @@ DEST=$1 # subshell so that we can export PATH without breaking other things ( - source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + source "${MAKEDIR}/.integration-daemon-start" # we need to wrap up everything in between integration-daemon-start and # integration-daemon-stop to make sure we kill the daemon and don't hang, @@ -76,7 +76,7 @@ DEST=$1 # clean up after ourselves rm -f Dockerfile.build - source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + source "${MAKEDIR}/.integration-daemon-stop" [ -z "$didFail" ] # "set -e" ftw ) 2>&1 | tee -a $DEST/test.log diff --git a/hack/make/cross b/hack/make/cross index 3c5cb0401dc7f..368ebc5ab970a 100644 --- a/hack/make/cross +++ b/hack/make/cross @@ -28,6 +28,6 @@ for platform in $DOCKER_CROSSPLATFORMS; do export LDFLAGS_STATIC_DOCKER="" # we just need a simple client for these platforms export BUILDFLAGS=( "${ORIG_BUILDFLAGS[@]/ daemon/}" ) # remove the "daemon" build tag from platforms that aren't supported fi - source "$(dirname "$BASH_SOURCE")/binary" "$DEST/$platform" + source "${MAKEDIR}/binary" "$DEST/$platform" ) done diff --git a/hack/make/dynbinary b/hack/make/dynbinary index f9b43b0e77773..e1b65b48efc41 100644 --- a/hack/make/dynbinary +++ b/hack/make/dynbinary @@ -4,7 +4,7 @@ set -e DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then - source "$(dirname "$BASH_SOURCE")/.dockerinit" + source "${MAKEDIR}/.dockerinit" hash_files "$DEST/dockerinit-$VERSION" else @@ -18,5 +18,5 @@ fi export LDFLAGS_STATIC_DOCKER='' export BUILDFLAGS=( "${BUILDFLAGS[@]/netgo /}" ) # disable netgo, since we don't need it for a dynamic binary export BUILDFLAGS=( "${BUILDFLAGS[@]/static_build /}" ) # we're not building a "static" binary here - source "$(dirname "$BASH_SOURCE")/binary" + source "${MAKEDIR}/binary" ) diff --git a/hack/make/dyngccgo b/hack/make/dyngccgo index 738e1450ac511..7bdd404f10573 100644 --- a/hack/make/dyngccgo +++ b/hack/make/dyngccgo @@ -4,7 +4,7 @@ set -e DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then - source "$(dirname "$BASH_SOURCE")/.dockerinit-gccgo" + source "${MAKEDIR}/.dockerinit-gccgo" hash_files "$DEST/dockerinit-$VERSION" else @@ -19,5 +19,5 @@ fi export LDFLAGS_STATIC_DOCKER='' export BUILDFLAGS=( "${BUILDFLAGS[@]/netgo /}" ) # disable netgo, since we don't need it for a dynamic binary export BUILDFLAGS=( "${BUILDFLAGS[@]/static_build /}" ) # we're not building a "static" binary here - source "$(dirname "$BASH_SOURCE")/gccgo" + source "${MAKEDIR}/gccgo" ) diff --git a/hack/make/gccgo b/hack/make/gccgo index c85d2fbda55d2..c61c190f3ae8e 100644 --- a/hack/make/gccgo +++ b/hack/make/gccgo @@ -6,7 +6,7 @@ BINARY_NAME="docker-$VERSION" BINARY_EXTENSION="$(binary_extension)" BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION" -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" go build -compiler=gccgo \ -o "$DEST/$BINARY_FULLNAME" \ diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py index b95cf40af511d..409cee0e4e0a6 100644 --- a/hack/make/test-docker-py +++ b/hack/make/test-docker-py @@ -5,7 +5,7 @@ DEST=$1 # subshell so that we can export PATH without breaking other things ( - source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + source "${MAKEDIR}/.integration-daemon-start" # we need to wrap up everything in between integration-daemon-start and # integration-daemon-stop to make sure we kill the daemon and don't hang, @@ -24,7 +24,7 @@ DEST=$1 didFail=1 fi - source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + source "${MAKEDIR}/.integration-daemon-stop" [ -z "$didFail" ] # "set -e" ftw ) 2>&1 | tee -a $DEST/test.log diff --git a/hack/make/test-integration b/hack/make/test-integration index b4cb6debdea6f..206e37abf0c4e 100644 --- a/hack/make/test-integration +++ b/hack/make/test-integration @@ -5,7 +5,7 @@ DEST=$1 INIT=$DEST/../dynbinary/dockerinit-$VERSION [ -x "$INIT" ] || { - source "$(dirname "$BASH_SOURCE")/.dockerinit" + source "${MAKEDIR}/.dockerinit" INIT="$DEST/dockerinit" } export TEST_DOCKERINIT_PATH="$INIT" diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 3ef41d919e55a..8e9b975704b21 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -9,23 +9,23 @@ bundle_test_integration_cli() { # subshell so that we can export PATH without breaking other things ( - source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + source "${MAKEDIR}/.integration-daemon-start" # we need to wrap up everything in between integration-daemon-start and # integration-daemon-stop to make sure we kill the daemon and don't hang, # even and especially on test failures didFail= if ! { - source "$(dirname "$BASH_SOURCE")/.ensure-frozen-images" - source "$(dirname "$BASH_SOURCE")/.ensure-httpserver" - source "$(dirname "$BASH_SOURCE")/.ensure-emptyfs" + source "${MAKEDIR}/.ensure-frozen-images" + source "${MAKEDIR}/.ensure-httpserver" + source "${MAKEDIR}/.ensure-emptyfs" bundle_test_integration_cli }; then didFail=1 fi - source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + source "${MAKEDIR}/.integration-daemon-stop" [ -z "$didFail" ] # "set -e" ftw -) 2>&1 | tee -a $DEST/test.log +) 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/test-unit b/hack/make/test-unit index 15595a89c3e7e..7b6ce089e2cdb 100644 --- a/hack/make/test-unit +++ b/hack/make/test-unit @@ -39,12 +39,12 @@ bundle_test_unit() { mkdir -p "$HOME/.parallel" touch "$HOME/.parallel/ignored_vars" - echo "$TESTDIRS" | parallel --jobs "$PARALLEL_JOBS" --env _ "$(dirname "$BASH_SOURCE")/.go-compile-test-dir" + echo "$TESTDIRS" | parallel --jobs "$PARALLEL_JOBS" --env _ "${MAKEDIR}/.go-compile-test-dir" rm -rf "$HOME" else # aww, no "parallel" available - fall back to boring for test_dir in $TESTDIRS; do - "$(dirname "$BASH_SOURCE")/.go-compile-test-dir" "$test_dir" || true + "${MAKEDIR}/.go-compile-test-dir" "$test_dir" || true # don't let one directory that fails to build tank _all_ our tests! done fi diff --git a/hack/make/validate-dco b/hack/make/validate-dco index 84c47f526d1d5..5ac98728f347d 100644 --- a/hack/make/validate-dco +++ b/hack/make/validate-dco @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" adds=$(validate_diff --numstat | awk '{ s += $1 } END { print s }') dels=$(validate_diff --numstat | awk '{ s += $2 } END { print s }') diff --git a/hack/make/validate-gofmt b/hack/make/validate-gofmt index 8fc88cc559dec..7ad9e85576458 100644 --- a/hack/make/validate-gofmt +++ b/hack/make/validate-gofmt @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) diff --git a/hack/make/validate-toml b/hack/make/validate-toml index 16c228d14eb97..18f26ee757997 100644 --- a/hack/make/validate-toml +++ b/hack/make/validate-toml @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- 'MAINTAINERS' || true) ) diff --git a/hack/make/validate-vet b/hack/make/validate-vet index e88f7549c36ef..febe93e5c1e2e 100644 --- a/hack/make/validate-vet +++ b/hack/make/validate-vet @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) From 960de9c8dd3d64dd4be0e851dd835e6d9427c70c Mon Sep 17 00:00:00 2001 From: Emir Ozer Date: Wed, 15 Apr 2015 15:24:43 +0200 Subject: [PATCH 125/332] closes #8945 Signed-off-by: Emir Ozer --- .../api/remote_api_client_libraries.md | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/docs/sources/reference/api/remote_api_client_libraries.md b/docs/sources/reference/api/remote_api_client_libraries.md index d79bbd89ab136..3226d0eee3a84 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.md +++ b/docs/sources/reference/api/remote_api_client_libraries.md @@ -61,110 +61,116 @@ will add the libraries here. Active + Haskell + docker-hs + https://github.com/denibertovic/docker-hs + Active + + Java docker-java https://github.com/docker-java/docker-java Active - + Java docker-client https://github.com/spotify/docker-client Active - + Java jclouds-docker https://github.com/jclouds/jclouds-labs/tree/master/docker Active - + JavaScript (NodeJS) dockerode https://github.com/apocas/dockerode Install via NPM: npm install dockerode Active - + JavaScript (NodeJS) docker.io https://github.com/appersonlabs/docker.io Install via NPM: npm install docker.io Active - + JavaScript docker-js https://github.com/dgoujard/docker-js Outdated - + JavaScript (Angular) WebUI docker-cp https://github.com/13W/docker-cp Active - + JavaScript (Angular) WebUI dockerui https://github.com/crosbymichael/dockerui Active - + Perl Net::Docker https://metacpan.org/pod/Net::Docker Active - + Perl Eixo::Docker https://github.com/alambike/eixo-docker Active - + PHP Alvine http://pear.alvine.io/ (alpha) Active - + PHP Docker-PHP http://stage1.github.io/docker-php/ Active - + Python docker-py https://github.com/docker/docker-py Active - + Ruby docker-api https://github.com/swipely/docker-api Active - + Ruby docker-client https://github.com/geku/docker-client Outdated - + Rust docker-rust https://github.com/abh1nav/docker-rust Active - + Scala tugboat https://github.com/softprops/tugboat Active - + Scala reactive-docker https://github.com/almoehi/reactive-docker From b3867b889960604904a4afbab6450bb9528afe06 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Tue, 14 Apr 2015 15:02:02 -0700 Subject: [PATCH 126/332] try to modprobe bridge Signed-off-by: Jessica Frazelle --- daemon/networkdriver/bridge/driver.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 80c32c28129d5..eda4713870ecb 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net" "os" + "os/exec" "strconv" "strings" "sync" @@ -113,6 +114,13 @@ func InitDriver(config *Config) error { addrsv6 []net.Addr bridgeIPv6 = "fe80::1/64" ) + + // try to modprobe bridge first + // see gh#12177 + if out, err := exec.Command("modprobe", "-va", "bridge", "nf_nat").Output(); err != nil { + logrus.Warnf("Running modprobe bridge nf_nat failed with message: %s, error: %v", out, err) + } + initPortMapper() if config.DefaultIp != nil { From 767df67e3149b83255db0809f6543b449a4f652e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 10 Apr 2015 17:05:21 -0700 Subject: [PATCH 127/332] Decode container configurations into typed structures. Signed-off-by: David Calavera --- api/server/server.go | 19 +- builder/dispatchers.go | 14 +- builder/internals.go | 19 +- daemon/create.go | 8 +- daemon/daemon.go | 21 ++- daemon/exec.go | 3 +- daemon/start.go | 8 +- daemon/utils.go | 3 +- daemon/utils_test.go | 7 +- graph/history.go | 2 +- integration/api_test.go | 34 ++-- integration/container_test.go | 10 +- integration/runtime_test.go | 48 ++--- integration/utils_test.go | 9 +- runconfig/compare.go | 21 ++- runconfig/config.go | 155 +++++++++++----- runconfig/config_test.go | 36 ++++ runconfig/fixtures/container_config_1_14.json | 30 ++++ runconfig/fixtures/container_config_1_17.json | 49 ++++++ runconfig/fixtures/container_config_1_19.json | 57 ++++++ runconfig/hostconfig.go | 165 +++++++++--------- runconfig/merge.go | 13 +- runconfig/parse.go | 15 +- 23 files changed, 486 insertions(+), 260 deletions(-) create mode 100644 runconfig/fixtures/container_config_1_14.json create mode 100644 runconfig/fixtures/container_config_1_17.json create mode 100644 runconfig/fixtures/container_config_1_19.json diff --git a/api/server/server.go b/api/server/server.go index 2cc9662496790..2abcbf91fc088 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -33,6 +33,7 @@ import ( "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" "github.com/docker/docker/registry" + "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -811,14 +812,14 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re var ( warnings []string name = r.Form.Get("name") - env = new(engine.Env) ) - if err := env.Decode(r.Body); err != nil { + config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) + if err != nil { return err } - containerId, warnings, err := getDaemon(eng).ContainerCreate(name, env) + containerId, warnings, err := getDaemon(eng).ContainerCreate(name, config, hostConfig) if err != nil { return err } @@ -917,10 +918,6 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - name = vars["name"] - env = new(engine.Env) - ) // If contentLength is -1, we can assumed chunked encoding // or more technically that the length is unknown @@ -928,17 +925,21 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res // net/http otherwise seems to swallow any headers related to chunked encoding // including r.TransferEncoding // allow a nil body for backwards compatibility + var hostConfig *runconfig.HostConfig if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { if err := checkForJson(r); err != nil { return err } - if err := env.Decode(r.Body); err != nil { + c, err := runconfig.DecodeHostConfig(r.Body) + if err != nil { return err } + + hostConfig = c } - if err := getDaemon(eng).ContainerStart(name, env); err != nil { + if err := getDaemon(eng).ContainerStart(vars["name"], hostConfig); err != nil { if err.Error() == "Container already started" { w.WriteHeader(http.StatusNotModified) return nil diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 820ac17e3bce1..e807f1aee1034 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -262,7 +262,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) b.Config.Cmd = config.Cmd runconfig.Merge(b.Config, config) - defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) + defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd) logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) @@ -301,13 +301,15 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) // Argument handling is the same as RUN. // func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { - b.Config.Cmd = handleJsonArgs(args, attributes) + cmdSlice := handleJsonArgs(args, attributes) if !attributes["json"] { - b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...) + cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) } - if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", b.Config.Cmd)); err != nil { + b.Config.Cmd = runconfig.NewCommand(cmdSlice...) + + if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { return err } @@ -332,13 +334,13 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original switch { case attributes["json"]: // ENTRYPOINT ["echo", "hi"] - b.Config.Entrypoint = parsed + b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...) case len(parsed) == 0: // ENTRYPOINT [] b.Config.Entrypoint = nil default: // ENTRYPOINT echo hi - b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]} + b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0]) } // when setting the entrypoint if a CMD was not explicitly set then diff --git a/builder/internals.go b/builder/internals.go index 728ccde8aefdc..be980a265d6e3 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -61,7 +61,7 @@ func (b *Builder) readContext(context io.Reader) error { return nil } -func (b *Builder) commit(id string, autoCmd []string, comment string) error { +func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string) error { if b.disableCommit { return nil } @@ -71,8 +71,8 @@ func (b *Builder) commit(id string, autoCmd []string, comment string) error { b.Config.Image = b.image if id == "" { cmd := b.Config.Cmd - b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} - defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) + b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment) + defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd) hit, err := b.probeCache() if err != nil { @@ -182,8 +182,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp } cmd := b.Config.Cmd - b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)} - defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) + b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)) + defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd) hit, err := b.probeCache() if err != nil { @@ -559,12 +559,13 @@ func (b *Builder) create() (*daemon.Container, error) { b.TmpContainers[c.ID] = struct{}{} fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID)) - if len(config.Cmd) > 0 { + if config.Cmd.Len() > 0 { // override the entry point that may have been picked up from the base image - c.Path = config.Cmd[0] - c.Args = config.Cmd[1:] + s := config.Cmd.Slice() + c.Path = s[0] + c.Args = s[1:] } else { - config.Cmd = []string{} + config.Cmd = runconfig.NewCommand() } return c, nil diff --git a/daemon/create.go b/daemon/create.go index da271043f2a37..eb8a252756b73 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" @@ -12,13 +11,10 @@ import ( "github.com/docker/libcontainer/label" ) -func (daemon *Daemon) ContainerCreate(name string, env *engine.Env) (string, []string, error) { +func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig) (string, []string, error) { var warnings []string - config := runconfig.ContainerConfigFromJob(env) - hostConfig := runconfig.ContainerHostConfigFromJob(env) - - if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { + if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) } if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { diff --git a/daemon/daemon.go b/daemon/daemon.go index 76ed5a5ddc354..23647ecf932f4 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,12 +119,8 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "container_stats": daemon.ContainerStats, - "export": daemon.ContainerExport, "info": daemon.CmdInfo, "restart": daemon.ContainerRestart, - "stop": daemon.ContainerStop, - "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { @@ -485,7 +481,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image. return nil, err } } - if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { + if config.Entrypoint.Len() == 0 && config.Cmd.Len() == 0 { return nil, fmt.Errorf("No command specified") } return warnings, nil @@ -577,17 +573,20 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) { } } -func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) { +func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *runconfig.Entrypoint, configCmd *runconfig.Command) (string, []string) { var ( entrypoint string args []string ) - if len(configEntrypoint) != 0 { - entrypoint = configEntrypoint[0] - args = append(configEntrypoint[1:], configCmd...) + + cmdSlice := configCmd.Slice() + if configEntrypoint.Len() != 0 { + eSlice := configEntrypoint.Slice() + entrypoint = eSlice[0] + args = append(eSlice[1:], cmdSlice...) } else { - entrypoint = configCmd[0] - args = configCmd[1:] + entrypoint = cmdSlice[0] + args = cmdSlice[1:] } return entrypoint, args } diff --git a/daemon/exec.go b/daemon/exec.go index 46c255a7cf0f0..4787189a77b28 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -132,7 +132,8 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { return err } - entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd) + cmd := runconfig.NewCommand(config.Cmd...) + entrypoint, args := d.getEntrypointAndArgs(runconfig.NewEntrypoint(), cmd) processConfig := execdriver.ProcessConfig{ Tty: config.Tty, diff --git a/daemon/start.go b/daemon/start.go index b0b6dc75c3e26..dbb3dd1810703 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -3,11 +3,10 @@ package daemon import ( "fmt" - "github.com/docker/docker/engine" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerStart(name string, env *engine.Env) error { +func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConfig) error { container, err := daemon.Get(name) if err != nil { return err @@ -21,15 +20,14 @@ func (daemon *Daemon) ContainerStart(name string, env *engine.Env) error { return fmt.Errorf("Container already started") } - // If no environment was set, then no hostconfig was passed. // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. - if len(env.Map()) > 0 { - hostConfig := runconfig.ContainerHostConfigFromJob(env) + if hostConfig != nil { if err := daemon.setHostConfig(container, hostConfig); err != nil { return err } } + if err := container.Start(); err != nil { container.LogEvent("die") return fmt.Errorf("Cannot start container %s: %s", name, err) diff --git a/daemon/utils.go b/daemon/utils.go index 6202e6d961677..ec001ca071574 100644 --- a/daemon/utils.go +++ b/daemon/utils.go @@ -42,7 +42,8 @@ func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig) ([]string, error) // merge in the lxc conf options into the generic config map if lxcConf := hostConfig.LxcConf; lxcConf != nil { - for _, pair := range lxcConf { + lxSlice := lxcConf.Slice() + for _, pair := range lxSlice { // because lxc conf gets the driver name lxc.XXXX we need to trim it off // and let the lxc driver add it back later if needed if !strings.Contains(pair.Key, ".") { diff --git a/daemon/utils_test.go b/daemon/utils_test.go index aabbeaf6fa327..f81843847c86d 100644 --- a/daemon/utils_test.go +++ b/daemon/utils_test.go @@ -7,10 +7,11 @@ import ( ) func TestMergeLxcConfig(t *testing.T) { + kv := []runconfig.KeyValuePair{ + {"lxc.cgroups.cpuset", "1,2"}, + } hostConfig := &runconfig.HostConfig{ - LxcConf: []runconfig.KeyValuePair{ - {Key: "lxc.cgroups.cpuset", Value: "1,2"}, - }, + LxcConf: runconfig.NewLxcConfig(kv), } out, err := mergeLxcConfIntoOptions(hostConfig) diff --git a/graph/history.go b/graph/history.go index 6f8581b9f32c8..56e759a8ebb02 100644 --- a/graph/history.go +++ b/graph/history.go @@ -31,7 +31,7 @@ func (s *TagStore) History(name string) ([]*types.ImageHistory, error) { history = append(history, &types.ImageHistory{ ID: img.ID, Created: img.Created.Unix(), - CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), + CreatedBy: strings.Join(img.ContainerConfig.Cmd.Slice(), " "), Tags: lookupMap[img.ID], Size: img.Size, Comment: img.Comment, diff --git a/integration/api_test.go b/integration/api_test.go index c527bcb927d59..3a795f94f9613 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -91,7 +91,7 @@ func TestGetContainersTop(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/sh", "-c", "cat"}, + Cmd: runconfig.NewCommand("/bin/sh", "-c", "cat"), OpenStdin: true, }, t, @@ -168,7 +168,7 @@ func TestPostCommit(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"touch", "/test"}, + Cmd: runconfig.NewCommand("touch", "/test"), }, t, ) @@ -201,9 +201,8 @@ func TestPostContainersCreate(t *testing.T) { defer mkDaemonFromEngine(eng, t).Nuke() configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Memory: 33554432, - Cmd: []string{"touch", "/test"}, + Image: unitTestImageID, + Cmd: runconfig.NewCommand("touch", "/test"), }) if err != nil { t.Fatal(err) @@ -242,9 +241,8 @@ func TestPostJsonVerify(t *testing.T) { defer mkDaemonFromEngine(eng, t).Nuke() configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Memory: 33554432, - Cmd: []string{"touch", "/test"}, + Image: unitTestImageID, + Cmd: runconfig.NewCommand("touch", "/test"), }) if err != nil { t.Fatal(err) @@ -330,8 +328,8 @@ func TestPostCreateNull(t *testing.T) { containerAssertExists(eng, containerID, t) c, _ := daemon.Get(containerID) - if c.Config.Cpuset != "" { - t.Fatalf("Cpuset should have been empty - instead its:" + c.Config.Cpuset) + if c.HostConfig().CpusetCpus != "" { + t.Fatalf("Cpuset should have been empty - instead its:" + c.HostConfig().CpusetCpus) } } @@ -342,7 +340,7 @@ func TestPostContainersKill(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/cat"}, + Cmd: runconfig.NewCommand("/bin/cat"), OpenStdin: true, }, t, @@ -379,7 +377,7 @@ func TestPostContainersRestart(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/top"}, + Cmd: runconfig.NewCommand("/bin/top"), OpenStdin: true, }, t, @@ -423,7 +421,7 @@ func TestPostContainersStart(t *testing.T) { eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/cat"}, + Cmd: runconfig.NewCommand("/bin/cat"), OpenStdin: true, }, t, @@ -473,7 +471,7 @@ func TestPostContainersStop(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/top"}, + Cmd: runconfig.NewCommand("/bin/top"), OpenStdin: true, }, t, @@ -525,7 +523,7 @@ func TestPostContainersWait(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/sleep", "1"}, + Cmd: runconfig.NewCommand("/bin/sleep", "1"), OpenStdin: true, }, t, @@ -561,7 +559,7 @@ func TestPostContainersAttach(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/cat"}, + Cmd: runconfig.NewCommand("/bin/cat"), OpenStdin: true, }, t, @@ -637,7 +635,7 @@ func TestPostContainersAttachStderr(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, + Cmd: runconfig.NewCommand("/bin/sh", "-c", "/bin/cat >&2"), OpenStdin: true, }, t, @@ -818,7 +816,7 @@ func TestPostContainersCopy(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"touch", "/test.txt"}, + Cmd: runconfig.NewCommand("touch", "/test.txt"), }, t, ) diff --git a/integration/container_test.go b/integration/container_test.go index b6cbfd096103a..01078734cfc89 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -14,7 +14,7 @@ func TestRestartStdin(t *testing.T) { defer nuke(daemon) container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"cat"}, + Cmd: runconfig.NewCommand("cat"), OpenStdin: true, }, @@ -79,7 +79,7 @@ func TestStdin(t *testing.T) { defer nuke(daemon) container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"cat"}, + Cmd: runconfig.NewCommand("cat"), OpenStdin: true, }, @@ -119,7 +119,7 @@ func TestTty(t *testing.T) { defer nuke(daemon) container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"cat"}, + Cmd: runconfig.NewCommand("cat"), OpenStdin: true, }, @@ -160,7 +160,7 @@ func BenchmarkRunSequential(b *testing.B) { for i := 0; i < b.N; i++ { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"echo", "-n", "foo"}, + Cmd: runconfig.NewCommand("echo", "-n", "foo"), }, &runconfig.HostConfig{}, "", @@ -194,7 +194,7 @@ func BenchmarkRunParallel(b *testing.B) { go func(i int, complete chan error) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"echo", "-n", "foo"}, + Cmd: runconfig.NewCommand("echo", "-n", "foo"), }, &runconfig.HostConfig{}, "", diff --git a/integration/runtime_test.go b/integration/runtime_test.go index f3485b2b36b67..2e456eabfd630 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -255,7 +255,7 @@ func TestDaemonCreate(t *testing.T) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), }, &runconfig.HostConfig{}, "", @@ -296,15 +296,16 @@ func TestDaemonCreate(t *testing.T) { } // Test that conflict error displays correct details + cmd := runconfig.NewCommand("ls", "-al") testContainer, _, _ := daemon.Create( &runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: cmd, }, &runconfig.HostConfig{}, "conflictname", ) - if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: []string{"ls", "-al"}}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) { + if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: cmd}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) { t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err) } @@ -316,7 +317,7 @@ func TestDaemonCreate(t *testing.T) { if _, _, err := daemon.Create( &runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{}, + Cmd: runconfig.NewCommand(), }, &runconfig.HostConfig{}, "", @@ -326,7 +327,7 @@ func TestDaemonCreate(t *testing.T) { config := &runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"/bin/ls"}, + Cmd: runconfig.NewCommand("/bin/ls"), PortSpecs: []string{"80"}, } container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "") @@ -339,7 +340,7 @@ func TestDaemonCreate(t *testing.T) { // test expose 80:8000 container, warnings, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), PortSpecs: []string{"80:8000"}, }, &runconfig.HostConfig{}, @@ -359,7 +360,7 @@ func TestDestroy(t *testing.T) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), }, &runconfig.HostConfig{}, "") @@ -451,13 +452,14 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) ep[p] = struct{}{} - env := new(engine.Env) - env.Set("Image", unitTestImageID) - env.SetList("Cmd", []string{"sh", "-c", cmd}) - env.SetList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) - env.SetJson("ExposedPorts", ep) + c := &runconfig.Config{ + Image: unitTestImageID, + Cmd: runconfig.NewCommand("sh", "-c", cmd), + PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, + ExposedPorts: ep, + } - id, _, err = daemon.ContainerCreate(unitTestImageID, env) + id, _, err = daemon.ContainerCreate(unitTestImageID, c, &runconfig.HostConfig{}) // 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! @@ -468,16 +470,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem } - portBindings := make(map[nat.Port][]nat.PortBinding) - portBindings[p] = []nat.PortBinding{ - {}, - } - - env := new(engine.Env) - if err := env.SetJson("PortsBindings", portBindings); err != nil { - t.Fatal(err) - } - if err := daemon.ContainerStart(id, env); err != nil { + if err := daemon.ContainerStart(id, &runconfig.HostConfig{}); err != nil { t.Fatal(err) } @@ -728,12 +721,7 @@ func TestContainerNameValidation(t *testing.T) { t.Fatal(err) } - env := new(engine.Env) - if err := env.Import(config); err != nil { - t.Fatal(err) - } - - containerId, _, err := daemon.ContainerCreate(test.Name, env) + containerId, _, err := daemon.ContainerCreate(test.Name, config, &runconfig.HostConfig{}) if err != nil { if !test.Valid { continue @@ -872,7 +860,7 @@ func TestDestroyWithInitLayer(t *testing.T) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), }, &runconfig.HostConfig{}, "") diff --git a/integration/utils_test.go b/integration/utils_test.go index 86b27fb735946..befd924eaf880 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -44,11 +44,7 @@ func mkDaemon(f Fataler) *daemon.Daemon { } func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { - env := new(engine.Env) - if err := env.Import(config); err != nil { - f.Fatal(err) - } - containerId, _, err := getDaemon(eng).ContainerCreate(name, env) + containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{}) if err != nil { f.Fatal(err) } @@ -60,8 +56,7 @@ func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler } func startContainer(eng *engine.Engine, id string, t Fataler) { - env := new(engine.Env) - if err := getDaemon(eng).ContainerStart(id, env); err != nil { + if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil { t.Fatal(err) } } diff --git a/runconfig/compare.go b/runconfig/compare.go index 60a21a79c0b16..1d969e9be88e7 100644 --- a/runconfig/compare.go +++ b/runconfig/compare.go @@ -10,25 +10,25 @@ func Compare(a, b *Config) bool { if a.AttachStdout != b.AttachStdout || a.AttachStderr != b.AttachStderr || a.User != b.User || - a.Memory != b.Memory || - a.MemorySwap != b.MemorySwap || - a.CpuShares != b.CpuShares || a.OpenStdin != b.OpenStdin || a.Tty != b.Tty { return false } - if len(a.Cmd) != len(b.Cmd) || + + if a.Cmd.Len() != b.Cmd.Len() || len(a.Env) != len(b.Env) || len(a.Labels) != len(b.Labels) || len(a.PortSpecs) != len(b.PortSpecs) || len(a.ExposedPorts) != len(b.ExposedPorts) || - len(a.Entrypoint) != len(b.Entrypoint) || + a.Entrypoint.Len() != b.Entrypoint.Len() || len(a.Volumes) != len(b.Volumes) { return false } - for i := 0; i < len(a.Cmd); i++ { - if a.Cmd[i] != b.Cmd[i] { + aCmd := a.Cmd.Slice() + bCmd := b.Cmd.Slice() + for i := 0; i < len(aCmd); i++ { + if aCmd[i] != bCmd[i] { return false } } @@ -52,8 +52,11 @@ func Compare(a, b *Config) bool { return false } } - for i := 0; i < len(a.Entrypoint); i++ { - if a.Entrypoint[i] != b.Entrypoint[i] { + + aEntrypoint := a.Entrypoint.Slice() + bEntrypoint := b.Entrypoint.Slice() + for i := 0; i < len(aEntrypoint); i++ { + if aEntrypoint[i] != bEntrypoint[i] { return false } } diff --git a/runconfig/config.go b/runconfig/config.go index 30dd1eb4cf768..844958be2c3ca 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -1,10 +1,103 @@ package runconfig import ( - "github.com/docker/docker/engine" + "encoding/json" + "io" + "github.com/docker/docker/nat" ) +// Entrypoint encapsulates the container entrypoint. +// It might be represented as a string or an array of strings. +// We need to override the json decoder to accept both options. +// The JSON decoder will fail if the api sends an string and +// we try to decode it into an array of string. +type Entrypoint struct { + parts []string +} + +func (e *Entrypoint) MarshalJSON() ([]byte, error) { + if e == nil { + return []byte{}, nil + } + return json.Marshal(e.Slice()) +} + +// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings. +func (e *Entrypoint) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + + p := make([]string, 0, 1) + if err := json.Unmarshal(b, &p); err != nil { + p = append(p, string(b)) + } + e.parts = p + return nil +} + +func (e *Entrypoint) Len() int { + if e == nil { + return 0 + } + return len(e.parts) +} + +func (e *Entrypoint) Slice() []string { + if e == nil { + return nil + } + return e.parts +} + +func NewEntrypoint(parts ...string) *Entrypoint { + return &Entrypoint{parts} +} + +type Command struct { + parts []string +} + +func (e *Command) MarshalJSON() ([]byte, error) { + if e == nil { + return []byte{}, nil + } + return json.Marshal(e.Slice()) +} + +// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings. +func (e *Command) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + + p := make([]string, 0, 1) + if err := json.Unmarshal(b, &p); err != nil { + p = append(p, string(b)) + } + e.parts = p + return nil +} + +func (e *Command) Len() int { + if e == nil { + return 0 + } + return len(e.parts) +} + +func (e *Command) Slice() []string { + if e == nil { + return nil + } + return e.parts +} + +func NewCommand(parts ...string) *Command { + return &Command{parts} +} + // Note: the Config structure should hold only portable information about the container. // Here, "portable" means "independent from the host we are running on". // Non-portable information *should* appear in HostConfig. @@ -12,10 +105,6 @@ type Config struct { Hostname string Domainname string User string - Memory int64 // FIXME: we keep it for backward compatibility, it has been moved to hostConfig. - MemorySwap int64 // FIXME: it has been moved to hostConfig. - CpuShares int64 // FIXME: it has been moved to hostConfig. - Cpuset string // FIXME: it has been moved to hostConfig and renamed to CpusetCpus. AttachStdin bool AttachStdout bool AttachStderr bool @@ -25,53 +114,37 @@ type Config struct { OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. Env []string - Cmd []string + Cmd *Command Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Volumes map[string]struct{} WorkingDir string - Entrypoint []string + Entrypoint *Entrypoint NetworkDisabled bool MacAddress string OnBuild []string Labels map[string]string } -func ContainerConfigFromJob(env *engine.Env) *Config { - config := &Config{ - Hostname: env.Get("Hostname"), - Domainname: env.Get("Domainname"), - User: env.Get("User"), - Memory: env.GetInt64("Memory"), - MemorySwap: env.GetInt64("MemorySwap"), - CpuShares: env.GetInt64("CpuShares"), - Cpuset: env.Get("Cpuset"), - AttachStdin: env.GetBool("AttachStdin"), - AttachStdout: env.GetBool("AttachStdout"), - AttachStderr: env.GetBool("AttachStderr"), - Tty: env.GetBool("Tty"), - OpenStdin: env.GetBool("OpenStdin"), - StdinOnce: env.GetBool("StdinOnce"), - Image: env.Get("Image"), - WorkingDir: env.Get("WorkingDir"), - NetworkDisabled: env.GetBool("NetworkDisabled"), - MacAddress: env.Get("MacAddress"), - } - env.GetJson("ExposedPorts", &config.ExposedPorts) - env.GetJson("Volumes", &config.Volumes) - if PortSpecs := env.GetList("PortSpecs"); PortSpecs != nil { - config.PortSpecs = PortSpecs - } - if Env := env.GetList("Env"); Env != nil { - config.Env = Env - } - if Cmd := env.GetList("Cmd"); Cmd != nil { - config.Cmd = Cmd +type ContainerConfigWrapper struct { + *Config + *hostConfigWrapper +} + +func (c ContainerConfigWrapper) HostConfig() *HostConfig { + if c.hostConfigWrapper == nil { + return new(HostConfig) } - env.GetJson("Labels", &config.Labels) + return c.hostConfigWrapper.GetHostConfig() +} - if Entrypoint := env.GetList("Entrypoint"); Entrypoint != nil { - config.Entrypoint = Entrypoint +func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) { + decoder := json.NewDecoder(src) + + var w ContainerConfigWrapper + if err := decoder.Decode(&w); err != nil { + return nil, nil, err } - return config + + return w.Config, w.HostConfig(), nil } diff --git a/runconfig/config_test.go b/runconfig/config_test.go index accbd9107e316..e36dacbf44795 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -1,7 +1,9 @@ package runconfig import ( + "bytes" "fmt" + "io/ioutil" "strings" "testing" @@ -260,5 +262,39 @@ func TestMerge(t *testing.T) { t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs) } } +} + +func TestDecodeContainerConfig(t *testing.T) { + fixtures := []struct { + file string + entrypoint *Entrypoint + }{ + {"fixtures/container_config_1_14.json", NewEntrypoint()}, + {"fixtures/container_config_1_17.json", NewEntrypoint("bash")}, + {"fixtures/container_config_1_19.json", NewEntrypoint("bash")}, + } + + for _, f := range fixtures { + b, err := ioutil.ReadFile(f.file) + if err != nil { + t.Fatal(err) + } + + c, h, err := DecodeContainerConfig(bytes.NewReader(b)) + if err != nil { + t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err)) + } + if c.Image != "ubuntu" { + t.Fatalf("Expected ubuntu image, found %s\n", c.Image) + } + + if c.Entrypoint.Len() != f.entrypoint.Len() { + t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint) + } + + if h.Memory != 1000 { + t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory) + } + } } diff --git a/runconfig/fixtures/container_config_1_14.json b/runconfig/fixtures/container_config_1_14.json new file mode 100644 index 0000000000000..b08334c0950e4 --- /dev/null +++ b/runconfig/fixtures/container_config_1_14.json @@ -0,0 +1,30 @@ +{ + "Hostname":"", + "Domainname": "", + "User":"", + "Memory": 1000, + "MemorySwap":0, + "CpuShares": 512, + "Cpuset": "0,1", + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "bash" + ], + "Image":"ubuntu", + "Volumes":{ + "/tmp": {} + }, + "WorkingDir":"", + "NetworkDisabled": false, + "ExposedPorts":{ + "22/tcp": {} + }, + "RestartPolicy": { "Name": "always" } +} diff --git a/runconfig/fixtures/container_config_1_17.json b/runconfig/fixtures/container_config_1_17.json new file mode 100644 index 0000000000000..60fc6e25e20d9 --- /dev/null +++ b/runconfig/fixtures/container_config_1_17.json @@ -0,0 +1,49 @@ +{ + "Hostname": "", + "Domainname": "", + "User": "", + "Memory": 1000, + "MemorySwap": 0, + "CpuShares": 512, + "Cpuset": "0,1", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Entrypoint": "bash", + "Image": "ubuntu", + "Volumes": { + "/tmp": {} + }, + "WorkingDir": "", + "NetworkDisabled": false, + "MacAddress": "12:34:56:78:9a:bc", + "ExposedPorts": { + "22/tcp": {} + }, + "SecurityOpt": [""], + "HostConfig": { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [] + } +} diff --git a/runconfig/fixtures/container_config_1_19.json b/runconfig/fixtures/container_config_1_19.json new file mode 100644 index 0000000000000..9a3ce205b3694 --- /dev/null +++ b/runconfig/fixtures/container_config_1_19.json @@ -0,0 +1,57 @@ +{ + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Entrypoint": "bash", + "Image": "ubuntu", + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, + "Volumes": { + "/tmp": {} + }, + "WorkingDir": "", + "NetworkDisabled": false, + "MacAddress": "12:34:56:78:9a:bc", + "ExposedPorts": { + "22/tcp": {} + }, + "HostConfig": { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "Memory": 1000, + "MemorySwap": 0, + "CpuShares": 512, + "CpusetCpus": "0,1", + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [], + "Ulimits": [{}], + "LogConfig": { "Type": "json-file", "Config": {} }, + "SecurityOpt": [""], + "CgroupParent": "" + } +} diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 470588a092643..a25ae183551cf 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -1,9 +1,10 @@ package runconfig import ( + "encoding/json" + "io" "strings" - "github.com/docker/docker/engine" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/ulimit" ) @@ -108,10 +109,59 @@ type LogConfig struct { Config map[string]string } +type LxcConfig struct { + values []KeyValuePair +} + +func (c *LxcConfig) MarshalJSON() ([]byte, error) { + if c == nil { + return []byte{}, nil + } + return json.Marshal(c.Slice()) +} + +func (c *LxcConfig) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + + var kv []KeyValuePair + if err := json.Unmarshal(b, &kv); err != nil { + var h map[string]string + if err := json.Unmarshal(b, &h); err != nil { + return err + } + for k, v := range h { + kv = append(kv, KeyValuePair{k, v}) + } + } + c.values = kv + + return nil +} + +func (c *LxcConfig) Len() int { + if c == nil { + return 0 + } + return len(c.values) +} + +func (c *LxcConfig) Slice() []KeyValuePair { + if c == nil { + return nil + } + return c.values +} + +func NewLxcConfig(values []KeyValuePair) *LxcConfig { + return &LxcConfig{values} +} + type HostConfig struct { Binds []string ContainerIDFile string - LxcConf []KeyValuePair + LxcConf *LxcConfig 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) @@ -138,96 +188,55 @@ type HostConfig struct { CgroupParent string // Parent cgroup. } -// This is used by the create command when you want to set both the -// Config and the HostConfig in the same call -type ConfigAndHostConfig struct { - Config - HostConfig HostConfig +func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper { + return &ContainerConfigWrapper{ + config, + &hostConfigWrapper{InnerHostConfig: hostConfig}, + } } -func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig { - return &ConfigAndHostConfig{ - *config, - *hostConfig, - } +type hostConfigWrapper struct { + InnerHostConfig *HostConfig `json:"HostConfig,omitempty"` + Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility. + + *HostConfig // Deprecated. Exported to read attrubutes from json that are not in the inner host config structure. } -func ContainerHostConfigFromJob(env *engine.Env) *HostConfig { - if env.Exists("HostConfig") { - hostConfig := HostConfig{} - env.GetJson("HostConfig", &hostConfig) +func (w hostConfigWrapper) GetHostConfig() *HostConfig { + hc := w.HostConfig - // FIXME: These are for backward compatibility, if people use these - // options with `HostConfig`, we should still make them workable. - if env.Exists("Memory") && hostConfig.Memory == 0 { - hostConfig.Memory = env.GetInt64("Memory") - } - if env.Exists("MemorySwap") && hostConfig.MemorySwap == 0 { - hostConfig.MemorySwap = env.GetInt64("MemorySwap") + if hc == nil && w.InnerHostConfig != nil { + hc = w.InnerHostConfig + } else if w.InnerHostConfig != nil { + if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 { + w.InnerHostConfig.Memory = hc.Memory } - if env.Exists("CpuShares") && hostConfig.CpuShares == 0 { - hostConfig.CpuShares = env.GetInt64("CpuShares") + if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 { + w.InnerHostConfig.MemorySwap = hc.MemorySwap } - if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = env.Get("Cpuset") + if hc.CpuShares != 0 && w.InnerHostConfig.CpuShares == 0 { + w.InnerHostConfig.CpuShares = hc.CpuShares } - return &hostConfig + hc = w.InnerHostConfig } - hostConfig := &HostConfig{ - ContainerIDFile: env.Get("ContainerIDFile"), - Memory: env.GetInt64("Memory"), - MemorySwap: env.GetInt64("MemorySwap"), - CpuShares: env.GetInt64("CpuShares"), - CpusetCpus: env.Get("CpusetCpus"), - Privileged: env.GetBool("Privileged"), - PublishAllPorts: env.GetBool("PublishAllPorts"), - NetworkMode: NetworkMode(env.Get("NetworkMode")), - IpcMode: IpcMode(env.Get("IpcMode")), - PidMode: PidMode(env.Get("PidMode")), - ReadonlyRootfs: env.GetBool("ReadonlyRootfs"), - CgroupParent: env.Get("CgroupParent"), + if hc != nil && w.Cpuset != "" && hc.CpusetCpus == "" { + hc.CpusetCpus = w.Cpuset } - // FIXME: This is for backward compatibility, if people use `Cpuset` - // in json, make it workable, we will only pass hostConfig.CpusetCpus - // to execDriver. - if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = env.Get("Cpuset") - } + return hc +} - env.GetJson("LxcConf", &hostConfig.LxcConf) - env.GetJson("PortBindings", &hostConfig.PortBindings) - env.GetJson("Devices", &hostConfig.Devices) - env.GetJson("RestartPolicy", &hostConfig.RestartPolicy) - env.GetJson("Ulimits", &hostConfig.Ulimits) - env.GetJson("LogConfig", &hostConfig.LogConfig) - hostConfig.SecurityOpt = env.GetList("SecurityOpt") - if Binds := env.GetList("Binds"); Binds != nil { - hostConfig.Binds = Binds - } - if Links := env.GetList("Links"); Links != nil { - hostConfig.Links = Links - } - if Dns := env.GetList("Dns"); Dns != nil { - hostConfig.Dns = Dns - } - if DnsSearch := env.GetList("DnsSearch"); DnsSearch != nil { - hostConfig.DnsSearch = DnsSearch - } - if ExtraHosts := env.GetList("ExtraHosts"); ExtraHosts != nil { - hostConfig.ExtraHosts = ExtraHosts - } - if VolumesFrom := env.GetList("VolumesFrom"); VolumesFrom != nil { - hostConfig.VolumesFrom = VolumesFrom - } - if CapAdd := env.GetList("CapAdd"); CapAdd != nil { - hostConfig.CapAdd = CapAdd - } - if CapDrop := env.GetList("CapDrop"); CapDrop != nil { - hostConfig.CapDrop = CapDrop +func DecodeHostConfig(src io.Reader) (*HostConfig, error) { + decoder := json.NewDecoder(src) + + var w hostConfigWrapper + if err := decoder.Decode(&w); err != nil { + return nil, err } - return hostConfig + hc := w.GetHostConfig() + + return hc, nil } diff --git a/runconfig/merge.go b/runconfig/merge.go index 68d3d6ee125e5..ce6697dbfc6cf 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -11,15 +11,6 @@ func Merge(userConf, imageConf *Config) error { if userConf.User == "" { userConf.User = imageConf.User } - if userConf.Memory == 0 { - userConf.Memory = imageConf.Memory - } - if userConf.MemorySwap == 0 { - userConf.MemorySwap = imageConf.MemorySwap - } - if userConf.CpuShares == 0 { - userConf.CpuShares = imageConf.CpuShares - } if len(userConf.ExposedPorts) == 0 { userConf.ExposedPorts = imageConf.ExposedPorts } else if imageConf.ExposedPorts != nil { @@ -94,8 +85,8 @@ func Merge(userConf, imageConf *Config) error { userConf.Labels = imageConf.Labels } - if len(userConf.Entrypoint) == 0 { - if len(userConf.Cmd) == 0 { + if userConf.Entrypoint.Len() == 0 { + if userConf.Cmd.Len() == 0 { userConf.Cmd = imageConf.Cmd } diff --git a/runconfig/parse.go b/runconfig/parse.go index d302330c82755..973fbbfc3f38e 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -185,21 +185,22 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe var ( parsedArgs = cmd.Args() - runCmd []string - entrypoint []string + runCmd *Command + entrypoint *Entrypoint image = cmd.Arg(0) ) if len(parsedArgs) > 1 { - runCmd = parsedArgs[1:] + runCmd = NewCommand(parsedArgs[1:]...) } if *flEntrypoint != "" { - entrypoint = []string{*flEntrypoint} + entrypoint = NewEntrypoint(*flEntrypoint) } - lxcConf, err := parseKeyValueOpts(flLxcOpts) + lc, err := parseKeyValueOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err } + lxcConf := NewLxcConfig(lc) var ( domainname string @@ -288,10 +289,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe Tty: *flTty, NetworkDisabled: !*flNetwork, OpenStdin: *flStdin, - Memory: flMemory, // FIXME: for backward compatibility - MemorySwap: MemorySwap, // FIXME: for backward compatibility - CpuShares: *flCpuShares, // FIXME: for backward compatibility - Cpuset: *flCpusetCpus, // FIXME: for backward compatibility AttachStdin: attachStdin, AttachStdout: attachStdout, AttachStderr: attachStderr, From bae3023eef0ed8e0eb517ca53f6f97f1bf6786d3 Mon Sep 17 00:00:00 2001 From: Deshi Xiao Date: Wed, 15 Apr 2015 16:57:52 +0800 Subject: [PATCH 128/332] client.StatusError don't be returned as a pointer closes #12373 1. remove & from client.StatusError 2. remove * from Error method Signed-off-by: Deshi Xiao --- api/client/attach.go | 2 +- api/client/build.go | 2 +- api/client/client.go | 2 +- api/client/exec.go | 4 ++-- api/client/inspect.go | 4 ++-- api/client/run.go | 2 +- api/client/start.go | 2 +- docker/docker.go | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/client/attach.go b/api/client/attach.go index 77947a2941a67..ef2b4ad12226e 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -80,7 +80,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil diff --git a/api/client/build.go b/api/client/build.go index dc54c22ffadb8..98f7864b02fd3 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -302,7 +302,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if jerr.Code == 0 { jerr.Code = 1 } - return &StatusError{Status: jerr.Message, StatusCode: jerr.Code} + return StatusError{Status: jerr.Message, StatusCode: jerr.Code} } return err } diff --git a/api/client/client.go b/api/client/client.go index c849fa40f6659..31708817441c6 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -12,6 +12,6 @@ type StatusError struct { StatusCode int } -func (e *StatusError) Error() string { +func (e StatusError) Error() string { return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) } diff --git a/api/client/exec.go b/api/client/exec.go index 25b7a85fd23d1..23545ae9bf477 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -20,7 +20,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { execConfig, err := runconfig.ParseExec(cmd, args) // just in case the ParseExec does not exit if execConfig.Container == "" || err != nil { - return &StatusError{StatusCode: 1} + return StatusError{StatusCode: 1} } stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) @@ -121,7 +121,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil diff --git a/api/client/inspect.go b/api/client/inspect.go index 8514b1ecbc558..f993030f9a1eb 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -26,7 +26,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { var err error if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) - return &StatusError{StatusCode: 64, + return StatusError{StatusCode: 64, Status: "Template parsing error: " + err.Error()} } } @@ -85,7 +85,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil } diff --git a/api/client/run.go b/api/client/run.go index b37b6bab29b24..74c656af3dece 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -241,7 +241,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil } diff --git a/api/client/start.go b/api/client/start.go index a03b8c1d2925e..d3dec9489d14a 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -155,7 +155,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } } return nil diff --git a/docker/docker.go b/docker/docker.go index cf5b715598162..d2d4986acad17 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -135,7 +135,7 @@ func main() { } if err := cli.Cmd(flag.Args()...); err != nil { - if sterr, ok := err.(*client.StatusError); ok { + if sterr, ok := err.(client.StatusError); ok { if sterr.Status != "" { logrus.Println(sterr.Status) } From 7afb2347415a8e92020c037bacf5d11467c78e9b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 15 Apr 2015 21:14:54 +0200 Subject: [PATCH 129/332] Fix TestInitializeCannotStatPathFileNameTooLong Signed-off-by: Antonio Murdaca --- volumes/volume_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volumes/volume_test.go b/volumes/volume_test.go index caf38c8bb4289..b30549d379551 100644 --- a/volumes/volume_test.go +++ b/volumes/volume_test.go @@ -1,7 +1,7 @@ package volumes import ( - "strings" + "os" "testing" "github.com/docker/docker/pkg/stringutils" @@ -33,8 +33,8 @@ func TestInitializeCannotMkdirOnNonExistentPath(t *testing.T) { t.Fatal("Expected not to initialize volume with a non existent path") } - if !strings.Contains(err.Error(), "mkdir : no such file or directory") { - t.Fatalf("Expected to get mkdir no such file or directory, got %s", err) + if !os.IsNotExist(err) { + t.Fatalf("Expected to get ErrNotExist error, got %s", err) } } @@ -49,7 +49,7 @@ func TestInitializeCannotStatPathFileNameTooLong(t *testing.T) { t.Fatal("Expected not to initialize volume with a non existent path") } - if !strings.Contains(err.Error(), "file name too long") { - t.Fatalf("Expected to get ENAMETOOLONG error, got %s", err) + if os.IsNotExist(err) { + t.Fatal("Expected to not get ErrNotExist") } } From 05641ccffc5088a382fa3bfb21f1276ccb6c1fc0 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Wed, 15 Apr 2015 00:16:43 -0700 Subject: [PATCH 130/332] Change syslog format and facility This patch changes two things 1. Set facility to LOG_DAEMON 2. Remove ": " from tag so that the tag + pid become a single column in the log Signed-off-by: Darren Shepherd --- daemon/logger/syslog/syslog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index 4de14aacfec3f..a250d6e93cb77 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -14,7 +14,7 @@ type Syslog struct { } func New(tag string) (logger.Logger, error) { - log, err := syslog.New(syslog.LOG_USER, fmt.Sprintf("%s: <%s> ", path.Base(os.Args[0]), tag)) + log, err := syslog.New(syslog.LOG_DAEMON, fmt.Sprintf("%s/%s", path.Base(os.Args[0]), tag)) if err != nil { return nil, err } From 6860c75b7b4011fd4cc48f5d9a1458e49fb86b60 Mon Sep 17 00:00:00 2001 From: Sabin Basyal Date: Wed, 15 Apr 2015 11:23:28 -0700 Subject: [PATCH 131/332] The link to issue 407 was broken The link to issue 407 was broken. The old link was: https://github.com/docker/docker/issues/407%20kernel%20versions The link must be: https://github.com/docker/docker/issues/407 Signed-off-by: Sabin Basyal --- docs/sources/installation/debian.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 709a44d41c34e..e3fb6e2921ee1 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -39,7 +39,7 @@ Which should download the `ubuntu` image, and then start `bash` in a container. Docker requires Kernel 3.8+, while Wheezy ships with Kernel 3.2 (for more details on why 3.8 is required, see discussion on -[bug #407](https://github.com/docker/docker/issues/407%20kernel%20versions)). +[bug #407](https://github.com/docker/docker/issues/407)). Fortunately, wheezy-backports currently has [Kernel 3.16 ](https://packages.debian.org/search?suite=wheezy-backports§ion=all&arch=any&searchon=names&keywords=linux-image-amd64), From d1855c6cc0cb28fed7426ee3024f147e74ac828e Mon Sep 17 00:00:00 2001 From: Steven Taylor Date: Wed, 15 Apr 2015 15:30:09 -0700 Subject: [PATCH 132/332] What if authConfig or factory is Null? Signed-off-by: Steven Taylor --- registry/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/session.go b/registry/session.go index c62745b5bc675..dce4accd030a2 100644 --- a/registry/session.go +++ b/registry/session.go @@ -53,7 +53,7 @@ func NewSession(authConfig *AuthConfig, factory *requestdecorator.RequestFactory if err != nil { return nil, err } - if info.Standalone { + if info.Standalone && authConfig != nil && factory != nil { logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String()) dec := requestdecorator.NewAuthDecorator(authConfig.Username, authConfig.Password) factory.AddDecorator(dec) From a5f7c4aa31fa1ee2a3bebf4d38f5fda7a4a28a0d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 15 Apr 2015 17:39:34 -0700 Subject: [PATCH 133/332] Ensure state is destroyed on daemont restart Signed-off-by: Michael Crosby --- daemon/daemon.go | 13 +------------ daemon/execdriver/native/driver.go | 14 +++++--------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index e2ca568052fbf..bf8eef6dc0a8b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -273,19 +273,8 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err if err := container.ToDisk(); err != nil { logrus.Debugf("saving stopped state to disk %s", err) } - - info := daemon.execDriver.Info(container.ID) - if !info.IsRunning() { - logrus.Debugf("Container %s was supposed to be running but is not.", container.ID) - - logrus.Debug("Marking as stopped") - - container.SetStopped(&execdriver.ExitStatus{ExitCode: -127}) - if err := container.ToDisk(); err != nil { - return err - } - } } + return nil } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 6caa78390fcc8..fba22c1c21fbb 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -268,29 +268,25 @@ func (d *driver) Unpause(c *execdriver.Command) error { func (d *driver) Terminate(c *execdriver.Command) error { defer d.cleanContainer(c.ID) - // lets check the start time for the process - active := d.activeContainers[c.ID] - if active == nil { - return fmt.Errorf("active container for %s does not exist", c.ID) + container, err := d.factory.Load(c.ID) + if err != nil { + return err } - state, err := active.State() + defer container.Destroy() + state, err := container.State() if err != nil { return err } pid := state.InitProcessPid - currentStartTime, err := system.GetProcessStartTime(pid) if err != nil { return err } - if state.InitProcessStartTime == currentStartTime { err = syscall.Kill(pid, 9) syscall.Wait4(pid, nil, 0, nil) } - return err - } func (d *driver) Info(id string) execdriver.Info { From fe8fb24b530016e56ab584526c093daccacf6040 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 15 Apr 2015 19:02:01 -0700 Subject: [PATCH 134/332] In with the old menu layout Signed-off-by: Mary Anthony --- docs/Dockerfile | 31 ++++++++++++++----- docs/mkdocs.yml | 15 +++++++-- .../reference/api/hub_registry_spec.md | 6 ++-- docs/sources/reference/api/registry_api.md | 4 +-- .../api/registry_api_client_libraries.md | 2 +- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 7914abf38007a..aa97313ae9bac 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -37,15 +37,32 @@ COPY ./release.sh release.sh # #ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml -#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/distribution/overview.md -#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/overview.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png /docs/sources/registry/images/registry.png -#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/install.md /docs/sources/distribution/install.md -#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/install.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/registry/overview.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/overview.md -#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/architecture.md /docs/sources/distribution/architecture.md -#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/architecture.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md /docs/sources/registry/deploying.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/deploying.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md /docs/sources/registry/configuration.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/configuration.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md /docs/sources/registry/storagedrivers.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/storagedrivers.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md /docs/sources/registry/notifications.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/notifications.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md /docs/sources/registry/spec/api.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/api.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md /docs/sources/registry/spec/json.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/json.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md # Docker Swarm #ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml @@ -88,4 +105,4 @@ ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/word RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md # Then build everything together, ready for mkdocs -RUN /docs/build.sh +RUN /docs/build.sh \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fd56ad15c36e9..f159ff28bf06a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -133,10 +133,19 @@ pages: - ['swarm/scheduler/filter.md', 'Reference', 'Swarm filters'] - ['swarm/API.md', 'Reference', 'Swarm API'] - ['reference/api/index.md', '**HIDDEN**'] +- ['registry/overview.md', 'Reference', 'Docker Registry 2.0'] +- ['registry/deploying.md', 'Reference', '    ▪  Deploy a registry' ] +- ['registry/configuration.md', 'Reference', '    ▪  Configure a registry' ] +- ['registry/storagedrivers.md', 'Reference', '    ▪  Storage driver model' ] +- ['registry/notifications.md', 'Reference', '    ▪  Work with notifications' ] +- ['registry/spec/api.md', 'Reference', '    ▪  Registry Service API v2' ] +- ['registry/spec/json.md', 'Reference', '    ▪  JSON format' ] +- ['registry/spec/auth/token.md', 'Reference', '    ▪  Authenticate via central service' ] +- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry 1.0'] +- ['reference/api/registry_api.md', 'Reference', '    ▪ Docker Registry API v1'] +- ['reference/api/registry_api_client_libraries.md', 'Reference', '    ▪ Docker Registry 1.0 API Client Libraries'] +#- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] -- ['reference/api/registry_api.md', 'Reference', 'Docker Registry API'] -- ['reference/api/registry_api_client_libraries.md', 'Reference', 'Docker Registry API Client Libraries'] -- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry Spec'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API'] - ['reference/api/docker_remote_api_v1.19.md', 'Reference', 'Docker Remote API v1.19'] diff --git a/docs/sources/reference/api/hub_registry_spec.md b/docs/sources/reference/api/hub_registry_spec.md index f01007587a37d..2999f453ff122 100644 --- a/docs/sources/reference/api/hub_registry_spec.md +++ b/docs/sources/reference/api/hub_registry_spec.md @@ -2,7 +2,7 @@ page_title: Registry Documentation page_description: Documentation for docker Registry and Registry API page_keywords: docker, registry, api, hub -# The Docker Hub and the Registry spec +# The Docker Hub and the Registry 1.0 spec ## The three roles @@ -28,9 +28,9 @@ The Docker Hub is authoritative for that information. There is only one instance of the Docker Hub, run and managed by Docker Inc. -### Registry +### Docker Registry 1.0 -The registry has the following characteristics: +The 1.0 registry has the following characteristics: - It stores the images and the graph for a set of repositories - It does not have user accounts data diff --git a/docs/sources/reference/api/registry_api.md b/docs/sources/reference/api/registry_api.md index 54a158934a77c..13a51356f0462 100644 --- a/docs/sources/reference/api/registry_api.md +++ b/docs/sources/reference/api/registry_api.md @@ -2,11 +2,11 @@ page_title: Registry API page_description: API Documentation for Docker Registry page_keywords: API, Docker, index, registry, REST, documentation -# Docker Registry API +# Docker Registry API v1 ## Introduction - - This is the REST API for the Docker Registry + - This is the REST API for the Docker Registry 1.0 - It stores the images and the graph for a set of repositories - It does not have user accounts data - It has no notion of user accounts or authorization diff --git a/docs/sources/reference/api/registry_api_client_libraries.md b/docs/sources/reference/api/registry_api_client_libraries.md index 6977af3cc462f..811ac859e420d 100644 --- a/docs/sources/reference/api/registry_api_client_libraries.md +++ b/docs/sources/reference/api/registry_api_client_libraries.md @@ -2,7 +2,7 @@ page_title: Registry API Client Libraries page_description: Various client libraries available to use with the Docker registry API page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala -# Docker Registry API Client Libraries +# Docker Registry 1.0 API Client Libraries These libraries have not been tested by the Docker maintainers for compatibility. Please file issues with the library owners. If you find From dc104ccb40b66a89611f11737d78a2ad31102427 Mon Sep 17 00:00:00 2001 From: Jason Smith Date: Mon, 23 Mar 2015 19:46:22 -0400 Subject: [PATCH 135/332] added documentation for functions Signed-off-by: Jason Smith --- pkg/etchosts/etchosts.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/etchosts/etchosts.go b/pkg/etchosts/etchosts.go index d7edef27f64ad..bef4a480cb07e 100644 --- a/pkg/etchosts/etchosts.go +++ b/pkg/etchosts/etchosts.go @@ -8,16 +8,19 @@ import ( "regexp" ) +// Structure for a single host record type Record struct { Hosts string IP string } +// Writes record to file and returns bytes written or error func (r Record) WriteTo(w io.Writer) (int64, error) { n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts) return int64(n), err } +// Default hosts config records slice var defaultContent = []Record{ {Hosts: "localhost", IP: "127.0.0.1"}, {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"}, @@ -27,9 +30,14 @@ var defaultContent = []Record{ {Hosts: "ip6-allrouters", IP: "ff02::2"}, } +// Build function +// path is path to host file string required +// IP, hostname, and domainname set main record leave empty for no master record +// extraContent is an array of extra host records. func Build(path, IP, hostname, domainname string, extraContent []Record) error { content := bytes.NewBuffer(nil) if IP != "" { + //set main record var mainRec Record mainRec.IP = IP if domainname != "" { @@ -41,13 +49,13 @@ func Build(path, IP, hostname, domainname string, extraContent []Record) error { return err } } - + // Write defaultContent slice to buffer for _, r := range defaultContent { if _, err := r.WriteTo(content); err != nil { return err } } - + // Write extra content from function arguments for _, r := range extraContent { if _, err := r.WriteTo(content); err != nil { return err @@ -57,6 +65,10 @@ func Build(path, IP, hostname, domainname string, extraContent []Record) error { return ioutil.WriteFile(path, content.Bytes(), 0644) } +// Update all IP addresses where hostname matches. +// path is path to host file +// IP is new IP address +// hostname is hostname to search for to replace IP func Update(path, IP, hostname string) error { old, err := ioutil.ReadFile(path) if err != nil { From 73bf9b5c195170b3d71f86b285ac12e50d26ef51 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 16 Apr 2015 17:36:45 +0800 Subject: [PATCH 136/332] add err check before getting term Signed-off-by: Ma Shimiao --- daemon/execdriver/lxc/driver.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 1637bc2c69b6f..15f57bfe0faff 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -85,16 +85,21 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba dataPath = d.containerDir(c.ID) ) + container, err := d.createContainer(c) + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + if c.ProcessConfig.Tty { term, err = NewTtyConsole(&c.ProcessConfig, pipes) } else { term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes) } - c.ProcessConfig.Terminal = term - container, err := d.createContainer(c) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } + c.ProcessConfig.Terminal = term + d.Lock() d.activeContainers[c.ID] = &activeContainer{ container: container, From 93cdb0071be29cde5e9f5574926ae628ef4cfc41 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 16 Apr 2015 14:31:52 +0800 Subject: [PATCH 137/332] optimize code to clarify logic Signed-off-by: Ma Shimiao --- daemon/create.go | 31 ++++++------------------------- daemon/daemon.go | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/daemon/create.go b/daemon/create.go index eb8a252756b73..db60355071866 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -2,7 +2,6 @@ package daemon import ( "fmt" - "strings" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -12,27 +11,9 @@ import ( ) func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig) (string, []string, error) { - var warnings []string - - if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { - return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) - } - if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { - return "", warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") - } - if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") - hostConfig.Memory = 0 - } - if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") - hostConfig.MemorySwap = -1 - } - if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { - return "", warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") - } - if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { - return "", warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + warnings, err := daemon.verifyHostConfig(hostConfig) + if err != nil { + return "", warnings, err } container, buildWarnings, err := daemon.Create(config, hostConfig, name) @@ -46,9 +27,6 @@ func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hos } return "", warnings, err } - if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { - warnings = append(warnings, "IPv4 forwarding is disabled.\n") - } container.LogEvent("create") warnings = append(warnings, buildWarnings...) @@ -80,6 +58,9 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil { return nil, nil, err } + if !config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { + warnings = append(warnings, "IPv4 forwarding is disabled.\n") + } if hostConfig == nil { hostConfig = &runconfig.HostConfig{} } diff --git a/daemon/daemon.go b/daemon/daemon.go index 6f50dc9f3cf2d..30a1f4be35f1e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1224,3 +1224,30 @@ func checkKernel() error { } return nil } + +func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]string, error) { + var warnings []string + + if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { + return warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) + } + if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { + return warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") + } + if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { + warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") + hostConfig.Memory = 0 + } + if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { + warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") + hostConfig.MemorySwap = -1 + } + if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { + return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") + } + if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { + return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + } + + return warnings, nil +} From 7e01ecc119ea3871058309a47a3f9cbf2a9483dd Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Thu, 16 Apr 2015 10:56:15 -0400 Subject: [PATCH 138/332] Fix a typo in docker/daemon/state.go Signed-off-by: Yan Feng --- daemon/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/state.go b/daemon/state.go index 6387e6fc535c9..4119d0e6cd924 100644 --- a/daemon/state.go +++ b/daemon/state.go @@ -183,7 +183,7 @@ func (s *State) setStopped(exitStatus *execdriver.ExitStatus) { s.waitChan = make(chan struct{}) } -// SetRestarting is when docker hanldes the auto restart of containers when they are +// SetRestarting is when docker handles the auto restart of containers when they are // in the middle of a stop and being restarted again func (s *State) SetRestarting(exitStatus *execdriver.ExitStatus) { s.Lock() From dcff07d03d9accbedd2467fe9c7c10fba7c2b35c Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Tue, 7 Apr 2015 20:37:36 -0400 Subject: [PATCH 139/332] Adding a verbose time option to output formatted timestamps Fixes #11413 Signed-off-by: Dave Henderson --- api/client/history.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 4ac46d92cef36..82a012265b82e 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -46,11 +46,11 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintf(w, stringid.TruncateID(entry.ID)) } if !*quiet { - fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) - if *noTrunc { + fmt.Fprintf(w, "\t%s\t", time.Unix(entry.Created, 0).Format(time.RFC3339)) fmt.Fprintf(w, "%s\t", entry.CreatedBy) } else { + fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45)) } fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) From ae5cf30c7c6630d201ef14e2e460f4164f58a261 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Thu, 16 Apr 2015 08:29:04 -0700 Subject: [PATCH 140/332] Add -H|--human flag to `docker history` Add a flag to print sizes and dates in human readable format. Signed-off-by: Arnaud Porterie --- api/client/history.go | 17 ++++++++++++++--- docs/man/docker-history.1.md | 3 +++ docs/sources/reference/commandline/cli.md | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 82a012265b82e..79c6f3f7a6244 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -18,6 +18,7 @@ import ( // Usage: docker history [OPTIONS] IMAGE func (cli *DockerCli) CmdHistory(args ...string) error { cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true) + human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format") quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") cmd.Require(flag.Exact, 1) @@ -46,14 +47,24 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintf(w, stringid.TruncateID(entry.ID)) } if !*quiet { - if *noTrunc { + if *human { + fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) + } else { fmt.Fprintf(w, "\t%s\t", time.Unix(entry.Created, 0).Format(time.RFC3339)) + } + + if *noTrunc { fmt.Fprintf(w, "%s\t", entry.CreatedBy) } else { - fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45)) } - fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) + + if *human { + fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) + } else { + fmt.Fprintf(w, "%d\t", entry.Size) + } + fmt.Fprintf(w, "%s", entry.Comment) } fmt.Fprintf(w, "\n") diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 2b38d83e6732d..268e378d0609a 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -19,6 +19,9 @@ Show the history of when and how an image was created. **--help** Print usage statement +**-H**. **--human**=*true*|*false* + Print sizes and dates in human readable format. The default is *true*. + **--no-trunc**=*true*|*false* Don't truncate output. The default is *false*. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076211c..70f67d13a6d6f 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1191,6 +1191,7 @@ This will create a new Bash session in the container `ubuntu_bash`. Show the history of an image + -H, --human=true Print sizes and dates in human readable format --no-trunc=false Don't truncate output -q, --quiet=false Only show numeric IDs From e41192a3f8cbfbbfecde03f58a3b2be2b1afd836 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 14 Apr 2015 01:34:34 +0200 Subject: [PATCH 141/332] Remove job from restart Signed-off-by: Antonio Murdaca --- api/server/server.go | 13 ++++++++++--- daemon/daemon.go | 1 - daemon/restart.go | 20 +++----------------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d4f445b93671f..d40413a4e878a 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -837,12 +837,19 @@ func postContainersRestart(eng *engine.Engine, version version.Version, w http.R if vars == nil { return fmt.Errorf("Missing parameter") } - job := eng.Job("restart", vars["name"]) - job.Setenv("t", r.Form.Get("t")) - if err := job.Run(); err != nil { + + s, err := strconv.Atoi(r.Form.Get("t")) + if err != nil { return err } + + d := getDaemon(eng) + if err := d.ContainerRestart(vars["name"], s); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 6f50dc9f3cf2d..959907d592460 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -120,7 +120,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, "info": daemon.CmdInfo, - "restart": daemon.ContainerRestart, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { diff --git a/daemon/restart.go b/daemon/restart.go index 1bd2f8ca10dcd..86cc97d7e7354 100644 --- a/daemon/restart.go +++ b/daemon/restart.go @@ -1,27 +1,13 @@ package daemon -import ( - "fmt" +import "fmt" - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerRestart(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } - var ( - name = job.Args[0] - t = 10 - ) - if job.EnvExists("t") { - t = job.GetenvInt("t") - } +func (daemon *Daemon) ContainerRestart(name string, seconds int) error { container, err := daemon.Get(name) if err != nil { return err } - if err := container.Restart(int(t)); err != nil { + if err := container.Restart(seconds); err != nil { return fmt.Errorf("Cannot restart container %s: %s\n", name, err) } container.LogEvent("restart") From 8232cc777e329a47e123dbdc42411dae65288a80 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 13 Apr 2015 22:53:54 -0400 Subject: [PATCH 142/332] Make sockRequestRaw return reader, not []byte Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 40 ++++++++++++++++--- integration-cli/docker_utils.go | 28 +++++++++---- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index d48b945724f91..68561a587da51 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -369,10 +369,15 @@ func TestBuildApiDockerfilePath(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - _, out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + _, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") if err == nil { + out, _ := readBody(body) t.Fatalf("Build was supposed to fail: %s", out) } + out, err := readBody(body) + if err != nil { + t.Fatal(err) + } if !strings.Contains(string(out), "must be within the build context") { t.Fatalf("Didn't complain about leaving build context: %s", out) @@ -393,10 +398,14 @@ RUN find /tmp/`, } defer server.Close() - _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } // Make sure Dockerfile exists. // Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL @@ -419,10 +428,15 @@ RUN echo from dockerfile`, } defer git.Close() - _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { + buf, _ := readBody(body) t.Fatalf("Build failed: %s\n%q", err, buf) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } out := string(buf) if !strings.Contains(out, "from dockerfile") { @@ -445,10 +459,15 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") if err != nil { + buf, _ := readBody(body) t.Fatalf("Build failed: %s\n%q", err, buf) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } out := string(buf) if !strings.Contains(out, "from baz") { @@ -472,10 +491,14 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } out := string(buf) if !strings.Contains(out, "from Dockerfile") { @@ -503,10 +526,15 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - _, out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + _, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") if err == nil { + out, _ := readBody(body) t.Fatalf("Build was supposed to fail: %s", out) } + out, err := readBody(body) + if err != nil { + t.Fatal(err) + } // The reason the error is "Cannot locate specified Dockerfile" is because // in the builder, the symlink is resolved within the context, therefore diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 4984b578bcdfe..ab200e1248ca5 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -22,6 +22,7 @@ import ( "time" "github.com/docker/docker/api" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" ) @@ -304,20 +305,27 @@ func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) return -1, nil, err } - return sockRequestRaw(method, endpoint, jsonData, "application/json") + status, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") + if err != nil { + b, _ := ioutil.ReadAll(body) + return status, b, err + } + var b []byte + b, err = readBody(body) + return status, b, err } -func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, []byte, error) { +func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io.ReadCloser, error) { c, err := sockConn(time.Duration(10 * time.Second)) if err != nil { return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err) } client := httputil.NewClientConn(c, nil) - defer client.Close() req, err := http.NewRequest(method, endpoint, data) if err != nil { + client.Close() return -1, nil, fmt.Errorf("could not create new request: %v", err) } @@ -328,17 +336,23 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, [] resp, err := client.Do(req) if err != nil { + client.Close() return -1, nil, fmt.Errorf("could not perform request: %v", err) } - defer resp.Body.Close() + body := ioutils.NewReadCloserWrapper(resp.Body, func() error { + defer client.Close() + return resp.Body.Close() + }) if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status) } - b, err := ioutil.ReadAll(resp.Body) + return resp.StatusCode, body, err +} - return resp.StatusCode, b, err +func readBody(b io.ReadCloser) ([]byte, error) { + defer b.Close() + return ioutil.ReadAll(b) } func deleteContainer(container string) error { From 6f5b895bc767585be9a8e1109672fb4946e56f15 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 13 Apr 2015 23:02:29 -0400 Subject: [PATCH 143/332] Move SaveAndThenload to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_images_test.go | 31 ++++++++++++ integration/api_test.go | 61 ----------------------- 2 files changed, 31 insertions(+), 61 deletions(-) diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index ee403a1880961..083d63204f1c7 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "net/url" + "strings" "testing" "github.com/docker/docker/api/types" @@ -67,3 +68,33 @@ func TestApiImagesFilter(t *testing.T) { logDone("images - filter param is applied") } + +func TestApiImagesSaveAndLoad(t *testing.T) { + out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false) + if err != nil { + t.Fatal(err) + } + id := strings.TrimSpace(out) + defer deleteImages("saveandload") + + _, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") + if err != nil { + t.Fatal(err) + } + defer body.Close() + + dockerCmd(t, "rmi", id) + + _, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") + if err != nil { + t.Fatal(err) + } + defer loadBody.Close() + + out, _, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) + if strings.TrimSpace(out) != id { + t.Fatal("load did not work properly") + } + + logDone("images API - save and load") +} diff --git a/integration/api_test.go b/integration/api_test.go index 3a795f94f9613..4808aa02def7e 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -23,67 +23,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestSaveImageAndThenLoad(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - // save image - r := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - tarball := r.Body - - // delete the image - r = httptest.NewRecorder() - req, err = http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - // make sure there is no image - r = httptest.NewRecorder() - req, err = http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusNotFound { - t.Fatalf("%d NotFound expected, received %d\n", http.StatusNotFound, r.Code) - } - - // load the image - r = httptest.NewRecorder() - req, err = http.NewRequest("POST", "/images/load", tarball) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - // finally make sure the image is there - r = httptest.NewRecorder() - req, err = http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } -} - func TestGetContainersTop(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From d9e4b14346c6a93bee8c24803a8538dc2d74911d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 17:14:29 -0400 Subject: [PATCH 144/332] Move TestGetContainersTop to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 41 ++++++++++ integration/api_test.go | 74 ------------------- 2 files changed, 41 insertions(+), 74 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 68561a587da51..26a8f115d7e8d 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -624,3 +624,44 @@ func TestContainerApiPause(t *testing.T) { logDone("container REST API - check POST containers/pause and unpause") } + +func TestContainerApiTop(t *testing.T) { + defer deleteAllContainers() + out, _, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + t.Fatal(err) + } + + type topResp struct { + Titles []string + Processes [][]string + } + var top topResp + _, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(b, &top); err != nil { + t.Fatal(err) + } + + if len(top.Titles) != 11 { + t.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles) + } + + if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { + t.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) + } + if len(top.Processes) != 2 { + t.Fatalf("expeted 2 processes, found %d: %v", len(top.Processes), top.Processes) + } + if top.Processes[0][10] != "/bin/sh -c cat" { + t.Fatalf("expected `/bin/sh -c cat`, found: %s", top.Processes[0][10]) + } + if top.Processes[1][10] != "cat" { + t.Fatalf("expected `cat`, found: %s", top.Processes[1][10]) + } + + logDone("containers REST API - GET /containers//top") +} diff --git a/integration/api_test.go b/integration/api_test.go index 4808aa02def7e..476fa35b37ee2 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -23,80 +23,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestGetContainersTop(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/sh", "-c", "cat"), - OpenStdin: true, - }, - t, - ) - defer func() { - // Make sure the process dies before destroying daemon - containerKill(eng, containerID, t) - containerWait(eng, containerID, t) - }() - - startContainer(eng, containerID, t) - - setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { - for { - if containerRunning(eng, containerID, t) { - break - } - time.Sleep(10 * time.Millisecond) - } - }) - - if !containerRunning(eng, containerID, t) { - t.Fatalf("Container should be running") - } - - // Make sure sh spawn up cat - setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - in, out := containerAttach(eng, containerID, t) - if err := assertPipe("hello\n", "hello", out, in, 150); err != nil { - t.Fatal(err) - } - }) - - r := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/containers/"+containerID+"/top?ps_args=aux", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - var procs engine.Env - if err := procs.Decode(r.Body); err != nil { - t.Fatal(err) - } - - if len(procs.GetList("Titles")) != 11 { - t.Fatalf("Expected 11 titles, found %d.", len(procs.GetList("Titles"))) - } - if procs.GetList("Titles")[0] != "USER" || procs.GetList("Titles")[10] != "COMMAND" { - t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.GetList("Titles")[0], procs.GetList("Titles")[10]) - } - processes := [][]string{} - if err := procs.GetJson("Processes", &processes); err != nil { - t.Fatal(err) - } - if len(processes) != 2 { - t.Fatalf("Expected 2 processes, found %d.", len(processes)) - } - if processes[0][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[0][10]) - } - if processes[1][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[1][10]) - } -} - func TestPostCommit(t *testing.T) { eng := NewTestEngine(t) b := &builder.BuilderJob{Engine: eng} From f19061ccfd526487391f7677bef7e1b38694d06a Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 20:48:03 -0400 Subject: [PATCH 145/332] Move TestPostCommit to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 29 ++++++++++++++ integration/api_test.go | 39 ------------------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 26a8f115d7e8d..5edd917f070a0 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -665,3 +665,32 @@ func TestContainerApiTop(t *testing.T) { logDone("containers REST API - GET /containers//top") } + +func TestContainerApiCommit(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") + id := strings.TrimSpace(out) + + name := "testcommit" + _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + t.Fatal(err) + } + + type resp struct { + Id string + } + var img resp + if err := json.Unmarshal(b, &img); err != nil { + t.Fatal(err) + } + defer deleteImages(img.Id) + + out, err = inspectField(img.Id, "Config.Cmd") + if out != "[/bin/sh -c touch /test]" { + t.Fatalf("got wrong Cmd from commit: %q", out) + } + // sanity check, make sure the image is what we think it is + dockerCmd(t, "run", img.Id, "ls", "/test") + + logDone("containers REST API - POST /commit") +} diff --git a/integration/api_test.go b/integration/api_test.go index 476fa35b37ee2..9ccab1c3b38b1 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -17,50 +17,11 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/server" "github.com/docker/docker/api/types" - "github.com/docker/docker/builder" "github.com/docker/docker/engine" "github.com/docker/docker/runconfig" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestPostCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - defer mkDaemonFromEngine(eng, t).Nuke() - - // Create a container and remove a file - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test"), - }, - t, - ) - - containerRun(eng, containerID, t) - - req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+containerID, bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusCreated { - t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) - } - - var env engine.Env - if err := env.Decode(r.Body); err != nil { - t.Fatal(err) - } - if err := eng.Job("image_inspect", env.Get("Id")).Run(); err != nil { - t.Fatalf("The image has not been committed") - } -} - func TestPostContainersCreate(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From 23fa7d41d5510131cdf7883a930087ac4fb34187 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 21:04:43 -0400 Subject: [PATCH 146/332] Move TestContainerApiCreate to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 27 +++++++++++++ integration/api_test.go | 40 ------------------- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 5edd917f070a0..cbfd99eee3cd4 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -694,3 +694,30 @@ func TestContainerApiCommit(t *testing.T) { logDone("containers REST API - POST /commit") } + +func TestContainerApiCreate(t *testing.T) { + defer deleteAllContainers() + config := map[string]interface{}{ + "Image": "busybox", + "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"}, + } + + _, b, err := sockRequest("POST", "/containers/create", config) + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + t.Fatal(err) + } + type createResp struct { + Id string + } + var container createResp + if err := json.Unmarshal(b, &container); err != nil { + t.Fatal(err) + } + + out, _, _ := dockerCmd(t, "start", "-a", container.Id) + if strings.TrimSpace(out) != "/test" { + t.Fatalf("expected output `/test`, got %q", out) + } + + logDone("containers REST API - POST /containers/create") +} diff --git a/integration/api_test.go b/integration/api_test.go index 9ccab1c3b38b1..318978e5adf56 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -22,46 +22,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestPostContainersCreate(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test"), - }) - if err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusCreated { - t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) - } - - var apiRun engine.Env - if err := apiRun.Decode(r.Body); err != nil { - t.Fatal(err) - } - containerID := apiRun.Get("Id") - - containerAssertExists(eng, containerID, t) - containerRun(eng, containerID, t) - - if !containerFileExists(eng, containerID, "test", t) { - t.Fatal("Test file was not created") - } -} - func TestPostJsonVerify(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From 5dc02a2fa8819b3a61bcd5a6fedcfb1a5e64cba5 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 21:55:04 -0400 Subject: [PATCH 147/332] Move TestPostJsonVerify to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 40 +++++++++++++++++++ integration-cli/docker_utils.go | 5 +-- integration/api_test.go | 38 ------------------ 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index cbfd99eee3cd4..cf18dcde2b859 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -721,3 +721,43 @@ func TestContainerApiCreate(t *testing.T) { logDone("containers REST API - POST /containers/create") } + +func TestContainerApiVerifyHeader(t *testing.T) { + defer deleteAllContainers() + config := map[string]interface{}{ + "Image": "busybox", + } + + create := func(ct string) (int, io.ReadCloser, error) { + jsonData := bytes.NewBuffer(nil) + if err := json.NewEncoder(jsonData).Encode(config); err != nil { + t.Fatal(err) + } + return sockRequestRaw("POST", "/containers/create", jsonData, ct) + } + + // Try with no content-type + _, body, err := create("") + if err == nil { + b, _ := readBody(body) + t.Fatalf("expected error when content-type is not set: %q", string(b)) + } + body.Close() + // Try with wrong content-type + _, body, err = create("application/xml") + if err == nil { + b, _ := readBody(body) + t.Fatalf("expected error when content-type is not set: %q", string(b)) + } + body.Close() + + // now application/json + _, body, err = create("application/json") + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + b, _ := readBody(body) + t.Fatalf("%v - %q", err, string(b)) + } + body.Close() + + logDone("containers REST API - verify create header") +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index ab200e1248ca5..367e518094739 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -329,10 +329,9 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io return -1, nil, fmt.Errorf("could not create new request: %v", err) } - if ct == "" { - ct = "application/json" + if ct != "" { + req.Header.Set("Content-Type", ct) } - req.Header.Set("Content-Type", ct) resp, err := client.Do(req) if err != nil { diff --git a/integration/api_test.go b/integration/api_test.go index 318978e5adf56..1663f416901d7 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -22,44 +22,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestPostJsonVerify(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test"), - }) - if err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON)) - if err != nil { - t.Fatal(err) - } - - r := httptest.NewRecorder() - - server.ServeRequest(eng, api.APIVERSION, r, req) - - // Don't add Content-Type header - // req.Header.Set("Content-Type", "application/json") - - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusInternalServerError || !strings.Contains(((*r.Body).String()), "application/json") { - t.Fatal("Create should have failed due to no Content-Type header - got:", r) - } - - // Now add header but with wrong type and retest - req.Header.Set("Content-Type", "application/xml") - - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusInternalServerError || !strings.Contains(((*r.Body).String()), "application/json") { - t.Fatal("Create should have failed due to wrong Content-Type header - got:", r) - } -} - // Issue 7941 - test to make sure a "null" in JSON is just ignored. // W/o this fix a null in JSON would be parsed into a string var as "null" func TestPostCreateNull(t *testing.T) { From 308a23021d65282cdf471ef0708ba5998b48c247 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 22:07:04 -0400 Subject: [PATCH 148/332] Move TestPostCreateNull to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 56 +++++++++++++++++ integration/api_test.go | 61 ------------------- 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index cf18dcde2b859..cabbfc2045918 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -761,3 +761,59 @@ func TestContainerApiVerifyHeader(t *testing.T) { logDone("containers REST API - verify create header") } + +// Issue 7941 - test to make sure a "null" in JSON is just ignored. +// W/o this fix a null in JSON would be parsed into a string var as "null" +func TestContainerApiPostCreateNull(t *testing.T) { + config := `{ + "Hostname":"", + "Domainname":"", + "Memory":0, + "MemorySwap":0, + "CpuShares":0, + "Cpuset":null, + "AttachStdin":true, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "ExposedPorts":{}, + "Tty":true, + "OpenStdin":true, + "StdinOnce":true, + "Env":[], + "Cmd":"ls", + "Image":"busybox", + "Volumes":{}, + "WorkingDir":"", + "Entrypoint":null, + "NetworkDisabled":false, + "OnBuild":null}` + + _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + b, _ := readBody(body) + t.Fatal(err, string(b)) + } + + b, err := readBody(body) + if err != nil { + t.Fatal(err) + } + type createResp struct { + Id string + } + var container createResp + if err := json.Unmarshal(b, &container); err != nil { + t.Fatal(err) + } + + out, err := inspectField(container.Id, "HostConfig.CpusetCpus") + if err != nil { + t.Fatal(err, out) + } + if out != "" { + t.Fatalf("expected empty string, got %q", out) + } + + logDone("containers REST API - Create Null") +} diff --git a/integration/api_test.go b/integration/api_test.go index 1663f416901d7..4b09e4917454a 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -4,13 +4,11 @@ import ( "bufio" "bytes" "encoding/json" - "fmt" "io" "io/ioutil" "net" "net/http" "net/http/httptest" - "strings" "testing" "time" @@ -22,65 +20,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -// Issue 7941 - test to make sure a "null" in JSON is just ignored. -// W/o this fix a null in JSON would be parsed into a string var as "null" -func TestPostCreateNull(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer daemon.Nuke() - - configStr := fmt.Sprintf(`{ - "Hostname":"", - "Domainname":"", - "Memory":0, - "MemorySwap":0, - "CpuShares":0, - "Cpuset":null, - "AttachStdin":true, - "AttachStdout":true, - "AttachStderr":true, - "PortSpecs":null, - "ExposedPorts":{}, - "Tty":true, - "OpenStdin":true, - "StdinOnce":true, - "Env":[], - "Cmd":"ls", - "Image":"%s", - "Volumes":{}, - "WorkingDir":"", - "Entrypoint":null, - "NetworkDisabled":false, - "OnBuild":null}`, unitTestImageID) - - req, err := http.NewRequest("POST", "/containers/create", strings.NewReader(configStr)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusCreated { - t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) - } - - var apiRun engine.Env - if err := apiRun.Decode(r.Body); err != nil { - t.Fatal(err) - } - containerID := apiRun.Get("Id") - - containerAssertExists(eng, containerID, t) - - c, _ := daemon.Get(containerID) - if c.HostConfig().CpusetCpus != "" { - t.Fatalf("Cpuset should have been empty - instead its:" + c.HostConfig().CpusetCpus) - } -} - func TestPostContainersKill(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From f44aa3b1fbe5e042fee0fb78507ebac35eca3d04 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 16 Apr 2015 11:33:49 -0700 Subject: [PATCH 149/332] for 1.6 Signed-off-by: Mary Anthony --- docs/Dockerfile | 6 +++--- docs/s3_website.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index aa97313ae9bac..d5ffae4609726 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -6,9 +6,9 @@ MAINTAINER Sven Dowideit (@SvenDowideit) # This section ensures we pull the correct version of each # sub project -ENV COMPOSE_BRANCH release -ENV SWARM_BRANCH v0.1.0 -ENV MACHINE_BRANCH v0.1.0 +ENV COMPOSE_BRANCH 1.2.0 +ENV SWARM_BRANCH v0.2.0 +ENV MACHINE_BRANCH master ENV DISTRIB_BRANCH master diff --git a/docs/s3_website.json b/docs/s3_website.json index 1142fc0d87fed..95e7109c6cb4c 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -42,7 +42,8 @@ { "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } }, { "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } }, { "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } }, - { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } } + { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }, + { "Condition": { "KeyPrefixEquals": "registry/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/overview/" } } ] } From b4b21ff0a6c45c1565c74e6f923b2c7e8ca565d6 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 16 Apr 2015 11:39:54 -0700 Subject: [PATCH 150/332] Updating with final version from Stephen Signed-off-by: Mary Anthony --- docs/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index d5ffae4609726..d82c64df83ebb 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -9,7 +9,7 @@ MAINTAINER Sven Dowideit (@SvenDowideit) ENV COMPOSE_BRANCH 1.2.0 ENV SWARM_BRANCH v0.2.0 ENV MACHINE_BRANCH master -ENV DISTRIB_BRANCH master +ENV DISTRIB_BRANCH v2.0.0 From 1c89c6ea2f34f51a05215279c9cdefca30bb13b1 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Apr 2015 21:22:32 +0200 Subject: [PATCH 151/332] Add minor stylistic fixes Signed-off-by: Antonio Murdaca --- builder/internals.go | 3 +-- daemon/networkdriver/bridge/driver.go | 6 +++--- pkg/iptables/iptables.go | 12 ++++++------ pkg/streamformatter/streamformatter.go | 3 ++- pkg/streamformatter/streamformatter_test.go | 3 ++- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/builder/internals.go b/builder/internals.go index 669d3d888def1..49cf9422f2a99 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -603,11 +603,10 @@ func (b *Builder) run(c *daemon.Container) error { // Wait for it to finish if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 { - err := &jsonmessage.JSONError{ + return &jsonmessage.JSONError{ Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret), Code: ret, } - return err } return nil diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index eda4713870ecb..e8627363a0786 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -308,7 +308,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { "-t", string(iptables.Nat), "-I", "POSTROUTING"}, natArgs...)...); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) } else if len(output) != 0 { - return &iptables.ChainError{Chain: "POSTROUTING", Output: output} + return iptables.ChainError{Chain: "POSTROUTING", Output: output} } } } @@ -349,7 +349,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, outgoingArgs...)...); err != nil { return fmt.Errorf("Unable to allow outgoing packets: %s", err) } else if len(output) != 0 { - return &iptables.ChainError{Chain: "FORWARD outgoing", Output: output} + return iptables.ChainError{Chain: "FORWARD outgoing", Output: output} } } @@ -360,7 +360,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, existingArgs...)...); err != nil { return fmt.Errorf("Unable to allow incoming packets: %s", err) } else if len(output) != 0 { - return &iptables.ChainError{Chain: "FORWARD incoming", Output: output} + return iptables.ChainError{Chain: "FORWARD incoming", Output: output} } } return nil diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index f8b3aa769d28e..204d703a6115d 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -41,7 +41,7 @@ type ChainError struct { Output []byte } -func (e *ChainError) Error() string { +func (e ChainError) Error() string { return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output)) } @@ -142,7 +142,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "FORWARD", Output: output} + return ChainError{Chain: "FORWARD", Output: output} } if output, err := Raw("-t", string(Filter), string(action), c.Name, @@ -154,7 +154,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri "-j", "ACCEPT"); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "FORWARD", Output: output} + return ChainError{Chain: "FORWARD", Output: output} } if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING", @@ -165,7 +165,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri "-j", "MASQUERADE"); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "FORWARD", Output: output} + return ChainError{Chain: "FORWARD", Output: output} } return nil @@ -208,7 +208,7 @@ func (c *Chain) Prerouting(action Action, args ...string) error { if output, err := Raw(append(a, "-j", c.Name)...); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "PREROUTING", Output: output} + return ChainError{Chain: "PREROUTING", Output: output} } return nil } @@ -222,7 +222,7 @@ func (c *Chain) Output(action Action, args ...string) error { if output, err := Raw(append(a, "-j", c.Name)...); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "OUTPUT", Output: output} + return ChainError{Chain: "OUTPUT", Output: output} } return nil } diff --git a/pkg/streamformatter/streamformatter.go b/pkg/streamformatter/streamformatter.go index 383e7adf9e882..90f2b695d3d93 100644 --- a/pkg/streamformatter/streamformatter.go +++ b/pkg/streamformatter/streamformatter.go @@ -3,8 +3,9 @@ package streamformatter import ( "encoding/json" "fmt" - "github.com/docker/docker/pkg/jsonmessage" "io" + + "github.com/docker/docker/pkg/jsonmessage" ) type StreamFormatter struct { diff --git a/pkg/streamformatter/streamformatter_test.go b/pkg/streamformatter/streamformatter_test.go index edc432e900710..1dee05aa6c590 100644 --- a/pkg/streamformatter/streamformatter_test.go +++ b/pkg/streamformatter/streamformatter_test.go @@ -3,9 +3,10 @@ package streamformatter import ( "encoding/json" "errors" - "github.com/docker/docker/pkg/jsonmessage" "reflect" "testing" + + "github.com/docker/docker/pkg/jsonmessage" ) func TestFormatStream(t *testing.T) { From 99d0413033e6ecf05a30eac18d51349ef859a9c7 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 16 Apr 2015 11:38:05 -0700 Subject: [PATCH 152/332] Bump version to v1.6.0 Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) (cherry picked from commit 47496519da9664202d900d3635bb840509fa9647) --- CHANGELOG.md | 20 ++++++++++++++++++++ VERSION | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e845e459a5c1..9c541388e5e0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 1.6.0 (2015-04-07) + +#### Builder ++ Building images from an image ID ++ build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...` ++ `commit --change` to apply specified Dockerfile instructions while committing the image ++ `import --change` to apply specified Dockerfile instructions while importing the image ++ basic build cancellation + +#### Client ++ Windows Support + +#### Runtime ++ Container and image Labels ++ `--cgroup-parent` for specifying a parent cgroup to place container cgroup within ++ Logging drivers, `json-file`, `syslog`, or `none` ++ Pulling images by ID ++ `--ulimit` to set the ulimit on a container ++ `--default-ulimit` option on the daemon which applies to all created containers (and overwritten by `--ulimit` on run) + ## 1.5.0 (2015-02-10) #### Builder diff --git a/VERSION b/VERSION index 59b9db0c75150..dc1e644a10143 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.0-dev +1.6.0 From 4c6d5e3a25557b2a053e8f8e5aafc84f5f6aada8 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Thu, 16 Apr 2015 12:54:56 -0700 Subject: [PATCH 153/332] Remove registry redirect ... it was causing an infinite redirect. Signed-off-by: Ben Firshman --- docs/s3_website.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/s3_website.json b/docs/s3_website.json index 95e7109c6cb4c..1142fc0d87fed 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -42,8 +42,7 @@ { "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } }, { "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } }, { "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } }, - { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }, - { "Condition": { "KeyPrefixEquals": "registry/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/overview/" } } + { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } } ] } From e79c77fb8d16ba38387e43fbf9224e341f252b95 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 16 Apr 2015 12:45:30 -0700 Subject: [PATCH 154/332] bump version to 1.6.0-dev Signed-off-by: Jessica Frazelle --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index dc1e644a10143..de023c91b16b6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.0 +1.7.0-dev From b57830aee4abc86155d401b6d8a0215c3a4b5a1b Mon Sep 17 00:00:00 2001 From: Yahya Date: Thu, 16 Apr 2015 00:17:18 +0200 Subject: [PATCH 155/332] adding elementary OS to install script It's basically just an Ubuntu, I'm a total noob but I've tried it on elementary OS "Freya" and it worked Signed-off-by: M Yahya --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index b0177e6708909..32e506cdd2b4b 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -126,7 +126,7 @@ do_install() { exit 0 ;; - ubuntu|debian|linuxmint) + ubuntu|debian|linuxmint|'elementary os') export DEBIAN_FRONTEND=noninteractive did_apt_get_update= From 01548ed1dc25e94bf6cc7decca1d2045069dc5b1 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Fri, 17 Apr 2015 09:25:06 +0800 Subject: [PATCH 156/332] update docker-inspect man page - sort inspect out - update output fields - format output - add doc about go template - other minor fix Signed-off-by: Qiang Huang --- docs/man/docker-inspect.1.md | 319 +++++++++++++++++++---------------- 1 file changed, 175 insertions(+), 144 deletions(-) diff --git a/docs/man/docker-inspect.1.md b/docs/man/docker-inspect.1.md index 85f6730004210..6f3cf51221b20 100644 --- a/docs/man/docker-inspect.1.md +++ b/docs/man/docker-inspect.1.md @@ -19,80 +19,120 @@ each result. # OPTIONS **--help** - Print usage statement + Print usage statement **-f**, **--format**="" - Format the output using the given go template. + Format the output using the given go template. # EXAMPLES ## Getting information on a container -To get information on a container use it's ID or instance name: +To get information on a container use its ID or instance name: - #docker inspect 1eb5fabf5a03 + $ docker inspect 1eb5fabf5a03 [{ - "ID": "1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b", - "Created": "2014-04-04T21:33:52.02361335Z", - "Path": "/usr/sbin/nginx", - "Args": [], - "Config": { - "Hostname": "1eb5fabf5a03", - "Domainname": "", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "CpuShares": 0, + "AppArmorProfile": "", + "Args": [], + "Config": { + "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, - "AttachStderr": false, - "PortSpecs": null, - "ExposedPorts": { - "80/tcp": {} - }, - "Tty": true, - "OpenStdin": false, - "StdinOnce": false, - "Env": [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ], "Cmd": [ "/usr/sbin/nginx" ], - "Dns": null, - "DnsSearch": null, - "Image": "summit/nginx", - "Volumes": null, - "VolumesFrom": "", - "WorkingDir": "", + "Domainname": "", "Entrypoint": null, + "Env": [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "ExposedPorts": { + "80/tcp": {} + }, + "Hostname": "1eb5fabf5a03", + "Image": "summit/nginx", + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, + "MacAddress": "", "NetworkDisabled": false, "OnBuild": null, - "Context": { - "mount_label": "system_u:object_r:svirt_sandbox_file_t:s0:c0,c650", - "process_label": "system_u:system_r:svirt_lxc_net_t:s0:c0,c650" - } - }, - "State": { - "Running": true, - "Pid": 858, - "ExitCode": 0, - "StartedAt": "2014-04-04T21:33:54.16259207Z", - "FinishedAt": "0001-01-01T00:00:00Z", - "Ghost": false + "OpenStdin": false, + "PortSpecs": null, + "StdinOnce": false, + "Tty": true, + "User": "", + "Volumes": null, + "WorkingDir": "", }, + "Created": "2014-04-04T21:33:52.02361335Z", + "Driver": "devicemapper", + "ExecDriver": "native-0.1", + "ExecIDs": null, + "HostConfig": { + "Binds": null, + "CapAdd": null, + "CapDrop": null, + "CgroupParent": "", + "ContainerIDFile": "", + "CpuShares": 512, + "CpusetCpus": "0,1", + "CpusetMems": "", + "Devices": [], + "Dns": null, + "DnsSearch": null, + "ExtraHosts": null, + "IpcMode": "", + "Links": null, + "LogConfig": { + "Config": null, + "Type": "json-file" + }, + "LxcConf": null, + "Memory": 16777216, + "MemorySwap": -1, + "NetworkMode": "", + "PidMode": "", + "PortBindings": { + "80/tcp": [ + { + "HostIp": "0.0.0.0", + "HostPort": "80" + } + ] + }, + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "RestartPolicy": { + "MaximumRetryCount": 0, + "Name": "" + }, + "SecurityOpt": null, + "Ulimits": null, + "VolumesFrom": null + } + "HostnamePath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hostname", + "HostsPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hosts", + "ID": "1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b", "Image": "df53773a4390e25936f9fd3739e0c0e60a62d024ea7b669282b27e65ae8458e6", - "Labels": { - "com.example.vendor": "Acme", - "com.example.license": "GPL", - "com.example.version": "1.0" - }, + "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log", + "MountLabel": "", + "Name": "/ecstatic_ptolemy", "NetworkSettings": { + "Bridge": "docker0", + "Gateway": "172.17.42.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, - "Gateway": "172.17.42.1", - "Bridge": "docker0", + "IPv6Gateway": "", + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "MacAddress": "", "PortMapping": null, "Ports": { "80/tcp": [ @@ -103,41 +143,31 @@ To get information on a container use it's ID or instance name: ] } }, + "Path": "/usr/sbin/nginx", + "ProcessLabel": "", "ResolvConfPath": "/etc/resolv.conf", - "HostnamePath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hostname", - "HostsPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hosts", - "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log", - "Name": "/ecstatic_ptolemy", - "Driver": "devicemapper", - "ExecDriver": "native-0.1", + "RestartCount": 0, + "State": { + "Dead": false, + "Error": "", + "ExitCode": 0, + "FinishedAt": "0001-01-01T00:00:00Z", + "OOMKilled": false, + "Paused": false, + "Pid": 858, + "Restarting": false, + "Running": true, + "StartedAt": "2014-04-04T21:33:54.16259207Z", + }, "Volumes": {}, "VolumesRW": {}, - "HostConfig": { - "Binds": null, - "ContainerIDFile": "", - "LxcConf": [], - "Privileged": false, - "PortBindings": { - "80/tcp": [ - { - "HostIp": "0.0.0.0", - "HostPort": "80" - } - ] - }, - "Links": null, - "PublishAllPorts": false, - "DriverOptions": { - "lxc": null - }, - "CliAddress": "" - } + } ## Getting the IP address of a container instance To get the IP address of a container use: - # docker inspect --format='{{.NetworkSettings.IPAddress}}' 1eb5fabf5a03 + $ docker inspect --format='{{.NetworkSettings.IPAddress}}' 1eb5fabf5a03 172.17.0.2 ## Listing all port bindings @@ -145,95 +175,96 @@ To get the IP address of a container use: One can loop over arrays and maps in the results to produce simple text output: - # docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} \ - {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 1eb5fabf5a03 + $ docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} \ + {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 1eb5fabf5a03 + 80/tcp -> 80 - 80/tcp -> 80 +You can get more information about how to write a go template from: +http://golang.org/pkg/text/template/. ## Getting information on an image Use an image's ID or name (e.g., repository/name[:tag]) to get information - on it. +on it. - # docker inspect 58394af37342 + $ docker inspect fc1203419df2 [{ - "id": "58394af373423902a1b97f209a31e3777932d9321ef10e64feaaa7b4df609cf9", - "parent": "8abc22bad04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", - "created": "2014-02-03T16:10:40.500814677Z", - "container": "f718f19a28a5147da49313c54620306243734bafa63c76942ef6f8c4b4113bc5", - "container_config": { - "Hostname": "88807319f25e", - "Domainname": "", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "CpuShares": 0, + "Architecture": "amd64", + "Author": "", + "Comment": "", + "Config": { + "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, - "AttachStderr": false, - "PortSpecs": null, + "Cmd": [ + "make", + "direct-test" + ], + "Domainname": "", + "Entrypoint": [ + "/dind" + ], + "Env": [ + "PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + ], "ExposedPorts": null, - "Tty": false, + "Hostname": "242978536a06", + "Image": "c2b774c744afc5bea603b5e6c5218539e506649326de3ea0135182f299d0519a", + "Labels": {}, + "MacAddress": "", + "NetworkDisabled": false, + "OnBuild": [], "OpenStdin": false, + "PortSpecs": null, "StdinOnce": false, - "Env": [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ], + "Tty": false, + "User": "", + "Volumes": null, + "WorkingDir": "/go/src/github.com/docker/libcontainer" + }, + "Container": "1c00417f3812a96d3ebc29e7fdee69f3d586d703ab89c8233fd4678d50707b39", + "ContainerConfig": { + "AttachStderr": false, + "AttachStdin": false, + "AttachStdout": false, "Cmd": [ "/bin/sh", "-c", - "#(nop) ADD fedora-20-dummy.tar.xz in /" + "#(nop) CMD [\"make\" \"direct-test\"]" ], - "Dns": null, - "DnsSearch": null, - "Image": "8abc22bad04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", - "Volumes": null, - "VolumesFrom": "", - "WorkingDir": "", - "Entrypoint": null, - "NetworkDisabled": false, - "OnBuild": null, - "Context": null - }, - "docker_version": "0.6.3", - "author": "I P Babble \u003clsm5@ipbabble.com\u003e - ./buildcontainers.sh", - "config": { - "Hostname": "88807319f25e", "Domainname": "", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "CpuShares": 0, - "AttachStdin": false, - "AttachStdout": false, - "AttachStderr": false, - "PortSpecs": null, + "Entrypoint": [ + "/dind" + ], + "Env": [ + "PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + ], "ExposedPorts": null, - "Tty": false, + "Hostname": "242978536a06", + "Image": "c2b774c744afc5bea603b5e6c5218539e506649326de3ea0135182f299d0519a", + "Labels": {}, + "MacAddress": "", + "NetworkDisabled": false, + "OnBuild": [], "OpenStdin": false, + "PortSpecs": null, "StdinOnce": false, - "Env": [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ], - "Cmd": null, - "Dns": null, - "DnsSearch": null, - "Image": "8abc22bad04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", + "Tty": false, + "User": "", "Volumes": null, - "VolumesFrom": "", - "WorkingDir": "", - "Entrypoint": null, - "NetworkDisabled": false, - "OnBuild": null, - "Context": null + "WorkingDir": "/go/src/github.com/docker/libcontainer" }, - "architecture": "x86_64", - "Size": 385520098 + "Created": "2015-04-07T05:34:39.079489206Z", + "DockerVersion": "1.5.0-dev", + "Id": "fc1203419df26ca82cad1dd04c709cb1b8a8a947bd5bcbdfbef8241a76f031db", + "Os": "linux", + "Parent": "c2b774c744afc5bea603b5e6c5218539e506649326de3ea0135182f299d0519a", + "Size": 0, + "VirtualSize": 613136466 }] # HISTORY -April 2014, Originally compiled by William Henry (whenry at redhat dot com) +April 2014, originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit +April 2015, updated by Qiang Huang From 4e356ee410e1abb86665ab15e4c4155d7c807866 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Thu, 16 Apr 2015 08:50:20 -0700 Subject: [PATCH 157/332] Improve export/import tests cleanup Signed-off-by: Arnaud Porterie --- .../docker_cli_export_import_test.go | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 2d03179ac68b0..3df8d60f86855 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -9,21 +9,24 @@ import ( // export an image and try to import it into a new one func TestExportContainerAndImportImage(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + containerID := "testexportcontainerandimportimage" + + defer deleteImages("repo/testexp:v1") + defer deleteContainer(containerID) + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := strings.TrimSpace(out) - - inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) + inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err) + t.Fatalf("output should've been a container id: %s %s ", containerID, err) } - exportCmd := exec.Command(dockerBinary, "export", cleanedContainerID) + exportCmd := exec.Command(dockerBinary, "export", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { t.Fatalf("failed to export container: %s, %v", out, err) } @@ -42,29 +45,31 @@ func TestExportContainerAndImportImage(t *testing.T) { t.Fatalf("output should've been an image id: %s, %v", out, err) } - deleteContainer(cleanedContainerID) - deleteImages("repo/testexp:v1") - logDone("export - export/import a container/image") } // Used to test output flag in the export command func TestExportContainerWithOutputAndImportImage(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + containerID := "testexportcontainerwithoutputandimportimage" + + defer deleteImages("repo/testexp:v1") + defer deleteContainer(containerID) + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := strings.TrimSpace(out) - - inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) + inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err) + t.Fatalf("output should've been a container id: %s %s ", containerID, err) } - exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", cleanedContainerID) + defer os.Remove("testexp.tar") + + exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { t.Fatalf("failed to export container: %s, %v", out, err) } @@ -88,10 +93,5 @@ func TestExportContainerWithOutputAndImportImage(t *testing.T) { t.Fatalf("output should've been an image id: %s, %v", out, err) } - deleteContainer(cleanedContainerID) - deleteImages("repo/testexp:v1") - - os.Remove("/tmp/testexp.tar") - logDone("export - export/import a container/image with output flag") } From 9a4fa9c19167756cf39a4d002efe81d4bcd3bb75 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Thu, 16 Apr 2015 23:05:47 -0700 Subject: [PATCH 158/332] Skip TestPullVerified Signed-off-by: Arnaud Porterie --- integration-cli/docker_cli_pull_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 6e5ddb84083aa..f9fd1785240ab 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -55,6 +55,8 @@ func TestPullImageWithAliases(t *testing.T) { // pulling library/hello-world should show verified message func TestPullVerified(t *testing.T) { + t.Skip("Skipping hub dependent test") + // Image must be pulled from central repository to get verified message // unless keychain is manually updated to contain the daemon's sign key. From b052f7a87cb73aa2b0d4827f6cf23b9eb0fdfa2e Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 17 Apr 2015 18:05:30 +0800 Subject: [PATCH 159/332] Change log severity for non-tlsverify bind closes #12459 Signed-off-by: Hu Keping --- api/server/server_linux.go | 2 +- api/server/server_windows.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 37ba7ed80dec2..06e1a3717d620 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -53,7 +53,7 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { return nil, nil case "tcp": if !job.GetenvBool("TlsVerify") { - logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { return nil, err diff --git a/api/server/server_windows.go b/api/server/server_windows.go index ad7b3c48ad511..c81313e258902 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -1,4 +1,5 @@ // +build windows + package server import ( @@ -25,7 +26,7 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { switch proto { case "tcp": if !job.GetenvBool("TlsVerify") { - logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { return nil, err From 3cdf94ed1a8a63c068e17310ffa764230b280cbe Mon Sep 17 00:00:00 2001 From: Masahito Zembutsu Date: Fri, 17 Apr 2015 16:01:18 +0900 Subject: [PATCH 160/332] fix typo Is this typo? Signed-off-by: Masahito Zembutsu --- docs/sources/installation/windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index a1bd1de1d2c88..f93f13e60da3d 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -30,7 +30,7 @@ is developed, you can launch only Linux containers from your Windows machine. 1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest). -2. Run the installer, which will install Docker Client or Windows, VirtualBox, +2. Run the installer, which will install Docker Client for Windows, VirtualBox, Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker management tool. ![](/installation/images/windows-installer.png) From 05a8de46853f8b3534ca6d0cb03121ae214e8a34 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Fri, 17 Apr 2015 15:28:12 +0800 Subject: [PATCH 161/332] Fix weird terminal output format Signed-off-by: Lei Jitang --- pkg/term/tc_linux_cgo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/term/tc_linux_cgo.go b/pkg/term/tc_linux_cgo.go index ae9516c99cf80..d47cf59b8dff7 100644 --- a/pkg/term/tc_linux_cgo.go +++ b/pkg/term/tc_linux_cgo.go @@ -24,6 +24,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState := oldState.termios C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState))) + newState.Oflag = newState.Oflag | C.OPOST if err := tcset(fd, &newState); err != 0 { return nil, err } From 70f1910a8bbeb3727829070a2454a636a91e2d48 Mon Sep 17 00:00:00 2001 From: bin liu Date: Thu, 16 Apr 2015 09:10:05 +0000 Subject: [PATCH 162/332] fix some typos Signed-off-by: bin liu --- pkg/term/winconsole/term_emulator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/term/winconsole/term_emulator_test.go b/pkg/term/winconsole/term_emulator_test.go index 65de5a79338e1..94104ff51f2f9 100644 --- a/pkg/term/winconsole/term_emulator_test.go +++ b/pkg/term/winconsole/term_emulator_test.go @@ -138,7 +138,7 @@ func TestAssertEqualBytesNegative(t *testing.T) { AssertBytesEqual(t, []byte{1, 2, 3}, []byte{1, 1, 1}, "content mismatch") }*/ -// Checks that the calls recieved +// Checks that the calls received func assertHandlerOutput(t *testing.T, mock *mockTerminal, plainText string, commands ...string) { text := make([]byte, 0, 3*len(plainText)) cmdIndex := 0 From 609fa93aa2fd98f2eac30933623f15ece59e4527 Mon Sep 17 00:00:00 2001 From: Peter Waller Date: Fri, 17 Apr 2015 09:44:12 +0100 Subject: [PATCH 163/332] Improve build cancelation description in CHANGELOG The existing text didn't explain what had changed. (See #9774) Signed-off-by: Peter Waller --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c541388e5e0d..d168ad280a3e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,10 @@ #### Builder + Building images from an image ID -+ build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...` ++ Build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...` + `commit --change` to apply specified Dockerfile instructions while committing the image + `import --change` to apply specified Dockerfile instructions while importing the image -+ basic build cancellation ++ Builds no longer continue in the background when canceled with CTRL-C #### Client + Windows Support From a0bf80fe0372196812a9cb295f209c08f8037601 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Apr 2015 21:48:04 +0200 Subject: [PATCH 164/332] Remove builtins Signed-off-by: Antonio Murdaca --- api/server/server.go | 48 ++++++++++++++++------ api/server/server_linux.go | 22 +++++----- api/server/server_unit_test.go | 29 ------------- api/server/server_windows.go | 4 +- api/server/tcp_socket.go | 13 +++--- api/types/types.go | 12 ++++++ builtins/builtins.go | 48 ---------------------- docker/daemon.go | 44 ++++++++------------ integration-cli/docker_api_version_test.go | 24 +++++++++++ integration-cli/docker_cli_run_test.go | 2 +- integration/runtime_test.go | 33 ++++++++------- integration/utils_test.go | 5 --- 12 files changed, 123 insertions(+), 161 deletions(-) delete mode 100644 builtins/builtins.go create mode 100644 integration-cli/docker_api_version_test.go diff --git a/api/server/server.go b/api/server/server.go index d40413a4e878a..f36f082f63110 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -3,6 +3,7 @@ package server import ( "bufio" "bytes" + "runtime" "time" "encoding/base64" @@ -21,6 +22,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/api/types" + "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -28,6 +30,7 @@ import ( "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" @@ -41,6 +44,19 @@ var ( activationLock = make(chan struct{}) ) +type ServerConfig struct { + Logging bool + EnableCors bool + CorsHeaders string + Version string + SocketGroup string + Tls bool + TlsVerify bool + TlsCa string + TlsCert string + TlsKey string +} + type HttpServer struct { srv *http.Server l net.Listener @@ -187,8 +203,20 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - eng.ServeHTTP(w, r) - return nil + + v := &types.Version{ + Version: dockerversion.VERSION, + ApiVersion: api.APIVERSION, + GitCommit: dockerversion.GITCOMMIT, + GoVersion: runtime.Version(), + Os: runtime.GOOS, + Arch: runtime.GOARCH, + } + if kernelVersion, err := kernel.GetKernelVersion(); err == nil { + v.KernelVersion = kernelVersion.String() + } + + return writeJSON(w, http.StatusOK, v) } func postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1588,28 +1616,22 @@ type Server interface { // ServeApi loops through all of the protocols sent in to docker and spawns // off a go routine to setup a serving http.Server for each. -func ServeApi(job *engine.Job) error { - if len(job.Args) == 0 { - return fmt.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) - } - var ( - protoAddrs = job.Args - chErrors = make(chan error, len(protoAddrs)) - ) +func ServeApi(protoAddrs []string, conf *ServerConfig, eng *engine.Engine) error { + var chErrors = make(chan error, len(protoAddrs)) for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) if len(protoAddrParts) != 2 { - return fmt.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) + return fmt.Errorf("bad format, expected PROTO://ADDR") } go func() { logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) - srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], job) + srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], conf, eng) if err != nil { chErrors <- err return } - job.Eng.OnShutdown(func() { + eng.OnShutdown(func() { if err := srv.Close(); err != nil { logrus.Error(err) } diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 06e1a3717d620..4d53a888ee0b8 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -13,16 +13,16 @@ import ( ) // NewServer sets up the required Server and does protocol specific checking. -func NewServer(proto, addr string, job *engine.Job) (Server, error) { +func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Server, error) { var ( err error l net.Listener r = createRouter( - job.Eng, - job.GetenvBool("Logging"), - job.GetenvBool("EnableCors"), - job.Getenv("CorsHeaders"), - job.Getenv("Version"), + eng, + conf.Logging, + conf.EnableCors, + conf.CorsHeaders, + conf.Version, ) ) switch proto { @@ -52,17 +52,17 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { } return nil, nil case "tcp": - if !job.GetenvBool("TlsVerify") { + if !conf.TlsVerify { logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } - if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { + if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(conf)); err != nil { return nil, err } if err := allocateDaemonPort(addr); err != nil { return nil, err } case "unix": - if l, err = NewUnixSocket(addr, job.Getenv("SocketGroup")); err != nil { + if l, err = NewUnixSocket(addr, conf.SocketGroup); err != nil { return nil, err } default: @@ -77,8 +77,7 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { }, nil } -// Called through eng.Job("acceptconnections") -func AcceptConnections(job *engine.Job) error { +func AcceptConnections() { // Tell the init daemon we are accepting requests go systemd.SdNotify("READY=1") // close the lock so the listeners start accepting connections @@ -87,5 +86,4 @@ func AcceptConnections(job *engine.Job) error { default: close(activationLock) } - return nil } diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 78b95b26bb6dd..aca3af9fff7a8 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -34,35 +34,6 @@ func TesthttpError(t *testing.T) { } } -func TestGetVersion(t *testing.T) { - eng := engine.New() - var called bool - eng.Register("version", func(job *engine.Job) error { - called = true - v := &engine.Env{} - v.SetJson("Version", "42.1") - v.Set("ApiVersion", "1.1.1.1.1") - v.Set("GoVersion", "2.42") - v.Set("Os", "Linux") - v.Set("Arch", "x86_64") - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/version", nil, eng, t) - if !called { - t.Fatalf("handler was not called") - } - v := readEnv(r.Body, t) - if v.Get("Version") != "42.1" { - t.Fatalf("%#v\n", v) - } - if r.HeaderMap.Get("Content-Type") != "application/json" { - t.Fatalf("%#v\n", r) - } -} - func TestGetInfo(t *testing.T) { eng := engine.New() var called bool diff --git a/api/server/server_windows.go b/api/server/server_windows.go index c81313e258902..e6b23b97eafb7 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -39,13 +39,11 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { } } -// Called through eng.Job("acceptconnections") -func AcceptConnections(job *engine.Job) error { +func AcceptConnections() { // close the lock so the listeners start accepting connections select { case <-activationLock: default: close(activationLock) } - return nil } diff --git a/api/server/tcp_socket.go b/api/server/tcp_socket.go index 415542c1430c7..8454e0c58b7d2 100644 --- a/api/server/tcp_socket.go +++ b/api/server/tcp_socket.go @@ -8,7 +8,6 @@ import ( "net" "os" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/listenbuffer" ) @@ -19,16 +18,16 @@ type tlsConfig struct { Verify bool } -func tlsConfigFromJob(job *engine.Job) *tlsConfig { - verify := job.GetenvBool("TlsVerify") - if !job.GetenvBool("Tls") && !verify { +func tlsConfigFromServerConfig(conf *ServerConfig) *tlsConfig { + verify := conf.TlsVerify + if !conf.Tls && !conf.TlsVerify { return nil } return &tlsConfig{ Verify: verify, - Certificate: job.Getenv("TlsCert"), - Key: job.Getenv("TlsKey"), - CA: job.Getenv("TlsCa"), + Certificate: conf.TlsCert, + Key: conf.TlsKey, + CA: conf.TlsCa, } } diff --git a/api/types/types.go b/api/types/types.go index d7defaf85a483..cdafe7e192b11 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -1,5 +1,7 @@ package types +import "github.com/docker/docker/pkg/version" + // ContainerCreateResponse contains the information returned to a client on the // creation of a new container. type ContainerCreateResponse struct { @@ -110,3 +112,13 @@ type ContainerProcessList struct { Processes [][]string Titles []string } + +type Version struct { + Version string + ApiVersion version.Version + GitCommit string + GoVersion string + Os string + Arch string + KernelVersion string `json:",omitempty"` +} diff --git a/builtins/builtins.go b/builtins/builtins.go deleted file mode 100644 index 8957b58332414..0000000000000 --- a/builtins/builtins.go +++ /dev/null @@ -1,48 +0,0 @@ -package builtins - -import ( - "runtime" - - "github.com/docker/docker/api" - apiserver "github.com/docker/docker/api/server" - "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/parsers/kernel" -) - -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 nil -} - -// remote: a RESTful api for cross-docker communication -func remote(eng *engine.Engine) error { - if err := eng.Register("serveapi", apiserver.ServeApi); err != nil { - return err - } - return eng.Register("acceptconnections", apiserver.AcceptConnections) -} - -// builtins jobs independent of any subsystem -func dockerVersion(job *engine.Job) error { - v := &engine.Env{} - v.SetJson("Version", dockerversion.VERSION) - v.SetJson("ApiVersion", api.APIVERSION) - v.SetJson("GitCommit", dockerversion.GITCOMMIT) - v.Set("GoVersion", runtime.Version()) - v.Set("Os", runtime.GOOS) - v.Set("Arch", runtime.GOARCH) - if kernelVersion, err := kernel.GetKernelVersion(); err == nil { - v.Set("KernelVersion", kernelVersion.String()) - } - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil -} diff --git a/docker/daemon.go b/docker/daemon.go index b1a92c52e592b..769b4f5bf68b0 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -10,9 +10,9 @@ import ( "strings" "github.com/Sirupsen/logrus" + apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/builder" - "github.com/docker/docker/builtins" "github.com/docker/docker/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" _ "github.com/docker/docker/daemon/execdriver/native" @@ -93,11 +93,6 @@ func mainDaemon() { } daemonCfg.TrustKeyPath = *flTrustKey - // Load builtins - if err := builtins.Register(eng); err != nil { - logrus.Fatal(err) - } - registryService := registry.NewService(registryCfg) // load the daemon in the background so we can immediately start // the http api so that connections don't fail while the daemon @@ -127,33 +122,30 @@ func mainDaemon() { // after the daemon is done setting up we can tell the api to start // accepting connections - if err := eng.Job("acceptconnections").Run(); err != nil { - daemonInitWait <- err - return - } + apiserver.AcceptConnections() + daemonInitWait <- nil }() - // Serve api - job := eng.Job("serveapi", flHosts...) - job.SetenvBool("Logging", true) - job.SetenvBool("EnableCors", daemonCfg.EnableCors) - job.Setenv("CorsHeaders", daemonCfg.CorsHeaders) - job.Setenv("Version", dockerversion.VERSION) - job.Setenv("SocketGroup", daemonCfg.SocketGroup) - - job.SetenvBool("Tls", *flTls) - job.SetenvBool("TlsVerify", *flTlsVerify) - job.Setenv("TlsCa", *flCa) - job.Setenv("TlsCert", *flCert) - job.Setenv("TlsKey", *flKey) - - // The serve API job never exits unless an error occurs + serverConfig := &apiserver.ServerConfig{ + Logging: true, + EnableCors: daemonCfg.EnableCors, + CorsHeaders: daemonCfg.CorsHeaders, + Version: dockerversion.VERSION, + SocketGroup: daemonCfg.SocketGroup, + Tls: *flTls, + TlsVerify: *flTlsVerify, + TlsCa: *flCa, + TlsCert: *flCert, + TlsKey: *flKey, + } + + // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so // daemon doesn't exit serveAPIWait := make(chan error) go func() { - if err := job.Run(); err != nil { + if err := apiserver.ServeApi(flHosts, serverConfig, eng); err != nil { logrus.Errorf("ServeAPI error: %v", err) serveAPIWait <- err return diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go new file mode 100644 index 0000000000000..2846fb1d398ce --- /dev/null +++ b/integration-cli/docker_api_version_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "encoding/json" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/autogen/dockerversion" +) + +func TestGetVersion(t *testing.T) { + _, body, err := sockRequest("GET", "/version", nil) + if err != nil { + t.Fatal(err) + } + var v types.Version + if err := json.Unmarshal(body, &v); err != nil { + t.Fatal(err) + } + + if v.Version != dockerversion.VERSION { + t.Fatal("Version mismatch") + } +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index e990f086ae1dc..b3cd1c73a2c9c 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3363,7 +3363,7 @@ func TestRunRestartMaxRetries(t *testing.T) { t.Fatal(string(out), err) } id := strings.TrimSpace(string(out)) - if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 5); err != nil { + if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 10); err != nil { t.Fatal(err) } count, err := inspectField(id, "RestartCount") diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 2e456eabfd630..b399c37455c80 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/Sirupsen/logrus" + apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" @@ -157,9 +158,9 @@ func spawnGlobalDaemon() { Scheme: testDaemonProto, Host: testDaemonAddr, } - job := eng.Job("serveapi", listenURL.String()) - job.SetenvBool("Logging", true) - if err := job.Run(); err != nil { + + serverConfig := &apiserver.ServerConfig{Logging: true} + if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -168,9 +169,7 @@ func spawnGlobalDaemon() { // FIXME: use inmem transports instead of tcp time.Sleep(time.Second) - if err := eng.Job("acceptconnections").Run(); err != nil { - logrus.Fatalf("Unable to accept connections for test api: %s", err) - } + apiserver.AcceptConnections() } func spawnLegitHttpsDaemon() { @@ -207,14 +206,15 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { Scheme: testDaemonHttpsProto, Host: addr, } - job := eng.Job("serveapi", listenURL.String()) - job.SetenvBool("Logging", true) - job.SetenvBool("Tls", true) - job.SetenvBool("TlsVerify", true) - job.Setenv("TlsCa", cacert) - job.Setenv("TlsCert", cert) - job.Setenv("TlsKey", key) - if err := job.Run(); err != nil { + serverConfig := &apiserver.ServerConfig{ + Logging: true, + Tls: true, + TlsVerify: true, + TlsCa: cacert, + TlsCert: cert, + TlsKey: key, + } + if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -222,9 +222,8 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { // Give some time to ListenAndServer to actually start time.Sleep(time.Second) - if err := eng.Job("acceptconnections").Run(); err != nil { - logrus.Fatalf("Unable to accept connections for test api: %s", err) - } + apiserver.AcceptConnections() + return eng } diff --git a/integration/utils_test.go b/integration/utils_test.go index befd924eaf880..9479d4296cd81 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -17,7 +17,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "github.com/docker/docker/api/types" - "github.com/docker/docker/builtins" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -170,10 +169,6 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { eng := engine.New() eng.Logging = false - // Load default plugins - if err := builtins.Register(eng); err != nil { - t.Fatal(err) - } // (This is manually copied and modified from main() until we have a more generic plugin system) cfg := &daemon.Config{ From 37b9ce61ac5aa762cf0ad34d84bc1924807a7617 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Steenis Date: Fri, 17 Apr 2015 14:54:40 +0200 Subject: [PATCH 165/332] Added Debian 8 note for adding backports Signed-off-by: Sebastiaan van Steenis --- docs/sources/installation/debian.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index e3fb6e2921ee1..aeee1ecb1f680 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -11,8 +11,7 @@ Docker is supported on the following versions of Debian: ## 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. +Debian 8 comes with a 3.16.0 Linux kernel, the `docker.io` package can be found in the `jessie-backports` repository. Reasoning behind this can be found here. Instructions how to enable the backports repository can be found here. > **Note**: > Debian contains a much older KDE3/GNOME2 package called ``docker``, so the @@ -20,6 +19,8 @@ installs all its prerequisites from Debian's repository. ### Installation +Make sure you enabled the `jessie-backports` repository, as stated above. + To install the latest Debian package (may not be the latest Docker release): $ sudo apt-get update From 621b601b3c602aab5ef0f07903fdf413881bb261 Mon Sep 17 00:00:00 2001 From: bobby abbott Date: Mon, 13 Apr 2015 22:16:19 -0700 Subject: [PATCH 166/332] Removed unnecessary error output from dockerCmd Changed method declaration. Fixed all calls to dockerCmd method to reflect the change. resolves #12355 Signed-off-by: bobby abbott --- integration-cli/docker_api_containers_test.go | 6 +- integration-cli/docker_api_images_test.go | 2 +- integration-cli/docker_cli_attach_test.go | 2 +- .../docker_cli_attach_unix_test.go | 4 +- integration-cli/docker_cli_build_test.go | 13 +- integration-cli/docker_cli_commit_test.go | 4 +- integration-cli/docker_cli_cp_test.go | 206 ++++++++---------- integration-cli/docker_cli_create_test.go | 2 +- integration-cli/docker_cli_events_test.go | 2 +- integration-cli/docker_cli_exec_test.go | 4 +- integration-cli/docker_cli_links_test.go | 4 +- integration-cli/docker_cli_pause_test.go | 4 +- integration-cli/docker_cli_rmi_test.go | 20 +- integration-cli/docker_cli_run_test.go | 18 +- integration-cli/docker_cli_start_test.go | 2 +- integration-cli/docker_utils.go | 10 +- 16 files changed, 127 insertions(+), 176 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index cabbfc2045918..b76eb6ba2a310 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -627,7 +627,7 @@ func TestContainerApiPause(t *testing.T) { func TestContainerApiTop(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") + out, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") id := strings.TrimSpace(out) if err := waitRun(id); err != nil { t.Fatal(err) @@ -667,7 +667,7 @@ func TestContainerApiTop(t *testing.T) { } func TestContainerApiCommit(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") + out, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") id := strings.TrimSpace(out) name := "testcommit" @@ -714,7 +714,7 @@ func TestContainerApiCreate(t *testing.T) { t.Fatal(err) } - out, _, _ := dockerCmd(t, "start", "-a", container.Id) + out, _ := dockerCmd(t, "start", "-a", container.Id) if strings.TrimSpace(out) != "/test" { t.Fatalf("expected output `/test`, got %q", out) } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 083d63204f1c7..276ee7f3c742f 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -91,7 +91,7 @@ func TestApiImagesSaveAndLoad(t *testing.T) { } defer loadBody.Close() - out, _, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) + out, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) if strings.TrimSpace(out) != id { t.Fatal("load did not work properly") } diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 04cb593989866..08b109f883260 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -138,7 +138,7 @@ func TestAttachTtyWithoutStdin(t *testing.T) { func TestAttachDisconnect(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") + out, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") id := strings.TrimSpace(out) cmd := exec.Command(dockerBinary, "attach", id) diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index ebc3804e3e8a7..e17256cf74d37 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -142,7 +142,7 @@ func TestAttachAfterDetach(t *testing.T) { // TestAttachDetach checks that attach in tty mode can be detached using the long container ID func TestAttachDetach(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") id := strings.TrimSpace(out) if err := waitRun(id); err != nil { t.Fatal(err) @@ -217,7 +217,7 @@ func TestAttachDetach(t *testing.T) { // TestAttachDetachTruncatedID checks that attach in tty mode can be detached func TestAttachDetachTruncatedID(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") id := stringid.TruncateID(strings.TrimSpace(out)) if err := waitRun(id); err != nil { t.Fatal(err) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 1ef5088e246f8..44370d4b9ff9a 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -3294,7 +3294,7 @@ func TestBuildNoContext(t *testing.T) { t.Fatalf("build failed to complete: %v %v", out, err) } - if out, _, err := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" || err != nil { + if out, _ := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" { t.Fatalf("run produced invalid output: %q, expected %q", out, "ok") } @@ -5562,10 +5562,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { if err != nil { t.Fatal(err, out) } - out, _, err = dockerCmd(t, "ps", "-lq") - if err != nil { - t.Fatal(err, out) - } + out, _ = dockerCmd(t, "ps", "-lq") cID := strings.TrimSpace(out) @@ -5593,10 +5590,8 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { } // Make sure constraints aren't saved to image - _, _, err = dockerCmd(t, "run", "--name=test", name) - if err != nil { - t.Fatal(err) - } + _, _ = dockerCmd(t, "run", "--name=test", name) + cfg, err = inspectFieldJSON("test", "HostConfig") if err != nil { t.Fatal(err) diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index a51360a9b68ae..5d4288192350d 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -284,13 +284,13 @@ func TestCommitChange(t *testing.T) { func TestCommitMergeConfigRun(t *testing.T) { defer deleteAllContainers() name := "commit-test" - out, _, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") + out, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") id := strings.TrimSpace(out) dockerCmd(t, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") defer deleteImages("commit-test") - out, _, _ = dockerCmd(t, "run", "--name", name, "commit-test") + out, _ = dockerCmd(t, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { t.Fatal("run config in commited container was not merged") } diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 12da76abcaa5a..b577e82982608 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -25,17 +25,17 @@ const ( // Test for #5656 // Check that garbage paths don't escape the container's rootfs func TestCpGarbagePath(t *testing.T) { - out, exitCode, err := dockerCmd(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) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -61,10 +61,7 @@ func TestCpGarbagePath(t *testing.T) { path := path.Join("../../../../../../../../../../../../", cpFullPath) - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from garbage path: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -87,17 +84,17 @@ func TestCpGarbagePath(t *testing.T) { // Check that relative paths are relative to the container's rootfs func TestCpRelativePath(t *testing.T) { - out, exitCode, err := dockerCmd(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) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -131,10 +128,7 @@ func TestCpRelativePath(t *testing.T) { t.Fatalf("path %s was assumed to be an absolute path", cpFullPath) } - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from relative path: %s:%s %s", cleanedContainerID, relPath, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -157,17 +151,17 @@ func TestCpRelativePath(t *testing.T) { // Check that absolute paths are relative to the container's rootfs func TestCpAbsolutePath(t *testing.T) { - out, exitCode, err := dockerCmd(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) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -194,10 +188,7 @@ func TestCpAbsolutePath(t *testing.T) { path := cpFullPath - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -221,17 +212,17 @@ func TestCpAbsolutePath(t *testing.T) { // Test for #5619 // Check that absolute symlinks are still relative to the container's rootfs func TestCpAbsoluteSymlink(t *testing.T) { - out, exitCode, err := dockerCmd(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) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -258,10 +249,7 @@ func TestCpAbsoluteSymlink(t *testing.T) { path := path.Join("/", "container_path") - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -285,17 +273,17 @@ func TestCpAbsoluteSymlink(t *testing.T) { // 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 := dockerCmd(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) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -322,10 +310,7 @@ func TestCpSymlinkComponent(t *testing.T) { path := path.Join("/", "container_path", cpTestName) - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from symlink path component: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -350,17 +335,17 @@ func TestCpSymlinkComponent(t *testing.T) { func TestCpUnprivilegedUser(t *testing.T) { testRequires(t, UnixCli) // uses chmod/su: not available on windows - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") @@ -393,24 +378,21 @@ func TestCpSpecialFiles(t *testing.T) { } defer os.RemoveAll(outDir) - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } // Copy actual /etc/resolv.conf - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) - if err != nil { - t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/resolv.conf", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf") actual, err := ioutil.ReadFile(outDir + "/resolv.conf") @@ -420,10 +402,7 @@ func TestCpSpecialFiles(t *testing.T) { } // Copy actual /etc/hosts - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/hosts", outDir) - if err != nil { - t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/hosts", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hosts", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts") actual, err = ioutil.ReadFile(outDir + "/hosts") @@ -433,10 +412,7 @@ func TestCpSpecialFiles(t *testing.T) { } // Copy actual /etc/resolv.conf - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/hostname", outDir) - if err != nil { - t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/hostname", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hostname", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname") actual, err = ioutil.ReadFile(outDir + "/hostname") @@ -466,24 +442,22 @@ func TestCpVolumePath(t *testing.T) { t.Fatal(err) } - out, exitCode, err := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer dockerCmd(t, "rm", "-fv", cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } // Copy actual volume path - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir) - if err != nil { - t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir) + stat, err := os.Stat(outDir + "/foo") if err != nil { t.Fatal(err) @@ -500,10 +474,8 @@ func TestCpVolumePath(t *testing.T) { } // Copy file nested in volume - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir) - if err != nil { - t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir) + stat, err = os.Stat(outDir + "/bar") if err != nil { t.Fatal(err) @@ -513,10 +485,7 @@ func TestCpVolumePath(t *testing.T) { } // Copy Bind-mounted dir - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir) - if err != nil { - t.Fatalf("couldn't copy from bind-mounted volume path: %s:%s %v", cleanedContainerID, "/baz", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir) stat, err = os.Stat(outDir + "/baz") if err != nil { t.Fatal(err) @@ -526,7 +495,7 @@ func TestCpVolumePath(t *testing.T) { } // Copy file nested in bind-mounted dir - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir) + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir) fb, err := ioutil.ReadFile(outDir + "/baz/test") if err != nil { t.Fatal(err) @@ -540,7 +509,7 @@ func TestCpVolumePath(t *testing.T) { } // Copy bind-mounted file - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir) + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir) fb, err = ioutil.ReadFile(outDir + "/test") if err != nil { t.Fatal(err) @@ -557,17 +526,17 @@ func TestCpVolumePath(t *testing.T) { } func TestCpToDot(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") @@ -583,10 +552,7 @@ func TestCpToDot(t *testing.T) { if err := os.Chdir(tmpdir); err != nil { t.Fatal(err) } - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", ".") - if err != nil { - t.Fatalf("couldn't docker cp to \".\" path: %s", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", ".") content, err := ioutil.ReadFile("./test") if string(content) != "lololol\n" { t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") @@ -595,22 +561,23 @@ func TestCpToDot(t *testing.T) { } func TestCpToStdout(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") - if err != nil || exitCode != 0 { - t.Fatalf("failed to create a container:%s\n%s", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") + if exitCode != 0 { + t.Fatalf("failed to create a container:%s\n", out) } cID := strings.TrimSpace(out) defer deleteContainer(cID) - out, _, err = dockerCmd(t, "wait", cID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatalf("failed to set up container:%s\n%s", out, err) + out, _ = dockerCmd(t, "wait", cID) + if strings.TrimSpace(out) != "0" { + t.Fatalf("failed to set up container:%s\n", out) } - out, _, err = runCommandPipelineWithOutput( + out, _, err := runCommandPipelineWithOutput( exec.Command(dockerBinary, "cp", cID+":/test", "-"), exec.Command("tar", "-vtf", "-")) + if err != nil { t.Fatalf("Failed to run commands: %s", err) } @@ -624,17 +591,17 @@ func TestCpToStdout(t *testing.T) { func TestCpNameHasColon(t *testing.T) { testRequires(t, SameHostDaemon) - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") @@ -642,10 +609,7 @@ func TestCpNameHasColon(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(tmpdir) - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) - if err != nil { - t.Fatalf("couldn't docker cp to %s: %s", tmpdir, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) content, err := ioutil.ReadFile(tmpdir + "/te:s:t") if string(content) != "lololol\n" { t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index ac10b264eb7f9..a8cc09106ea4b 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -307,7 +307,7 @@ func TestCreateLabelFromImage(t *testing.T) { } func TestCreateHostnameWithNumber(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") + out, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") if strings.TrimSpace(out) != "web.0" { t.Fatalf("hostname not set, expected `web.0`, got: %s", out) } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 767af55018392..3e4c005b2cc95 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -38,7 +38,7 @@ func TestEventsUntag(t *testing.T) { func TestEventsContainerFailStartDie(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] eventsCmd := exec.Command(dockerBinary, "run", "--name", "testeventdie", image, "blerg") _, _, err := runCommandWithOutput(eventsCmd) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index d4e4839717857..6a0999eefbfce 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -500,12 +500,12 @@ func TestLinksPingLinkedContainersOnRename(t *testing.T) { defer deleteAllContainers() var out string - out, _, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) if idA == "" { t.Fatal(out, "id should not be nil") } - out, _, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) if idB == "" { t.Fatal(out, "id should not be nil") diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 04718c23f0bed..0f48cbb1e2057 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -110,9 +110,9 @@ func TestLinksPingLinkedContainers(t *testing.T) { func TestLinksPingLinkedContainersAfterRename(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) - out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) dockerCmd(t, "rename", "container1", "container_new") dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go index 41147b206291e..6c620e7cde943 100644 --- a/integration-cli/docker_cli_pause_test.go +++ b/integration-cli/docker_cli_pause_test.go @@ -12,7 +12,7 @@ func TestPause(t *testing.T) { defer unpauseAllContainers() name := "testeventpause" - out, _, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] dockerCmd(t, "run", "-d", "--name", name, image, "top") @@ -55,7 +55,7 @@ func TestPauseMultipleContainers(t *testing.T) { "testpausewithmorecontainers1", "testpausewithmorecontainers2", } - out, _, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] for _, name := range containers { dockerCmd(t, "run", "-d", "--name", name, image, "top") diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 7161a09228c71..f6b12aa6c7df8 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -29,7 +29,7 @@ func TestRmiWithContainerFails(t *testing.T) { } // make sure it didn't delete the busybox name - images, _, _ := dockerCmd(t, "images") + images, _ := dockerCmd(t, "images") if !strings.Contains(images, "busybox") { t.Fatalf("The name 'busybox' should not have been removed from images: %q", images) } @@ -40,19 +40,19 @@ func TestRmiWithContainerFails(t *testing.T) { } func TestRmiTag(t *testing.T) { - imagesBefore, _, _ := dockerCmd(t, "images", "-a") + imagesBefore, _ := dockerCmd(t, "images", "-a") dockerCmd(t, "tag", "busybox", "utest:tag1") dockerCmd(t, "tag", "busybox", "utest/docker:tag2") dockerCmd(t, "tag", "busybox", "utest:5000/docker:tag3") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+3 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } dockerCmd(t, "rmi", "utest/docker:tag2") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+2 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } @@ -60,7 +60,7 @@ func TestRmiTag(t *testing.T) { } dockerCmd(t, "rmi", "utest:5000/docker:tag3") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+1 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } @@ -68,7 +68,7 @@ func TestRmiTag(t *testing.T) { } dockerCmd(t, "rmi", "utest:tag1") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+0 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } @@ -90,22 +90,22 @@ func TestRmiImgIDForce(t *testing.T) { t.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) } - imagesBefore, _, _ := dockerCmd(t, "images", "-a") + imagesBefore, _ := dockerCmd(t, "images", "-a") dockerCmd(t, "tag", "busybox-test", "utest:tag1") dockerCmd(t, "tag", "busybox-test", "utest:tag2") dockerCmd(t, "tag", "busybox-test", "utest/docker:tag3") dockerCmd(t, "tag", "busybox-test", "utest:5000/docker:tag4") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 { t.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) } } - out, _, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") + out, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") imgID := strings.TrimSpace(out) dockerCmd(t, "rmi", "-f", imgID) { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Contains(imagesAfter, imgID[:12]) { t.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index e990f086ae1dc..137f1776e2976 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -501,9 +501,7 @@ func TestRunCreateVolumesInSymlinkDir(t *testing.T) { } defer deleteImages(name) - if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil { - t.Fatal(err, out) - } + dockerCmd(t, "run", "-v", "/test/test", name) logDone("run - create volume in symlink directory") } @@ -1058,9 +1056,9 @@ func TestRunLoopbackWhenNetworkDisabled(t *testing.T) { func TestRunNetHostNotAllowedWithLinks(t *testing.T) { defer deleteAllContainers() - _, _, err := dockerCmd(t, "run", "--name", "linked", "busybox", "true") + _, _ = dockerCmd(t, "run", "--name", "linked", "busybox", "true") cmd := exec.Command(dockerBinary, "run", "--net=host", "--link", "linked:linked", "busybox", "true") - _, _, err = runCommandWithOutput(cmd) + _, _, err := runCommandWithOutput(cmd) if err == nil { t.Fatal("Expected error") } @@ -1493,10 +1491,7 @@ func TestRunModeHostname(t *testing.T) { func TestRunRootWorkdir(t *testing.T) { defer deleteAllContainers() - s, _, err := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd") - if err != nil { - t.Fatal(s, err) - } + s, _ := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd") if s != "/\n" { t.Fatalf("pwd returned %q (expected /\\n)", s) } @@ -1507,10 +1502,7 @@ func TestRunRootWorkdir(t *testing.T) { func TestRunAllowBindMountingRoot(t *testing.T) { defer deleteAllContainers() - s, _, err := dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host") - if err != nil { - t.Fatal(s, err) - } + _, _ = dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host") logDone("run - bind mount / as volume") } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index c703e434c1237..342209dc4b539 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -157,7 +157,7 @@ func TestStartVolumesFromFailsCleanly(t *testing.T) { dockerCmd(t, "start", "consumer") // Check that we have the volumes we want - out, _, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer") + out, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer") nVolumes := strings.Trim(out, " \r\n'") if nVolumes != "2" { t.Fatalf("Missing volumes: expected 2, got %s", nVolumes) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 367e518094739..4e622b84c79fc 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -478,12 +478,12 @@ func pullImageIfNotExist(image string) (err error) { return } -func dockerCmd(t *testing.T, args ...string) (string, int, error) { +func dockerCmd(t *testing.T, args ...string) (string, int) { out, status, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) if err != nil { t.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err) } - return out, status, err + return out, status } // execute a docker command with a timeout @@ -784,9 +784,9 @@ func getContainerState(t *testing.T, id string) (int, bool, error) { exitStatus int running bool ) - out, exitCode, err := dockerCmd(t, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) - if err != nil || exitCode != 0 { - return 0, false, fmt.Errorf("%q doesn't exist: %s", id, err) + out, exitCode := dockerCmd(t, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) + if exitCode != 0 { + return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out) } out = strings.Trim(out, "\n") From 6e38a53f96403b4cbd38e49e6b294128ed054a20 Mon Sep 17 00:00:00 2001 From: Simei He Date: Wed, 15 Apr 2015 19:43:15 +0800 Subject: [PATCH 167/332] remove job from pull and import Closes #12396 Signed-off-by: Simei He Signed-off-by: Alexander Morozov --- api/server/server.go | 54 ++++++++++++++++++++++++------------- builder/internals.go | 18 ++++++++----- graph/import.go | 37 +++++++++++++------------ graph/pull.go | 37 +++++++++++-------------- graph/service.go | 2 -- integration-cli/utils.go | 1 + integration/runtime_test.go | 11 +++++--- 7 files changed, 89 insertions(+), 71 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 7ebeb4b6aaf49..429767298608e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -639,7 +639,6 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon image = r.Form.Get("fromImage") repo = r.Form.Get("repo") tag = r.Form.Get("tag") - job *engine.Job ) authEncoded := r.Header.Get("X-Registry-Auth") authConfig := ®istry.AuthConfig{} @@ -651,6 +650,9 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon authConfig = ®istry.AuthConfig{} } } + + d := getDaemon(eng) + if image != "" { //pull if tag == "" { image, tag = parsers.ParseRepositoryTag(image) @@ -661,31 +663,45 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon metaHeaders[k] = v } } - job = eng.Job("pull", image, tag) - job.SetenvBool("parallel", version.GreaterThan("1.3")) - job.SetenvJson("metaHeaders", metaHeaders) - job.SetenvJson("authConfig", authConfig) + + imagePullConfig := &graph.ImagePullConfig{ + Parallel: version.GreaterThan("1.3"), + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + OutStream: utils.NewWriteFlusher(w), + } + if version.GreaterThan("1.0") { + imagePullConfig.Json = true + w.Header().Set("Content-Type", "application/json") + } else { + imagePullConfig.Json = false + } + + if err := d.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { + return err + } } else { //import if tag == "" { repo, tag = parsers.ParseRepositoryTag(repo) } - job = eng.Job("import", r.Form.Get("fromSrc"), repo, tag) - job.Stdin.Add(r.Body) - job.SetenvList("changes", r.Form["changes"]) - } - if version.GreaterThan("1.0") { - job.SetenvBool("json", true) - streamJSON(job, w, true) - } else { - job.Stdout.Add(utils.NewWriteFlusher(w)) - } - if err := job.Run(); err != nil { - if !job.Stdout.Used() { + src := r.Form.Get("fromSrc") + imageImportConfig := &graph.ImageImportConfig{ + Changes: r.Form["changes"], + InConfig: r.Body, + OutStream: utils.NewWriteFlusher(w), + } + if version.GreaterThan("1.0") { + imageImportConfig.Json = true + w.Header().Set("Content-Type", "application/json") + } else { + imageImportConfig.Json = false + } + + if err := d.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { return err } - sf := streamformatter.NewStreamFormatter(version.GreaterThan("1.0")) - w.Write(sf.FormatError(err)) + } return nil diff --git a/builder/internals.go b/builder/internals.go index 728ccde8aefdc..dec84ffe0c676 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -22,6 +22,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" + "github.com/docker/docker/graph" imagepkg "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" @@ -434,7 +435,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { if tag == "" { tag = "latest" } - job := b.Engine.Job("pull", remote, tag) + pullRegistryAuth := b.AuthConfig if len(b.AuthConfigFile.Configs) > 0 { // The request came with a full auth config file, we prefer to use that @@ -445,13 +446,18 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index) pullRegistryAuth = &resolvedAuth } - job.SetenvBool("json", b.StreamFormatter.Json()) - job.SetenvBool("parallel", true) - job.SetenvJson("authConfig", pullRegistryAuth) - job.Stdout.Add(ioutils.NopWriteCloser(b.OutOld)) - if err := job.Run(); err != nil { + + imagePullConfig := &graph.ImagePullConfig{ + Parallel: true, + AuthConfig: pullRegistryAuth, + OutStream: ioutils.NopWriteCloser(b.OutOld), + Json: b.StreamFormatter.Json(), + } + + if err := b.Daemon.Repositories().Pull(remote, tag, imagePullConfig, b.Engine); err != nil { return nil, err } + image, err := b.Daemon.Repositories().LookupImage(name) if err != nil { return nil, err diff --git a/graph/import.go b/graph/import.go index 0ba03d0f53dd6..a970b22d9b8c3 100644 --- a/graph/import.go +++ b/graph/import.go @@ -3,7 +3,7 @@ package graph import ( "bytes" "encoding/json" - "fmt" + "io" "net/http" "net/url" @@ -16,26 +16,25 @@ import ( "github.com/docker/docker/utils" ) -func (s *TagStore) CmdImport(job *engine.Job) error { - if n := len(job.Args); n != 2 && n != 3 { - return fmt.Errorf("Usage: %s SRC REPO [TAG]", job.Name) - } +type ImageImportConfig struct { + Changes []string + InConfig io.ReadCloser + Json bool + OutStream io.Writer + //OutStream WriteFlusher +} + +func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig, eng *engine.Engine) error { var ( - src = job.Args[0] - repo = job.Args[1] - tag string - sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) + sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) archive archive.ArchiveReader resp *http.Response stdoutBuffer = bytes.NewBuffer(nil) newConfig runconfig.Config ) - if len(job.Args) > 2 { - tag = job.Args[2] - } if src == "-" { - archive = job.Stdin + archive = imageImportConfig.InConfig } else { u, err := url.Parse(src) if err != nil { @@ -46,14 +45,14 @@ func (s *TagStore) CmdImport(job *engine.Job) error { u.Host = src u.Path = "" } - job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u)) + imageImportConfig.OutStream.Write(sf.FormatStatus("", "Downloading from %s", u)) resp, err = httputils.Download(u.String()) if err != nil { return err } progressReader := progressreader.New(progressreader.Config{ In: resp.Body, - Out: job.Stdout, + Out: imageImportConfig.OutStream, Formatter: sf, Size: int(resp.ContentLength), NewLines: true, @@ -64,11 +63,11 @@ func (s *TagStore) CmdImport(job *engine.Job) error { archive = progressReader } - buildConfigJob := job.Eng.Job("build_config") + buildConfigJob := eng.Job("build_config") buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.Setenv("changes", job.Getenv("changes")) + buildConfigJob.SetenvList("changes", imageImportConfig.Changes) // FIXME this should be remove when we remove deprecated config param - buildConfigJob.Setenv("config", job.Getenv("config")) + //buildConfigJob.Setenv("config", job.Getenv("config")) if err := buildConfigJob.Run(); err != nil { return err @@ -87,7 +86,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { return err } } - job.Stdout.Write(sf.FormatStatus("", img.ID)) + imageImportConfig.OutStream.Write(sf.FormatStatus("", img.ID)) logID := img.ID if tag != "" { logID = utils.ImageReference(logID, tag) diff --git a/graph/pull.go b/graph/pull.go index a9f91b4a27ce8..4cb5957a54761 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -21,37 +21,30 @@ import ( "github.com/docker/docker/utils" ) -func (s *TagStore) CmdPull(job *engine.Job) error { - if n := len(job.Args); n != 1 && n != 2 { - return fmt.Errorf("Usage: %s IMAGE [TAG|DIGEST]", job.Name) - } +type ImagePullConfig struct { + Parallel bool + MetaHeaders map[string][]string + AuthConfig *registry.AuthConfig + Json bool + OutStream io.Writer +} +func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig, eng *engine.Engine) error { var ( - localName = job.Args[0] - tag string - sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) - authConfig = ®istry.AuthConfig{} - metaHeaders map[string][]string + sf = streamformatter.NewStreamFormatter(imagePullConfig.Json) ) // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := s.registryService.ResolveRepository(localName) + repoInfo, err := s.registryService.ResolveRepository(image) if err != nil { return err } - if len(job.Args) > 1 { - tag = job.Args[1] - } - - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", &metaHeaders) - c, err := s.poolAdd("pull", utils.ImageReference(repoInfo.LocalName, tag)) if err != nil { if c != nil { // Another pull of the same repository is already taking place; just wait for it to finish - job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName)) + imagePullConfig.OutStream.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName)) <-c return nil } @@ -65,7 +58,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { return err } - r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true) + r, err := registry.NewSession(imagePullConfig.AuthConfig, registry.HTTPRequestFactory(imagePullConfig.MetaHeaders), endpoint, true) if err != nil { return err } @@ -77,14 +70,14 @@ func (s *TagStore) CmdPull(job *engine.Job) error { if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Index.Official || endpoint.Version == registry.APIVersion2) { if repoInfo.Official { - j := job.Eng.Job("trust_update_base") + j := eng.Job("trust_update_base") if err = j.Run(); err != nil { logrus.Errorf("error updating trust base graph: %s", err) } } logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) - if err := s.pullV2Repository(job.Eng, r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err == nil { + if err := s.pullV2Repository(eng, r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err == nil { s.eventsService.Log("pull", logName, "") return nil } else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable { @@ -95,7 +88,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { } logrus.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName) - if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil { + if err = s.pullRepository(r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err != nil { return err } diff --git a/graph/service.go b/graph/service.go index 46f83103dbf42..adc775235eb75 100644 --- a/graph/service.go +++ b/graph/service.go @@ -19,8 +19,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_export": s.CmdImageExport, "viz": s.CmdViz, "load": s.CmdLoad, - "import": s.CmdImport, - "pull": s.CmdPull, "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 1fcf44535eeb2..c3a84bbc5928e 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -143,6 +143,7 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in if i > 0 { prevCmd := cmds[i-1] cmd.Stdin, err = prevCmd.StdoutPipe() + if err != nil { return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 6881fbea60ad3..cd3033939feca 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -27,6 +27,7 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -132,9 +133,13 @@ func setupBaseImage() { // If the unit test is not found, try to download it. if err := job.Run(); err != nil || img.Get("Id") != unitTestImageID { // Retrieve the Image - job = eng.Job("pull", unitTestImageName) - job.Stdout.Add(ioutils.NopWriteCloser(os.Stdout)) - if err := job.Run(); err != nil { + imagePullConfig := &graph.ImagePullConfig{ + Parallel: true, + OutStream: ioutils.NopWriteCloser(os.Stdout), + AuthConfig: ®istry.AuthConfig{}, + } + d := getDaemon(eng) + if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig, eng); err != nil { logrus.Fatalf("Unable to pull the test image: %s", err) } } From cdc63ce5d032de593fc2fd13997311b316c0103b Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Fri, 17 Apr 2015 10:56:12 -0700 Subject: [PATCH 168/332] Updated message severity in graphdriver Signed-off-by: Megan Kostick --- daemon/graphdriver/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 0260e532d942a..c57dd87136cb7 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -146,7 +146,7 @@ func GetDriver(name, home string, options []string) (Driver, error) { func New(root string, options []string) (driver Driver, err error) { for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { - logrus.Infof("[graphdriver] trying provided driver %q", name) // so the logs show specified driver + logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver return GetDriver(name, root, options) } } From 3a883672417fcb2b3ac0d57d992285849840bfb2 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Fri, 17 Apr 2015 12:53:30 -0700 Subject: [PATCH 169/332] Updates to Compose docs and ENV vars - Compose teamhad forgotten some documentation - Updated ENV for Distribution also - Forgot one of the readability sections Signed-off-by: Mary Anthony --- docs/Dockerfile | 36 ++++++++++++++++++++++++++++++++---- docs/mkdocs.yml | 23 +++++++++++------------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index d82c64df83ebb..d1a0f04c0a824 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -6,11 +6,10 @@ MAINTAINER Sven Dowideit (@SvenDowideit) # This section ensures we pull the correct version of each # sub project -ENV COMPOSE_BRANCH 1.2.0 +ENV COMPOSE_BRANCH release ENV SWARM_BRANCH v0.2.0 ENV MACHINE_BRANCH master -ENV DISTRIB_BRANCH v2.0.0 - +ENV DISTRIB_BRANCH release/2.0 # TODO: need the full repo source to get the git version info @@ -33,8 +32,10 @@ COPY ./s3_website.json s3_website.json COPY ./release.sh release.sh +####################### # Docker Distribution -# +######################## + #ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png @@ -64,45 +65,72 @@ RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/jso ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md +####################### # Docker Swarm +####################### + #ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/index.md /docs/sources/swarm/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/index.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/discovery/README.md /docs/sources/swarm/discovery.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/discovery.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/api/README.md /docs/sources/swarm/API.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/API.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/filter.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/strategy.md +####################### # Docker Machine +####################### #ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-machine.yml + ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/index.md /docs/sources/machine/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/machine/index.md +####################### # Docker Compose +####################### + #ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-compose.yml + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md /docs/sources/compose/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/index.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md /docs/sources/compose/install.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/install.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md /docs/sources/compose/cli.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/cli.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md /docs/sources/compose/yml.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/yml.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md /docs/sources/compose/env.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/env.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md /docs/sources/compose/completion.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/completion.md ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md /docs/sources/compose/django.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/django.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md /docs/sources/compose/rails.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/rails.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md /docs/sources/compose/wordpress.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/extends.md /docs/sources/compose/extends.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/extends.md + +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/production.md /docs/sources/compose/production.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/production.md + # Then build everything together, ready for mkdocs RUN /docs/build.sh \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f159ff28bf06a..5fb99ffc4057d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Docker Documentation -#site_url: https://docs.docker.com/ +#site_url: http://docs.docker.com/ site_url: / site_description: Documentation for fast and lightweight Docker container based virtualization framework. site_favicon: img/favicon.png @@ -66,6 +66,8 @@ pages: - ['userguide/level1.md', '**HIDDEN**' ] - ['userguide/level2.md', '**HIDDEN**' ] - ['compose/index.md', 'User Guide', 'Docker Compose' ] +- ['compose/production.md', 'User Guide', '    ▪  Use Compose in production' ] +- ['compose/extends.md', 'User Guide', '    ▪  Extend Compose services' ] - ['machine/index.md', 'User Guide', 'Docker Machine' ] - ['swarm/index.md', 'User Guide', 'Docker Swarm' ] @@ -122,7 +124,6 @@ pages: - ['reference/commandline/cli.md', 'Reference', 'Docker command line'] - ['reference/builder.md', 'Reference', 'Dockerfile'] - ['faq.md', 'Reference', 'FAQ'] -- ['reference/glossary.md', 'Reference', 'Glossary'] - ['reference/run.md', 'Reference', 'Run Reference'] - ['compose/cli.md', 'Reference', 'Compose command line'] - ['compose/yml.md', 'Reference', 'Compose yml'] @@ -148,10 +149,9 @@ pages: - ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API'] -- ['reference/api/docker_remote_api_v1.19.md', 'Reference', 'Docker Remote API v1.19'] - ['reference/api/docker_remote_api_v1.18.md', 'Reference', 'Docker Remote API v1.18'] - ['reference/api/docker_remote_api_v1.17.md', 'Reference', 'Docker Remote API v1.17'] -- ['reference/api/docker_remote_api_v1.16.md', '**HIDDEN**'] +- ['reference/api/docker_remote_api_v1.16.md', 'Reference', 'Docker Remote API v1.16'] - ['reference/api/docker_remote_api_v1.15.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.14.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.13.md', '**HIDDEN**'] @@ -187,17 +187,16 @@ pages: # Project: - ['project/index.md', '**HIDDEN**'] - ['project/who-written-for.md', 'Contributor Guide', 'README first'] -- ['project/software-required.md', 'Contributor Guide', 'Get required software'] -- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] -- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] +- ['project/software-required.md', 'Contributor Guide', 'Get required software'] +- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] +- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] - ['project/test-and-docs.md', 'Contributor Guide', 'Run tests and test documentation'] - ['project/make-a-contribution.md', 'Contributor Guide', 'Understand contribution workflow'] -- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] -- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] -- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] -- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] +- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] +- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] +- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] +- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] - ['project/advanced-contributing.md', 'Contributor Guide', 'Advanced contributing'] - ['project/get-help.md', 'Contributor Guide', 'Where to get help'] - ['project/coding-style.md', 'Contributor Guide', 'Coding style guide'] - ['project/doc-style.md', 'Contributor Guide', 'Documentation style guide'] - From bbe6df128802b22605f9eb079f105460ec78ac6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sat, 18 Apr 2015 12:59:20 +0200 Subject: [PATCH 170/332] docs: speed up build by reducing build steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - should be also easier to maintain Signed-off-by: Jörg Thalheim --- docs/Dockerfile | 99 +++++++++++++++++-------------------------------- 1 file changed, 35 insertions(+), 64 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index d1a0f04c0a824..956879fec0e7a 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -27,10 +27,7 @@ COPY ./VERSION VERSION #COPY ./image/spec/v1.md /docs/sources/reference/image-spec-v1.md # TODO: don't do this - look at merging the yml file in build.sh -COPY ./mkdocs.yml mkdocs.yml -COPY ./s3_website.json s3_website.json -COPY ./release.sh release.sh - +COPY ./mkdocs.yml ./s3_website.json ./release.sh ./ ####################### # Docker Distribution @@ -38,32 +35,27 @@ COPY ./release.sh release.sh #ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png /docs/sources/registry/images/registry.png - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/registry/overview.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/overview.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md /docs/sources/registry/deploying.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/deploying.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md /docs/sources/registry/configuration.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/configuration.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md /docs/sources/registry/storagedrivers.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/storagedrivers.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png \ + /docs/sources/registry/images/ -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md /docs/sources/registry/notifications.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/notifications.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md \ + /docs/sources/registry/ -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md /docs/sources/registry/spec/api.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/api.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md /docs/sources/registry/spec/json.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/json.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md \ + /docs/sources/registry/spec/ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md + +RUN sed -i.old '1s;^;no_version_dropdown: true;' \ + /docs/sources/registry/*.md \ + /docs/sources/registry/spec/*.md \ + /docs/sources/registry/spec/auth/*.md ####################### # Docker Swarm @@ -71,19 +63,16 @@ RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/aut #ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/index.md /docs/sources/swarm/index.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/index.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/discovery/README.md /docs/sources/swarm/discovery.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/discovery.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/api/README.md /docs/sources/swarm/API.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/API.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/filter.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/strategy.md + +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/*.md /docs/sources/swarm/scheduler/*.md ####################### # Docker Machine @@ -99,38 +88,20 @@ RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/machine/index.md #ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-compose.yml -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md /docs/sources/compose/index.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/index.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md /docs/sources/compose/install.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/install.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md /docs/sources/compose/cli.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/cli.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md /docs/sources/compose/yml.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/yml.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md /docs/sources/compose/env.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/env.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md /docs/sources/compose/completion.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/completion.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md /docs/sources/compose/django.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/django.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md /docs/sources/compose/rails.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/rails.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md /docs/sources/compose/wordpress.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/extends.md /docs/sources/compose/extends.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/extends.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/production.md /docs/sources/compose/production.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/production.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/extends.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/production.md \ + /docs/sources/compose/ + +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/*.md # Then build everything together, ready for mkdocs -RUN /docs/build.sh \ No newline at end of file +RUN /docs/build.sh From 89df65be5d3ee72cd6e1b4aa53953a62f4b5d00a Mon Sep 17 00:00:00 2001 From: Jeff Nickoloff Date: Sat, 18 Apr 2015 11:34:28 -0700 Subject: [PATCH 171/332] Update builder.md Single value labels do not work in 1.6 and multi-label instructions only work when separated by non-EOL whitespace. I also added an example snip from the inspect output with the labels that are included in this guide. Signed-off-by: Jeff Nickoloff --- docs/sources/reference/builder.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index d837541aa2401..a4fcbebc1b4bc 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -373,9 +373,8 @@ blackslashes as you would in command-line parsing. LABEL "com.example.vendor"="ACME Incorporated" An image can have more than one label. To specify multiple labels, separate each -key-value pair by an EOL. +key-value pair with whitespace. - LABEL com.example.label-without-value LABEL com.example.label-with-value="foo" LABEL version="1.0" LABEL description="This text illustrates \ @@ -385,6 +384,8 @@ Docker recommends combining labels in a single `LABEL` instruction where possible. Each `LABEL` instruction produces a new layer which can result in an inefficient image if you use many labels. This example results in four image layers. + + LABEL multi.label1="value1" multi.label2="value2" other="value3" Labels are additive including `LABEL`s in `FROM` images. As the system encounters and then applies a new label, new `key`s override any previous labels @@ -392,6 +393,16 @@ with identical keys. To view an image's labels, use the `docker inspect` command. + "Labels": { + "com.example.vendor": "ACME Incorporated" + "com.example.label-with-value": "foo", + "version": "1.0", + "description": "This text illustrates that label-values can span multiple lines.", + "multi.label1": "value1", + "multi.label2": "value2", + "other": "value3" + }, + ## EXPOSE EXPOSE [...] From 7b2b7df3866d0c0101e9367b7f4f63bfed5faac4 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Sat, 18 Apr 2015 17:42:24 -0700 Subject: [PATCH 172/332] Docker Registry Server > Docker Registry Fixing registry index Tested on beta and this redirect works Signed-off-by: Mary Anthony --- docs/Dockerfile | 2 +- docs/man/docker-login.1.md | 2 +- docs/man/docker-logout.1.md | 4 ++-- docs/man/docker.1.md | 8 ++++---- docs/mkdocs.yml | 2 +- docs/s3_website.json | 4 +++- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 956879fec0e7a..91cf52bc10194 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -39,7 +39,7 @@ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png \ /docs/sources/registry/images/ -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md \ +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/index.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md \ diff --git a/docs/man/docker-login.1.md b/docs/man/docker-login.1.md index f73df77ed8db4..87ad31b703c9c 100644 --- a/docs/man/docker-login.1.md +++ b/docs/man/docker-login.1.md @@ -13,7 +13,7 @@ docker-login - Register or log in to a Docker registry. [SERVER] # DESCRIPTION -Register or log in to a Docker Registry Service located on the specified +Register or log in to a Docker Registry located on the specified `SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you do not specify a `SERVER`, the command uses Docker's public registry located at `https://registry-1.docker.io/` by default. To get a username/password for Docker's public registry, create an account on Docker Hub. diff --git a/docs/man/docker-logout.1.md b/docs/man/docker-logout.1.md index d464f00fd1287..3726fd66ca02a 100644 --- a/docs/man/docker-logout.1.md +++ b/docs/man/docker-logout.1.md @@ -2,14 +2,14 @@ % Docker Community % JUNE 2014 # NAME -docker-logout - Log out from a Docker Registry Service. +docker-logout - Log out from a Docker Registry. # SYNOPSIS **docker logout** [SERVER] # DESCRIPTION -Log out of a Docker Registry Service located on the specified `SERVER`. You can +Log out of a Docker Registry located on the specified `SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you do not specify a `SERVER`, the command attempts to log you out of Docker's public registry located at `https://registry-1.docker.io/` by default. diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index ec79850de96f0..afa14b661c32f 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -172,10 +172,10 @@ inside it) Load an image from a tar archive **docker-login(1)** - Register or login to a Docker Registry Service + Register or login to a Docker Registry **docker-logout(1)** - Log the user out of a Docker Registry Service + Log the user out of a Docker Registry **docker-logs(1)** Fetch the logs of a container @@ -190,10 +190,10 @@ inside it) List containers **docker-pull(1)** - Pull an image or a repository from a Docker Registry Service + Pull an image or a repository from a Docker Registry **docker-push(1)** - Push an image or a repository to a Docker Registry Service + Push an image or a repository to a Docker Registry **docker-restart(1)** Restart a running container diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 5fb99ffc4057d..49c9b80b77426 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -134,7 +134,7 @@ pages: - ['swarm/scheduler/filter.md', 'Reference', 'Swarm filters'] - ['swarm/API.md', 'Reference', 'Swarm API'] - ['reference/api/index.md', '**HIDDEN**'] -- ['registry/overview.md', 'Reference', 'Docker Registry 2.0'] +- ['registry/index.md', 'Reference', 'Docker Registry 2.0'] - ['registry/deploying.md', 'Reference', '    ▪  Deploy a registry' ] - ['registry/configuration.md', 'Reference', '    ▪  Configure a registry' ] - ['registry/storagedrivers.md', 'Reference', '    ▪  Storage driver model' ] diff --git a/docs/s3_website.json b/docs/s3_website.json index 1142fc0d87fed..b2479bc338b29 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -42,7 +42,9 @@ { "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } }, { "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } }, { "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } }, - { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } } + { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }, + { "Condition": { "KeyPrefixEquals": "registry/overview/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/" } } + ] } From 99251f60c2f554fc03ddc9e7b478de6bd5cf0b49 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Sat, 18 Apr 2015 13:14:44 -0700 Subject: [PATCH 173/332] Document the download location of binaries Signed-off-by: Ankush Agarwal --- docs/sources/installation/binaries.md | 89 ++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/docs/sources/installation/binaries.md b/docs/sources/installation/binaries.md index ef9f5cafa2271..855d4602874b6 100644 --- a/docs/sources/installation/binaries.md +++ b/docs/sources/installation/binaries.md @@ -78,16 +78,91 @@ exhibit unexpected behaviour. > vendor for the system, and might break regulations and security > policies in heavily regulated environments. -## Get the docker binary: +## Get the docker binary + +You can download either the latest release binary or a specific version. +After downloading a binary file, you must set the file's execute bit to run it. + +To set the file's execute bit on Linux and OS X: - $ wget https://get.docker.com/builds/Linux/x86_64/docker-latest -O docker $ chmod +x docker -> **Note**: -> If you have trouble downloading the binary, you can also get the smaller -> compressed release file: -> [https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz]( -> https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz) +To get the list of stable release version numbers from Github, view the +`docker/docker` [releases page](https://github.com/docker/docker/releases). + +> **Note** +> +> 1) You can get the MD5 and SHA256 hashes by appending .md5 and .sha256 to the URLs respectively +> +> 2) You can get the compressed binaries by appending .tgz to the URLs + +### Get the Linux binary + +To download the latest version for Linux, use the +following URLs: + + https://get.docker.com/builds/Linux/i386/docker-latest + + https://get.docker.com/builds/Linux/x86_64/docker-latest + +To download a specific version for Linux, use the +following URL patterns: + + https://get.docker.com/builds/Linux/i386/docker- + + https://get.docker.com/builds/Linux/x86_64/docker- + +For example: + + https://get.docker.com/builds/Linux/i386/docker-1.6.0 + + https://get.docker.com/builds/Linux/x86_64/docker-1.6.0 + + +### Get the Mac OS X binary + +The Mac OS X binary is only a client. You cannot use it to run the `docker` +daemon. To download the latest version for Mac OS X, use the following URLs: + + https://get.docker.com/builds/Darwin/i386/docker-latest + + https://get.docker.com/builds/Darwin/x86_64/docker-latest + +To download a specific version for Mac OS X, use the +following URL patterns: + + https://get.docker.com/builds/Darwin/i386/docker- + + https://get.docker.com/builds/Darwin/x86_64/docker- + +For example: + + https://get.docker.com/builds/Darwin/i386/docker-1.6.0 + + https://get.docker.com/builds/Darwin/x86_64/docker-1.6.0 + +### Get the Windows binary + +You can only download the Windows client binary for version `1.6.0` onwards. +Moreover, the binary is only a client, you cannot use it to run the `docker` daemon. +To download the latest version for Windows, use the following URLs: + + https://get.docker.com/builds/Windows/i386/docker-latest.exe + + https://get.docker.com/builds/Windows/x86_64/docker-latest.exe + +To download a specific version for Windows, use the following URL pattern: + + https://get.docker.com/builds/Windows/i386/docker-.exe + + https://get.docker.com/builds/Windows/x86_64/docker-.exe + +For example: + + https://get.docker.com/builds/Windows/i386/docker-1.6.0.exe + + https://get.docker.com/builds/Windows/x86_64/docker-1.6.0.exe + ## Run the docker daemon From 99f6309b97041bf82cc845340734dc8e47977c8a Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 14 Apr 2015 10:46:29 +0800 Subject: [PATCH 174/332] remove job from tag Signed-off-by: Simei He --- api/server/server.go | 8 +++-- builder/job.go | 2 +- daemon/commit.go | 2 +- graph/import.go | 2 +- graph/manifest_test.go | 2 +- graph/pull.go | 4 +-- graph/service.go | 1 - graph/tag.go | 18 ---------- graph/tags.go | 2 +- graph/tags_unit_test.go | 4 +-- integration/api_test.go | 3 +- integration/server_test.go | 71 -------------------------------------- 12 files changed, 16 insertions(+), 103 deletions(-) delete mode 100644 graph/tag.go delete mode 100644 integration/server_test.go diff --git a/api/server/server.go b/api/server/server.go index 7ed3abc889fc9..7d31c8420ec5b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -586,9 +586,11 @@ func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseW return fmt.Errorf("Missing parameter") } - job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag")) - job.Setenv("force", r.Form.Get("force")) - if err := job.Run(); err != nil { + d := getDaemon(eng) + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + force := toBool(r.Form.Get("force")) + if err := d.Repositories().Tag(repo, tag, vars["name"], force); err != nil { return err } w.WriteHeader(http.StatusCreated) diff --git a/builder/job.go b/builder/job.go index 89ed52f873d74..7e1d035d3ccb3 100644 --- a/builder/job.go +++ b/builder/job.go @@ -164,7 +164,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } if repoName != "" { - b.Daemon.Repositories().Set(repoName, tag, id, true) + b.Daemon.Repositories().Tag(repoName, tag, id, true) } return nil } diff --git a/daemon/commit.go b/daemon/commit.go index 1e534cf6288cc..a60ed08191f39 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -101,7 +101,7 @@ func (daemon *Daemon) Commit(container *Container, repository, tag, comment, aut // Register the image if needed if repository != "" { - if err := daemon.repositories.Set(repository, tag, img.ID, true); err != nil { + if err := daemon.repositories.Tag(repository, tag, img.ID, true); err != nil { return img, err } } diff --git a/graph/import.go b/graph/import.go index eb63af0b6046b..7c6485d9d4f5e 100644 --- a/graph/import.go +++ b/graph/import.go @@ -82,7 +82,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { } // Optionally register the image at REPO/TAG if repo != "" { - if err := s.Set(repo, tag, img.ID, true); err != nil { + if err := s.Tag(repo, tag, img.ID, true); err != nil { return err } } diff --git a/graph/manifest_test.go b/graph/manifest_test.go index 9137041827b33..2702dcaf560aa 100644 --- a/graph/manifest_test.go +++ b/graph/manifest_test.go @@ -135,7 +135,7 @@ func TestManifestTarsumCache(t *testing.T) { if err := store.graph.Register(img, archive); err != nil { t.Fatal(err) } - if err := store.Set(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil { + if err := store.Tag(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil { t.Fatal(err) } diff --git a/graph/pull.go b/graph/pull.go index a9f91b4a27ce8..ce66a03335994 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -249,7 +249,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * if askedTag != "" && tag != askedTag { continue } - if err := s.Set(repoInfo.LocalName, tag, id, true); err != nil { + if err := s.Tag(repoInfo.LocalName, tag, id, true); err != nil { return err } } @@ -617,7 +617,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri } } else { // only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest) - if err = s.Set(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil { + if err = s.Tag(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil { return false, err } } diff --git a/graph/service.go b/graph/service.go index 46f83103dbf42..dfa35565d2372 100644 --- a/graph/service.go +++ b/graph/service.go @@ -12,7 +12,6 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ "image_set": s.CmdSet, - "tag": s.CmdTag, "image_get": s.CmdGet, "image_inspect": s.CmdLookup, "image_tarlayer": s.CmdTarLayer, diff --git a/graph/tag.go b/graph/tag.go deleted file mode 100644 index c0b269946f297..0000000000000 --- a/graph/tag.go +++ /dev/null @@ -1,18 +0,0 @@ -package graph - -import ( - "fmt" - - "github.com/docker/docker/engine" -) - -func (s *TagStore) CmdTag(job *engine.Job) error { - if len(job.Args) != 2 && len(job.Args) != 3 { - return fmt.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) - } - var tag string - if len(job.Args) == 3 { - tag = job.Args[2] - } - return s.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")) -} diff --git a/graph/tags.go b/graph/tags.go index 6346ea8b50dc6..444e74f726b7e 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -224,7 +224,7 @@ func (store *TagStore) Delete(repoName, ref string) (bool, error) { return deleted, store.save() } -func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { +func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error { return store.SetLoad(repoName, tag, imageName, force, nil) } diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index be5624245c596..4a4ddbe4b3c7c 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -72,7 +72,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err := graph.Register(img, officialArchive); err != nil { t.Fatal(err) } - if err := store.Set(testOfficialImageName, "", testOfficialImageID, false); err != nil { + if err := store.Tag(testOfficialImageName, "", testOfficialImageID, false); err != nil { t.Fatal(err) } privateArchive, err := fakeTar() @@ -83,7 +83,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err := graph.Register(img, privateArchive); err != nil { t.Fatal(err) } - if err := store.Set(testPrivateImageName, "", testPrivateImageID, false); err != nil { + if err := store.Tag(testPrivateImageName, "", testPrivateImageID, false); err != nil { t.Fatal(err) } if err := store.SetDigest(testPrivateImageName, testPrivateImageDigest, testPrivateImageID); err != nil { diff --git a/integration/api_test.go b/integration/api_test.go index c527bcb927d59..5a8c7d459a309 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -761,7 +761,8 @@ func TestDeleteImages(t *testing.T) { initialImages := getImages(eng, t, true, "") - if err := eng.Job("tag", unitTestImageName, "test", "test").Run(); err != nil { + d := getDaemon(eng) + if err := d.Repositories().Tag("test", "test", unitTestImageName, true); err != nil { t.Fatal(err) } diff --git a/integration/server_test.go b/integration/server_test.go deleted file mode 100644 index 9745d9ce0ff12..0000000000000 --- a/integration/server_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package docker - -import "testing" - -func TestCreateNumberHostname(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{"-h", "web.0", unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - createTestContainer(eng, config, t) -} - -func TestRunWithTooLowMemoryLimit(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. - job := eng.Job("create") - job.Setenv("Image", unitTestImageID) - job.Setenv("Memory", "524287") - job.Setenv("CpuShares", "1000") - job.SetenvList("Cmd", []string{"/bin/cat"}) - if err := job.Run(); err == nil { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") - } -} - -func TestImagesFilter(t *testing.T) { - eng := NewTestEngine(t) - defer nuke(mkDaemonFromEngine(eng, t)) - - if err := eng.Job("tag", unitTestImageName, "utest", "tag1").Run(); err != nil { - t.Fatal(err) - } - - if err := eng.Job("tag", unitTestImageName, "utest/docker", "tag2").Run(); err != nil { - t.Fatal(err) - } - - if err := eng.Job("tag", unitTestImageName, "utest:5000/docker", "tag3").Run(); err != nil { - t.Fatal(err) - } - - images := getImages(eng, t, false, "utest*/*") - - if len(images[0].RepoTags) != 2 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "*5000*/*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } -} From 448a1a7139cf89a0f4f985ec027a93799d0befb7 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Sat, 18 Apr 2015 17:55:40 -0700 Subject: [PATCH 175/332] Enhanced port integration-cli tests THe port tests in integration-cli tests just for the port-mapping as seen by Docker daemon. But it doesn't perform a more indepth testing by checking for the exposed port on the host. This change helps to fill that gap. Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_port_test.go | 83 +++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/integration-cli/docker_cli_port_test.go b/integration-cli/docker_cli_port_test.go index 91c1ee30098e2..8fe6c2dc62d95 100644 --- a/integration-cli/docker_cli_port_test.go +++ b/integration-cli/docker_cli_port_test.go @@ -1,6 +1,7 @@ package main import ( + "net" "os/exec" "sort" "strings" @@ -145,3 +146,85 @@ func assertPortList(t *testing.T, out string, expected []string) bool { return true } + +func TestPortHostBinding(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox", + "nc", "-l", "-p", "80") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + firstID := strings.TrimSpace(out) + + runCmd = exec.Command(dockerBinary, "port", firstID, "80") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + + if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { + t.Error("Port list is not correct") + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", "9876") + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", "9876") + if out, _, err = runCommandWithOutput(runCmd); err == nil { + t.Error("Port is still bound after the Container is removed") + } + logDone("port - test host binding done") +} + +func TestPortExposeHostBinding(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--expose", "80", "busybox", + "nc", "-l", "-p", "80") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + firstID := strings.TrimSpace(out) + + runCmd = exec.Command(dockerBinary, "port", firstID, "80") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + + _, exposedPort, err := net.SplitHostPort(out) + + if err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", strings.TrimSpace(exposedPort)) + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", strings.TrimSpace(exposedPort)) + if out, _, err = runCommandWithOutput(runCmd); err == nil { + t.Error("Port is still bound after the Container is removed") + } + logDone("port - test port expose done") +} From 8655214b3dc8abb4edbca3db3e04557e09a1149b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 19 Apr 2015 15:23:48 +0200 Subject: [PATCH 176/332] Refactor else branches Signed-off-by: Antonio Murdaca --- registry/session.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/registry/session.go b/registry/session.go index c62745b5bc675..e9d6a33dffb54 100644 --- a/registry/session.go +++ b/registry/session.go @@ -222,10 +222,10 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token [] logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) defer res.Body.Close() - if res.StatusCode != 200 && res.StatusCode != 404 { - continue - } else if res.StatusCode == 404 { + if res.StatusCode == 404 { return nil, fmt.Errorf("Repository not found") + } else if res.StatusCode != 200 { + continue } result := make(map[string]string) @@ -524,21 +524,19 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate } return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res) } - if res.Header.Get("X-Docker-Token") != "" { - tokens = res.Header["X-Docker-Token"] - logrus.Debugf("Auth token: %v", tokens) - } else { + if res.Header.Get("X-Docker-Token") == "" { return nil, fmt.Errorf("Index response didn't contain an access token") } + tokens = res.Header["X-Docker-Token"] + logrus.Debugf("Auth token: %v", tokens) - if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1)) - if err != nil { - return nil, err - } - } else { + if res.Header.Get("X-Docker-Endpoints") == "" { return nil, fmt.Errorf("Index response didn't contain any endpoints") } + endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1)) + if err != nil { + return nil, err + } } if validate { if res.StatusCode != 204 { From 5f2b051ec5a2f639857a1628f3c994fbfd0b3da0 Mon Sep 17 00:00:00 2001 From: Rick Wieman Date: Sun, 19 Apr 2015 23:36:58 +0200 Subject: [PATCH 177/332] Removes redundant else in registry/session.go Fixes #12523 Signed-off-by: Rick Wieman --- registry/session.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/session.go b/registry/session.go index e9d6a33dffb54..940e407e90562 100644 --- a/registry/session.go +++ b/registry/session.go @@ -224,7 +224,8 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token [] if res.StatusCode == 404 { return nil, fmt.Errorf("Repository not found") - } else if res.StatusCode != 200 { + } + if res.StatusCode != 200 { continue } From edf541c22b5253a980ee061b35110d0da8fdb905 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Mon, 20 Apr 2015 01:08:01 -0700 Subject: [PATCH 178/332] gofmt whole directory Signed-off-by: Ankush Agarwal --- api/client/stats.go | 2 +- api/client/utils.go | 2 +- daemon/networkdriver/portmapper/proxy.go | 2 +- daemon/stats_collector.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/client/stats.go b/api/client/stats.go index 317a4fd84ad47..b2dd36d683482 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -145,7 +145,7 @@ func (cli *DockerCli) CmdStats(args ...string) error { if len(errs) > 0 { return fmt.Errorf("%s", strings.Join(errs, ", ")) } - for _ = range time.Tick(500 * time.Millisecond) { + for range time.Tick(500 * time.Millisecond) { printHeader() toRemove := []int{} for i, s := range cStats { diff --git a/api/client/utils.go b/api/client/utils.go index cf11fefe5bba1..3d766a1531b42 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -299,7 +299,7 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { sigchan := make(chan os.Signal, 1) gosignal.Notify(sigchan, signal.SIGWINCH) go func() { - for _ = range sigchan { + for range sigchan { cli.resizeTty(id, isExec) } }() diff --git a/daemon/networkdriver/portmapper/proxy.go b/daemon/networkdriver/portmapper/proxy.go index 5d0aa0be0d1c1..80b0027c703fd 100644 --- a/daemon/networkdriver/portmapper/proxy.go +++ b/daemon/networkdriver/portmapper/proxy.go @@ -84,7 +84,7 @@ func handleStopSignals(p proxy.Proxy) { s := make(chan os.Signal, 10) signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP) - for _ = range s { + for range s { p.Close() os.Exit(0) diff --git a/daemon/stats_collector.go b/daemon/stats_collector.go index 926dd256e4c37..5677a8634a6f3 100644 --- a/daemon/stats_collector.go +++ b/daemon/stats_collector.go @@ -76,7 +76,7 @@ func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) { } func (s *statsCollector) run() { - for _ = range time.Tick(s.interval) { + for range time.Tick(s.interval) { for container, publisher := range s.publishers { systemUsage, err := s.getSystemCpuUsage() if err != nil { From 641a7ec9ad61a42b255d203b7791198431761104 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 20 Apr 2015 16:27:47 +0800 Subject: [PATCH 179/332] remove unused function in server_unit_test.go After engine refactor, some functions are no longer used. Signed-off-by: Qiang Huang --- api/server/server_unit_test.go | 47 ---------------------------------- 1 file changed, 47 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index aca3af9fff7a8..7ac0c22688e07 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -1,7 +1,6 @@ package server import ( - "bytes" "encoding/json" "fmt" "io" @@ -10,7 +9,6 @@ import ( "testing" "github.com/docker/docker/api" - "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/version" ) @@ -160,53 +158,8 @@ func readEnv(src io.Reader, t *testing.T) *engine.Env { return v } -func toJson(data interface{}, t *testing.T) io.Reader { - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(data); err != nil { - t.Fatal(err) - } - return &buf -} - func assertContentType(recorder *httptest.ResponseRecorder, contentType string, t *testing.T) { if recorder.HeaderMap.Get("Content-Type") != contentType { t.Fatalf("%#v\n", recorder) } } - -// XXX: Duplicated from integration/utils_test.go, but maybe that's OK as that -// should die as soon as we converted all integration tests? -// assertHttpNotError expect the given response to not have an error. -// Otherwise the it causes the test to fail. -func assertHttpNotError(r *httptest.ResponseRecorder, t *testing.T) { - // Non-error http status are [200, 400) - if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest { - t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code)) - } -} - -func createEnvFromGetImagesJSONStruct(data getImagesJSONStruct) types.Image { - return types.Image{ - RepoTags: data.RepoTags, - ID: data.Id, - Created: int(data.Created), - Size: int(data.Size), - VirtualSize: int(data.VirtualSize), - } -} - -type getImagesJSONStruct struct { - RepoTags []string - Id string - Created int64 - Size int64 - VirtualSize int64 -} - -var sampleImage getImagesJSONStruct = getImagesJSONStruct{ - RepoTags: []string{"test-name:test-tag"}, - Id: "ID", - Created: 999, - Size: 777, - VirtualSize: 666, -} From e607bb49c48e0478b07fceb640d3e765151050e4 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Mon, 20 Apr 2015 16:08:12 +0800 Subject: [PATCH 180/332] clenaup: delete unused function getEnv Signed-off-by: Ma Shimiao --- daemon/execdriver/lxc/init.go | 10 ---------- daemon/execdriver/native/driver.go | 11 ----------- 2 files changed, 21 deletions(-) diff --git a/daemon/execdriver/lxc/init.go b/daemon/execdriver/lxc/init.go index e99502667d7b9..6cdbf775ea90c 100644 --- a/daemon/execdriver/lxc/init.go +++ b/daemon/execdriver/lxc/init.go @@ -141,13 +141,3 @@ func setupWorkingDirectory(args *InitArgs) error { } return nil } - -func getEnv(args *InitArgs, key string) string { - for _, kv := range args.Env { - parts := strings.SplitN(kv, "=", 2) - if parts[0] == key && len(parts) == 2 { - return parts[1] - } - } - return "" -} diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index fba22c1c21fbb..7bc28f10f5fb7 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "sync" "syscall" "time" @@ -349,16 +348,6 @@ func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) { }, nil } -func getEnv(key string, env []string) string { - for _, pair := range env { - parts := strings.Split(pair, "=") - if parts[0] == key { - return parts[1] - } - } - return "" -} - type TtyConsole struct { console libcontainer.Console } From 49be84842e41a612601ff9d0563ec30f138e95bc Mon Sep 17 00:00:00 2001 From: Shijiang Wei Date: Mon, 20 Apr 2015 16:48:17 +0800 Subject: [PATCH 181/332] Remove some unsupported instructions in the docs. Signed-off-by: Shijiang Wei --- docs/man/docker-commit.1.md | 2 +- docs/man/docker-import.1.md | 2 +- docs/sources/reference/commandline/cli.md | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/man/docker-commit.1.md b/docs/man/docker-commit.1.md index 003cb6f69f0e7..5a290682d09f9 100644 --- a/docs/man/docker-commit.1.md +++ b/docs/man/docker-commit.1.md @@ -22,7 +22,7 @@ Using an existing container's name or ID you can create a new image. **-c** , **--change**=[] Apply specified Dockerfile instructions while committing the image - Supported Dockerfile instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY` + Supported Dockerfile instructions: `CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` **--help** Print usage statement diff --git a/docs/man/docker-import.1.md b/docs/man/docker-import.1.md index 6b3899b6a7153..b45bf5d4c6bbf 100644 --- a/docs/man/docker-import.1.md +++ b/docs/man/docker-import.1.md @@ -13,7 +13,7 @@ URL|- [REPOSITORY[:TAG]] # OPTIONS **-c**, **--change**=[] Apply specified Dockerfile instructions while importing the image - Supported Dockerfile instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY` + Supported Dockerfile instructions: `CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` # DESCRIPTION Create a new filesystem image from the contents of a tarball (`.tar`, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076211c..4b6c1bb4f8959 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -839,7 +839,8 @@ If this behavior is undesired, set the 'p' option to false. The `--change` option will apply `Dockerfile` instructions to the image that is created. -Supported `Dockerfile` instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY` +Supported `Dockerfile` instructions: +`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` #### Commit a container @@ -1347,8 +1348,8 @@ the `-` parameter to take the data from `STDIN`. The `--change` option will apply `Dockerfile` instructions to the image that is created. -Supported `Dockerfile` instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`, -`ONBUILD`, `USER`, `VOLUME`, `WORKDIR` +Supported `Dockerfile` instructions: +`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` #### Examples From f4942ed864f00a31591ef0257a971ef41ddd4c70 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Sat, 11 Apr 2015 01:26:30 +0800 Subject: [PATCH 182/332] Remove Job from Info API Two main things - Create a real struct Info for all of the data with the proper types - Add test for REST API get info Signed-off-by: Hu Keping --- api/client/info.go | 141 ++++++++---------------- api/server/server.go | 9 +- api/server/server_unit_test.go | 27 ----- api/types/types.go | 33 ++++++ daemon/daemon.go | 1 - daemon/info.go | 73 ++++++------ integration-cli/docker_api_info_test.go | 38 +++++++ 7 files changed, 158 insertions(+), 164 deletions(-) create mode 100644 integration-cli/docker_api_info_test.go diff --git a/api/client/info.go b/api/client/info.go index 0f509d83f9710..65578888299a4 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -1,12 +1,11 @@ package client import ( + "encoding/json" "fmt" "os" - "time" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/units" ) @@ -19,127 +18,75 @@ func (cli *DockerCli) CmdInfo(args ...string) error { cmd.Require(flag.Exact, 0) cmd.ParseFlags(args, false) - body, _, err := readBody(cli.call("GET", "/info", nil, nil)) + rdr, _, err := cli.call("GET", "/info", nil, nil) if err != nil { return err } - out := engine.NewOutput() - remoteInfo, err := out.AddEnv() - if err != nil { - return err + info := &types.Info{} + if err := json.NewDecoder(rdr).Decode(info); err != nil { + return fmt.Errorf("Error reading remote info: %v", err) } - if _, err := out.Write(body); err != nil { - logrus.Errorf("Error reading remote info: %s", err) - return err - } - out.Close() - - if remoteInfo.Exists("Containers") { - fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers")) - } - if remoteInfo.Exists("Images") { - fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images")) - } - if remoteInfo.Exists("Driver") { - fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver")) - } - if remoteInfo.Exists("DriverStatus") { - var driverStatus [][2]string - if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil { - return err - } - for _, pair := range driverStatus { + fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers) + fmt.Fprintf(cli.out, "Images: %d\n", info.Images) + fmt.Fprintf(cli.out, "Storage Driver: %s\n", info.Driver) + if info.DriverStatus != nil { + for _, pair := range info.DriverStatus { fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) } } - if remoteInfo.Exists("ExecutionDriver") { - fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) - } - if remoteInfo.Exists("LoggingDriver") { - fmt.Fprintf(cli.out, "Logging Driver: %s\n", remoteInfo.Get("LoggingDriver")) - } - if remoteInfo.Exists("KernelVersion") { - fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) - } - if remoteInfo.Exists("OperatingSystem") { - fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem")) - } - if remoteInfo.Exists("NCPU") { - fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU")) - } - if remoteInfo.Exists("MemTotal") { - fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal")))) - } - if remoteInfo.Exists("Name") { - fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name")) - } - if remoteInfo.Exists("ID") { - fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID")) - } + fmt.Fprintf(cli.out, "Execution Driver: %s\n", info.ExecutionDriver) + fmt.Fprintf(cli.out, "Logging Driver: %s\n", info.LoggingDriver) + fmt.Fprintf(cli.out, "Kernel Version: %s\n", info.KernelVersion) + fmt.Fprintf(cli.out, "Operating System: %s\n", info.OperatingSystem) + fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU) + fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal))) + fmt.Fprintf(cli.out, "Name: %s\n", info.Name) + fmt.Fprintf(cli.out, "ID: %s\n", info.ID) - if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" { - if remoteInfo.Exists("Debug") { - fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug")) - } + if info.Debug || os.Getenv("DEBUG") != "" { + fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug) fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") - if remoteInfo.Exists("NFd") { - fmt.Fprintf(cli.out, "File Descriptors: %d\n", remoteInfo.GetInt("NFd")) - } - if remoteInfo.Exists("NGoroutines") { - fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) - } - if remoteInfo.Exists("SystemTime") { - t, err := remoteInfo.GetTime("SystemTime") - if err != nil { - logrus.Errorf("Error reading system time: %v", err) - } else { - fmt.Fprintf(cli.out, "System Time: %s\n", t.Format(time.UnixDate)) - } - } - if remoteInfo.Exists("NEventsListener") { - fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) - } - if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" { - fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) - } - if initPath := remoteInfo.Get("InitPath"); initPath != "" { - fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) - } - if root := remoteInfo.Get("DockerRootDir"); root != "" { - fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root) - } + fmt.Fprintf(cli.out, "File Descriptors: %d\n", info.NFd) + fmt.Fprintf(cli.out, "Goroutines: %d\n", info.NGoroutines) + fmt.Fprintf(cli.out, "System Time: %s\n", info.SystemTime) + fmt.Fprintf(cli.out, "EventsListeners: %d\n", info.NEventsListener) + fmt.Fprintf(cli.out, "Init SHA1: %s\n", info.InitSha1) + fmt.Fprintf(cli.out, "Init Path: %s\n", info.InitPath) + fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir) } - if remoteInfo.Exists("HttpProxy") { - fmt.Fprintf(cli.out, "Http Proxy: %s\n", remoteInfo.Get("HttpProxy")) + + if info.HttpProxy != "" { + fmt.Fprintf(cli.out, "Http Proxy: %s\n", info.HttpProxy) } - if remoteInfo.Exists("HttpsProxy") { - fmt.Fprintf(cli.out, "Https Proxy: %s\n", remoteInfo.Get("HttpsProxy")) + if info.HttpsProxy != "" { + fmt.Fprintf(cli.out, "Https Proxy: %s\n", info.HttpsProxy) } - if remoteInfo.Exists("NoProxy") { - fmt.Fprintf(cli.out, "No Proxy: %s\n", remoteInfo.Get("NoProxy")) + if info.NoProxy != "" { + fmt.Fprintf(cli.out, "No Proxy: %s\n", info.NoProxy) } - if len(remoteInfo.GetList("IndexServerAddress")) != 0 { + + if info.IndexServerAddress != "" { cli.LoadConfigFile() - u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username + u := cli.configFile.Configs[info.IndexServerAddress].Username if len(u) > 0 { fmt.Fprintf(cli.out, "Username: %v\n", u) - fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress")) + fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress) } } - if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") { + if !info.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") } - if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") { + if !info.SwapLimit { fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") } - if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") { + if !info.IPv4Forwarding { fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") } - if remoteInfo.Exists("Labels") { + if info.Labels != nil { fmt.Fprintln(cli.out, "Labels:") - for _, attribute := range remoteInfo.GetList("Labels") { + for _, attribute := range info.Labels { fmt.Fprintf(cli.out, " %s\n", attribute) } } diff --git a/api/server/server.go b/api/server/server.go index 847ec6c21af07..d12bec95765a1 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -367,8 +367,13 @@ func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWr func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - eng.ServeHTTP(w, r) - return nil + + info, err := getDaemon(eng).SystemInfo() + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, info) } func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index aca3af9fff7a8..a2abee188818e 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -34,33 +34,6 @@ func TesthttpError(t *testing.T) { } } -func TestGetInfo(t *testing.T) { - eng := engine.New() - var called bool - eng.Register("info", func(job *engine.Job) error { - called = true - v := &engine.Env{} - v.SetInt("Containers", 1) - v.SetInt("Images", 42000) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/info", nil, eng, t) - if !called { - t.Fatalf("handler was not called") - } - v := readEnv(r.Body, t) - if v.GetInt("Images") != 42000 { - t.Fatalf("%#v\n", v) - } - if v.GetInt("Containers") != 1 { - t.Fatalf("%#v\n", v) - } - assertContentType(r, "application/json", t) -} - func TestGetContainersByName(t *testing.T) { eng := engine.New() name := "container_name" diff --git a/api/types/types.go b/api/types/types.go index cdafe7e192b11..2a641fbaa3eb5 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -122,3 +122,36 @@ type Version struct { Arch string KernelVersion string `json:",omitempty"` } + +// GET "/info" +type Info struct { + ID string + Containers int + Images int + Driver string + DriverStatus [][2]string + MemoryLimit bool + SwapLimit bool + IPv4Forwarding bool + Debug bool + NFd int + NGoroutines int + SystemTime string + ExecutionDriver string + LoggingDriver string + NEventsListener int + KernelVersion string + OperatingSystem string + IndexServerAddress string + RegistryConfig interface{} + InitSha1 string + InitPath string + NCPU int + MemTotal int64 + DockerRootDir string + HttpProxy string + HttpsProxy string + NoProxy string + Name string + Labels []string +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 7d8249b07960e..ccf254b2cf2b2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,7 +119,6 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "info": daemon.CmdInfo, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { diff --git a/daemon/info.go b/daemon/info.go index a77e3efd16320..fa019fe08f17a 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -6,8 +6,8 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/parsers/operatingsystem" @@ -16,7 +16,7 @@ import ( "github.com/docker/docker/utils" ) -func (daemon *Daemon) CmdInfo(job *engine.Job) error { +func (daemon *Daemon) SystemInfo() (*types.Info, error) { images, _ := daemon.Graph().Map() var imgcount int if images == nil { @@ -52,47 +52,46 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { initPath = daemon.SystemInitPath() } - v := &engine.Env{} - v.SetJson("ID", daemon.ID) - v.SetInt("Containers", len(daemon.List())) - v.SetInt("Images", imgcount) - v.Set("Driver", daemon.GraphDriver().String()) - v.SetJson("DriverStatus", daemon.GraphDriver().Status()) - v.SetBool("MemoryLimit", daemon.SystemConfig().MemoryLimit) - v.SetBool("SwapLimit", daemon.SystemConfig().SwapLimit) - v.SetBool("IPv4Forwarding", !daemon.SystemConfig().IPv4ForwardingDisabled) - v.SetBool("Debug", os.Getenv("DEBUG") != "") - v.SetInt("NFd", fileutils.GetTotalUsedFds()) - v.SetInt("NGoroutines", runtime.NumGoroutine()) - v.Set("SystemTime", time.Now().Format(time.RFC3339Nano)) - v.Set("ExecutionDriver", daemon.ExecutionDriver().Name()) - v.Set("LoggingDriver", daemon.defaultLogConfig.Type) - v.SetInt("NEventsListener", daemon.EventsService.SubscribersCount()) - v.Set("KernelVersion", kernelVersion) - v.Set("OperatingSystem", operatingSystem) - v.Set("IndexServerAddress", registry.IndexServerAddress()) - v.SetJson("RegistryConfig", daemon.RegistryService.Config) - v.Set("InitSha1", dockerversion.INITSHA1) - v.Set("InitPath", initPath) - v.SetInt("NCPU", runtime.NumCPU()) - v.SetInt64("MemTotal", meminfo.MemTotal) - v.Set("DockerRootDir", daemon.Config().Root) + v := &types.Info{ + ID: daemon.ID, + Containers: len(daemon.List()), + Images: imgcount, + Driver: daemon.GraphDriver().String(), + DriverStatus: daemon.GraphDriver().Status(), + MemoryLimit: daemon.SystemConfig().MemoryLimit, + SwapLimit: daemon.SystemConfig().SwapLimit, + IPv4Forwarding: !daemon.SystemConfig().IPv4ForwardingDisabled, + Debug: os.Getenv("DEBUG") != "", + NFd: fileutils.GetTotalUsedFds(), + NGoroutines: runtime.NumGoroutine(), + SystemTime: time.Now().Format(time.RFC3339Nano), + ExecutionDriver: daemon.ExecutionDriver().Name(), + LoggingDriver: daemon.defaultLogConfig.Type, + NEventsListener: daemon.EventsService.SubscribersCount(), + KernelVersion: kernelVersion, + OperatingSystem: operatingSystem, + IndexServerAddress: registry.IndexServerAddress(), + RegistryConfig: daemon.RegistryService.Config, + InitSha1: dockerversion.INITSHA1, + InitPath: initPath, + NCPU: runtime.NumCPU(), + MemTotal: meminfo.MemTotal, + DockerRootDir: daemon.Config().Root, + Labels: daemon.Config().Labels, + } + if httpProxy := os.Getenv("http_proxy"); httpProxy != "" { - v.Set("HttpProxy", httpProxy) + v.HttpProxy = httpProxy } if httpsProxy := os.Getenv("https_proxy"); httpsProxy != "" { - v.Set("HttpsProxy", httpsProxy) + v.HttpsProxy = httpsProxy } if noProxy := os.Getenv("no_proxy"); noProxy != "" { - v.Set("NoProxy", noProxy) + v.NoProxy = noProxy } - if hostname, err := os.Hostname(); err == nil { - v.SetJson("Name", hostname) + v.Name = hostname } - v.SetList("Labels", daemon.Config().Labels) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil + + return v, nil } diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go new file mode 100644 index 0000000000000..a934ed6ccd3b6 --- /dev/null +++ b/integration-cli/docker_api_info_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "net/http" + "strings" + "testing" +) + +func TestInfoApi(t *testing.T) { + endpoint := "/info" + + statusCode, body, err := sockRequest("GET", endpoint, nil) + if err != nil || statusCode != http.StatusOK { + t.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) + } + + // always shown fields + stringsToCheck := []string{ + "ID", + "Containers", + "Images", + "ExecutionDriver", + "LoggingDriver", + "OperatingSystem", + "NCPU", + "MemTotal", + "KernelVersion", + "Driver"} + + out := string(body) + for _, linePrefix := range stringsToCheck { + if !strings.Contains(out, linePrefix) { + t.Errorf("couldn't find string %v in output", linePrefix) + } + } + + logDone("container REST API - check GET /info") +} From 8301dcc6d702a97feeb968ee79ae381fd8a4997a Mon Sep 17 00:00:00 2001 From: Jiri Popelka Date: Wed, 26 Nov 2014 12:14:50 +0100 Subject: [PATCH 183/332] Support for Firewalld Firewalld [1] is a firewall managing daemon with D-Bus interface. What sort of problem are we trying to solve with this ? Firewalld internally also executes iptables/ip6tables to change firewall settings. It might happen on systems where both docker and firewalld are running concurrently, that both of them try to call iptables at the same time. The result is that the second one fails because the first one is holding a xtables lock. One workaround is to use --wait/-w option in both docker & firewalld when calling iptables. It's already been done in both upstreams: https://github.com/docker/docker/commit/b315c380f4acd65cc0428009702f99a266f96c59 https://github.com/t-woerner/firewalld/commit/b3b451d6f8946986b8f50c8bcddeef50ed7a5f8f But it'd still be better if docker used firewalld when it's running. Other problem the firewalld support would solve is that iptables/firewalld service's restart flushes all firewall rules previously added by docker. See next patch for possible solution. This patch utilizes firewalld's D-Bus interface. If firewalld is running, we call direct.passthrough() [2] method instead of executing iptables directly. direct.passthrough() takes the same arguments as iptables tool itself and passes them through to iptables tool. It might be better to use other methods, like direct.addChain and direct.addRule [3] so it'd be more intergrated with firewalld, but that'd make the patch much bigger. If firewalld is not running, everything works as before. [1] http://www.firewalld.org/ [2] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.passthrough [3] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.addChain https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.addRule Signed-off-by: Jiri Popelka --- daemon/networkdriver/bridge/driver.go | 4 ++ pkg/iptables/firewalld.go | 94 +++++++++++++++++++++++++++ pkg/iptables/iptables.go | 7 ++ 3 files changed, 105 insertions(+) create mode 100644 pkg/iptables/firewalld.go diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index eda4713870ecb..bea62187080f1 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -222,6 +222,10 @@ func InitDriver(config *Config) error { bridgeIPv6Addr = networkv6.IP } + if config.EnableIptables { + iptables.FirewalldInit() + } + // Configure iptables for link support if config.EnableIptables { if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { diff --git a/pkg/iptables/firewalld.go b/pkg/iptables/firewalld.go new file mode 100644 index 0000000000000..cb7e0b4b751af --- /dev/null +++ b/pkg/iptables/firewalld.go @@ -0,0 +1,94 @@ +package iptables + +import ( + "github.com/Sirupsen/logrus" + "github.com/godbus/dbus" +) + +type IPV string + +const ( + Iptables IPV = "ipv4" + Ip6tables IPV = "ipv6" + Ebtables IPV = "eb" +) +const ( + dbusInterface = "org.fedoraproject.FirewallD1" + dbusPath = "/org/fedoraproject/FirewallD1" +) + +// Conn is a connection to firewalld dbus endpoint. +type Conn struct { + sysconn *dbus.Conn + sysobj *dbus.Object + signal chan *dbus.Signal +} + +var ( + connection *Conn + firewalldRunning bool // is Firewalld service running +) + +func FirewalldInit() { + var err error + + connection, err = newConnection() + + if err != nil { + logrus.Errorf("Failed to connect to D-Bus system bus: %s", err) + } + + firewalldRunning = checkRunning() +} + +// New() establishes a connection to the system bus. +func newConnection() (*Conn, error) { + c := new(Conn) + if err := c.initConnection(); err != nil { + return nil, err + } + + return c, nil +} + +// Innitialize D-Bus connection. +func (c *Conn) initConnection() error { + var err error + + c.sysconn, err = dbus.SystemBus() + if err != nil { + return err + } + + // This never fails, even if the service is not running atm. + c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath)) + + return nil +} + +// Call some remote method to see whether the service is actually running. +func checkRunning() bool { + var zone string + var err error + + if connection != nil { + err = connection.sysobj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone) + logrus.Infof("Firewalld running: %t", err == nil) + return err == nil + } + logrus.Info("Firewalld not running") + return false +} + +// Firewalld's passthrough method simply passes args through to iptables/ip6tables +func Passthrough(ipv IPV, args ...string) ([]byte, error) { + var output string + + logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args) + err := connection.sysobj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output) + if output != "" { + logrus.Debugf("passthrough output: %s", output) + } + + return []byte(output), err +} diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index f8b3aa769d28e..9019f346379e6 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -275,6 +275,13 @@ func Exists(table Table, chain string, rule ...string) bool { // Call 'iptables' system command, passing supplied arguments func Raw(args ...string) ([]byte, error) { + if firewalldRunning { + output, err := Passthrough(Iptables, args...) + if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") { + return output, err + } + + } if err := initCheck(); err != nil { return nil, err From b052827e025267336f0d426df44ec536745821f8 Mon Sep 17 00:00:00 2001 From: Jiri Popelka Date: Wed, 26 Nov 2014 19:10:35 +0100 Subject: [PATCH 184/332] React to firewalld's reload/restart When firewalld (or iptables service) restarts/reloads, all previously added docker firewall rules are flushed. With firewalld we can react to its Reloaded() [1] D-Bus signal and recreate the firewall rules. Also when firewalld gets restarted (stopped & started) we can catch the NameOwnerChanged signal [2]. To specify which signals we want to react to we use AddMatch [3]. Libvirt has been doing this for quite a long time now. Docker changes firewall rules on basically 3 places. 1) daemon/networkdriver/portmapper/mapper.go - port mappings Portmapper fortunatelly keeps list of mapped ports, so we can easily recreate firewall rules on firewalld restart/reload New ReMapAll() function does that 2) daemon/networkdriver/bridge/driver.go When setting a bridge, basic firewall rules are created. This is done at once during start, it's parametrized and nowhere tracked so how can one know what and how to set it again when there's been firewalld restart/reload ? The only solution that came to my mind is using of closures [4], i.e. I keep list of references to closures (anonymous functions together with a referencing environment) and when there's firewalld restart/reload I re-call them in the same order. 3) links/links.go - linking containers Link is added in Enable() and removed in Disable(). In Enable() we add a callback function, which creates the link, that's OK so far. It'd be ideal if we could remove the same function from the list in Disable(). Unfortunatelly that's not possible AFAICT, because we don't know the reference to that function at that moment, so we can only add a reference to function, which removes the link. That means that after creating and removing a link there are 2 functions in the list, one adding and one removing the link and after firewalld restart/reload both are called. It works, but it's far from ideal. [1] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.Signals.Reloaded [2] http://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed [3] http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules [4] https://en.wikipedia.org/wiki/Closure_%28computer_programming%29 Signed-off-by: Jiri Popelka --- daemon/networkdriver/bridge/driver.go | 13 ++++- daemon/networkdriver/portmapper/mapper.go | 12 ++++ links/links.go | 6 +- pkg/iptables/firewalld.go | 71 ++++++++++++++++++++++- 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index bea62187080f1..64772bfd70705 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -232,7 +232,8 @@ func InitDriver(config *Config) error { logrus.Errorf("Error configuing iptables: %s", err) return err } - + // call this on Firewalld reload + iptables.OnReloaded(func() { setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq) }) } if config.EnableIpForward { @@ -262,10 +263,16 @@ func InitDriver(config *Config) error { if err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) }) + chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) if err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) }) + portMapper.SetIptablesChain(chain) } @@ -298,6 +305,10 @@ func InitDriver(config *Config) error { // Block BridgeIP in IP allocator ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) + if config.EnableIptables { + iptables.OnReloaded(portMapper.ReMapAll) // call this on Firewalld reload + } + return nil } diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 8f79bae3f2deb..09952ba35b30b 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -132,6 +132,18 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host return m.host, nil } +// re-apply all port mappings +func (pm *PortMapper) ReMapAll() { + logrus.Debugln("Re-applying all port mappings.") + for _, data := range pm.currentMappings { + containerIP, containerPort := getIPAndPort(data.container) + hostIP, hostPort := getIPAndPort(data.host) + if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + logrus.Errorf("Error on iptables add: %s", err) + } + } +} + func (pm *PortMapper) Unmap(host net.Addr) error { pm.lock.Lock() defer pm.lock.Unlock() diff --git a/links/links.go b/links/links.go index 1ae8f23aeaf59..8bbacdd3d7527 100644 --- a/links/links.go +++ b/links/links.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/nat" + "github.com/docker/docker/pkg/iptables" ) type Link struct { @@ -143,6 +144,8 @@ func (l *Link) Enable() error { if err := l.toggle("-A", false); err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { l.toggle("-I", false) }) l.IsEnabled = true return nil } @@ -152,7 +155,8 @@ func (l *Link) Disable() { // exist in iptables // -D == iptables delete flag l.toggle("-D", true) - + // call this on Firewalld reload + iptables.OnReloaded(func() { l.toggle("-D", true) }) l.IsEnabled = false } diff --git a/pkg/iptables/firewalld.go b/pkg/iptables/firewalld.go index cb7e0b4b751af..3087794131467 100644 --- a/pkg/iptables/firewalld.go +++ b/pkg/iptables/firewalld.go @@ -1,8 +1,10 @@ package iptables import ( + "fmt" "github.com/Sirupsen/logrus" "github.com/godbus/dbus" + "strings" ) type IPV string @@ -26,7 +28,8 @@ type Conn struct { var ( connection *Conn - firewalldRunning bool // is Firewalld service running + firewalldRunning bool // is Firewalld service running + onReloaded []*func() // callbacks when Firewalld has been reloaded ) func FirewalldInit() { @@ -63,9 +66,75 @@ func (c *Conn) initConnection() error { // This never fails, even if the service is not running atm. c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath)) + rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'", + dbusPath, dbusInterface, dbusInterface) + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", + dbusInterface) + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + c.signal = make(chan *dbus.Signal, 10) + c.sysconn.Signal(c.signal) + go signalHandler() + return nil } +func signalHandler() { + if connection != nil { + for signal := range connection.signal { + if strings.Contains(signal.Name, "NameOwnerChanged") { + firewalldRunning = checkRunning() + dbusConnectionChanged(signal.Body) + } else if strings.Contains(signal.Name, "Reloaded") { + reloaded() + } + } + } +} + +func dbusConnectionChanged(args []interface{}) { + name := args[0].(string) + old_owner := args[1].(string) + new_owner := args[2].(string) + + if name != dbusInterface { + return + } + + if len(new_owner) > 0 { + connectionEstablished() + } else if len(old_owner) > 0 { + connectionLost() + } +} + +func connectionEstablished() { + reloaded() +} + +func connectionLost() { + // Doesn't do anything for now. Libvirt also doesn't react to this. +} + +// call all callbacks +func reloaded() { + for _, pf := range onReloaded { + (*pf)() + } +} + +// add callback +func OnReloaded(callback func()) { + for _, pf := range onReloaded { + if pf == &callback { + return + } + } + onReloaded = append(onReloaded, &callback) +} + // Call some remote method to see whether the service is actually running. func checkRunning() bool { var zone string From 379773905c7ff4db3c16e2235f831a9552b4e158 Mon Sep 17 00:00:00 2001 From: Jiri Popelka Date: Thu, 15 Jan 2015 18:23:20 +0100 Subject: [PATCH 185/332] Firewalld tests Signed-off-by: Jiri Popelka --- pkg/iptables/firewalld_test.go | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 pkg/iptables/firewalld_test.go diff --git a/pkg/iptables/firewalld_test.go b/pkg/iptables/firewalld_test.go new file mode 100644 index 0000000000000..3896007d646b3 --- /dev/null +++ b/pkg/iptables/firewalld_test.go @@ -0,0 +1,78 @@ +package iptables + +import ( + "net" + "strconv" + "testing" +) + +func TestFirewalldInit(t *testing.T) { + FirewalldInit() +} + +func TestReloaded(t *testing.T) { + var err error + var fwdChain *Chain + + fwdChain, err = NewChain("FWD", "lo", Filter) + if err != nil { + t.Fatal(err) + } + defer fwdChain.Remove() + + // copy-pasted from iptables_test:TestLink + ip1 := net.ParseIP("192.168.1.1") + ip2 := net.ParseIP("192.168.1.2") + port := 1234 + proto := "tcp" + + err = fwdChain.Link(Append, ip1, ip2, port, proto) + if err != nil { + t.Fatal(err) + } else { + // to be re-called again later + OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto) }) + } + + rule1 := []string{ + "-i", fwdChain.Bridge, + "-o", fwdChain.Bridge, + "-p", proto, + "-s", ip1.String(), + "-d", ip2.String(), + "--dport", strconv.Itoa(port), + "-j", "ACCEPT"} + + if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + t.Fatalf("rule1 does not exist") + } + + // flush all rules + fwdChain.Remove() + + reloaded() + + // make sure the rules have been recreated + if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + t.Fatalf("rule1 hasn't been recreated") + } +} + +func TestPassthrough(t *testing.T) { + rule1 := []string{ + "-i", "lo", + "-p", "udp", + "--dport", "123", + "-j", "ACCEPT"} + + if firewalldRunning { + _, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...) + if err != nil { + t.Fatal(err) + } + if !Exists(Filter, "INPUT", rule1...) { + t.Fatalf("rule1 does not exist") + } + } + +} From acb6127c1a3f7054c25d1468b67f2eb269f4ecbf Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 18 Dec 2014 10:09:42 +0100 Subject: [PATCH 186/332] Allow specifying a default gateway for bridge networking Signed-off-by: Sylvain Baubeau --- daemon/config.go | 2 + daemon/networkdriver/bridge/driver.go | 60 ++++++++++++++++++++--- docs/man/docker.1.md | 6 +++ docs/sources/articles/networking.md | 23 ++++++--- docs/sources/reference/commandline/cli.md | 2 + 5 files changed, 80 insertions(+), 13 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 40019e5036df2..952fb5f743474 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -58,6 +58,8 @@ func (config *Config) InstallFlags() { flag.StringVar(&config.Bridge.Iface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge") flag.StringVar(&config.Bridge.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs") flag.StringVar(&config.Bridge.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs") + flag.StringVar(&config.Bridge.DefaultGatewayIPv4, []string{"-default-gateway"}, "", "Container default gateway IPv4 address") + flag.StringVar(&config.Bridge.DefaultGatewayIPv6, []string{"-default-gateway-v6"}, "", "Container default gateway IPv6 address") flag.BoolVar(&config.Bridge.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication") flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Storage driver to use") flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Exec driver to use") diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index eda4713870ecb..14102632b0b1c 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -77,8 +77,10 @@ var ( bridgeIface string bridgeIPv4Network *net.IPNet + gatewayIPv4 net.IP bridgeIPv6Addr net.IP globalIPv6Network *net.IPNet + gatewayIPv6 net.IP portMapper *portmapper.PortMapper once sync.Once @@ -103,6 +105,8 @@ type Config struct { IP string FixedCIDR string FixedCIDRv6 string + DefaultGatewayIPv4 string + DefaultGatewayIPv6 string InterContainerCommunication bool } @@ -278,6 +282,12 @@ func InitDriver(config *Config) error { } } + if gateway, err := requestDefaultGateway(config.DefaultGatewayIPv4, bridgeIPv4Network); err != nil { + return err + } else { + gatewayIPv4 = gateway + } + if config.FixedCIDRv6 != "" { _, subnet, err := net.ParseCIDR(config.FixedCIDRv6) if err != nil { @@ -289,6 +299,12 @@ func InitDriver(config *Config) error { return err } globalIPv6Network = subnet + + if gateway, err := requestDefaultGateway(config.DefaultGatewayIPv6, globalIPv6Network); err != nil { + return err + } else { + gatewayIPv6 = gateway + } } // Block BridgeIP in IP allocator @@ -473,6 +489,24 @@ func setupIPv6Bridge(bridgeIPv6 string) error { return nil } +func requestDefaultGateway(requestedGateway string, network *net.IPNet) (gateway net.IP, err error) { + if requestedGateway != "" { + gateway = net.ParseIP(requestedGateway) + + if gateway == nil { + return nil, fmt.Errorf("Bad parameter: invalid gateway ip %s", requestedGateway) + } + + if !network.Contains(gateway) { + return nil, fmt.Errorf("Gateway ip %s must be part of the network %s", requestedGateway, network.String()) + } + + ipAllocator.RequestIP(network, gateway) + } + + return gateway, nil +} + func createBridgeIface(name string) error { kv, err := kernel.GetKernelVersion() // Only set the bridge's mac address if the kernel version is > 3.3 @@ -522,10 +556,12 @@ func linkLocalIPv6FromMac(mac string) (string, error) { // Allocate a network interface func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Settings, error) { var ( - ip net.IP - mac net.HardwareAddr - err error - globalIPv6 net.IP + ip net.IP + mac net.HardwareAddr + err error + globalIPv6 net.IP + defaultGWIPv4 net.IP + defaultGWIPv6 net.IP ) ip, err = ipAllocator.RequestIP(bridgeIPv4Network, net.ParseIP(requestedIP)) @@ -560,6 +596,18 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set maskSize, _ := bridgeIPv4Network.Mask.Size() + if gatewayIPv4 != nil { + defaultGWIPv4 = gatewayIPv4 + } else { + defaultGWIPv4 = bridgeIPv4Network.IP + } + + if gatewayIPv6 != nil { + defaultGWIPv6 = gatewayIPv6 + } else { + defaultGWIPv6 = bridgeIPv6Addr + } + // If linklocal IPv6 localIPv6Net, err := linkLocalIPv6FromMac(mac.String()) if err != nil { @@ -569,7 +617,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set networkSettings := &network.Settings{ IPAddress: ip.String(), - Gateway: bridgeIPv4Network.IP.String(), + Gateway: defaultGWIPv4.String(), MacAddress: mac.String(), Bridge: bridgeIface, IPPrefixLen: maskSize, @@ -580,7 +628,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set networkSettings.GlobalIPv6Address = globalIPv6.String() maskV6Size, _ := globalIPv6Network.Mask.Size() networkSettings.GlobalIPv6PrefixLen = maskV6Size - networkSettings.IPv6Gateway = bridgeIPv6Addr.String() + networkSettings.IPv6Gateway = defaultGWIPv6.String() } currentInterfaces.Set(id, &networkInterface{ diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index afa14b661c32f..53c54f9037c50 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -41,6 +41,12 @@ To see the man page for a command run **man docker **. **-d**, **--daemon**=*true*|*false* Enable daemon mode. Default is false. +**--default-gateway**="" + IPv4 address of the container default gateway; this address must be part of the bridge subnet (which is defined by \-b or \--bip) + +**--default-gateway-v6**="" + IPv6 address of the container default gateway + **--dns**="" Force Docker to use specific DNS servers diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 46a907f7e27bc..2ce52ce0e0512 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -56,6 +56,12 @@ server when it starts up, and cannot be changed once it is running: * `--bip=CIDR` — see [Customizing docker0](#docker0) + * `--default-gateway=IP_ADDRESS` — see + [How Docker networks a container](#container-networking) + + * `--default-gateway-v6=IP_ADDRESS` — see + [IPv6](#ipv6) + * `--fixed-cidr` — see [Customizing docker0](#docker0) @@ -499,7 +505,9 @@ want to configure `eth0` via Router Advertisements you should set: ![](/article-img/ipv6_basic_host_config.svg) Every new container will get an IPv6 address from the defined subnet. Further -a default route will be added via the gateway `fe80::1` on `eth0`: +a default route will be added on `eth0` in the container via the address +specified by the daemon option `--default-gateway-v6` if present, otherwise +via `fe80::1`: docker run -it ubuntu bash -c "ip -6 addr show dev eth0; ip -6 route show" @@ -865,12 +873,13 @@ The steps with which Docker configures a container are: parameter or generate a random one. 5. 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. The MAC - address is generated from the IP address unless otherwise specified. - This prevents ARP cache invalidation problems, when a new container - comes up with an IP used in the past by another container with another - MAC. + bridge's range of network addresses. The default route is set to the + IP address passed to the Docker daemon using the `--default-gateway` + option if specified, otherwise to the IP address that the Docker host + owns on the bridge. The MAC address is generated from the IP address + unless otherwise specified. This prevents ARP cache invalidation + problems, when a new container comes up with an IP used in the past by + another container with another MAC. With these steps complete, the container now possesses an `eth0` (virtual) network card and will find itself able to communicate with diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076211c..60518c3a93f4b 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -116,6 +116,8 @@ expect an integer, and they can only be specified once. --bip="" Specify network bridge IP -D, --debug=false Enable debug mode -d, --daemon=false Enable daemon mode + --default-gateway="" Container default gateway IPv4 address + --default-gateway-v6="" Container default gateway IPv6 address --dns=[] DNS server to use --dns-search=[] DNS search domains to use -e, --exec-driver="native" Exec driver to use From 2d5ede67c098dd11803d502865ab546388f2a553 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Mon, 20 Apr 2015 22:00:03 +0800 Subject: [PATCH 187/332] Remove redundant '\n' in daemon.go and correct the warning messages for memory swap Signed-off-by: Lei Jitang --- daemon/daemon.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2cf2b2..8428cc8273d94 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1233,18 +1233,18 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri return warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") } if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") hostConfig.Memory = 0 } if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support swap limit capabilities, memory limited without swap.") hostConfig.MemorySwap = -1 } if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { - return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") + return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.") } if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { - return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.") } return warnings, nil From 181fea24aac7499a3d6dc0c8c9de67e6c0036140 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 17 Apr 2015 12:03:11 -0700 Subject: [PATCH 188/332] Make daemon initialization in main goroutine It is simplifies code and lead to next refactoring step, where daemon will be incorporated to some structure which represents API. Signed-off-by: Alexander Morozov --- docker/daemon.go | 78 ++++++++--------------- integration-cli/docker_cli_daemon_test.go | 4 +- 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/docker/daemon.go b/docker/daemon.go index 769b4f5bf68b0..0fe10de65b112 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -7,7 +7,6 @@ import ( "io" "os" "path/filepath" - "strings" "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" @@ -93,40 +92,6 @@ func mainDaemon() { } daemonCfg.TrustKeyPath = *flTrustKey - registryService := registry.NewService(registryCfg) - // load the daemon in the background so we can immediately start - // the http api so that connections don't fail while the daemon - // is booting - daemonInitWait := make(chan error) - go func() { - d, err := daemon.NewDaemon(daemonCfg, eng, registryService) - if err != nil { - daemonInitWait <- err - return - } - - logrus.WithFields(logrus.Fields{ - "version": dockerversion.VERSION, - "commit": dockerversion.GITCOMMIT, - "execdriver": d.ExecutionDriver().Name(), - "graphdriver": d.GraphDriver().String(), - }).Info("Docker daemon") - - if err := d.Install(eng); err != nil { - daemonInitWait <- err - return - } - - b := &builder.BuilderJob{eng, d} - b.Install() - - // after the daemon is done setting up we can tell the api to start - // accepting connections - apiserver.AcceptConnections() - - daemonInitWait <- nil - }() - serverConfig := &apiserver.ServerConfig{ Logging: true, EnableCors: daemonCfg.EnableCors, @@ -153,30 +118,37 @@ func mainDaemon() { serveAPIWait <- nil }() - // Wait for the daemon startup goroutine to finish - // This makes sure we can actually cleanly shutdown the daemon - logrus.Debug("waiting for daemon to initialize") - errDaemon := <-daemonInitWait - if errDaemon != nil { + registryService := registry.NewService(registryCfg) + d, err := daemon.NewDaemon(daemonCfg, eng, registryService) + if err != nil { eng.Shutdown() - outStr := fmt.Sprintf("Shutting down daemon due to errors: %v", errDaemon) - if strings.Contains(errDaemon.Error(), "engine is shutdown") { - // if the error is "engine is shutdown", we've already reported (or - // will report below in API server errors) the error - outStr = "Shutting down daemon due to reported errors" - } - // we must "fatal" exit here as the API server may be happy to - // continue listening forever if the error had no impact to API - logrus.Fatal(outStr) - } else { - logrus.Info("Daemon has completed initialization") + logrus.Fatalf("Error starting daemon: %v", err) } + if err := d.Install(eng); err != nil { + eng.Shutdown() + logrus.Fatalf("Error starting daemon: %v", err) + } + + logrus.Info("Daemon has completed initialization") + + logrus.WithFields(logrus.Fields{ + "version": dockerversion.VERSION, + "commit": dockerversion.GITCOMMIT, + "execdriver": d.ExecutionDriver().Name(), + "graphdriver": d.GraphDriver().String(), + }).Info("Docker daemon") + + b := &builder.BuilderJob{eng, d} + b.Install() + + // after the daemon is done setting up we can tell the api to start + // accepting connections + apiserver.AcceptConnections() + // Daemon is fully initialized and handling API traffic // Wait for serve API job to complete errAPI := <-serveAPIWait - // If we have an error here it is unique to API (as daemonErr would have - // exited the daemon process above) eng.Shutdown() if errAPI != nil { logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 384c96803c273..d81ad3c807c9b 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -498,9 +498,9 @@ func TestDaemonExitOnFailure(t *testing.T) { t.Fatalf("Expected daemon not to start, got %v", err) } // look in the log and make sure we got the message that daemon is shutting down - runCmd := exec.Command("grep", "Shutting down daemon due to", d.LogfileName()) + runCmd := exec.Command("grep", "Error starting daemon", d.LogfileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf("Expected 'shutting down daemon due to error' message; but doesn't exist in log: %q, err: %v", out, err) + t.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) } } else { //if we didn't get an error and the daemon is running, this is a failure From d9ed3165228b60cb89c31d0d66b99e01ab83eb3e Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 17 Apr 2015 14:32:18 -0700 Subject: [PATCH 189/332] Make API server datastructure Added daemon field to it, will use it later for acces to daemon from handlers Signed-off-by: Alexander Morozov --- api/server/server.go | 122 +++++++++++++++++++++-------------- api/server/server_linux.go | 32 ++++----- api/server/server_windows.go | 30 +++++---- api/server/tcp_socket.go | 4 +- api/server/unix_socket.go | 4 +- docker/daemon.go | 8 ++- integration/runtime_test.go | 28 ++++---- pkg/listenbuffer/buffer.go | 8 +-- 8 files changed, 131 insertions(+), 105 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d12bec95765a1..3146e09ed0087 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -40,10 +40,6 @@ import ( "github.com/docker/docker/utils" ) -var ( - activationLock = make(chan struct{}) -) - type ServerConfig struct { Logging bool EnableCors bool @@ -57,6 +53,80 @@ type ServerConfig struct { TlsKey string } +type Server struct { + daemon *daemon.Daemon + cfg *ServerConfig + router *mux.Router + start chan struct{} + + // TODO: delete engine + eng *engine.Engine +} + +func New(cfg *ServerConfig, eng *engine.Engine) *Server { + r := createRouter( + eng, + cfg.Logging, + cfg.EnableCors, + cfg.CorsHeaders, + cfg.Version, + ) + return &Server{ + cfg: cfg, + router: r, + start: make(chan struct{}), + eng: eng, + } +} + +func (s *Server) SetDaemon(d *daemon.Daemon) { + s.daemon = d +} + +type serverCloser interface { + Serve() error + Close() error +} + +// ServeApi loops through all of the protocols sent in to docker and spawns +// off a go routine to setup a serving http.Server for each. +func (s *Server) ServeApi(protoAddrs []string) error { + var chErrors = make(chan error, len(protoAddrs)) + + for _, protoAddr := range protoAddrs { + protoAddrParts := strings.SplitN(protoAddr, "://", 2) + if len(protoAddrParts) != 2 { + return fmt.Errorf("bad format, expected PROTO://ADDR") + } + go func(proto, addr string) { + logrus.Infof("Listening for HTTP on %s (%s)", proto, addr) + srv, err := s.newServer(proto, addr) + if err != nil { + chErrors <- err + return + } + s.eng.OnShutdown(func() { + if err := srv.Close(); err != nil { + logrus.Error(err) + } + }) + if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { + err = nil + } + chErrors <- err + }(protoAddrParts[0], protoAddrParts[1]) + } + + for i := 0; i < len(protoAddrs); i++ { + err := <-chErrors + if err != nil { + return err + } + } + + return nil +} + type HttpServer struct { srv *http.Server l net.Listener @@ -1632,50 +1702,6 @@ func allocateDaemonPort(addr string) error { return nil } -type Server interface { - Serve() error - Close() error -} - -// ServeApi loops through all of the protocols sent in to docker and spawns -// off a go routine to setup a serving http.Server for each. -func ServeApi(protoAddrs []string, conf *ServerConfig, eng *engine.Engine) error { - var chErrors = make(chan error, len(protoAddrs)) - - for _, protoAddr := range protoAddrs { - protoAddrParts := strings.SplitN(protoAddr, "://", 2) - if len(protoAddrParts) != 2 { - return fmt.Errorf("bad format, expected PROTO://ADDR") - } - go func() { - logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) - srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], conf, eng) - if err != nil { - chErrors <- err - return - } - eng.OnShutdown(func() { - if err := srv.Close(); err != nil { - logrus.Error(err) - } - }) - if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { - err = nil - } - chErrors <- err - }() - } - - for i := 0; i < len(protoAddrs); i++ { - err := <-chErrors - if err != nil { - return err - } - } - - return nil -} - func toBool(s string) bool { s = strings.ToLower(strings.TrimSpace(s)) return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 4d53a888ee0b8..43f0eefe0ecfa 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -8,22 +8,15 @@ import ( "net/http" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/systemd" ) -// NewServer sets up the required Server and does protocol specific checking. -func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Server, error) { +// newServer sets up the required serverCloser and does protocol specific checking. +func (s *Server) newServer(proto, addr string) (serverCloser, error) { var ( err error l net.Listener - r = createRouter( - eng, - conf.Logging, - conf.EnableCors, - conf.CorsHeaders, - conf.Version, - ) ) switch proto { case "fd": @@ -35,13 +28,13 @@ func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Serv // We don't want to start serving on these sockets until the // daemon is initialized and installed. Otherwise required handlers // won't be ready. - <-activationLock + <-s.start // Since ListenFD will return one or more sockets we have // to create a go func to spawn off multiple serves for i := range ls { listener := ls[i] go func() { - httpSrv := http.Server{Handler: r} + httpSrv := http.Server{Handler: s.router} chErrors <- httpSrv.Serve(listener) }() } @@ -52,17 +45,17 @@ func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Serv } return nil, nil case "tcp": - if !conf.TlsVerify { + if !s.cfg.TlsVerify { logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } - if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(conf)); err != nil { + if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(s.cfg), s.start); err != nil { return nil, err } if err := allocateDaemonPort(addr); err != nil { return nil, err } case "unix": - if l, err = NewUnixSocket(addr, conf.SocketGroup); err != nil { + if l, err = NewUnixSocket(addr, s.cfg.SocketGroup, s.start); err != nil { return nil, err } default: @@ -71,19 +64,20 @@ func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Serv return &HttpServer{ &http.Server{ Addr: addr, - Handler: r, + Handler: s.router, }, l, }, nil } -func AcceptConnections() { +func (s *Server) AcceptConnections(d *daemon.Daemon) { // Tell the init daemon we are accepting requests + s.daemon = d go systemd.SdNotify("READY=1") // close the lock so the listeners start accepting connections select { - case <-activationLock: + case <-s.start: default: - close(activationLock) + close(s.start) } } diff --git a/api/server/server_windows.go b/api/server/server_windows.go index e6b23b97eafb7..c121bbd3e8471 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -5,30 +5,24 @@ package server import ( "errors" "net" + "net/http" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/daemon" ) // NewServer sets up the required Server and does protocol specific checking. -func NewServer(proto, addr string, job *engine.Job) (Server, error) { +func (s *Server) newServer(proto, addr string) (Server, error) { var ( err error l net.Listener - r = createRouter( - job.Eng, - job.GetenvBool("Logging"), - job.GetenvBool("EnableCors"), - job.Getenv("CorsHeaders"), - job.Getenv("Version"), - ) ) switch proto { case "tcp": - if !job.GetenvBool("TlsVerify") { + if !s.cfg.TlsVerify { logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } - if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { + if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(s.cfg)); err != nil { return nil, err } if err := allocateDaemonPort(addr); err != nil { @@ -37,13 +31,21 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { default: return nil, errors.New("Invalid protocol format. Windows only supports tcp.") } + return &HttpServer{ + &http.Server{ + Addr: addr, + Handler: s.router, + }, + l, + }, nil } -func AcceptConnections() { +func (s *Server) AcceptConnections(d *daemon.Daemon) { + s.daemon = d // close the lock so the listeners start accepting connections select { - case <-activationLock: + case <-s.start: default: - close(activationLock) + close(s.start) } } diff --git a/api/server/tcp_socket.go b/api/server/tcp_socket.go index 8454e0c58b7d2..a1f57231a5d05 100644 --- a/api/server/tcp_socket.go +++ b/api/server/tcp_socket.go @@ -31,8 +31,8 @@ func tlsConfigFromServerConfig(conf *ServerConfig) *tlsConfig { } } -func NewTcpSocket(addr string, config *tlsConfig) (net.Listener, error) { - l, err := listenbuffer.NewListenBuffer("tcp", addr, activationLock) +func NewTcpSocket(addr string, config *tlsConfig, activate <-chan struct{}) (net.Listener, error) { + l, err := listenbuffer.NewListenBuffer("tcp", addr, activate) if err != nil { return nil, err } diff --git a/api/server/unix_socket.go b/api/server/unix_socket.go index e472efd0a4a09..157005da6f05e 100644 --- a/api/server/unix_socket.go +++ b/api/server/unix_socket.go @@ -12,13 +12,13 @@ import ( "github.com/docker/libcontainer/user" ) -func NewUnixSocket(path, group string) (net.Listener, error) { +func NewUnixSocket(path, group string, activate <-chan struct{}) (net.Listener, error) { if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { return nil, err } mask := syscall.Umask(0777) defer syscall.Umask(mask) - l, err := listenbuffer.NewListenBuffer("unix", path, activationLock) + l, err := listenbuffer.NewListenBuffer("unix", path, activate) if err != nil { return nil, err } diff --git a/docker/daemon.go b/docker/daemon.go index 0fe10de65b112..0602ddf654511 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -105,12 +105,14 @@ func mainDaemon() { TlsKey: *flKey, } + api := apiserver.New(serverConfig, eng) + // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so // daemon doesn't exit serveAPIWait := make(chan error) go func() { - if err := apiserver.ServeApi(flHosts, serverConfig, eng); err != nil { + if err := api.ServeApi(flHosts); err != nil { logrus.Errorf("ServeAPI error: %v", err) serveAPIWait <- err return @@ -143,8 +145,8 @@ func mainDaemon() { b.Install() // after the daemon is done setting up we can tell the api to start - // accepting connections - apiserver.AcceptConnections() + // accepting connections with specified daemon + api.AcceptConnections(d) // Daemon is fully initialized and handling API traffic // Wait for serve API job to complete diff --git a/integration/runtime_test.go b/integration/runtime_test.go index cd9be89a0bd3e..beb15b8747c78 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -156,6 +156,8 @@ func spawnGlobalDaemon() { globalEngine = eng globalDaemon = mkDaemonFromEngine(eng, t) + serverConfig := &apiserver.ServerConfig{Logging: true} + api := apiserver.New(serverConfig, eng) // Spawn a Daemon go func() { logrus.Debugf("Spawning global daemon for integration tests") @@ -164,8 +166,7 @@ func spawnGlobalDaemon() { Host: testDaemonAddr, } - serverConfig := &apiserver.ServerConfig{Logging: true} - if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { + if err := api.ServeApi([]string{listenURL.String()}); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -174,7 +175,7 @@ func spawnGlobalDaemon() { // FIXME: use inmem transports instead of tcp time.Sleep(time.Second) - apiserver.AcceptConnections() + api.AcceptConnections(getDaemon(eng)) } func spawnLegitHttpsDaemon() { @@ -204,6 +205,15 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { eng := newTestEngine(t, true, root) + serverConfig := &apiserver.ServerConfig{ + Logging: true, + Tls: true, + TlsVerify: true, + TlsCa: cacert, + TlsCert: cert, + TlsKey: key, + } + api := apiserver.New(serverConfig, eng) // Spawn a Daemon go func() { logrus.Debugf("Spawning https daemon for integration tests") @@ -211,15 +221,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { Scheme: testDaemonHttpsProto, Host: addr, } - serverConfig := &apiserver.ServerConfig{ - Logging: true, - Tls: true, - TlsVerify: true, - TlsCa: cacert, - TlsCert: cert, - TlsKey: key, - } - if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { + if err := api.ServeApi([]string{listenURL.String()}); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -227,7 +229,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { // Give some time to ListenAndServer to actually start time.Sleep(time.Second) - apiserver.AcceptConnections() + api.AcceptConnections(getDaemon(eng)) return eng } diff --git a/pkg/listenbuffer/buffer.go b/pkg/listenbuffer/buffer.go index 6e3656d2c4fa8..97d622c15fda4 100644 --- a/pkg/listenbuffer/buffer.go +++ b/pkg/listenbuffer/buffer.go @@ -32,7 +32,7 @@ import "net" // NewListenBuffer returns a net.Listener listening on addr with the protocol // passed. The channel passed is used to activate the listenbuffer when the // caller is ready to accept connections. -func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, error) { +func NewListenBuffer(proto, addr string, activate <-chan struct{}) (net.Listener, error) { wrapped, err := net.Listen(proto, addr) if err != nil { return nil, err @@ -46,9 +46,9 @@ func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, // defaultListener is the buffered wrapper around the net.Listener type defaultListener struct { - wrapped net.Listener // The net.Listener wrapped by listenbuffer - ready bool // Whether the listenbuffer has been activated - activate chan struct{} // Channel to control activation of the listenbuffer + wrapped net.Listener // The net.Listener wrapped by listenbuffer + ready bool // Whether the listenbuffer has been activated + activate <-chan struct{} // Channel to control activation of the listenbuffer } // Close closes the wrapped socket. From dcc50e1d593fd7995189872791c6d7a013f16970 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Mon, 20 Apr 2015 08:16:47 -0700 Subject: [PATCH 190/332] Add support cpu cfs quota Signed-off-by: Lei Jitang --- api/types/types.go | 1 + contrib/completion/bash/docker | 1 + daemon/container.go | 1 + daemon/daemon.go | 4 +++ daemon/execdriver/driver.go | 2 ++ daemon/execdriver/lxc/lxc_template.go | 3 +++ daemon/info.go | 1 + docs/man/docker-create.1.md | 4 +++ docs/man/docker-run.1.md | 8 ++++++ docs/sources/reference/commandline/cli.md | 2 ++ docs/sources/reference/run.md | 10 ++++++++ integration-cli/docker_cli_run_test.go | 30 +++++++++++++++++++++++ pkg/sysinfo/sysinfo.go | 14 +++++++++++ runconfig/hostconfig.go | 1 + runconfig/parse.go | 2 ++ 15 files changed, 84 insertions(+) diff --git a/api/types/types.go b/api/types/types.go index 2a641fbaa3eb5..01b5d38f1f78e 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -132,6 +132,7 @@ type Info struct { DriverStatus [][2]string MemoryLimit bool SwapLimit bool + CpuCfsQuota bool IPv4Forwarding bool Debug bool NFd int diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f669352119bd0..7f87e50f5d453 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -770,6 +770,7 @@ _docker_run() { --cidfile --cpuset --cpu-shares -c + --cpu-quota --device --dns --dns-search diff --git a/daemon/container.go b/daemon/container.go index 5c90f1406de15..9dc0696ea79c9 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -356,6 +356,7 @@ func populateCommand(c *Container, env []string) error { CpuShares: c.hostConfig.CpuShares, CpusetCpus: c.hostConfig.CpusetCpus, CpusetMems: c.hostConfig.CpusetMems, + CpuQuota: c.hostConfig.CpuQuota, Rlimits: rlimits, } diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2cf2b2..0de7b1848f951 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1246,6 +1246,10 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") } + if hostConfig.CpuQuota > 0 && !daemon.SystemConfig().CpuCfsQuota { + warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.") + hostConfig.CpuQuota = 0 + } return warnings, nil } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index fc3b5caba412e..ce196df2015a3 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -111,6 +111,7 @@ type Resources struct { CpuShares int64 `json:"cpu_shares"` CpusetCpus string `json:"cpuset_cpus"` CpusetMems string `json:"cpuset_mems"` + CpuQuota int64 `json:"cpu_quota"` Rlimits []*ulimit.Rlimit `json:"rlimits"` } @@ -206,6 +207,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.MemorySwap = c.Resources.MemorySwap container.Cgroups.CpusetCpus = c.Resources.CpusetCpus container.Cgroups.CpusetMems = c.Resources.CpusetMems + container.Cgroups.CpuQuota = c.Resources.CpuQuota } return nil diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index ece924d38f0f2..b3be7f8c51884 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -113,6 +113,9 @@ lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}} {{if .Resources.CpusetMems}} lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}} {{end}} +{{if .Resources.CpuQuota}} +lxc.cgroup.cpu.cfs_quota_us = {{.Resources.CpuQuota}} +{{end}} {{end}} {{if .LxcConfig}} diff --git a/daemon/info.go b/daemon/info.go index fa019fe08f17a..270abda598c11 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -60,6 +60,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { DriverStatus: daemon.GraphDriver().Status(), MemoryLimit: daemon.SystemConfig().MemoryLimit, SwapLimit: daemon.SystemConfig().SwapLimit, + CpuCfsQuota: daemon.SystemConfig().CpuCfsQuota, IPv4Forwarding: !daemon.SystemConfig().IPv4ForwardingDisabled, Debug: os.Getenv("DEBUG") != "", NFd: fileutils.GetTotalUsedFds(), diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 6ce7fe6bdc3b4..bb9cbdc8fcb72 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -14,6 +14,7 @@ docker-create - Create a new container [**--cidfile**[=*CIDFILE*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]] +[**--cpu-quota**[=*0*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] [**--dns**[=*[]*]] @@ -82,6 +83,9 @@ IMAGE [COMMAND] [ARG...] then processes in your Docker container will only use memory from the first two memory nodes. +**-cpu-quota**=0 + Limit the CPU CFS (Completely Fair Scheduler) quota + **--device**=[] Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 42eeeb349dcbd..2893437bbe39c 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -15,6 +15,7 @@ docker-run - Run a command in a new container [**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]] [**-d**|**--detach**[=*false*]] +[**--cpu-quota**[=*0*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] [**--dns**[=*[]*]] @@ -142,6 +143,13 @@ division of CPU shares: then processes in your Docker container will only use memory from the first two memory nodes. +**--cpu-quota**=0 + Limit the CPU CFS (Completely Fair Scheduler) quota + + Limit the container's CPU usage. By default, containers run with the full +CPU resource. This flag tell the kernel to restrict the container's CPU usage +to the quota you specify. + **-d**, **--detach**=*true*|*false* Detached mode: run the container in the background and print the new container ID. The default is *false*. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076211c..eb423ab19b220 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -894,6 +894,7 @@ Creates a new container. --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) + --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota --device=[] Add a host device to the container --dns=[] Set custom DNS servers --dns-search=[] Set custom DNS search domains @@ -1847,6 +1848,7 @@ To remove an image using its digest: --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) + --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota -d, --detach=false Run container in background and print container ID --device=[] Add a host device to the container --dns=[] Set custom DNS servers diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index b5784cba784cb..10178b382a969 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -475,6 +475,7 @@ container: -c, --cpu-shares=0: CPU shares (relative weight) --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + --cpu-quota=0: Limit the CPU CFS (Completely Fair Scheduler) quota ### Memory constraints @@ -615,6 +616,15 @@ memory nodes 1 and 3. This example restricts the processes in the container to only use memory from memory nodes 0, 1 and 2. +### CPU quota constraint + +The `--cpu-quota` flag limits the container's CPU usage. The default 0 value +allows the container to take 100% of a CPU resource (1 CPU). The CFS (Completely Fair +Scheduler) handles resource allocation for executing processes and is default +Linux Scheduler used by the kernel. Set this value to 50000 to limit the container +to 50% of a CPU resource. For multiple CPUs, adjust the `--cpu-quota` as necessary. +For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt). + ## Runtime privilege, Linux capabilities, and LXC configuration --cap-add: Add Linux capabilities diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index de53fd49414cc..e434261d8279a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -105,6 +105,36 @@ func TestRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) { logDone("run - echo with CPU and memory limit") } +// "test" should be printed +func TestRunEchoStdoutWitCPUQuota(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + t.Fatalf("failed to run container: %v, output: %q", err, out) + } + out = strings.TrimSpace(out) + if strings.Contains(out, "Your kernel does not support CPU cfs quota") { + t.Skip("Your kernel does not support CPU cfs quota, skip this test") + } + if out != "test" { + t.Errorf("container should've printed 'test'") + } + + cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.CpuQuota}}", "test") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to inspect container: %s, %v", out, err) + } + out = strings.TrimSpace(out) + if out != "8000" { + t.Errorf("setting the CPU CFS quota failed") + } + + logDone("run - echo with CPU quota") +} + // "test" should be printed func TestRunEchoNamedContainer(t *testing.T) { defer deleteAllContainers() diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 16839bcb4cc16..d1dcea3bf8470 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -13,6 +13,7 @@ import ( type SysInfo struct { MemoryLimit bool SwapLimit bool + CpuCfsQuota bool IPv4ForwardingDisabled bool AppArmor bool } @@ -39,6 +40,19 @@ func New(quiet bool) *SysInfo { } } + if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil { + if !quiet { + logrus.Warnf("WARING: %s\n", err) + } + } else { + _, err1 := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us")) + logrus.Warnf("%s", cgroupCpuMountpoint) + sysInfo.CpuCfsQuota = err1 == nil + if !sysInfo.CpuCfsQuota && !quiet { + logrus.Warnf("WARING: Your kernel does not support cgroup cfs quotas") + } + } + // Check if AppArmor is supported. if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { sysInfo.AppArmor = false diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9d338d7fb8d1b..171671b6efddd 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -167,6 +167,7 @@ type HostConfig struct { CpuShares int64 // CPU shares (relative weight vs. other containers) CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 + CpuQuota int64 Privileged bool PortBindings nat.PortMap Links []string diff --git a/runconfig/parse.go b/runconfig/parse.go index 81dbf2d491597..2cdb2d331d727 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -65,6 +65,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") + flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") @@ -312,6 +313,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe CpuShares: *flCpuShares, CpusetCpus: *flCpusetCpus, CpusetMems: *flCpusetMems, + CpuQuota: *flCpuQuota, Privileged: *flPrivileged, PortBindings: portBindings, Links: flLinks.GetAll(), From 66239ab5c914ce716c044b676ba1ad4757ce6f2b Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Mon, 20 Apr 2015 17:49:51 +0800 Subject: [PATCH 191/332] change httpError logic Signed-off-by: Zhang Wei Signed-off-by: Zhang Wei --- api/server/server.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 847ec6c21af07..c3ec548eb7a4b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -130,6 +130,10 @@ func parseMultipartForm(r *http.Request) error { } func httpError(w http.ResponseWriter, err error) { + if err == nil || w == nil { + logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling") + return + } statusCode := http.StatusInternalServerError // FIXME: this is brittle and should not be necessary. // If we need to differentiate between different possible error types, we should @@ -149,10 +153,8 @@ func httpError(w http.ResponseWriter, err error) { statusCode = http.StatusForbidden } - if err != nil { - logrus.Errorf("HTTP Error: statusCode=%d %v", statusCode, err) - http.Error(w, err.Error(), statusCode) - } + logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error") + http.Error(w, err.Error(), statusCode) } // writeJSONEnv writes the engine.Env values to the http response stream as a From da7bca449652396dd8a4a0ff2e303d3d3c7a58a2 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 17 Apr 2015 15:18:28 -0700 Subject: [PATCH 192/332] Make all http handlers api.server.Server methods Signed-off-by: Alexander Morozov --- api/server/server.go | 316 ++++++++++++++++-------------------- integration/runtime_test.go | 3 - 2 files changed, 143 insertions(+), 176 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 3146e09ed0087..50dc84ff7ff46 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -64,19 +64,14 @@ type Server struct { } func New(cfg *ServerConfig, eng *engine.Engine) *Server { - r := createRouter( - eng, - cfg.Logging, - cfg.EnableCors, - cfg.CorsHeaders, - cfg.Version, - ) - return &Server{ - cfg: cfg, - router: r, - start: make(chan struct{}), - eng: eng, + srv := &Server{ + cfg: cfg, + start: make(chan struct{}), + eng: eng, } + r := createRouter(srv, eng) + srv.router = r + return srv } func (s *Server) SetDaemon(d *daemon.Daemon) { @@ -250,19 +245,14 @@ func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) { } } -func getDaemon(eng *engine.Engine) *daemon.Daemon { - return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) -} - -func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var config *registry.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() if err != nil { return err } - d := getDaemon(eng) - status, err := d.RegistryService.Auth(config) + status, err := s.daemon.RegistryService.Auth(config) if err != nil { return err } @@ -271,7 +261,7 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter }) } -func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") v := &types.Version{ @@ -289,7 +279,7 @@ func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWrit return writeJSON(w, http.StatusOK, v) } -func postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersKill(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") } @@ -317,7 +307,7 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp } } - if err = getDaemon(eng).ContainerKill(name, sig); err != nil { + if err = s.daemon.ContainerKill(name, sig); err != nil { return err } @@ -325,7 +315,7 @@ 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 { +func (s *Server) 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") } @@ -334,8 +324,7 @@ func postContainersPause(eng *engine.Engine, version version.Version, w http.Res } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -350,7 +339,7 @@ func postContainersPause(eng *engine.Engine, version version.Version, w http.Res return nil } -func postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) 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") } @@ -359,8 +348,7 @@ func postContainersUnpause(eng *engine.Engine, version version.Version, w http.R } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -375,17 +363,15 @@ func postContainersUnpause(eng *engine.Engine, version version.Version, w http.R return nil } -func getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) 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") } - d := getDaemon(eng) - - return d.ContainerExport(vars["name"], w) + return s.daemon.ContainerExport(vars["name"], w) } -func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesJSON(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 } @@ -397,7 +383,7 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW All: toBool(r.Form.Get("all")), } - images, err := getDaemon(eng).Repositories().Images(&imagesConfig) + images, err := s.daemon.Repositories().Images(&imagesConfig) if err != nil { return err } @@ -426,7 +412,7 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW return writeJSON(w, http.StatusOK, legacyImages) } -func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.GreaterThan("1.6") { w.WriteHeader(http.StatusNotFound) return fmt.Errorf("This is now implemented in the client.") @@ -435,10 +421,10 @@ func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWr return nil } -func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - info, err := getDaemon(eng).SystemInfo() + info, err := s.daemon.SystemInfo() if err != nil { return err } @@ -446,7 +432,7 @@ func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, return writeJSON(w, http.StatusOK, info) } -func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getEvents(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 } @@ -497,7 +483,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite return true } - d := getDaemon(eng) + d := s.daemon es := d.EventsService w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(utils.NewWriteFlusher(w)) @@ -550,13 +536,13 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite } } -func getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesHistory(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") } name := vars["name"] - history, err := getDaemon(eng).Repositories().History(name) + history, err := s.daemon.Repositories().History(name) if err != nil { return err } @@ -564,14 +550,13 @@ func getImagesHistory(eng *engine.Engine, version version.Version, w http.Respon return writeJSON(w, http.StatusOK, history) } -func getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersChanges(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") } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -584,7 +569,7 @@ func getContainersChanges(eng *engine.Engine, version version.Version, w http.Re return writeJSON(w, http.StatusOK, changes) } -func getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.4") { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } @@ -597,7 +582,7 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon return err } - procList, err := getDaemon(eng).ContainerTop(vars["name"], r.Form.Get("ps_args")) + procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args")) if err != nil { return err } @@ -605,7 +590,7 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon return writeJSON(w, http.StatusOK, procList) } -func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersJSON(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 } @@ -626,7 +611,7 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo config.Limit = limit } - containers, err := getDaemon(eng).Containers(config) + containers, err := s.daemon.Containers(config) if err != nil { return err } @@ -634,7 +619,7 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo return writeJSON(w, http.StatusOK, containers) } -func getContainersStats(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersStats(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 } @@ -642,12 +627,10 @@ func getContainersStats(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) - - return d.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) + return s.daemon.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) } -func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersLogs(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 } @@ -670,15 +653,14 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo OutStream: utils.NewWriteFlusher(w), } - d := getDaemon(eng) - if err := d.ContainerLogs(vars["name"], logsConfig); err != nil { + if err := s.daemon.ContainerLogs(vars["name"], logsConfig); err != nil { fmt.Fprintf(w, "Error running logs job: %s\n", err) } return nil } -func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesTag(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 } @@ -686,18 +668,17 @@ func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseW return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) repo := r.Form.Get("repo") tag := r.Form.Get("tag") force := toBool(r.Form.Get("force")) - if err := d.Repositories().Tag(repo, tag, vars["name"], force); err != nil { + if err := s.daemon.Repositories().Tag(repo, tag, vars["name"], force); err != nil { return err } w.WriteHeader(http.StatusCreated) return nil } -func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postCommit(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 } @@ -723,9 +704,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit Config: r.Body, } - d := getDaemon(eng) - - imgID, err := d.ContainerCommit(cont, containerCommitConfig) + imgID, err := s.daemon.ContainerCommit(cont, containerCommitConfig) if err != nil { return err } @@ -736,7 +715,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit } // Creates an image from Pull or from Import -func postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesCreate(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 } @@ -757,8 +736,6 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon } } - d := getDaemon(eng) - if image != "" { //pull if tag == "" { image, tag = parsers.ParseRepositoryTag(image) @@ -783,7 +760,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon imagePullConfig.Json = false } - if err := d.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { + if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { return err } } else { //import @@ -804,7 +781,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon imageImportConfig.Json = false } - if err := d.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { + if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { return err } @@ -813,7 +790,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon return nil } -func getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesSearch(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 } @@ -836,15 +813,14 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons headers[k] = v } } - d := getDaemon(eng) - query, err := d.RegistryService.Search(r.Form.Get("term"), config, headers) + query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers) if err != nil { return err } return json.NewEncoder(w).Encode(query.Results) } -func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) 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") } @@ -896,7 +872,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response return nil } -func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesGet(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") } @@ -916,14 +892,14 @@ func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWr return job.Run() } -func postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { job := eng.Job("load") job.Stdin.Add(r.Body) job.Stdout.Add(w) return job.Run() } -func postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -940,7 +916,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re return err } - containerId, warnings, err := getDaemon(eng).ContainerCreate(name, config, hostConfig) + containerId, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig) if err != nil { return err } @@ -951,7 +927,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re }) } -func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersRestart(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 } @@ -959,13 +935,12 @@ func postContainersRestart(eng *engine.Engine, version version.Version, w http.R return fmt.Errorf("Missing parameter") } - s, err := strconv.Atoi(r.Form.Get("t")) + timeout, err := strconv.Atoi(r.Form.Get("t")) if err != nil { return err } - d := getDaemon(eng) - if err := d.ContainerRestart(vars["name"], s); err != nil { + if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil { return err } @@ -974,7 +949,7 @@ func postContainersRestart(eng *engine.Engine, version version.Version, w http.R return nil } -func postContainerRename(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerRename(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 } @@ -982,17 +957,16 @@ func postContainerRename(eng *engine.Engine, version version.Version, w http.Res return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) name := vars["name"] newName := r.Form.Get("name") - if err := d.ContainerRename(name, newName); err != nil { + if err := s.daemon.ContainerRename(name, newName); err != nil { return err } w.WriteHeader(http.StatusNoContent) return nil } -func deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteContainers(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 } @@ -1001,14 +975,13 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon } name := vars["name"] - d := getDaemon(eng) config := &daemon.ContainerRmConfig{ ForceRemove: toBool(r.Form.Get("force")), RemoveVolume: toBool(r.Form.Get("v")), RemoveLink: toBool(r.Form.Get("link")), } - if err := d.ContainerRm(name, config); err != nil { + if err := s.daemon.ContainerRm(name, config); err != nil { // Force a 404 for the empty string if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { return fmt.Errorf("no such id: \"\"") @@ -1021,7 +994,7 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon return nil } -func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteImages(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 } @@ -1029,12 +1002,11 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) name := vars["name"] force := toBool(r.Form.Get("force")) noprune := toBool(r.Form.Get("noprune")) - list, err := d.ImageDelete(name, force, noprune) + list, err := s.daemon.ImageDelete(name, force, noprune) if err != nil { return err } @@ -1042,7 +1014,7 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr return writeJSON(w, http.StatusOK, list) } -func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStart(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") } @@ -1067,7 +1039,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res hostConfig = c } - if err := getDaemon(eng).ContainerStart(vars["name"], hostConfig); err != nil { + if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil { if err.Error() == "Container already started" { w.WriteHeader(http.StatusNotModified) return nil @@ -1078,7 +1050,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res return nil } -func postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStop(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 } @@ -1086,13 +1058,12 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) seconds, err := strconv.Atoi(r.Form.Get("t")) if err != nil { return err } - if err := d.ContainerStop(vars["name"], seconds); err != nil { + if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil { if err.Error() == "Container already stopped" { w.WriteHeader(http.StatusNotModified) return nil @@ -1104,14 +1075,13 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp return nil } -func postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersWait(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") } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -1123,7 +1093,7 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp }) } -func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersResize(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 } @@ -1140,8 +1110,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re return err } - d := getDaemon(eng) - cont, err := d.Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { return err } @@ -1149,7 +1118,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re return cont.Resize(height, width) } -func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersAttach(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 } @@ -1157,9 +1126,7 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) - - cont, err := d.Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { return err } @@ -1206,16 +1173,14 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re return nil } -func wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) wsContainersAttach(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") } - d := getDaemon(eng) - - cont, err := d.Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { return err } @@ -1234,7 +1199,7 @@ func wsContainersAttach(eng *engine.Engine, version version.Version, w http.Resp return nil } -func getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersByName(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") } @@ -1246,13 +1211,12 @@ func getContainersByName(eng *engine.Engine, version version.Version, w http.Res return job.Run() } -func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getExecByID(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 'id'") } - d := getDaemon(eng) - eConfig, err := d.ContainerExecInspect(vars["id"]) + eConfig, err := s.daemon.ContainerExecInspect(vars["id"]) if err != nil { return err } @@ -1260,7 +1224,7 @@ func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWri return writeJSON(w, http.StatusOK, eConfig) } -func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesByName(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") } @@ -1272,7 +1236,7 @@ func getImagesByName(eng *engine.Engine, version version.Version, w http.Respons return job.Run() } -func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.3") { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } @@ -1362,7 +1326,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite return nil } -func postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCopy(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") } @@ -1386,7 +1350,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp res = res[1:] } - cont, err := getDaemon(eng).Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { logrus.Errorf("%v", err) if strings.Contains(strings.ToLower(err.Error()), "no such id") { @@ -1412,7 +1376,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp return nil } -func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1449,7 +1413,7 @@ func postContainerExecCreate(eng *engine.Engine, version version.Version, w http } // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. -func postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1500,7 +1464,7 @@ func postContainerExecStart(eng *engine.Engine, version version.Version, w http. return nil } -func postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecResize(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 } @@ -1517,12 +1481,10 @@ func postContainerExecResize(eng *engine.Engine, version version.Version, w http return err } - d := getDaemon(eng) - - return d.ContainerExecResize(vars["name"], height, width) + return s.daemon.ContainerExecResize(vars["name"], height, width) } -func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil } @@ -1533,7 +1495,7 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { _, err := w.Write([]byte{'O', 'K'}) return err } @@ -1574,71 +1536,72 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } // we keep enableCors just for legacy usage, need to be removed in the future -func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders string, dockerVersion string) *mux.Router { +func createRouter(s *Server, eng *engine.Engine) *mux.Router { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { ProfilerSetup(r, "/debug/") } m := map[string]map[string]HttpApiFunc{ "GET": { - "/_ping": ping, - "/events": getEvents, - "/info": getInfo, - "/version": getVersion, - "/images/json": getImagesJSON, - "/images/viz": getImagesViz, - "/images/search": getImagesSearch, - "/images/get": getImagesGet, - "/images/{name:.*}/get": getImagesGet, - "/images/{name:.*}/history": getImagesHistory, - "/images/{name:.*}/json": getImagesByName, - "/containers/ps": getContainersJSON, - "/containers/json": getContainersJSON, - "/containers/{name:.*}/export": getContainersExport, - "/containers/{name:.*}/changes": getContainersChanges, - "/containers/{name:.*}/json": getContainersByName, - "/containers/{name:.*}/top": getContainersTop, - "/containers/{name:.*}/logs": getContainersLogs, - "/containers/{name:.*}/stats": getContainersStats, - "/containers/{name:.*}/attach/ws": wsContainersAttach, - "/exec/{id:.*}/json": getExecByID, + "/_ping": s.ping, + "/events": s.getEvents, + "/info": s.getInfo, + "/version": s.getVersion, + "/images/json": s.getImagesJSON, + "/images/viz": s.getImagesViz, + "/images/search": s.getImagesSearch, + "/images/get": s.getImagesGet, + "/images/{name:.*}/get": s.getImagesGet, + "/images/{name:.*}/history": s.getImagesHistory, + "/images/{name:.*}/json": s.getImagesByName, + "/containers/ps": s.getContainersJSON, + "/containers/json": s.getContainersJSON, + "/containers/{name:.*}/export": s.getContainersExport, + "/containers/{name:.*}/changes": s.getContainersChanges, + "/containers/{name:.*}/json": s.getContainersByName, + "/containers/{name:.*}/top": s.getContainersTop, + "/containers/{name:.*}/logs": s.getContainersLogs, + "/containers/{name:.*}/stats": s.getContainersStats, + "/containers/{name:.*}/attach/ws": s.wsContainersAttach, + "/exec/{id:.*}/json": s.getExecByID, }, "POST": { - "/auth": postAuth, - "/commit": postCommit, - "/build": postBuild, - "/images/create": postImagesCreate, - "/images/load": postImagesLoad, - "/images/{name:.*}/push": postImagesPush, - "/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, - "/containers/{name:.*}/wait": postContainersWait, - "/containers/{name:.*}/resize": postContainersResize, - "/containers/{name:.*}/attach": postContainersAttach, - "/containers/{name:.*}/copy": postContainersCopy, - "/containers/{name:.*}/exec": postContainerExecCreate, - "/exec/{name:.*}/start": postContainerExecStart, - "/exec/{name:.*}/resize": postContainerExecResize, - "/containers/{name:.*}/rename": postContainerRename, + "/auth": s.postAuth, + "/commit": s.postCommit, + "/build": s.postBuild, + "/images/create": s.postImagesCreate, + "/images/load": s.postImagesLoad, + "/images/{name:.*}/push": s.postImagesPush, + "/images/{name:.*}/tag": s.postImagesTag, + "/containers/create": s.postContainersCreate, + "/containers/{name:.*}/kill": s.postContainersKill, + "/containers/{name:.*}/pause": s.postContainersPause, + "/containers/{name:.*}/unpause": s.postContainersUnpause, + "/containers/{name:.*}/restart": s.postContainersRestart, + "/containers/{name:.*}/start": s.postContainersStart, + "/containers/{name:.*}/stop": s.postContainersStop, + "/containers/{name:.*}/wait": s.postContainersWait, + "/containers/{name:.*}/resize": s.postContainersResize, + "/containers/{name:.*}/attach": s.postContainersAttach, + "/containers/{name:.*}/copy": s.postContainersCopy, + "/containers/{name:.*}/exec": s.postContainerExecCreate, + "/exec/{name:.*}/start": s.postContainerExecStart, + "/exec/{name:.*}/resize": s.postContainerExecResize, + "/containers/{name:.*}/rename": s.postContainerRename, }, "DELETE": { - "/containers/{name:.*}": deleteContainers, - "/images/{name:.*}": deleteImages, + "/containers/{name:.*}": s.deleteContainers, + "/images/{name:.*}": s.deleteImages, }, "OPTIONS": { - "": optionsHandler, + "": s.optionsHandler, }, } // If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*" // otherwise, all head values will be passed to HTTP handler - if corsHeaders == "" && enableCors { + corsHeaders := s.cfg.CorsHeaders + if corsHeaders == "" && s.cfg.EnableCors { corsHeaders = "*" } @@ -1651,7 +1614,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders stri localMethod := method // build the handler function - f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, corsHeaders, version.Version(dockerVersion)) + f := makeHttpHandler(eng, s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) // add the new route if localRoute == "" { @@ -1670,7 +1633,14 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders stri // FIXME: refactor this to be part of Server and not require re-creating a new // router each time. This requires first moving ListenAndServe into Server. func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) { - router := createRouter(eng, false, true, "", "") + cfg := &ServerConfig{ + EnableCors: true, + Version: string(apiversion), + } + api := New(cfg, eng) + daemon, _ := eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) + api.AcceptConnections(daemon) + router := createRouter(api, eng) // Insert APIVERSION into the request as a convenience req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path) router.ServeHTTP(w, req) diff --git a/integration/runtime_test.go b/integration/runtime_test.go index beb15b8747c78..2e106972e46ac 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -48,9 +48,7 @@ const ( ) var ( - // FIXME: globalDaemon is deprecated by globalEngine. All tests should be converted. globalDaemon *daemon.Daemon - globalEngine *engine.Engine globalHttpsEngine *engine.Engine globalRogueHttpsEngine *engine.Engine startFds int @@ -153,7 +151,6 @@ func spawnGlobalDaemon() { } t := std_log.New(os.Stderr, "", 0) eng := NewTestEngine(t) - globalEngine = eng globalDaemon = mkDaemonFromEngine(eng, t) serverConfig := &apiserver.ServerConfig{Logging: true} From fcdb1fbfa1e691bbdfb73a289ff84fbf6b26fad6 Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Wed, 15 Apr 2015 17:11:58 -0400 Subject: [PATCH 193/332] Improve documentation in "project" directory. Signed-off-by: Raghuram Devarakonda --- docs/sources/project/advanced-contributing.md | 2 +- docs/sources/project/coding-style.md | 4 +- docs/sources/project/create-pr.md | 30 +++++---- docs/sources/project/find-an-issue.md | 16 ++--- docs/sources/project/set-up-git.md | 4 +- docs/sources/project/software-required.md | 2 +- docs/sources/project/work-issue.md | 66 ++++++++----------- 7 files changed, 58 insertions(+), 66 deletions(-) diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md index 0c9b5d1ce8811..ee958f4b47d28 100644 --- a/docs/sources/project/advanced-contributing.md +++ b/docs/sources/project/advanced-contributing.md @@ -89,7 +89,7 @@ The following provides greater detail on the process: This is a Markdown file that describes your idea. Your proposal should include information like: - * Why is this changed needed or what are the use cases? + * Why is this change needed or what are the use cases? * What are the requirements this change should meet? * What are some ways to design/implement this feature? * Which design/implementation do you think is best and why? diff --git a/docs/sources/project/coding-style.md b/docs/sources/project/coding-style.md index e5b6f5fe9cfb1..bf8267e716398 100644 --- a/docs/sources/project/coding-style.md +++ b/docs/sources/project/coding-style.md @@ -6,8 +6,8 @@ page_keywords: change, commit, squash, request, pull request, test, unit test, i This checklist summarizes the material you experienced working through [make a code contribution](/project/make-a-contribution) and [advanced -contributing](/project/advanced-contributing). The checklist applies to code -that is program code or code that is documentation code. +contributing](/project/advanced-contributing). The checklist applies to both +program code and documentation code. ## Change and commit code diff --git a/docs/sources/project/create-pr.md b/docs/sources/project/create-pr.md index 197aee849d0bf..f39f0aa98a72d 100644 --- a/docs/sources/project/create-pr.md +++ b/docs/sources/project/create-pr.md @@ -22,7 +22,7 @@ Before you create a pull request, check your work. 2. Checkout your feature branch. $ git checkout 11038-fix-rhel-link - Already on '11038-fix-rhel-link' + Switched to branch '11038-fix-rhel-link' 3. Run the full test suite on your branch. @@ -41,7 +41,11 @@ Before you create a pull request, check your work. Always rebase and squash your commits before making a pull request. -1. Fetch any of the last minute changes from `docker/docker`. +1. Checkout your feature branch in your local `docker-fork` repository. + + This is the branch associated with your request. + +2. Fetch any last minute changes from `docker/docker`. $ git fetch upstream master From github.com:docker/docker @@ -56,28 +60,28 @@ Always rebase and squash your commits before making a pull request. pick 1a79f55 Tweak some of the other text for grammar pick 53e4983 Fix a link pick 3ce07bb Add a new line about RHEL - - If you run into trouble, `git --rebase abort` removes any changes and gets - you back to where you started. -4. Squash the `pick` keyword with `squash` on all but the first commit. +5. Replace the `pick` keyword with `squash` on all but the first commit. pick 1a79f55 Tweak some of the other text for grammar squash 53e4983 Fix a link squash 3ce07bb Add a new line about RHEL - After closing the file, `git` opens your editor again to edit the commit - message. + After you save the changes and quit from the editor, git starts + the rebase, reporting the progress along the way. Sometimes + your changes can conflict with the work of others. If git + encounters a conflict, it stops the rebase, and prints guidance + for how to correct the conflict. -5. Edit and save your commit message. +6. Edit and save your commit message. `git commit -s` Make sure your message includes FETCH_HEAD -3. Fetch all the changes from the `upstream master` branch. +3. Start an interactive rebase. - $ git fetch upstream master + $ git rebase -i upstream/master - This command says get all the changes from the `master` branch belonging to - the `upstream` remote. +4. Rebase opens an editor with a list of commits. -4. Rebase your master with the local copy of Docker's `master` branch. + pick 1a79f55 Tweak some of the other text for grammar + pick 53e4983 Fix a link + pick 3ce07bb Add a new line about RHEL - $ git rebase -i upstream/master - - This command starts an interactive rebase to rewrite all the commits from - Docker's `upstream/master` onto your local branch, and then re-apply each of - your commits on top of the upstream changes. If you aren't familiar or - comfortable with rebase, you can learn more about rebasing on the web. - -5. Rebase opens an editor with a list of commits. +5. Replace the `pick` keyword with `squash` on all but the first commit. - pick 1a79f55 Tweak some of the other text for grammar - pick 53e4983 Fix a link - pick 3ce07bb Add a new line about RHEL - - If you run into trouble, `git --rebase abort` removes any changes and gets - you back to where you started. + pick 1a79f55 Tweak some of the other text for grammar + squash 53e4983 Fix a link + squash 3ce07bb Add a new line about RHEL -6. Squash the `pick` keyword with `squash` on all but the first commit. + After you save the changes and quit from the editor, git starts + the rebase, reporting the progress along the way. Sometimes + your changes can conflict with the work of others. If git + encounters a conflict, it stops the rebase, and prints guidance + for how to correct the conflict. - pick 1a79f55 Tweak some of the other text for grammar - squash 53e4983 Fix a link - squash 3ce07bb Add a new line about RHEL +6. Edit and save your commit message. - After closing the file, `git` opens your editor again to edit the commit - message. + `git commit -s` -7. Edit the commit message to reflect the entire change. + Make sure your message includes Date: Wed, 15 Apr 2015 22:43:18 +0000 Subject: [PATCH 195/332] Port test from integration tests Addresses #12255 Signed-off-by: Srini Brahmaroutu --- daemon/daemon.go | 4 ++ daemon/start.go | 4 ++ integration-cli/docker_api_containers_test.go | 51 +++++++++++++++++++ integration-cli/docker_cli_run_test.go | 12 ----- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2cf2b2..e5fd6f939584c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1226,6 +1226,10 @@ func checkKernel() error { func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]string, error) { var warnings []string + if hostConfig == nil { + return warnings, nil + } + if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { return warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) } diff --git a/daemon/start.go b/daemon/start.go index dbb3dd1810703..d3af073a884da 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -20,6 +20,10 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConf return fmt.Errorf("Container already started") } + if _, err = daemon.verifyHostConfig(hostConfig); err != nil { + return err + } + // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. if hostConfig != nil { diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index b76eb6ba2a310..1dea478452571 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -817,3 +817,54 @@ func TestContainerApiPostCreateNull(t *testing.T) { logDone("containers REST API - Create Null") } + +func TestCreateWithTooLowMemoryLimit(t *testing.T) { + defer deleteAllContainers() + config := `{ + "Image": "busybox", + "Cmd": "ls", + "OpenStdin": true, + "CpuShares": 100, + "Memory": 524287 + }` + + _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + b, err2 := readBody(body) + if err2 != nil { + t.Fatal(err2) + } + + if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { + t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + } + + logDone("container REST API - create can't set too low memory limit") +} + +func TestStartWithTooLowMemoryLimit(t *testing.T) { + defer deleteAllContainers() + + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox")) + if err != nil { + t.Fatal(err, out) + } + + containerID := strings.TrimSpace(out) + + config := `{ + "CpuShares": 100, + "Memory": 524287 + }` + + _, body, err := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") + b, err2 := readBody(body) + if err2 != nil { + t.Fatal(err2) + } + + if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { + t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + } + + logDone("container REST API - start can't set too low memory limit") +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index de53fd49414cc..f2e4eb9b2bdf8 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3496,15 +3496,3 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) { } logDone("run - can kill container with pid-host and some childs of pid 1") } - -func TestRunWithTooSmallMemoryLimit(t *testing.T) { - defer deleteAllContainers() - // this memory limit is 1 byte less than the min, which is 4MB - // https://github.com/docker/docker/blob/v1.5.0/daemon/create.go#L22 - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-m", "4194303", "busybox")) - if err == nil || !strings.Contains(out, "Minimum memory limit allowed is 4MB") { - t.Fatalf("expected run to fail when using too low a memory limit: %q", out) - } - - logDone("run - can't set too low memory limit") -} From 716e21be2b9780306cc1ffe02b2a784e2dece94e Mon Sep 17 00:00:00 2001 From: Sergey Evstifeev Date: Mon, 20 Apr 2015 21:13:05 +0200 Subject: [PATCH 196/332] Add missing testRequires(t, Network) Fixes #12552 Signed-off-by: Sergey Evstifeev --- integration-cli/docker_api_images_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 276ee7f3c742f..1774e6b74d4fb 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -70,6 +70,7 @@ func TestApiImagesFilter(t *testing.T) { } func TestApiImagesSaveAndLoad(t *testing.T) { + testRequires(t, Network) out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false) if err != nil { t.Fatal(err) From 9e50bf6270f426f6ef6649b1985036988b207407 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 20 Apr 2015 12:48:33 -0700 Subject: [PATCH 197/332] Remove engine from trust Signed-off-by: Alexander Morozov --- daemon/daemon.go | 28 +++++++++--------- graph/manifest.go | 34 ++++++++++------------ graph/pull.go | 5 +--- graph/tags.go | 21 ++++++++++---- graph/tags_unit_test.go | 6 +++- trust/service.go | 63 ++++++++++++++--------------------------- 6 files changed, 73 insertions(+), 84 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2cf2b2..7dc0f21976cc5 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -108,7 +108,6 @@ type Daemon struct { containerGraph *graphdb.Database driver graphdriver.Driver execDriver execdriver.Driver - trustStore *trust.TrustStore statsCollector *statsCollector defaultLogConfig runconfig.LogConfig RegistryService *registry.Service @@ -129,9 +128,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { if err := daemon.Repositories().Install(eng); err != nil { return err } - if err := daemon.trustStore.Install(eng); err != nil { - return err - } // FIXME: this hack is necessary for legacy integration tests to access // the daemon object. eng.HackSetGlobalVar("httpapi.daemon", daemon) @@ -903,22 +899,29 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService return nil, err } - eventsService := events.New() - logrus.Debug("Creating repository list") - repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey, registryService, eventsService) - if err != nil { - return nil, fmt.Errorf("Couldn't create Tag store: %s", err) - } - trustDir := path.Join(config.Root, "trust") if err := os.MkdirAll(trustDir, 0700); err != nil && !os.IsExist(err) { return nil, err } - t, err := trust.NewTrustStore(trustDir) + trustService, err := trust.NewTrustStore(trustDir) if err != nil { return nil, fmt.Errorf("could not create trust store: %s", err) } + eventsService := events.New() + logrus.Debug("Creating repository list") + tagCfg := &graph.TagStoreConfig{ + Graph: g, + Key: trustKey, + Registry: registryService, + Events: eventsService, + Trust: trustService, + } + repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), tagCfg) + if err != nil { + return nil, fmt.Errorf("Couldn't create Tag store: %s", err) + } + if !config.DisableNetwork { if err := bridge.InitDriver(&config.Bridge); err != nil { return nil, fmt.Errorf("Error initializing Bridge: %v", err) @@ -980,7 +983,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService sysInitPath: sysInitPath, execDriver: ed, eng: eng, - trustStore: t, statsCollector: newStatsCollector(1 * time.Second), defaultLogConfig: config.LogConfig, RegistryService: registryService, diff --git a/graph/manifest.go b/graph/manifest.go index 7e9281537e3bd..e6d5ebc39ce3f 100644 --- a/graph/manifest.go +++ b/graph/manifest.go @@ -1,7 +1,6 @@ package graph import ( - "bytes" "encoding/json" "fmt" @@ -9,6 +8,7 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/registry" + "github.com/docker/docker/trust" "github.com/docker/docker/utils" "github.com/docker/libtrust" ) @@ -69,32 +69,28 @@ func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, var verified bool for _, key := range keys { - job := eng.Job("trust_key_check") - b, err := key.MarshalJSON() - if err != nil { - return nil, false, fmt.Errorf("error marshalling public key: %s", err) - } namespace := manifest.Name if namespace[0] != '/' { namespace = "/" + namespace } - stdoutBuffer := bytes.NewBuffer(nil) - - job.Args = append(job.Args, namespace) - job.Setenv("PublicKey", string(b)) + b, err := key.MarshalJSON() + if err != nil { + return nil, false, fmt.Errorf("error marshalling public key: %s", err) + } // Check key has read/write permission (0x03) - job.SetenvInt("Permission", 0x03) - job.Stdout.Add(stdoutBuffer) - if err = job.Run(); err != nil { - return nil, false, fmt.Errorf("error running key check: %s", err) + v, err := s.trustService.CheckKey(namespace, b, 0x03) + if err != nil { + vErr, ok := err.(trust.NotVerifiedError) + if !ok { + return nil, false, fmt.Errorf("error running key check: %s", err) + } + logrus.Debugf("Key check result: %v", vErr) } - result := engine.Tail(stdoutBuffer, 1) - logrus.Debugf("Key check result: %q", result) - if result == "verified" { - verified = true + verified = v + if verified { + logrus.Debug("Key check result: verified") } } - return &manifest, verified, nil } diff --git a/graph/pull.go b/graph/pull.go index fa22d335d36ee..edc67df9d653f 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -70,10 +70,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Index.Official || endpoint.Version == registry.APIVersion2) { if repoInfo.Official { - j := eng.Job("trust_update_base") - if err = j.Run(); err != nil { - logrus.Errorf("error updating trust base graph: %s", err) - } + s.trustService.UpdateBase() } logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) diff --git a/graph/tags.go b/graph/tags.go index 444e74f726b7e..39f0ffc29a93b 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" + "github.com/docker/docker/trust" "github.com/docker/docker/utils" "github.com/docker/libtrust" ) @@ -42,6 +43,7 @@ type TagStore struct { pushingPool map[string]chan struct{} registryService *registry.Service eventsService *events.Events + trustService *trust.TrustStore } type Repository map[string]string @@ -64,7 +66,15 @@ func (r Repository) Contains(u Repository) bool { return true } -func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registryService *registry.Service, eventsService *events.Events) (*TagStore, error) { +type TagStoreConfig struct { + Graph *Graph + Key libtrust.PrivateKey + Registry *registry.Service + Events *events.Events + Trust *trust.TrustStore +} + +func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) { abspath, err := filepath.Abs(path) if err != nil { return nil, err @@ -72,13 +82,14 @@ func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registrySer store := &TagStore{ path: abspath, - graph: graph, - trustKey: key, + graph: cfg.Graph, + trustKey: cfg.Key, Repositories: make(map[string]Repository), pullingPool: make(map[string]chan struct{}), pushingPool: make(map[string]chan struct{}), - registryService: registryService, - eventsService: eventsService, + registryService: cfg.Registry, + eventsService: cfg.Events, + trustService: cfg.Trust, } // Load the json file if it exists, otherwise create it. if err := store.reload(); os.IsNotExist(err) { diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index 4a4ddbe4b3c7c..0482fa58e3caa 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -60,7 +60,11 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err != nil { t.Fatal(err) } - store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil, events.New()) + tagCfg := &TagStoreConfig{ + Graph: graph, + Events: events.New(), + } + store, err := NewTagStore(path.Join(root, "tags"), tagCfg) if err != nil { t.Fatal(err) } diff --git a/trust/service.go b/trust/service.go index 12b9645667025..6a804faf5297b 100644 --- a/trust/service.go +++ b/trust/service.go @@ -5,70 +5,49 @@ import ( "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/libtrust" ) -func (t *TrustStore) Install(eng *engine.Engine) error { - for name, handler := range map[string]engine.Handler{ - "trust_key_check": t.CmdCheckKey, - "trust_update_base": t.CmdUpdateBase, - } { - if err := eng.Register(name, handler); err != nil { - return fmt.Errorf("Could not register %q: %v", name, err) - } - } - return nil -} +type NotVerifiedError string -func (t *TrustStore) CmdCheckKey(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s NAMESPACE", job.Name) - } - var ( - namespace = job.Args[0] - keyBytes = job.Getenv("PublicKey") - ) +func (e NotVerifiedError) Error() string { + return string(e) +} - if keyBytes == "" { - return fmt.Errorf("Missing PublicKey") +func (t *TrustStore) CheckKey(ns string, key []byte, perm uint16) (bool, error) { + if len(key) == 0 { + return false, fmt.Errorf("Missing PublicKey") } - pk, err := libtrust.UnmarshalPublicKeyJWK([]byte(keyBytes)) + pk, err := libtrust.UnmarshalPublicKeyJWK(key) if err != nil { - return fmt.Errorf("Error unmarshalling public key: %s", err) + return false, fmt.Errorf("Error unmarshalling public key: %v", err) } - permission := uint16(job.GetenvInt("Permission")) - if permission == 0 { - permission = 0x03 + if perm == 0 { + perm = 0x03 } t.RLock() defer t.RUnlock() if t.graph == nil { - job.Stdout.Write([]byte("no graph")) - return nil + return false, NotVerifiedError("no graph") } // Check if any expired grants - verified, err := t.graph.Verify(pk, namespace, permission) + verified, err := t.graph.Verify(pk, ns, perm) if err != nil { - return fmt.Errorf("Error verifying key to namespace: %s", namespace) + return false, fmt.Errorf("Error verifying key to namespace: %s", ns) } if !verified { - logrus.Debugf("Verification failed for %s using key %s", namespace, pk.KeyID()) - job.Stdout.Write([]byte("not verified")) - } else if t.expiration.Before(time.Now()) { - job.Stdout.Write([]byte("expired")) - } else { - job.Stdout.Write([]byte("verified")) + logrus.Debugf("Verification failed for %s using key %s", ns, pk.KeyID()) + return false, NotVerifiedError("not verified") } - - return nil + if t.expiration.Before(time.Now()) { + return false, NotVerifiedError("expired") + } + return true, nil } -func (t *TrustStore) CmdUpdateBase(job *engine.Job) error { +func (t *TrustStore) UpdateBase() { t.fetch() - - return nil } From 18c9b6c6455f116ae59cde8544413b3d7d294a5e Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 1 Apr 2015 15:39:37 -0700 Subject: [PATCH 198/332] Add .docker/config.json and support for HTTP Headers This PR does the following: - migrated ~/.dockerfg to ~/.docker/config.json. The data is migrated but the old file remains in case its needed - moves the auth json in that fie into an "auth" property so we can add new top-level properties w/o messing with the auth stuff - adds support for an HttpHeaders property in ~/.docker/config.json which adds these http headers to all msgs from the cli In a follow-on PR I'll move the config file process out from under "registry" since it not specific to that any more. I didn't do it here because I wanted the diff to be smaller so people can make sure I didn't break/miss any auth code during my edits. Signed-off-by: Doug Davis --- api/client/build.go | 4 +- api/client/cli.go | 15 ++- api/client/create.go | 3 - api/client/hijack.go | 7 ++ api/client/info.go | 3 +- api/client/login.go | 24 ++-- api/client/logout.go | 7 +- api/client/pull.go | 2 - api/client/push.go | 2 - api/client/search.go | 2 - api/client/utils.go | 9 +- builder/evaluator.go | 4 +- builder/internals.go | 4 +- builder/job.go | 2 +- docs/sources/reference/commandline/cli.md | 29 +++++ integration-cli/docker_cli_config_test.go | 58 ++++++++++ registry/auth.go | 105 ++++++++++++----- registry/auth_test.go | 26 +++-- registry/config_file_test.go | 135 ++++++++++++++++++++++ 19 files changed, 360 insertions(+), 81 deletions(-) create mode 100644 integration-cli/docker_cli_config_test.go create mode 100644 registry/config_file_test.go diff --git a/api/client/build.go b/api/client/build.go index 788319edf7480..63cc63bc9e795 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -286,10 +286,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("dockerfile", *dockerfileName) - cli.LoadConfigFile() - headers := http.Header(make(map[string][]string)) - buf, err := json.Marshal(cli.configFile) + buf, err := json.Marshal(cli.configFile.AuthConfigs) if err != nil { return err } diff --git a/api/client/cli.go b/api/client/cli.go index dfa5fe520f3dd..3146b17b31414 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "path/filepath" "reflect" "strings" "text/template" @@ -120,14 +121,6 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo return flags } -func (cli *DockerCli) LoadConfigFile() (err error) { - cli.configFile, err = registry.LoadConfig(homedir.Get()) - if err != nil { - fmt.Fprintf(cli.err, "WARNING: %s\n", err) - } - return err -} - func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { // In order to attach to a container tty, input stream for the client must // be a tty itself: redirecting or piping the client standard input is @@ -184,9 +177,15 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } + configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker")) + if e != nil { + fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) + } + return &DockerCli{ proto: proto, addr: addr, + configFile: configFile, in: in, out: out, err: err, diff --git a/api/client/create.go b/api/client/create.go index bb84d5e4638ad..d2987a67e46e7 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -37,9 +37,6 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { return err } - // Load the auth config file, to be able to pull the image - cli.LoadConfigFile() - // Resolve the Auth config relevant for this server authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) buf, err := json.Marshal(authConfig) diff --git a/api/client/hijack.go b/api/client/hijack.go index 1635384168797..5f4794a5e7d39 100644 --- a/api/client/hijack.go +++ b/api/client/hijack.go @@ -142,6 +142,13 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if err != nil { return err } + + // Add CLI Config's HTTP Headers BEFORE we set the Docker headers + // then the user can't change OUR headers + for k, v := range cli.configFile.HttpHeaders { + req.Header.Set(k, v) + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Header.Set("Content-Type", "text/plain") req.Header.Set("Connection", "Upgrade") diff --git a/api/client/info.go b/api/client/info.go index 65578888299a4..432ccac40fcce 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -68,8 +68,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { } if info.IndexServerAddress != "" { - cli.LoadConfigFile() - u := cli.configFile.Configs[info.IndexServerAddress].Username + u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username if len(u) > 0 { fmt.Fprintf(cli.out, "Username: %v\n", u) fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress) diff --git a/api/client/login.go b/api/client/login.go index b24ef7df7e03c..e8e87fc5e3834 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -6,11 +6,9 @@ import ( "fmt" "io" "os" - "path" "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" @@ -56,8 +54,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { return string(line) } - cli.LoadConfigFile() - authconfig, ok := cli.configFile.Configs[serverAddress] + authconfig, ok := cli.configFile.AuthConfigs[serverAddress] if !ok { authconfig = registry.AuthConfig{} } @@ -113,12 +110,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig.Password = password authconfig.Email = email authconfig.ServerAddress = serverAddress - cli.configFile.Configs[serverAddress] = authconfig + cli.configFile.AuthConfigs[serverAddress] = authconfig - stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], nil) + stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil) if statusCode == 401 { - delete(cli.configFile.Configs, serverAddress) - registry.SaveConfig(cli.configFile) + delete(cli.configFile.AuthConfigs, serverAddress) + if err2 := cli.configFile.Save(); err2 != nil { + fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2) + } return err } if err != nil { @@ -127,12 +126,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var response types.AuthResponse if err := json.NewDecoder(stream).Decode(&response); err != nil { - cli.configFile, _ = registry.LoadConfig(homedir.Get()) + // Upon error, remove entry + delete(cli.configFile.AuthConfigs, serverAddress) return err } - registry.SaveConfig(cli.configFile) - fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) + if err := cli.configFile.Save(); err != nil { + return fmt.Errorf("Error saving config file: %v", err) + } + fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename()) if response.Status != "" { fmt.Fprintf(cli.out, "%s\n", response.Status) diff --git a/api/client/logout.go b/api/client/logout.go index 9282f22f0c2ac..74d0c278faae9 100644 --- a/api/client/logout.go +++ b/api/client/logout.go @@ -22,14 +22,13 @@ func (cli *DockerCli) CmdLogout(args ...string) error { serverAddress = cmd.Arg(0) } - cli.LoadConfigFile() - if _, ok := cli.configFile.Configs[serverAddress]; !ok { + if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok { fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) } else { fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) - delete(cli.configFile.Configs, serverAddress) + delete(cli.configFile.AuthConfigs, serverAddress) - if err := registry.SaveConfig(cli.configFile); err != nil { + if err := cli.configFile.Save(); err != nil { return fmt.Errorf("Failed to save docker config: %v", err) } } diff --git a/api/client/pull.go b/api/client/pull.go index a554e1f4568de..17abe4bb65b0c 100644 --- a/api/client/pull.go +++ b/api/client/pull.go @@ -42,8 +42,6 @@ func (cli *DockerCli) CmdPull(args ...string) error { return err } - cli.LoadConfigFile() - _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") return err } diff --git a/api/client/push.go b/api/client/push.go index a31a04ed448ba..d4fc4c5c9e2ad 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -20,8 +20,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { name := cmd.Arg(0) - cli.LoadConfigFile() - remote, tag := parsers.ParseRepositoryTag(name) // Resolve the Repository name from fqn to RepositoryInfo diff --git a/api/client/search.go b/api/client/search.go index 5e0a22f014deb..2cff7708d3872 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -44,8 +44,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error { return err } - cli.LoadConfigFile() - rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search") if err != nil { return err diff --git a/api/client/utils.go b/api/client/utils.go index cf11fefe5bba1..026593d00dac9 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -65,6 +65,13 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m if err != nil { return nil, "", -1, err } + + // Add CLI Config's HTTP Headers BEFORE we set the Docker headers + // then the user can't change OUR headers + for k, v := range cli.configFile.HttpHeaders { + req.Header.Set(k, v) + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.URL.Host = cli.addr req.URL.Scheme = cli.scheme @@ -299,7 +306,7 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { sigchan := make(chan os.Signal, 1) gosignal.Notify(sigchan, signal.SIGWINCH) go func() { - for _ = range sigchan { + for range sigchan { cli.resizeTty(id, isExec) } }() diff --git a/builder/evaluator.go b/builder/evaluator.go index c159e51bf9774..0eba4a6ebd826 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -101,8 +101,8 @@ type Builder struct { // the final configs of the Dockerfile but dont want the layers disableCommit bool - AuthConfig *registry.AuthConfig - AuthConfigFile *registry.ConfigFile + AuthConfig *registry.AuthConfig + ConfigFile *registry.ConfigFile // Deprecated, original writer used for ImagePull. To be removed. OutOld io.Writer diff --git a/builder/internals.go b/builder/internals.go index bf47714ee3efe..c15cad4e5e72a 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -437,13 +437,13 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { } pullRegistryAuth := b.AuthConfig - if len(b.AuthConfigFile.Configs) > 0 { + if len(b.ConfigFile.AuthConfigs) > 0 { // The request came with a full auth config file, we prefer to use that repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote) if err != nil { return nil, err } - resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index) + resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index) pullRegistryAuth = &resolvedAuth } diff --git a/builder/job.go b/builder/job.go index 60f9071191dcf..930d7b7f16bc6 100644 --- a/builder/job.go +++ b/builder/job.go @@ -150,7 +150,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { OutOld: job.Stdout, StreamFormatter: sf, AuthConfig: authConfig, - AuthConfigFile: configFile, + ConfigFile: configFile, dockerfileName: dockerfileName, cpuShares: cpuShares, cpuSetCpus: cpuSetCpus, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076211c..821de99e304bb 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -48,6 +48,35 @@ These Go environment variables are case-insensitive. See the [Go specification](http://golang.org/pkg/net/http/) for details on these variables. +## Configuration Files + +The Docker command line stores its configuration files in a directory called +`.docker` within your `HOME` directory. Docker manages most of the files in +`.docker` and you should not modify them. However, you *can modify* the +`.docker/config.json` file to control certain aspects of how the `docker` +command behaves. + +Currently, you can modify the `docker` command behavior using environment +variables or command-line options. You can also use options within +`config.json` to modify some of the same behavior. When using these +mechanisms, you must keep in mind the order of precedence among them. Command +line options override environment variables and environment variables override +properties you specify in a `config.json` file. + +The `config.json` file stores a JSON encoding of a single `HttpHeaders` +property. The property specifies a set of headers to include in all +messages sent from the Docker client to the daemon. Docker does not try to +interpret or understand these header; it simply puts them into the messages. +Docker does not allow these headers to change any headers it sets for itself. + +Following is a sample `config.json` file: + + { + "HttpHeaders: { + "MyHeader": "MyValue" + } + } + ## Help To list the help on any command just execute the command, followed by the `--help` option. diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go new file mode 100644 index 0000000000000..23ad700698bc1 --- /dev/null +++ b/integration-cli/docker_cli_config_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/docker/docker/pkg/homedir" +) + +func TestConfigHttpHeader(t *testing.T) { + testRequires(t, UnixCli) // Can't set/unset HOME on windows right now + // We either need a level of Go that supports Unsetenv (for cases + // when HOME/USERPROFILE isn't set), or we need to be able to use + // os/user but user.Current() only works if we aren't statically compiling + + var headers map[string][]string + + server := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + headers = r.Header + })) + defer server.Close() + + homeKey := homedir.Key() + homeVal := homedir.Get() + tmpDir, _ := ioutil.TempDir("", "fake-home") + defer os.RemoveAll(tmpDir) + + dotDocker := filepath.Join(tmpDir, ".docker") + os.Mkdir(dotDocker, 0600) + tmpCfg := filepath.Join(dotDocker, "config.json") + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpDir) + + data := `{ + "HttpHeaders": { "MyHeader": "MyValue" } + }` + + err := ioutil.WriteFile(tmpCfg, []byte(data), 0600) + if err != nil { + t.Fatalf("Err creating file(%s): %v", tmpCfg, err) + } + + cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "ps") + out, _, _ := runCommandWithOutput(cmd) + + if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { + t.Fatalf("Missing/bad header: %q\nout:%v", headers, out) + } + + logDone("config - add new http headers") +} diff --git a/registry/auth.go b/registry/auth.go index 51b781dd92a81..bccf58fc5acef 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -8,24 +8,27 @@ import ( "io/ioutil" "net/http" "os" - "path" + "path/filepath" "strings" "sync" "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/requestdecorator" ) const ( // Where we store the config file - CONFIGFILE = ".dockercfg" + CONFIGFILE = "config.json" + OLD_CONFIGFILE = ".dockercfg" ) var ( ErrConfigFileMissing = errors.New("The Auth config file is missing") ) +// Registry Auth Info type AuthConfig struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` @@ -34,9 +37,11 @@ type AuthConfig struct { ServerAddress string `json:"serveraddress,omitempty"` } +// ~/.docker/config.json file info type ConfigFile struct { - Configs map[string]AuthConfig `json:"configs,omitempty"` - rootPath string + AuthConfigs map[string]AuthConfig `json:"auths"` + HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` + filename string // Note: not serialized - for internal use only } type RequestAuthorization struct { @@ -147,18 +152,58 @@ func decodeAuth(authStr string) (string, string, error) { // load up the auth config information and return values // FIXME: use the internal golang config parser -func LoadConfig(rootPath string) (*ConfigFile, error) { - configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} - confFile := path.Join(rootPath, CONFIGFILE) +func LoadConfig(configDir string) (*ConfigFile, error) { + if configDir == "" { + configDir = filepath.Join(homedir.Get(), ".docker") + } + + configFile := ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + filename: filepath.Join(configDir, CONFIGFILE), + } + + // Try happy path first - latest config file + if _, err := os.Stat(configFile.filename); err == nil { + file, err := os.Open(configFile.filename) + if err != nil { + return &configFile, err + } + defer file.Close() + + if err := json.NewDecoder(file).Decode(&configFile); err != nil { + return &configFile, err + } + + for addr, ac := range configFile.AuthConfigs { + ac.Username, ac.Password, err = decodeAuth(ac.Auth) + if err != nil { + return &configFile, err + } + ac.Auth = "" + ac.ServerAddress = addr + configFile.AuthConfigs[addr] = ac + } + + return &configFile, nil + } else if !os.IsNotExist(err) { + // if file is there but we can't stat it for any reason other + // than it doesn't exist then stop + return &configFile, err + } + + // Can't find latest config file so check for the old one + confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) + if _, err := os.Stat(confFile); err != nil { return &configFile, nil //missing file is not an error } + b, err := ioutil.ReadFile(confFile) if err != nil { return &configFile, err } - if err := json.Unmarshal(b, &configFile.Configs); err != nil { + if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { arr := strings.Split(string(b), "\n") if len(arr) < 2 { return &configFile, fmt.Errorf("The Auth config file is empty") @@ -179,48 +224,52 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { authConfig.Email = origEmail[1] authConfig.ServerAddress = IndexServerAddress() // *TODO: Switch to using IndexServerName() instead? - configFile.Configs[IndexServerAddress()] = authConfig + configFile.AuthConfigs[IndexServerAddress()] = authConfig } else { - for k, authConfig := range configFile.Configs { + for k, authConfig := range configFile.AuthConfigs { authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) if err != nil { return &configFile, err } authConfig.Auth = "" authConfig.ServerAddress = k - configFile.Configs[k] = authConfig + configFile.AuthConfigs[k] = authConfig } } return &configFile, nil } -// save the auth config -func SaveConfig(configFile *ConfigFile) error { - confFile := path.Join(configFile.rootPath, CONFIGFILE) - if len(configFile.Configs) == 0 { - os.Remove(confFile) - return nil - } - - configs := make(map[string]AuthConfig, len(configFile.Configs)) - for k, authConfig := range configFile.Configs { +func (configFile *ConfigFile) Save() error { + // Encode sensitive data into a new/temp struct + tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) + for k, authConfig := range configFile.AuthConfigs { authCopy := authConfig authCopy.Auth = encodeAuth(&authCopy) authCopy.Username = "" authCopy.Password = "" authCopy.ServerAddress = "" - configs[k] = authCopy + tmpAuthConfigs[k] = authCopy } - b, err := json.MarshalIndent(configs, "", "\t") + saveAuthConfigs := configFile.AuthConfigs + configFile.AuthConfigs = tmpAuthConfigs + defer func() { configFile.AuthConfigs = saveAuthConfigs }() + + data, err := json.MarshalIndent(configFile, "", "\t") if err != nil { return err } - err = ioutil.WriteFile(confFile, b, 0600) + + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0600); err != nil { + return err + } + + err = ioutil.WriteFile(configFile.filename, data, 0600) if err != nil { return err } + return nil } @@ -431,7 +480,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { configKey := index.GetAuthConfigKey() // First try the happy case - if c, found := config.Configs[configKey]; found || index.Official { + if c, found := config.AuthConfigs[configKey]; found || index.Official { return c } @@ -450,7 +499,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // Maybe they have a legacy config file, we will iterate the keys converting // them to the new format and testing - for registry, config := range config.Configs { + for registry, config := range config.AuthConfigs { if configKey == convertToHostname(registry) { return config } @@ -459,3 +508,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // When all else fails, return an empty auth config return AuthConfig{} } + +func (config *ConfigFile) Filename() string { + return config.filename +} diff --git a/registry/auth_test.go b/registry/auth_test.go index 9cc299aabe958..b07aa7dbc8e67 100644 --- a/registry/auth_test.go +++ b/registry/auth_test.go @@ -3,6 +3,7 @@ package registry import ( "io/ioutil" "os" + "path/filepath" "testing" ) @@ -31,13 +32,14 @@ func setupTempConfigFile() (*ConfigFile, error) { if err != nil { return nil, err } + root = filepath.Join(root, CONFIGFILE) configFile := &ConfigFile{ - rootPath: root, - Configs: make(map[string]AuthConfig), + AuthConfigs: make(map[string]AuthConfig), + filename: root, } for _, registry := range []string{"testIndex", IndexServerAddress()} { - configFile.Configs[registry] = AuthConfig{ + configFile.AuthConfigs[registry] = AuthConfig{ Username: "docker-user", Password: "docker-pass", Email: "docker@docker.io", @@ -52,14 +54,14 @@ func TestSameAuthDataPostSave(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) - err = SaveConfig(configFile) + err = configFile.Save() if err != nil { t.Fatal(err) } - authConfig := configFile.Configs["testIndex"] + authConfig := configFile.AuthConfigs["testIndex"] if authConfig.Username != "docker-user" { t.Fail() } @@ -79,9 +81,9 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) - indexConfig := configFile.Configs[IndexServerAddress()] + indexConfig := configFile.AuthConfigs[IndexServerAddress()] officialIndex := &IndexInfo{ Official: true, @@ -102,7 +104,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) registryAuth := AuthConfig{ Username: "foo-user", @@ -119,7 +121,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { Password: "baz-pass", Email: "baz@example.com", } - configFile.Configs[IndexServerAddress()] = officialAuth + configFile.AuthConfigs[IndexServerAddress()] = officialAuth expectedAuths := map[string]AuthConfig{ "registry.example.com": registryAuth, @@ -157,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { Name: configKey, } for _, registry := range registries { - configFile.Configs[registry] = configured + configFile.AuthConfigs[registry] = configured resolved := configFile.ResolveAuthConfig(index) if resolved.Email != configured.Email { t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) } - delete(configFile.Configs, registry) + delete(configFile.AuthConfigs, registry) resolved = configFile.ResolveAuthConfig(index) if resolved.Email == configured.Email { t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email) diff --git a/registry/config_file_test.go b/registry/config_file_test.go new file mode 100644 index 0000000000000..9abb8ee95cbfe --- /dev/null +++ b/registry/config_file_test.go @@ -0,0 +1,135 @@ +package registry + +import ( + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/docker/docker/pkg/homedir" +) + +func TestMissingFile(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on missing file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestEmptyFile(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + ioutil.WriteFile(fn, []byte(""), 0600) + + _, err := LoadConfig(tmpHome) + if err == nil { + t.Fatalf("Was supposed to fail") + } +} + +func TestEmptyJson(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + ioutil.WriteFile(fn, []byte("{}"), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestOldJson(t *testing.T) { + if runtime.GOOS == "windows" { + return + } + + tmpHome, _ := ioutil.TempDir("", "config-test") + defer os.RemoveAll(tmpHome) + + homeKey := homedir.Key() + homeVal := homedir.Get() + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpHome) + + fn := filepath.Join(tmpHome, OLD_CONFIGFILE) + js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` + ioutil.WriteFile(fn, []byte(js), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + ac := config.AuthConfigs["https://index.docker.io/v1/"] + if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { + t.Fatalf("Missing data from parsing:\n%q", config) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) || + !strings.Contains(string(buf), "user@example.com") { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestNewJson(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` + ioutil.WriteFile(fn, []byte(js), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + ac := config.AuthConfigs["https://index.docker.io/v1/"] + if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { + t.Fatalf("Missing data from parsing:\n%q", config) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) || + !strings.Contains(string(buf), "user@example.com") { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} From ff9f0134aefbee1d3d757454f42e8dd99afb528e Mon Sep 17 00:00:00 2001 From: Kent Johnson Date: Mon, 20 Apr 2015 13:46:55 -0600 Subject: [PATCH 199/332] Clarify data volume init with existing data Though I am not clear on the intent of the sentence if it means that existing data in the base image is copied into the new volume then the additions I propose make that more clear than the present language. Signed-off-by: Kent Johnson --- docs/sources/userguide/dockervolumes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index c319ecee5cdf9..e80483fc2fb4b 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -25,8 +25,8 @@ System*](/terms/layer/#union-file-system). Data volumes provide several useful features for persistent or shared data: - Volumes are initialized when a container is created. If the container's - base image contains data at the specified mount point, that data is - copied into the new volume. + base image contains data at the specified mount point, that existing data is + copied into the new volume upon volume initialization. - Data volumes can be shared and reused among containers. - Changes to a data volume are made directly. - Changes to a data volume will not be included when you update an image. From 1f4ef5192c05723e231acc47813c4c8455a0166b Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Mon, 20 Apr 2015 11:07:01 -0700 Subject: [PATCH 200/332] Adding in 1.60 release notes Updating with thaJetzah's comments Adding in Fred's copy edits Signed-off-by: Mary Anthony --- docs/sources/release-notes.md | 192 ++++++++++++++++++++++------------ 1 file changed, 125 insertions(+), 67 deletions(-) diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md index fe79d881dc986..87e5c41972cea 100644 --- a/docs/sources/release-notes.md +++ b/docs/sources/release-notes.md @@ -2,74 +2,132 @@ page_title: Docker 1.x Series Release Notes page_description: Release Notes for Docker 1.x. page_keywords: docker, documentation, about, technology, understanding, release -# Release Notes +# Release Notes Version 1.6.0 +(2015-04-16) You can view release notes for earlier version of Docker by selecting the -desired version from the drop-down list at the top right of this page. - -## Version 1.5.0 -(2015-02-03) - -For a complete list of patches, fixes, and other improvements, see the -[merge PR on GitHub](https://github.com/docker/docker/pull/10286). - -*New Features* - -* [1.6] The Docker daemon will no longer ignore unknown commands - while processing a `Dockerfile`. Instead it will generate an error and halt - processing. -* The Docker daemon has now supports for IPv6 networking between containers - and on the `docker0` bridge. For more information see the - [IPv6 networking reference](/articles/networking/#ipv6). -* Docker container filesystems can now be set to`--read-only`, restricting your - container to writing to volumes [PR# 10093](https://github.com/docker/docker/pull/10093). -* A new `docker stats CONTAINERID` command has been added to allow users to view a - continuously updating stream of container resource usage statistics. See the - [`stats` command line reference](/reference/commandline/cli/#stats) and the - [container `stats` API reference](/reference/api/docker_remote_api_v1.17/#get-container-stats-based-on-resource-usage). - **Note**: this feature is only enabled for the `libcontainer` exec-driver at this point. -* Users can now specify the file to use as the `Dockerfile` by running - `docker build -f alternate.dockerfile .`. This will allow the definition of multiple - `Dockerfile`s for a single project. See the [`docker build` command reference]( -/reference/commandline/cli/#build) for more information. -* The v1 Open Image specification has been created to document the current Docker image - format and metadata. Please see [the Open Image specification document]( -https://github.com/docker/docker/blob/master/image/spec/v1.md) for more details. -* This release also includes a number of significant performance improvements in - build and image management ([PR #9720](https://github.com/docker/docker/pull/9720), - [PR #8827](https://github.com/docker/docker/pull/8827)) -* The `docker inspect` command now lists ExecIDs generated for each `docker exec` process. - See [PR #9800](https://github.com/docker/docker/pull/9800)) for more details. -* The `docker inspect` command now shows the number of container restarts when there - is a restart policy ([PR #9621](https://github.com/docker/docker/pull/9621)) -* This version of Docker is built using Go 1.4 - -> **Note:** -> Development history prior to version 1.0 can be found by -> searching in the [Docker GitHub repo](https://github.com/docker/docker). - -## Known Issues - -This section lists significant known issues present in Docker as of release -date. It is not exhaustive; it lists only issues with potentially significant -impact on users. This list will be updated as issues are resolved. - -* **Unexpected File Permissions in Containers** -An idiosyncrasy in AUFS prevents permissions from propagating predictably -between upper and lower layers. This can cause issues with accessing private -keys, database instances, etc. - -For systems that have recent aufs version (i.e., `dirperm1` mount option can -be set), docker will attempt to fix the issue automatically by mounting -the layers with `dirperm1` option. More details on `dirperm1` option can be -found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html) - -For complete information and workarounds see +desired version from the drop-down list at the top right of this page. For the +formal release announcement, see [the Docker +blog](https://blog.docker.com/2015/04/docker-release-1-6/). + + + +## Docker Engine 1.6.0 Features + +For a complete list of engine patches, fixes, and other improvements, see the +[merge PR on GitHub](https://github.com/docker/docker/pull/11635). You'll also +find [a changelog in the project +repository](https://github.com/docker/docker/blob/master/CHANGELOG.md). + + +| Feature | Description | +|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Container and Image Labels | Labels allow you to attach user-defined metadata to containers and images that can be used by your tools. For additional information on using labels, see [Apply custom metadata](http://docs.docker.com/userguide/labels-custom-metadata/#add-labels-to-images-the-label-instruction) in the documentation. | +| Windows Client preview | The Windows Client can be used just like the Mac OS X client is today with a remote host. Our testing infrastructure was scaled out to accommodate Windows Client testing on every PR to the Engine. See the Azure blog for [details on using this new client](http://azure.microsoft.com/blog/2015/04/16/docker-client-for-windows-is-now-available). | +| Logging drivers | The new logging driver follows the exec driver and storage driver concepts already available in Engine today. There is a new option `--log-driver` to `docker run` command. See the `run` reference for a [description on how to use this option](http://docs.docker.com/reference/run/#logging-drivers-log-driver). | +| Image digests | When you pull, build, or run images, you specify them in the form `namespace/repository:tag`, or even just `repository`. In this release, you are now able to pull, run, build and refer to images by a new content addressable identifier called a “digest” with the syntax `namespace/repo@digest`. See the the command line reference for [examples of using the digest](http://docs.docker.com/reference/commandline/cli/#listing-image-digests). | +| Custom cgroups | Containers are made from a combination of namespaces, capabilities, and cgroups. Docker already supports custom namespaces and capabilities. Additionally, in this release we’ve added support for custom cgroups. Using the `--cgroup-parent` flag, you can pass a specific `cgroup` to run a container in. See [the command line reference for more information](http://docs.docker.com/reference/commandline/cli/#create). | +| Ulimits | You can now specify the default `ulimit` settings for all containers when configuring the daemon. For example:`docker -d --default-ulimit nproc=1024:2048` See [Default Ulimits](http://docs.docker.com/reference/commandline/cli/#default-ulimits) in this documentation. | +| Commit and import Dockerfile | You can now make changes to images on the fly without having to re-build the entire image. The feature `commit --change` and `import --change` allows you to apply standard changes to a new image. These are expressed in the Dockerfile syntax and used to modify the image. For details on how to use these, see the [commit](http://docs.docker.com/reference/commandline/cli/#commit) and [import](http://docs.docker.com/reference/commandline/cli/#import). | + +### Known Issues in Engine + +This section lists significant known issues present in Docker as of release date. +For an exhaustive list of issues, see [the issues list on the project +repository](https://github.com/docker/docker/issues/). + +* *Unexpected File Permissions in Containers* +An idiosyncrasy in AUFS prevented permissions from propagating predictably +between upper and lower layers. This caused issues with accessing private +keys, database instances, etc. This issue was closed in this release: [Github Issue 783](https://github.com/docker/docker/issues/783). -* **Docker Hub incompatible with Safari 8** -Docker Hub has multiple issues displaying on Safari 8, the default browser -for OS X 10.10 (Yosemite). Users should access the hub using a different -browser. Most notably, changes in the way Safari handles cookies means that the -user is repeatedly logged out. For more information, see the [Docker -forum post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300). + +* *Docker Hub incompatible with Safari 8* +Docker Hub had multiple issues displaying on Safari 8, the default browser for +OS X 10.10 (Yosemite). Most notably, changes in the way Safari handled cookies +means that the user was repeatedly logged out. +Recently, Safari fixed the bug that was causing all the issues. If you upgrade +to Safari 8.0.5 which was just released last week and see if that fixes your +issues. You might have to flush your cookies if it doesn't work right away. +For more information, see the [Docker forum +post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300). + +## Docker Registry 2.0 Features + +This release includes Registry 2.0. The Docker Registry is a central server for +pushing and pulling images. In this release, it was completely rewritten in Go +around a new set of distribution APIs + +- **Webhook notifications**: You can now configure the Registry to send Webhooks +when images are pushed. Spin off a CI build, send a notification to IRC – +whatever you want! Included in the documentation is a detailed [notification +specification](http://docs.docker.com/registry/notifications/). + +- **Native TLS support**: This release makes it easier to secure a registry with +TLS. This documentation includes [expanded examples of secure +deployments](http://docs.docker.com/registry/deploying/). + +- **New Distribution APIs**: This release includes an expanded set of new +distribution APIs. You can read the [detailed specification +here](http://docs.docker.com/registry/spec/api/). + + +## Docker Compose 1.2 + +For a complete list of compose patches, fixes, and other improvements, see the +[changelog in the project +repository](https://github.com/docker/compose/blob/master/CHANGES.md). The +project also makes a [set of release +notes](https://github.com/docker/compose/releases/tag/1.2.0) on the project. + +- **extends**: You can use `extends` to share configuration between services +with the keyword “extends”. With extends, you can refer to a service defined +elsewhere and include its configuration in a locally-defined service, while also +adding or overriding configuration as necessary. The documentation describes +[how to use extends in your +configuration](http://docs.docker.com/compose/extends/#extending-services-in- +compose). + +- **Relative directory handling may cause breaking change**: Compose now treats +directories passed to build, filenames passed to `env_file` and volume host +paths passed to volumes as relative to the configuration file's directory. +Previously, they were treated as relative to the directory where you were +running `docker-compose`. In the majority of cases, the location of the +configuration file and where you ran `docker-compose` were the same directory. +Now, you can use the `-f|--file` argument to specify a configuration file in +another directory. + + +## Docker Swarm 0.2 + +You'll find the [release for download on +GitHub](https://github.com/docker/swarm/releases/tag/v0.2.0) and [the +documentation here](http://docs.docker.com/swarm/). This release includes the +following features: + +- **Spread strategy**: A new strategy for scheduling containers on your cluster +which evenly spreads them over available nodes. +- **More Docker commands supported**: More progress has been made towards +supporting the complete Docker API, such as pulling and inspecting images. +- **Clustering drivers**: There are not any third-party drivers yet, but the +first steps have been made towards making a pluggable driver interface that will +make it possible to use Swarm with clustering systems such as Mesos. + + +## Docker Machine 0.2 Pre-release + +You'll find the [release for download on +GitHub](https://github.com/docker/machine/releases) and [the documentation +here](http://docs.docker.com/machine/). For a complete list of machine changes +see [the changelog in the project +repository](https://github.com/docker/machine/blob/master/CHANGES.md#020-2015-03 +-22). + +- **Cleaner driver interface**: It is now much easier to write drivers for providers. +- **More reliable and consistent provisioning**: Provisioning servers is now +handled centrally by Machine instead of letting each driver individually do it. +- **Regenerate TLS certificates**: A new command has been added to regenerate a +host’s TLS certificates for good security practice and for if a host’s IP +address changes. + From 9a2c00975123c17033ac7b50762af82051ec73db Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 16 Apr 2015 11:11:26 -0700 Subject: [PATCH 201/332] Remove engine.Job references from builder.CmdBuild Signed-off-by: David Calavera --- api/server/server.go | 70 ++++++++++++--------- builder/job.go | 144 ++++++++++++++++++++++++++----------------- 2 files changed, 131 insertions(+), 83 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 50dc84ff7ff46..65e1391670b37 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/builder" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -236,12 +237,12 @@ func writeJSON(w http.ResponseWriter, code int, v interface{}) error { return json.NewEncoder(w).Encode(v) } -func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) { +func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) { w.Header().Set("Content-Type", "application/json") if flush { - job.Stdout.Add(utils.NewWriteFlusher(w)) + out.Add(utils.NewWriteFlusher(w)) } else { - job.Stdout.Add(w) + out.Add(w) } } @@ -857,7 +858,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h job.Setenv("tag", r.Form.Get("tag")) if version.GreaterThan("1.0") { job.SetenvBool("json", true) - streamJSON(job, w, true) + streamJSON(job.Stdout, w, true) } else { job.Stdout.Add(utils.NewWriteFlusher(w)) } @@ -1207,7 +1208,7 @@ func (s *Server) getContainersByName(eng *engine.Engine, version version.Version if version.LessThan("1.12") { job.SetenvBool("raw", true) } - streamJSON(job, w, false) + streamJSON(job.Stdout, w, false) return job.Run() } @@ -1232,7 +1233,7 @@ func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w if version.LessThan("1.12") { job.SetenvBool("raw", true) } - streamJSON(job, w, false) + streamJSON(job.Stdout, w, false) return job.Run() } @@ -1245,9 +1246,11 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R authConfig = ®istry.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = ®istry.ConfigFile{} - job = eng.Job("build") + job = builder.NewBuildConfig(eng.Logging, eng.Stderr) ) + b := &builder.BuilderJob{eng, getDaemon(eng)} + // This block can be removed when API versions prior to 1.9 are deprecated. // Both headers will be parsed and sent along to the daemon, but if a non-empty // ConfigFile is present, any value provided as an AuthConfig directly will @@ -1271,36 +1274,38 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } if version.GreaterThanOrEqualTo("1.8") { - job.SetenvBool("json", true) - streamJSON(job, w, true) + job.JSONFormat = true + streamJSON(job.Stdout, w, true) } else { job.Stdout.Add(utils.NewWriteFlusher(w)) } if toBool(r.FormValue("forcerm")) && version.GreaterThanOrEqualTo("1.12") { - job.Setenv("rm", "1") + job.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { - job.Setenv("rm", "1") + job.Remove = true } else { - job.Setenv("rm", r.FormValue("rm")) + job.Remove = toBool(r.FormValue("rm")) } if toBool(r.FormValue("pull")) && version.GreaterThanOrEqualTo("1.16") { - job.Setenv("pull", "1") + job.Pull = true } job.Stdin.Add(r.Body) - job.Setenv("remote", r.FormValue("remote")) - job.Setenv("dockerfile", r.FormValue("dockerfile")) - job.Setenv("t", r.FormValue("t")) - job.Setenv("q", r.FormValue("q")) - job.Setenv("nocache", r.FormValue("nocache")) - job.Setenv("forcerm", r.FormValue("forcerm")) - job.SetenvJson("authConfig", authConfig) - job.SetenvJson("configFile", configFile) - job.Setenv("memswap", r.FormValue("memswap")) - job.Setenv("memory", r.FormValue("memory")) - job.Setenv("cpusetcpus", r.FormValue("cpusetcpus")) - job.Setenv("cpusetmems", r.FormValue("cpusetmems")) - job.Setenv("cpushares", r.FormValue("cpushares")) + + // FIXME(calavera): !!!!! Remote might not be used. Solve the mistery before merging + //job.Setenv("remote", r.FormValue("remote")) + job.DockerfileName = r.FormValue("dockerfile") + job.RepoName = r.FormValue("t") + job.SuppressOutput = toBool(r.FormValue("q")) + job.NoCache = toBool(r.FormValue("nocache")) + job.ForceRemove = toBool(r.FormValue("forcerm")) + job.AuthConfig = authConfig + job.ConfigFile = configFile + job.MemorySwap = toInt64(r.FormValue("memswap")) + job.Memory = toInt64(r.FormValue("memory")) + job.CpuShares = toInt64(r.FormValue("cpushares")) + job.CpuSetCpus = r.FormValue("cpusetcpus") + job.CpuSetMems = r.FormValue("cpusetmems") // Job cancellation. Note: not all job types support this. if closeNotifier, ok := w.(http.CloseNotifier); ok { @@ -1310,13 +1315,13 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R select { case <-finished: case <-closeNotifier.CloseNotify(): - logrus.Infof("Client disconnected, cancelling job: %s", job.Name) + logrus.Infof("Client disconnected, cancelling job: build") job.Cancel() } }() } - if err := job.Run(); err != nil { + if err := b.CmdBuild(job); err != nil { if !job.Stdout.Used() { return err } @@ -1676,3 +1681,12 @@ func toBool(s string) bool { s = strings.ToLower(strings.TrimSpace(s)) return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") } + +// FIXME(calavera): This is a copy of the Env.GetInt64 +func toInt64(s string) int64 { + val, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return 0 + } + return val +} diff --git a/builder/job.go b/builder/job.go index 930d7b7f16bc6..6ad64d716b1b9 100644 --- a/builder/job.go +++ b/builder/job.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "strings" + "sync" "github.com/docker/docker/api" "github.com/docker/docker/builder/parser" @@ -17,6 +18,7 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" @@ -41,41 +43,73 @@ type BuilderJob struct { Daemon *daemon.Daemon } +type Config struct { + DockerfileName string + RemoteURL string + RepoName string + SuppressOutput bool + NoCache bool + Remove bool + ForceRemove bool + Pull bool + JSONFormat bool + Memory int64 + MemorySwap int64 + CpuShares int64 + CpuSetCpus string + CpuSetMems string + AuthConfig *registry.AuthConfig + ConfigFile *registry.ConfigFile + + Stdout *engine.Output + Stderr *engine.Output + Stdin *engine.Input + // When closed, the job has been cancelled. + // Note: not all jobs implement cancellation. + // See Job.Cancel() and Job.WaitCancelled() + cancelled chan struct{} + cancelOnce sync.Once +} + +// When called, causes the Job.WaitCancelled channel to unblock. +func (b *Config) Cancel() { + b.cancelOnce.Do(func() { + close(b.cancelled) + }) +} + +// Returns a channel which is closed ("never blocks") when the job is cancelled. +func (b *Config) WaitCancelled() <-chan struct{} { + return b.cancelled +} + +func NewBuildConfig(logging bool, err io.Writer) *Config { + c := &Config{ + Stdout: engine.NewOutput(), + Stderr: engine.NewOutput(), + Stdin: engine.NewInput(), + cancelled: make(chan struct{}), + } + if logging { + c.Stderr.Add(ioutils.NopWriteCloser(err)) + } + return c +} + func (b *BuilderJob) Install() { - b.Engine.Register("build", b.CmdBuild) b.Engine.Register("build_config", b.CmdBuildConfig) } -func (b *BuilderJob) CmdBuild(job *engine.Job) error { - if len(job.Args) != 0 { - return fmt.Errorf("Usage: %s\n", job.Name) - } +func (b *BuilderJob) CmdBuild(buildConfig *Config) error { var ( - dockerfileName = job.Getenv("dockerfile") - remoteURL = job.Getenv("remote") - repoName = job.Getenv("t") - suppressOutput = job.GetenvBool("q") - noCache = job.GetenvBool("nocache") - rm = job.GetenvBool("rm") - forceRm = job.GetenvBool("forcerm") - pull = job.GetenvBool("pull") - memory = job.GetenvInt64("memory") - memorySwap = job.GetenvInt64("memswap") - cpuShares = job.GetenvInt64("cpushares") - cpuSetCpus = job.Getenv("cpusetcpus") - cpuSetMems = job.Getenv("cpusetmems") - authConfig = ®istry.AuthConfig{} - configFile = ®istry.ConfigFile{} - tag string - context io.ReadCloser + repoName string + tag string + context io.ReadCloser ) - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("configFile", configFile) - - repoName, tag = parsers.ParseRepositoryTag(repoName) + repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName) if repoName != "" { - if err := registry.ValidateRepositoryName(repoName); err != nil { + if err := registry.ValidateRepositoryName(buildConfig.RepoName); err != nil { return err } if len(tag) > 0 { @@ -85,11 +119,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } } - if remoteURL == "" { - context = ioutil.NopCloser(job.Stdin) - } else if urlutil.IsGitURL(remoteURL) { - if !urlutil.IsGitTransport(remoteURL) { - remoteURL = "https://" + remoteURL + if buildConfig.RemoteURL == "" { + context = ioutil.NopCloser(buildConfig.Stdin) + } else if urlutil.IsGitURL(buildConfig.RemoteURL) { + if !urlutil.IsGitTransport(buildConfig.RemoteURL) { + buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL } root, err := ioutil.TempDir("", "docker-build-git") if err != nil { @@ -97,7 +131,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } defer os.RemoveAll(root) - if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil { + if output, err := exec.Command("git", "clone", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { return fmt.Errorf("Error trying to use git: %s (%s)", err, output) } @@ -106,8 +140,8 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { return err } context = c - } else if urlutil.IsURL(remoteURL) { - f, err := httputils.Download(remoteURL) + } else if urlutil.IsURL(buildConfig.RemoteURL) { + f, err := httputils.Download(buildConfig.RemoteURL) if err != nil { return err } @@ -119,9 +153,9 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { // When we're downloading just a Dockerfile put it in // the default name - don't allow the client to move/specify it - dockerfileName = api.DefaultDockerfileName + buildConfig.DockerfileName = api.DefaultDockerfileName - c, err := archive.Generate(dockerfileName, string(dockerFile)) + c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile)) if err != nil { return err } @@ -129,35 +163,35 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } defer context.Close() - sf := streamformatter.NewStreamFormatter(job.GetenvBool("json")) + sf := streamformatter.NewStreamFormatter(buildConfig.JSONFormat) builder := &Builder{ Daemon: b.Daemon, Engine: b.Engine, OutStream: &streamformatter.StdoutFormater{ - Writer: job.Stdout, + Writer: buildConfig.Stdout, StreamFormatter: sf, }, ErrStream: &streamformatter.StderrFormater{ - Writer: job.Stdout, + Writer: buildConfig.Stdout, StreamFormatter: sf, }, - Verbose: !suppressOutput, - UtilizeCache: !noCache, - Remove: rm, - ForceRemove: forceRm, - Pull: pull, - OutOld: job.Stdout, + Verbose: !buildConfig.SuppressOutput, + UtilizeCache: !buildConfig.NoCache, + Remove: buildConfig.Remove, + ForceRemove: buildConfig.ForceRemove, + Pull: buildConfig.Pull, + OutOld: buildConfig.Stdout, StreamFormatter: sf, - AuthConfig: authConfig, - ConfigFile: configFile, - dockerfileName: dockerfileName, - cpuShares: cpuShares, - cpuSetCpus: cpuSetCpus, - cpuSetMems: cpuSetMems, - memory: memory, - memorySwap: memorySwap, - cancelled: job.WaitCancelled(), + AuthConfig: buildConfig.AuthConfig, + ConfigFile: buildConfig.ConfigFile, + dockerfileName: buildConfig.DockerfileName, + cpuShares: buildConfig.CpuShares, + cpuSetCpus: buildConfig.CpuSetCpus, + cpuSetMems: buildConfig.CpuSetMems, + memory: buildConfig.Memory, + memorySwap: buildConfig.MemorySwap, + cancelled: buildConfig.WaitCancelled(), } id, err := builder.Run(context) From ae4063585e7936780154101f7fe416a080c6ff7c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 16 Apr 2015 14:26:33 -0700 Subject: [PATCH 202/332] Remove engine.Job from builder.CmdBuildConfig. Signed-off-by: David Calavera --- api/server/form.go | 20 ++++++ api/server/form_test.go | 55 +++++++++++++++++ api/server/server.go | 132 ++++++++++++++++++++-------------------- builder/job.go | 96 +++++++++++++---------------- daemon/commit.go | 50 +-------------- docker/daemon.go | 5 -- graph/import.go | 38 +++--------- 7 files changed, 195 insertions(+), 201 deletions(-) create mode 100644 api/server/form.go create mode 100644 api/server/form_test.go diff --git a/api/server/form.go b/api/server/form.go new file mode 100644 index 0000000000000..af1cd2075e738 --- /dev/null +++ b/api/server/form.go @@ -0,0 +1,20 @@ +package server + +import ( + "net/http" + "strconv" + "strings" +) + +func boolValue(r *http.Request, k string) bool { + s := strings.ToLower(strings.TrimSpace(r.FormValue(k))) + return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") +} + +func int64Value(r *http.Request, k string) int64 { + val, err := strconv.ParseInt(r.FormValue(k), 10, 64) + if err != nil { + return 0 + } + return val +} diff --git a/api/server/form_test.go b/api/server/form_test.go new file mode 100644 index 0000000000000..5cf6c82c14dfd --- /dev/null +++ b/api/server/form_test.go @@ -0,0 +1,55 @@ +package server + +import ( + "net/http" + "net/url" + "testing" +) + +func TestBoolValue(t *testing.T) { + cases := map[string]bool{ + "": false, + "0": false, + "no": false, + "false": false, + "none": false, + "1": true, + "yes": true, + "true": true, + "one": true, + "100": true, + } + + for c, e := range cases { + v := url.Values{} + v.Set("test", c) + r, _ := http.NewRequest("POST", "", nil) + r.Form = v + + a := boolValue(r, "test") + if a != e { + t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) + } + } +} + +func TestInt64Value(t *testing.T) { + cases := map[string]int64{ + "": 0, + "asdf": 0, + "0": 0, + "1": 1, + } + + for c, e := range cases { + v := url.Values{} + v.Set("test", c) + r, _ := http.NewRequest("POST", "", nil) + r.Form = v + + a := int64Value(r, "test") + if a != e { + t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) + } + } +} diff --git a/api/server/server.go b/api/server/server.go index 65e1391670b37..1002526f9aac8 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -381,7 +381,7 @@ func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w ht Filters: r.Form.Get("filters"), // FIXME this parameter could just be a match filter Filter: r.Form.Get("filter"), - All: toBool(r.Form.Get("all")), + All: boolValue(r, "all"), } images, err := s.daemon.Repositories().Images(&imagesConfig) @@ -597,8 +597,8 @@ func (s *Server) getContainersJSON(eng *engine.Engine, version version.Version, } config := &daemon.ContainersConfig{ - All: toBool(r.Form.Get("all")), - Size: toBool(r.Form.Get("size")), + All: boolValue(r, "all"), + Size: boolValue(r, "size"), Since: r.Form.Get("since"), Before: r.Form.Get("before"), Filters: r.Form.Get("filters"), @@ -640,14 +640,14 @@ func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, } // Validate args here, because we can't return not StatusOK after job.Run() call - stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr")) + stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr") if !(stdout || stderr) { return fmt.Errorf("Bad parameters: you must choose at least one stream") } logsConfig := &daemon.ContainerLogsConfig{ - Follow: toBool(r.Form.Get("follow")), - Timestamps: toBool(r.Form.Get("timestamps")), + Follow: boolValue(r, "follow"), + Timestamps: boolValue(r, "timestamps"), Tail: r.Form.Get("tail"), UseStdout: stdout, UseStderr: stderr, @@ -671,7 +671,7 @@ func (s *Server) postImagesTag(eng *engine.Engine, version version.Version, w ht repo := r.Form.Get("repo") tag := r.Form.Get("tag") - force := toBool(r.Form.Get("force")) + force := boolValue(r, "force") if err := s.daemon.Repositories().Tag(repo, tag, vars["name"], force); err != nil { return err } @@ -690,11 +690,20 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. cont := r.Form.Get("container") - pause := toBool(r.Form.Get("pause")) + pause := boolValue(r, "pause") if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { pause = true } + c, _, err := runconfig.DecodeContainerConfig(r.Body) + if err != nil && err != io.EOF { //Do not fail if body is empty. + return err + } + + if c == nil { + c = &runconfig.Config{} + } + containerCommitConfig := &daemon.ContainerCommitConfig{ Pause: pause, Repo: r.Form.Get("repo"), @@ -702,10 +711,10 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. Author: r.Form.Get("author"), Comment: r.Form.Get("comment"), Changes: r.Form["changes"], - Config: r.Body, + Config: c, } - imgID, err := s.daemon.ContainerCommit(cont, containerCommitConfig) + imgID, err := builder.Commit(s.daemon, eng, cont, containerCommitConfig) if err != nil { return err } @@ -782,10 +791,15 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imageImportConfig.Json = false } - if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { + newConfig, err := builder.BuildFromConfig(s.daemon, eng, &runconfig.Config{}, imageImportConfig.Changes) + if err != nil { return err } + imageImportConfig.ContainerConfig = newConfig + if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig); err != nil { + return err + } } return nil @@ -977,9 +991,9 @@ func (s *Server) deleteContainers(eng *engine.Engine, version version.Version, w name := vars["name"] config := &daemon.ContainerRmConfig{ - ForceRemove: toBool(r.Form.Get("force")), - RemoveVolume: toBool(r.Form.Get("v")), - RemoveLink: toBool(r.Form.Get("link")), + ForceRemove: boolValue(r, "force"), + RemoveVolume: boolValue(r, "v"), + RemoveLink: boolValue(r, "link"), } if err := s.daemon.ContainerRm(name, config); err != nil { @@ -1004,8 +1018,8 @@ func (s *Server) deleteImages(eng *engine.Engine, version version.Version, w htt } name := vars["name"] - force := toBool(r.Form.Get("force")) - noprune := toBool(r.Form.Get("noprune")) + force := boolValue(r, "force") + noprune := boolValue(r, "noprune") list, err := s.daemon.ImageDelete(name, force, noprune) if err != nil { @@ -1152,19 +1166,19 @@ func (s *Server) postContainersAttach(eng *engine.Engine, version version.Versio } else { errStream = outStream } - logs := toBool(r.Form.Get("logs")) - stream := toBool(r.Form.Get("stream")) + logs := boolValue(r, "logs") + stream := boolValue(r, "stream") var stdin io.ReadCloser var stdout, stderr io.Writer - if toBool(r.Form.Get("stdin")) { + if boolValue(r, "stdin") { stdin = inStream } - if toBool(r.Form.Get("stdout")) { + if boolValue(r, "stdout") { stdout = outStream } - if toBool(r.Form.Get("stderr")) { + if boolValue(r, "stderr") { stderr = errStream } @@ -1246,11 +1260,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R authConfig = ®istry.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = ®istry.ConfigFile{} - job = builder.NewBuildConfig(eng.Logging, eng.Stderr) + buildConfig = builder.NewBuildConfig() ) - b := &builder.BuilderJob{eng, getDaemon(eng)} - // This block can be removed when API versions prior to 1.9 are deprecated. // Both headers will be parsed and sent along to the daemon, but if a non-empty // ConfigFile is present, any value provided as an AuthConfig directly will @@ -1273,39 +1285,41 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } } + stdout := engine.NewOutput() + stdout.Set(utils.NewWriteFlusher(w)) + if version.GreaterThanOrEqualTo("1.8") { - job.JSONFormat = true - streamJSON(job.Stdout, w, true) - } else { - job.Stdout.Add(utils.NewWriteFlusher(w)) + w.Header().Set("Content-Type", "application/json") + buildConfig.JSONFormat = true } - if toBool(r.FormValue("forcerm")) && version.GreaterThanOrEqualTo("1.12") { - job.Remove = true + if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { + buildConfig.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { - job.Remove = true + buildConfig.Remove = true } else { - job.Remove = toBool(r.FormValue("rm")) + buildConfig.Remove = boolValue(r, "rm") } - if toBool(r.FormValue("pull")) && version.GreaterThanOrEqualTo("1.16") { - job.Pull = true + if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { + buildConfig.Pull = true } - job.Stdin.Add(r.Body) - // FIXME(calavera): !!!!! Remote might not be used. Solve the mistery before merging - //job.Setenv("remote", r.FormValue("remote")) - job.DockerfileName = r.FormValue("dockerfile") - job.RepoName = r.FormValue("t") - job.SuppressOutput = toBool(r.FormValue("q")) - job.NoCache = toBool(r.FormValue("nocache")) - job.ForceRemove = toBool(r.FormValue("forcerm")) - job.AuthConfig = authConfig - job.ConfigFile = configFile - job.MemorySwap = toInt64(r.FormValue("memswap")) - job.Memory = toInt64(r.FormValue("memory")) - job.CpuShares = toInt64(r.FormValue("cpushares")) - job.CpuSetCpus = r.FormValue("cpusetcpus") - job.CpuSetMems = r.FormValue("cpusetmems") + buildConfig.Stdout = stdout + buildConfig.Context = r.Body + + buildConfig.RemoteURL = r.FormValue("remote") + buildConfig.DockerfileName = r.FormValue("dockerfile") + buildConfig.RepoName = r.FormValue("t") + buildConfig.SuppressOutput = boolValue(r, "q") + buildConfig.NoCache = boolValue(r, "nocache") + buildConfig.ForceRemove = boolValue(r, "forcerm") + buildConfig.AuthConfig = authConfig + buildConfig.ConfigFile = configFile + buildConfig.MemorySwap = int64Value(r, "memswap") + buildConfig.Memory = int64Value(r, "memory") + buildConfig.CpuShares = int64Value(r, "cpushares") + buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") + buildConfig.CpuSetMems = r.FormValue("cpusetmems") // Job cancellation. Note: not all job types support this. if closeNotifier, ok := w.(http.CloseNotifier); ok { @@ -1316,13 +1330,13 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R case <-finished: case <-closeNotifier.CloseNotify(): logrus.Infof("Client disconnected, cancelling job: build") - job.Cancel() + buildConfig.Cancel() } }() } - if err := b.CmdBuild(job); err != nil { - if !job.Stdout.Used() { + if err := builder.Build(s.daemon, eng, buildConfig); err != nil { + if !stdout.Used() { return err } sf := streamformatter.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) @@ -1676,17 +1690,3 @@ func allocateDaemonPort(addr string) error { } return nil } - -func toBool(s string) bool { - s = strings.ToLower(strings.TrimSpace(s)) - return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") -} - -// FIXME(calavera): This is a copy of the Env.GetInt64 -func toInt64(s string) int64 { - val, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0 - } - return val -} diff --git a/builder/job.go b/builder/job.go index 6ad64d716b1b9..8a1cf3054caaa 100644 --- a/builder/job.go +++ b/builder/job.go @@ -2,7 +2,6 @@ package builder import ( "bytes" - "encoding/json" "fmt" "io" "io/ioutil" @@ -18,7 +17,6 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" - "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" @@ -38,11 +36,6 @@ var validCommitCommands = map[string]bool{ "onbuild": true, } -type BuilderJob struct { - Engine *engine.Engine - Daemon *daemon.Daemon -} - type Config struct { DockerfileName string RemoteURL string @@ -61,9 +54,8 @@ type Config struct { AuthConfig *registry.AuthConfig ConfigFile *registry.ConfigFile - Stdout *engine.Output - Stderr *engine.Output - Stdin *engine.Input + Stdout io.Writer + Context io.ReadCloser // When closed, the job has been cancelled. // Note: not all jobs implement cancellation. // See Job.Cancel() and Job.WaitCancelled() @@ -83,24 +75,15 @@ func (b *Config) WaitCancelled() <-chan struct{} { return b.cancelled } -func NewBuildConfig(logging bool, err io.Writer) *Config { - c := &Config{ - Stdout: engine.NewOutput(), - Stderr: engine.NewOutput(), - Stdin: engine.NewInput(), - cancelled: make(chan struct{}), +func NewBuildConfig() *Config { + return &Config{ + AuthConfig: ®istry.AuthConfig{}, + ConfigFile: ®istry.ConfigFile{}, + cancelled: make(chan struct{}), } - if logging { - c.Stderr.Add(ioutils.NopWriteCloser(err)) - } - return c -} - -func (b *BuilderJob) Install() { - b.Engine.Register("build_config", b.CmdBuildConfig) } -func (b *BuilderJob) CmdBuild(buildConfig *Config) error { +func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { var ( repoName string tag string @@ -109,7 +92,7 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName) if repoName != "" { - if err := registry.ValidateRepositoryName(buildConfig.RepoName); err != nil { + if err := registry.ValidateRepositoryName(repoName); err != nil { return err } if len(tag) > 0 { @@ -120,7 +103,7 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { } if buildConfig.RemoteURL == "" { - context = ioutil.NopCloser(buildConfig.Stdin) + context = ioutil.NopCloser(buildConfig.Context) } else if urlutil.IsGitURL(buildConfig.RemoteURL) { if !urlutil.IsGitTransport(buildConfig.RemoteURL) { buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL @@ -166,8 +149,8 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { sf := streamformatter.NewStreamFormatter(buildConfig.JSONFormat) builder := &Builder{ - Daemon: b.Daemon, - Engine: b.Engine, + Daemon: d, + Engine: e, OutStream: &streamformatter.StdoutFormater{ Writer: buildConfig.Stdout, StreamFormatter: sf, @@ -200,41 +183,28 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { } if repoName != "" { - b.Daemon.Repositories().Tag(repoName, tag, id, true) + return d.Repositories().Tag(repoName, tag, id, true) } return nil } -func (b *BuilderJob) CmdBuildConfig(job *engine.Job) error { - if len(job.Args) != 0 { - return fmt.Errorf("Usage: %s\n", job.Name) - } - - var ( - changes = job.GetenvList("changes") - newConfig runconfig.Config - ) - - if err := job.GetenvJson("config", &newConfig); err != nil { - return err - } - +func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, changes []string) (*runconfig.Config, error) { ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) if err != nil { - return err + return nil, err } // ensure that the commands are valid for _, n := range ast.Children { if !validCommitCommands[n.Value] { - return fmt.Errorf("%s is not a valid change command", n.Value) + return nil, fmt.Errorf("%s is not a valid change command", n.Value) } } builder := &Builder{ - Daemon: b.Daemon, - Engine: b.Engine, - Config: &newConfig, + Daemon: d, + Engine: e, + Config: c, OutStream: ioutil.Discard, ErrStream: ioutil.Discard, disableCommit: true, @@ -242,12 +212,32 @@ func (b *BuilderJob) CmdBuildConfig(job *engine.Job) error { for i, n := range ast.Children { if err := builder.dispatch(i, n); err != nil { - return err + return nil, err } } - if err := json.NewEncoder(job.Stdout).Encode(builder.Config); err != nil { - return err + return builder.Config, nil +} + +func Commit(d *daemon.Daemon, eng *engine.Engine, name string, c *daemon.ContainerCommitConfig) (string, error) { + container, err := d.Get(name) + if err != nil { + return "", err } - return nil + + newConfig, err := BuildFromConfig(d, eng, c.Config, c.Changes) + if err != nil { + return "", err + } + + if err := runconfig.Merge(newConfig, container.Config); err != nil { + return "", err + } + + img, err := d.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, newConfig) + if err != nil { + return "", err + } + + return img.ID, nil } diff --git a/daemon/commit.go b/daemon/commit.go index a60ed08191f39..0c49eb2c95193 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -1,12 +1,6 @@ package daemon import ( - "bytes" - "encoding/json" - "io" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/runconfig" ) @@ -18,49 +12,7 @@ type ContainerCommitConfig struct { Author string Comment string Changes []string - Config io.ReadCloser -} - -func (daemon *Daemon) ContainerCommit(name string, c *ContainerCommitConfig) (string, error) { - container, err := daemon.Get(name) - if err != nil { - return "", err - } - - var ( - subenv engine.Env - config = container.Config - stdoutBuffer = bytes.NewBuffer(nil) - newConfig runconfig.Config - ) - - if err := subenv.Decode(c.Config); err != nil { - logrus.Errorf("%s", err) - } - - buildConfigJob := daemon.eng.Job("build_config") - buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.SetenvList("changes", c.Changes) - // FIXME this should be remove when we remove deprecated config param - buildConfigJob.SetenvSubEnv("config", &subenv) - - if err := buildConfigJob.Run(); err != nil { - return "", err - } - if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return "", err - } - - if err := runconfig.Merge(&newConfig, config); err != nil { - return "", err - } - - img, err := daemon.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, &newConfig) - if err != nil { - return "", err - } - - return img.ID, nil + Config *runconfig.Config } // Commit creates a new filesystem image from the current state of a container. diff --git a/docker/daemon.go b/docker/daemon.go index 0602ddf654511..c6241b60602fb 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -11,7 +11,6 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/builder" "github.com/docker/docker/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" _ "github.com/docker/docker/daemon/execdriver/native" @@ -141,9 +140,6 @@ func mainDaemon() { "graphdriver": d.GraphDriver().String(), }).Info("Docker daemon") - b := &builder.BuilderJob{eng, d} - b.Install() - // after the daemon is done setting up we can tell the api to start // accepting connections with specified daemon api.AcceptConnections(d) @@ -155,7 +151,6 @@ func mainDaemon() { if errAPI != nil { logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } - } // currentUserIsOwner checks whether the current user is the owner of the given diff --git a/graph/import.go b/graph/import.go index 5d86ba2cb9b6b..50e605c948e1e 100644 --- a/graph/import.go +++ b/graph/import.go @@ -1,13 +1,10 @@ package graph import ( - "bytes" - "encoding/json" "io" "net/http" "net/url" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/progressreader" @@ -17,20 +14,18 @@ import ( ) type ImageImportConfig struct { - Changes []string - InConfig io.ReadCloser - Json bool - OutStream io.Writer - //OutStream WriteFlusher + Changes []string + InConfig io.ReadCloser + Json bool + OutStream io.Writer + ContainerConfig *runconfig.Config } -func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig, eng *engine.Engine) error { +func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig) error { var ( - sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) - archive archive.ArchiveReader - resp *http.Response - stdoutBuffer = bytes.NewBuffer(nil) - newConfig runconfig.Config + sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) + archive archive.ArchiveReader + resp *http.Response ) if src == "-" { @@ -63,20 +58,7 @@ func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig archive = progressReader } - buildConfigJob := eng.Job("build_config") - buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.SetenvList("changes", imageImportConfig.Changes) - // FIXME this should be remove when we remove deprecated config param - //buildConfigJob.Setenv("config", job.Getenv("config")) - - if err := buildConfigJob.Run(); err != nil { - return err - } - if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return err - } - - img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, &newConfig) + img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, imageImportConfig.ContainerConfig) if err != nil { return err } From 2ed4ed50be3a0379d83b9267f62c4c7f665b849f Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 16 Apr 2015 14:17:23 +0200 Subject: [PATCH 203/332] Add some stdcopy_test (coverage) Signed-off-by: Vincent Demeester --- pkg/stdcopy/stdcopy.go | 4 --- pkg/stdcopy/stdcopy_test.go | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/pkg/stdcopy/stdcopy.go b/pkg/stdcopy/stdcopy.go index 4f78f20d1b024..dbb74e5a20876 100644 --- a/pkg/stdcopy/stdcopy.go +++ b/pkg/stdcopy/stdcopy.go @@ -54,10 +54,6 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) { // `t` indicates the id of the stream to encapsulate. // It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. func NewStdWriter(w io.Writer, t StdType) *StdWriter { - if len(t) != StdWriterPrefixLen { - return nil - } - return &StdWriter{ Writer: w, prefix: t, diff --git a/pkg/stdcopy/stdcopy_test.go b/pkg/stdcopy/stdcopy_test.go index 14e6ed3115ce7..a9fd73a49eddc 100644 --- a/pkg/stdcopy/stdcopy_test.go +++ b/pkg/stdcopy/stdcopy_test.go @@ -3,9 +3,74 @@ package stdcopy import ( "bytes" "io/ioutil" + "strings" "testing" ) +func TestNewStdWriter(t *testing.T) { + writer := NewStdWriter(ioutil.Discard, Stdout) + if writer == nil { + t.Fatalf("NewStdWriter with an invalid StdType should not return nil.") + } +} + +func TestWriteWithUnitializedStdWriter(t *testing.T) { + writer := StdWriter{ + Writer: nil, + prefix: Stdout, + sizeBuf: make([]byte, 4), + } + n, err := writer.Write([]byte("Something here")) + if n != 0 || err == nil { + t.Fatalf("Should fail when given an uncomplete or uninitialized StdWriter") + } +} + +func TestWriteWithNilBytes(t *testing.T) { + writer := NewStdWriter(ioutil.Discard, Stdout) + n, err := writer.Write(nil) + if err != nil { + t.Fatalf("Shouldn't have fail when given no data") + } + if n > 0 { + t.Fatalf("Write should have written 0 byte, but has written %d", n) + } +} + +func TestWrite(t *testing.T) { + writer := NewStdWriter(ioutil.Discard, Stdout) + data := []byte("Test StdWrite.Write") + n, err := writer.Write(data) + if err != nil { + t.Fatalf("Error while writing with StdWrite") + } + if n != len(data) { + t.Fatalf("Write should have writen %d byte but wrote %d.", len(data), n) + } +} + +func TestStdCopyWithInvalidInputHeader(t *testing.T) { + dstOut := NewStdWriter(ioutil.Discard, Stdout) + dstErr := NewStdWriter(ioutil.Discard, Stderr) + src := strings.NewReader("Invalid input") + _, err := StdCopy(dstOut, dstErr, src) + if err == nil { + t.Fatal("StdCopy with invalid input header should fail.") + } +} + +func TestStdCopyWithCorruptedPrefix(t *testing.T) { + data := []byte{0x01, 0x02, 0x03} + src := bytes.NewReader(data) + written, err := StdCopy(nil, nil, src) + if err != nil { + t.Fatalf("StdCopy should not return an error with corrupted prefix.") + } + if written != 0 { + t.Fatalf("StdCopy should have written 0, but has written %d", written) + } +} + func BenchmarkWrite(b *testing.B) { w := NewStdWriter(ioutil.Discard, Stdout) data := []byte("Test line for testing stdwriter performance\n") From 29c5596176769fc99a22b1d0a78dd5ea279fec69 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Mon, 20 Apr 2015 21:07:21 +0000 Subject: [PATCH 204/332] moving integration tests to graph unit tests Addresses #12255 Signed-off-by: Srini Brahmaroutu --- {integration => graph}/graph_test.go | 47 ++++++++++------------------ 1 file changed, 17 insertions(+), 30 deletions(-) rename {integration => graph}/graph_test.go (93%) diff --git a/integration/graph_test.go b/graph/graph_test.go similarity index 93% rename from integration/graph_test.go rename to graph/graph_test.go index 8b6d7626f666e..81471b6749457 100644 --- a/integration/graph_test.go +++ b/graph/graph_test.go @@ -1,4 +1,4 @@ -package docker +package graph import ( "errors" @@ -11,9 +11,7 @@ import ( "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon/graphdriver" - "github.com/docker/docker/graph" "github.com/docker/docker/image" - "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/stringid" ) @@ -47,6 +45,7 @@ func TestMount(t *testing.T) { if _, err := driver.Get(image.ID, ""); err != nil { t.Fatal(err) } + } func TestInit(t *testing.T) { @@ -166,18 +165,6 @@ func TestDeletePrefix(t *testing.T) { assertNImages(graph, t, 0) } -func createTestImage(graph *graph.Graph, t *testing.T) *image.Image { - archive, err := fakeTar() - if err != nil { - t.Fatal(err) - } - img, err := graph.Create(archive, "", "", "Test image", "", nil, nil) - if err != nil { - t.Fatal(err) - } - return img -} - func TestDelete(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) @@ -277,11 +264,19 @@ func TestByParent(t *testing.T) { } } -/* - * HELPER FUNCTIONS - */ +func createTestImage(graph *Graph, t *testing.T) *image.Image { + archive, err := fakeTar() + if err != nil { + t.Fatal(err) + } + img, err := graph.Create(archive, "", "", "Test image", "", nil, nil) + if err != nil { + t.Fatal(err) + } + return img +} -func assertNImages(graph *graph.Graph, t *testing.T, n int) { +func assertNImages(graph *Graph, t *testing.T, n int) { if images, err := graph.Map(); err != nil { t.Fatal(err) } else if actualN := len(images); actualN != n { @@ -289,7 +284,7 @@ func assertNImages(graph *graph.Graph, t *testing.T, n int) { } } -func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) { +func tempGraph(t *testing.T) (*Graph, graphdriver.Driver) { tmp, err := ioutil.TempDir("", "docker-graph-") if err != nil { t.Fatal(err) @@ -298,22 +293,14 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) { if err != nil { t.Fatal(err) } - graph, err := graph.NewGraph(tmp, driver) + graph, err := NewGraph(tmp, driver) if err != nil { t.Fatal(err) } return graph, driver } -func nukeGraph(graph *graph.Graph) { +func nukeGraph(graph *Graph) { graph.Driver().Cleanup() os.RemoveAll(graph.Root) } - -func testArchive(t *testing.T) archive.Archive { - archive, err := fakeTar() - if err != nil { - t.Fatal(err) - } - return archive -} From 92849fdcce257dfd61a5c95f57cde085ff22b431 Mon Sep 17 00:00:00 2001 From: Lorenzo Fontana Date: Mon, 20 Apr 2015 22:06:17 +0200 Subject: [PATCH 205/332] Removed go1.3.3 support Signed-off-by: Lorenzo Fontana --- Dockerfile | 6 +-- pkg/requestdecorator/requestdecorator_test.go | 40 ++----------------- project/PACKAGERS.md | 2 +- 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/Dockerfile b/Dockerfile index b1c7c4a6f0fe8..6be471a7bb5f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -98,9 +98,9 @@ RUN cd /usr/local/go/src \ ./make.bash --no-clean 2>&1; \ done -# We still support compiling with older Go, so need to grab older "gofmt" -ENV GOFMT_VERSION 1.3.3 -RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt +# This has been commented out and kept as reference because we don't support compiling with older Go anymore. +# ENV GOFMT_VERSION 1.3.3 +# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt # Update this sha when we upgrade to go 1.5.0 ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9 diff --git a/pkg/requestdecorator/requestdecorator_test.go b/pkg/requestdecorator/requestdecorator_test.go index f1f9ef756b845..ed61135467bd6 100644 --- a/pkg/requestdecorator/requestdecorator_test.go +++ b/pkg/requestdecorator/requestdecorator_test.go @@ -1,45 +1,11 @@ package requestdecorator import ( - "encoding/base64" "net/http" "strings" "testing" ) -// The following 2 functions are here for 1.3.3 support -// After we drop 1.3.3 support we can use the functions supported -// in go v1.4.0 + -// BasicAuth returns the username and password provided in the request's -// Authorization header, if the request uses HTTP Basic Authentication. -// See RFC 2617, Section 2. -func basicAuth(r *http.Request) (username, password string, ok bool) { - auth := r.Header.Get("Authorization") - if auth == "" { - return - } - return parseBasicAuth(auth) -} - -// parseBasicAuth parses an HTTP Basic Authentication string. -// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). -func parseBasicAuth(auth string) (username, password string, ok bool) { - const prefix = "Basic " - if !strings.HasPrefix(auth, prefix) { - return - } - c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) - if err != nil { - return - } - cs := string(c) - s := strings.IndexByte(cs, ':') - if s < 0 { - return - } - return cs[:s], cs[s+1:], true -} - func TestUAVersionInfo(t *testing.T) { uavi := NewUAVersionInfo("foo", "bar") if !uavi.isValid() { @@ -147,7 +113,7 @@ func TestAuthDecorator(t *testing.T) { t.Fatal(err) } - username, password, ok := basicAuth(reqDecorated) + username, password, ok := reqDecorated.BasicAuth() if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } @@ -189,7 +155,7 @@ func TestRequestFactory(t *testing.T) { t.Fatal(err) } - username, password, ok := basicAuth(req) + username, password, ok := req.BasicAuth() if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } @@ -220,7 +186,7 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { t.Fatal(err) } - username, password, ok := basicAuth(req) + username, password, ok := req.BasicAuth() if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md index 6acd4aef35fae..d321a900d683d 100644 --- a/project/PACKAGERS.md +++ b/project/PACKAGERS.md @@ -45,7 +45,7 @@ need to package Docker your way, without denaturing it in the process. To build Docker, you will need the following: * A recent version of Git and Mercurial -* Go version 1.3 or later +* Go version 1.4 or later * A clean checkout of the source added to a valid [Go workspace](https://golang.org/doc/code.html#Workspaces) under the path *src/github.com/docker/docker* (unless you plan to use `AUTO_GOPATH`, From bfeb98a23607c835c1d9241e282b84acd8dc3606 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Mon, 20 Apr 2015 14:09:41 -0700 Subject: [PATCH 206/332] Make .docker dir have 0700 perms not 0600 Thanks to @dmcgowan for noticing. Added a testcase to make sure Save() can create the dir and then read from it. Signed-off-by: Doug Davis --- registry/auth.go | 2 +- registry/config_file_test.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/registry/auth.go b/registry/auth.go index bccf58fc5acef..ef4985abcd746 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -261,7 +261,7 @@ func (configFile *ConfigFile) Save() error { return err } - if err := os.MkdirAll(filepath.Dir(configFile.filename), 0600); err != nil { + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { return err } diff --git a/registry/config_file_test.go b/registry/config_file_test.go index 9abb8ee95cbfe..6f8bd74f534eb 100644 --- a/registry/config_file_test.go +++ b/registry/config_file_test.go @@ -31,6 +31,28 @@ func TestMissingFile(t *testing.T) { } } +func TestSaveFileToDirs(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + + tmpHome += "/.docker" + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on missing file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + func TestEmptyFile(t *testing.T) { tmpHome, _ := ioutil.TempDir("", "config-test") fn := filepath.Join(tmpHome, CONFIGFILE) From 3b05005a1262e53d042512e88c52a6dae0f2e93d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 20 Apr 2015 14:18:14 -0700 Subject: [PATCH 207/332] Add flusher check to utils.WriteFlusher. That way we can know when the stream has been flushed. Signed-off-by: David Calavera --- api/server/server.go | 10 +++++----- utils/utils.go | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 1002526f9aac8..fb271916e1475 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1285,9 +1285,6 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } } - stdout := engine.NewOutput() - stdout.Set(utils.NewWriteFlusher(w)) - if version.GreaterThanOrEqualTo("1.8") { w.Header().Set("Content-Type", "application/json") buildConfig.JSONFormat = true @@ -1304,7 +1301,8 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R buildConfig.Pull = true } - buildConfig.Stdout = stdout + output := utils.NewWriteFlusher(w) + buildConfig.Stdout = output buildConfig.Context = r.Body buildConfig.RemoteURL = r.FormValue("remote") @@ -1336,7 +1334,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } if err := builder.Build(s.daemon, eng, buildConfig); err != nil { - if !stdout.Used() { + // Do not write the error in the http output if it's still empty. + // This prevents from writing a 200(OK) when there is an interal error. + if !output.Flushed() { return err } sf := streamformatter.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) diff --git a/utils/utils.go b/utils/utils.go index a151fc3f0ea07..ab59826783141 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -128,12 +128,14 @@ type WriteFlusher struct { sync.Mutex w io.Writer flusher http.Flusher + flushed bool } func (wf *WriteFlusher) Write(b []byte) (n int, err error) { wf.Lock() defer wf.Unlock() n, err = wf.w.Write(b) + wf.flushed = true wf.flusher.Flush() return n, err } @@ -142,9 +144,16 @@ func (wf *WriteFlusher) Write(b []byte) (n int, err error) { func (wf *WriteFlusher) Flush() { wf.Lock() defer wf.Unlock() + wf.flushed = true wf.flusher.Flush() } +func (wf *WriteFlusher) Flushed() bool { + wf.Lock() + defer wf.Unlock() + return wf.flushed +} + func NewWriteFlusher(w io.Writer) *WriteFlusher { var flusher http.Flusher if f, ok := w.(http.Flusher); ok { From a8253ec7e7ec13c81d5195ddfe3cf2571ce6afff Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Mon, 20 Apr 2015 11:24:28 -0700 Subject: [PATCH 208/332] Early API version bump Signed-off-by: Arnaud Porterie --- project/RELEASE-CHECKLIST.md | 43 +++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/project/RELEASE-CHECKLIST.md b/project/RELEASE-CHECKLIST.md index 8a2070e58d8c9..d2b9650805f72 100644 --- a/project/RELEASE-CHECKLIST.md +++ b/project/RELEASE-CHECKLIST.md @@ -49,7 +49,17 @@ git cherry-pick ... ``` -### 2. Update CHANGELOG.md +### 2. Bump the API version on master + +We don't want to stop contributions to master just because we are releasing. At +the same time, now that the release branch exists, we don't want API changes to +go to the now frozen API version. + +Create a new entry in `docs/sources/reference/api/` by copying the latest and +bumping the version number (in both the file's name and content), and submit +this in a PR against master. + +### 3. Update CHANGELOG.md You can run this command for reference with git 2.0: @@ -124,7 +134,7 @@ git log --format='%aN <%aE>' v0.7.0...bump_v0.8.0 | sort -uf Obviously, you'll need to adjust version numbers as necessary. If you just need a count, add a simple `| wc -l`. -### 3. Change the contents of the VERSION file +### 4. Change the contents of the VERSION file Before the big thing, you'll want to make successive release candidates and get people to test. The release candidate number `N` should be part of the version: @@ -134,7 +144,7 @@ export RC_VERSION=${VERSION}-rcN echo ${RC_VERSION#v} > VERSION ``` -### 4. Test the docs +### 5. Test the docs Make sure that your tree includes documentation for any modified or new features, syntax or semantic changes. @@ -153,7 +163,7 @@ To make a shared test at https://beta-docs.docker.io: make AWS_S3_BUCKET=beta-docs.docker.io BUILD_ROOT=yes docs-release ``` -### 5. Commit and create a pull request to the "release" branch +### 6. Commit and create a pull request to the "release" branch ```bash git add VERSION CHANGELOG.md @@ -166,7 +176,7 @@ That last command will give you the proper link to visit to ensure that you open the PR against the "release" branch instead of accidentally against "master" (like so many brave souls before you already have). -### 6. Publish release candidate binaries +### 7. Publish release candidate binaries To run this you will need access to the release credentials. Get them from the Core maintainers. @@ -219,7 +229,7 @@ We recommend announcing the release candidate on: - The [docker-maintainers](https://groups.google.com/a/dockerproject.org/forum/#!forum/maintainers) group - Any social media that can bring some attention to the release candidate -### 7. Iterate on successive release candidates +### 8. Iterate on successive release candidates Spend several days along with the community explicitly investing time and resources to try and break Docker in every possible way, documenting any @@ -269,7 +279,7 @@ git push -f $GITHUBUSER bump_$VERSION Repeat step 6 to tag the code, publish new binaries, announce availability, and get help testing. -### 8. Finalize the bump branch +### 9. Finalize the bump branch When you're happy with the quality of a release candidate, you can move on and create the real thing. @@ -285,9 +295,9 @@ git commit --amend You will then repeat step 6 to publish the binaries to test -### 9. Get 2 other maintainers to validate the pull request +### 10. Get 2 other maintainers to validate the pull request -### 10. Publish final binaries +### 11. Publish final binaries Once they're tested and reasonably believed to be working, run against get.docker.com: @@ -303,7 +313,7 @@ docker run \ hack/release.sh ``` -### 9. Apply tag +### 12. Apply tag It's very important that we don't make the tag until after the official release is uploaded to get.docker.com! @@ -313,12 +323,12 @@ git tag -a $VERSION -m $VERSION bump_$VERSION git push origin $VERSION ``` -### 10. Go to github to merge the `bump_$VERSION` branch into release +### 13. Go to github to merge the `bump_$VERSION` branch into release Don't forget to push that pretty blue button to delete the leftover branch afterwards! -### 11. Update the docs branch +### 14. Update the docs branch If this is a MAJOR.MINOR.0 release, you need to make an branch for the previous release's documentation: @@ -350,7 +360,7 @@ distributed CDN system) is flushed. The `make docs-release` command will do this _if_ the `DISTRIBUTION_ID` is set correctly - this will take at least 15 minutes to run and you can check its progress with the CDN Cloudfront Chrome addin. -### 12. Create a new pull request to merge your bump commit back into master +### 15. Create a new pull request to merge your bump commit back into master ```bash git checkout master @@ -364,17 +374,14 @@ echo "https://github.com/$GITHUBUSER/docker/compare/docker:master...$GITHUBUSER: Again, get two maintainers to validate, then merge, then push that pretty blue button to delete your branch. -### 13. Update the API docs and VERSION files +### 16. Update the VERSION files Now that version X.Y.Z is out, time to start working on the next! Update the content of the `VERSION` file to be the next minor (incrementing Y) and add the `-dev` suffix. For example, after 1.5.0 release, the `VERSION` file gets updated to `1.6.0-dev` (as in "1.6.0 in the making"). -Also create a new entry in `docs/sources/reference/api/` by copying the latest -and bumping the version number (in both the file's name and content). - -### 14. Rejoice and Evangelize! +### 17. Rejoice and Evangelize! Congratulations! You're done. From f3d4c33213088ce3c381156760064f07da76de30 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 20 Apr 2015 17:13:48 -0700 Subject: [PATCH 209/332] actually depreciate -rm, -sig-proxy, -name, seriously its been forever Signed-off-by: Jessica Frazelle --- api/client/run.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/client/run.go b/api/client/run.go index 74c656af3dece..628e725f1bfba 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -43,10 +43,10 @@ func (cli *DockerCli) CmdRun(args ...string) error { // These are flags not stored in Config/HostConfig var ( - flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits") + flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") - flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process") - flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") + flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process") + flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") flAttach *opts.ListOpts ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") From 27811355bd15dd2d0f102dd3b02512c677d5da44 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 18:14:39 +0200 Subject: [PATCH 210/332] Remove writeJSONEnv Signed-off-by: Antonio Murdaca --- api/server/server.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 9142e530d087a..94338097ba405 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -223,14 +223,6 @@ func httpError(w http.ResponseWriter, err error) { http.Error(w, err.Error(), statusCode) } -// writeJSONEnv writes the engine.Env values to the http response stream as a -// json encoded body. -func writeJSONEnv(w http.ResponseWriter, code int, v engine.Env) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - return v.Encode(w) -} - // writeJSON writes the value v to the http response stream as json with standard // json encoding. func writeJSON(w http.ResponseWriter, code int, v interface{}) error { From 364287b74118de4f04d049426ef37fa9936d2065 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Mon, 20 Apr 2015 14:26:39 -0400 Subject: [PATCH 211/332] Add journald as a supported logger for containers Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- daemon/container.go | 7 ++++ daemon/logger/journald/journald.go | 35 +++++++++++++++++++ docs/man/docker-create.1.md | 2 +- docs/man/docker-run.1.md | 2 +- docs/man/docker.1.md | 2 +- .../reference/api/docker_remote_api_v1.19.md | 4 +-- docs/sources/reference/run.md | 4 +++ 7 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 daemon/logger/journald/journald.go diff --git a/daemon/container.go b/daemon/container.go index 9dc0696ea79c9..93e60d4fe6b43 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -21,6 +21,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/logger" + "github.com/docker/docker/daemon/logger/journald" "github.com/docker/docker/daemon/logger/jsonfilelog" "github.com/docker/docker/daemon/logger/syslog" "github.com/docker/docker/daemon/network" @@ -1420,6 +1421,12 @@ func (container *Container) startLogging() error { return err } l = dl + case "journald": + dl, err := journald.New(container.ID[:12]) + if err != nil { + return err + } + l = dl case "none": return nil default: diff --git a/daemon/logger/journald/journald.go b/daemon/logger/journald/journald.go new file mode 100644 index 0000000000000..5eb141ac83f70 --- /dev/null +++ b/daemon/logger/journald/journald.go @@ -0,0 +1,35 @@ +package journald + +import ( + "fmt" + + "github.com/coreos/go-systemd/journal" + "github.com/docker/docker/daemon/logger" +) + +type Journald struct { + Jmap map[string]string +} + +func New(id string) (logger.Logger, error) { + if !journal.Enabled() { + return nil, fmt.Errorf("journald is not enabled on this host") + } + jmap := map[string]string{"MESSAGE_ID": id} + return &Journald{Jmap: jmap}, nil +} + +func (s *Journald) Log(msg *logger.Message) error { + if msg.Source == "stderr" { + return journal.Send(string(msg.Line), journal.PriErr, s.Jmap) + } + return journal.Send(string(msg.Line), journal.PriInfo, s.Jmap) +} + +func (s *Journald) Close() error { + return nil +} + +func (s *Journald) Name() string { + return "Journald" +} diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index bb9cbdc8fcb72..7aba222b298e9 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -133,7 +133,7 @@ two memory nodes. **--lxc-conf**=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -**--log-driver**="|*json-file*|*syslog*|*none*" +**--log-driver**="|*json-file*|*syslog*|*journald*|*none*" Logging driver for container. Default is defined by daemon `--log-driver` flag. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 2893437bbe39c..f2ce4b7774ae5 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -238,7 +238,7 @@ which interface and port to use. **--lxc-conf**=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -**--log-driver**="|*json-file*|*syslog*|*none*" +**--log-driver**="|*json-file*|*syslog*|*journald*|*none*" Logging driver for container. Default is defined by daemon `--log-driver` flag. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 53c54f9037c50..0196b6364eef6 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -95,7 +95,7 @@ unix://[/path/to/socket] to use. **--label**="[]" Set key=value labels to the daemon (displayed in `docker info`) -**--log-driver**="*json-file*|*syslog*|*none*" +**--log-driver**="*json-file*|*syslog*|*journald*|*none*" Default driver for container logs. Default is `json-file`. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 3a4d05ea9669c..a3b580f1b4a83 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -261,7 +261,7 @@ Json Parameters: systems, such as SELinux. - **LogConfig** - Log configuration for the container, specified as `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. + Available types: `json-file`, `syslog`, `journald`, `none`. `json-file` logging driver. - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. @@ -762,7 +762,7 @@ Json Parameters: systems, such as SELinux. - **LogConfig** - Log configuration for the container, specified as `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. + Available types: `json-file`, `syslog`, `journald`, `none`. `json-file` logging driver. - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 10178b382a969..a0d66937f1566 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -788,6 +788,10 @@ command is available only for this logging driver Syslog logging driver for Docker. Writes log messages to syslog. `docker logs` command is not available for this logging driver +#### Logging driver: journald + +Journald logging driver for Docker. Writes log messages to journald. `docker logs` command is not available for this logging driver + ## Overriding Dockerfile image defaults When a developer builds an image from a [*Dockerfile*](/reference/builder) From 6dcdf832a3dddc8de17b7f8b1fb1ddb8b20f9077 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Sat, 18 Apr 2015 09:45:24 -0700 Subject: [PATCH 212/332] Add gocheck to vendored deps Signed-off-by: Alexander Morozov --- hack/vendor.sh | 2 + .../src/github.com/go-check/check/.gitignore | 4 + vendor/src/github.com/go-check/check/LICENSE | 25 + .../src/github.com/go-check/check/README.md | 20 + vendor/src/github.com/go-check/check/TODO | 2 + .../github.com/go-check/check/benchmark.go | 163 +++ .../go-check/check/benchmark_test.go | 91 ++ .../go-check/check/bootstrap_test.go | 82 ++ vendor/src/github.com/go-check/check/check.go | 945 ++++++++++++++++++ .../github.com/go-check/check/check_test.go | 207 ++++ .../src/github.com/go-check/check/checkers.go | 458 +++++++++ .../go-check/check/checkers_test.go | 272 +++++ .../github.com/go-check/check/export_test.go | 9 + .../github.com/go-check/check/fixture_test.go | 484 +++++++++ .../go-check/check/foundation_test.go | 335 +++++++ .../src/github.com/go-check/check/helpers.go | 231 +++++ .../github.com/go-check/check/helpers_test.go | 519 ++++++++++ .../src/github.com/go-check/check/printer.go | 168 ++++ .../github.com/go-check/check/printer_test.go | 104 ++ vendor/src/github.com/go-check/check/run.go | 175 ++++ .../src/github.com/go-check/check/run_test.go | 419 ++++++++ 21 files changed, 4715 insertions(+) create mode 100644 vendor/src/github.com/go-check/check/.gitignore create mode 100644 vendor/src/github.com/go-check/check/LICENSE create mode 100644 vendor/src/github.com/go-check/check/README.md create mode 100644 vendor/src/github.com/go-check/check/TODO create mode 100644 vendor/src/github.com/go-check/check/benchmark.go create mode 100644 vendor/src/github.com/go-check/check/benchmark_test.go create mode 100644 vendor/src/github.com/go-check/check/bootstrap_test.go create mode 100644 vendor/src/github.com/go-check/check/check.go create mode 100644 vendor/src/github.com/go-check/check/check_test.go create mode 100644 vendor/src/github.com/go-check/check/checkers.go create mode 100644 vendor/src/github.com/go-check/check/checkers_test.go create mode 100644 vendor/src/github.com/go-check/check/export_test.go create mode 100644 vendor/src/github.com/go-check/check/fixture_test.go create mode 100644 vendor/src/github.com/go-check/check/foundation_test.go create mode 100644 vendor/src/github.com/go-check/check/helpers.go create mode 100644 vendor/src/github.com/go-check/check/helpers_test.go create mode 100644 vendor/src/github.com/go-check/check/printer.go create mode 100644 vendor/src/github.com/go-check/check/printer_test.go create mode 100644 vendor/src/github.com/go-check/check/run.go create mode 100644 vendor/src/github.com/go-check/check/run_test.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 2e0437e67c9d4..8fed05852217f 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -57,6 +57,8 @@ clone git github.com/Sirupsen/logrus v0.7.2 clone git github.com/go-fsnotify/fsnotify v1.0.4 +clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673 + # get Go tip's archive/tar, for xattr support and improved performance # TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep if [ "$1" = '--go' ]; then diff --git a/vendor/src/github.com/go-check/check/.gitignore b/vendor/src/github.com/go-check/check/.gitignore new file mode 100644 index 0000000000000..191a5360b759f --- /dev/null +++ b/vendor/src/github.com/go-check/check/.gitignore @@ -0,0 +1,4 @@ +_* +*.swp +*.[568] +[568].out diff --git a/vendor/src/github.com/go-check/check/LICENSE b/vendor/src/github.com/go-check/check/LICENSE new file mode 100644 index 0000000000000..545cf2d3311b0 --- /dev/null +++ b/vendor/src/github.com/go-check/check/LICENSE @@ -0,0 +1,25 @@ +Gocheck - A rich testing framework for Go + +Copyright (c) 2010-2013 Gustavo Niemeyer + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/src/github.com/go-check/check/README.md b/vendor/src/github.com/go-check/check/README.md new file mode 100644 index 0000000000000..0ca9e57260468 --- /dev/null +++ b/vendor/src/github.com/go-check/check/README.md @@ -0,0 +1,20 @@ +Instructions +============ + +Install the package with: + + go get gopkg.in/check.v1 + +Import it with: + + import "gopkg.in/check.v1" + +and use _check_ as the package name inside the code. + +For more details, visit the project page: + +* http://labix.org/gocheck + +and the API documentation: + +* https://gopkg.in/check.v1 diff --git a/vendor/src/github.com/go-check/check/TODO b/vendor/src/github.com/go-check/check/TODO new file mode 100644 index 0000000000000..33498270eae3a --- /dev/null +++ b/vendor/src/github.com/go-check/check/TODO @@ -0,0 +1,2 @@ +- Assert(slice, Contains, item) +- Parallel test support diff --git a/vendor/src/github.com/go-check/check/benchmark.go b/vendor/src/github.com/go-check/check/benchmark.go new file mode 100644 index 0000000000000..48cb8c8114e7a --- /dev/null +++ b/vendor/src/github.com/go-check/check/benchmark.go @@ -0,0 +1,163 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package check + +import ( + "fmt" + "runtime" + "time" +) + +var memStats runtime.MemStats + +// testingB is a type passed to Benchmark functions to manage benchmark +// timing and to specify the number of iterations to run. +type timer struct { + start time.Time // Time test or benchmark started + duration time.Duration + N int + bytes int64 + timerOn bool + benchTime time.Duration + // The initial states of memStats.Mallocs and memStats.TotalAlloc. + startAllocs uint64 + startBytes uint64 + // The net total of this test after being run. + netAllocs uint64 + netBytes uint64 +} + +// StartTimer starts timing a test. This function is called automatically +// before a benchmark starts, but it can also used to resume timing after +// a call to StopTimer. +func (c *C) StartTimer() { + if !c.timerOn { + c.start = time.Now() + c.timerOn = true + + runtime.ReadMemStats(&memStats) + c.startAllocs = memStats.Mallocs + c.startBytes = memStats.TotalAlloc + } +} + +// StopTimer stops timing a test. This can be used to pause the timer +// while performing complex initialization that you don't +// want to measure. +func (c *C) StopTimer() { + if c.timerOn { + c.duration += time.Now().Sub(c.start) + c.timerOn = false + runtime.ReadMemStats(&memStats) + c.netAllocs += memStats.Mallocs - c.startAllocs + c.netBytes += memStats.TotalAlloc - c.startBytes + } +} + +// ResetTimer sets the elapsed benchmark time to zero. +// It does not affect whether the timer is running. +func (c *C) ResetTimer() { + if c.timerOn { + c.start = time.Now() + runtime.ReadMemStats(&memStats) + c.startAllocs = memStats.Mallocs + c.startBytes = memStats.TotalAlloc + } + c.duration = 0 + c.netAllocs = 0 + c.netBytes = 0 +} + +// SetBytes informs the number of bytes that the benchmark processes +// on each iteration. If this is called in a benchmark it will also +// report MB/s. +func (c *C) SetBytes(n int64) { + c.bytes = n +} + +func (c *C) nsPerOp() int64 { + if c.N <= 0 { + return 0 + } + return c.duration.Nanoseconds() / int64(c.N) +} + +func (c *C) mbPerSec() float64 { + if c.bytes <= 0 || c.duration <= 0 || c.N <= 0 { + return 0 + } + return (float64(c.bytes) * float64(c.N) / 1e6) / c.duration.Seconds() +} + +func (c *C) timerString() string { + if c.N <= 0 { + return fmt.Sprintf("%3.3fs", float64(c.duration.Nanoseconds())/1e9) + } + mbs := c.mbPerSec() + mb := "" + if mbs != 0 { + mb = fmt.Sprintf("\t%7.2f MB/s", mbs) + } + nsop := c.nsPerOp() + ns := fmt.Sprintf("%10d ns/op", nsop) + if c.N > 0 && nsop < 100 { + // The format specifiers here make sure that + // the ones digits line up for all three possible formats. + if nsop < 10 { + ns = fmt.Sprintf("%13.2f ns/op", float64(c.duration.Nanoseconds())/float64(c.N)) + } else { + ns = fmt.Sprintf("%12.1f ns/op", float64(c.duration.Nanoseconds())/float64(c.N)) + } + } + memStats := "" + if c.benchMem { + allocedBytes := fmt.Sprintf("%8d B/op", int64(c.netBytes)/int64(c.N)) + allocs := fmt.Sprintf("%8d allocs/op", int64(c.netAllocs)/int64(c.N)) + memStats = fmt.Sprintf("\t%s\t%s", allocedBytes, allocs) + } + return fmt.Sprintf("%8d\t%s%s%s", c.N, ns, mb, memStats) +} + +func min(x, y int) int { + if x > y { + return y + } + return x +} + +func max(x, y int) int { + if x < y { + return y + } + return x +} + +// roundDown10 rounds a number down to the nearest power of 10. +func roundDown10(n int) int { + var tens = 0 + // tens = floor(log_10(n)) + for n > 10 { + n = n / 10 + tens++ + } + // result = 10^tens + result := 1 + for i := 0; i < tens; i++ { + result *= 10 + } + return result +} + +// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. +func roundUp(n int) int { + base := roundDown10(n) + if n < (2 * base) { + return 2 * base + } + if n < (5 * base) { + return 5 * base + } + return 10 * base +} diff --git a/vendor/src/github.com/go-check/check/benchmark_test.go b/vendor/src/github.com/go-check/check/benchmark_test.go new file mode 100644 index 0000000000000..4dd827c160da4 --- /dev/null +++ b/vendor/src/github.com/go-check/check/benchmark_test.go @@ -0,0 +1,91 @@ +// These tests verify the test running logic. + +package check_test + +import ( + "time" + . "gopkg.in/check.v1" +) + +var benchmarkS = Suite(&BenchmarkS{}) + +type BenchmarkS struct{} + +func (s *BenchmarkS) TestCountSuite(c *C) { + suitesRun += 1 +} + +func (s *BenchmarkS) TestBasicTestTiming(c *C) { + helper := FixtureHelper{sleepOn: "Test1", sleep: 1000000 * time.Nanosecond} + output := String{} + runConf := RunConf{Output: &output, Verbose: true} + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t0\\.001s\n" + + "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t0\\.000s\n" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestStreamTestTiming(c *C) { + helper := FixtureHelper{sleepOn: "SetUpSuite", sleep: 1000000 * time.Nanosecond} + output := String{} + runConf := RunConf{Output: &output, Stream: true} + Run(&helper, &runConf) + + expected := "(?s).*\nPASS: check_test\\.go:[0-9]+: FixtureHelper\\.SetUpSuite\t *0\\.001s\n.*" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestBenchmark(c *C) { + helper := FixtureHelper{sleep: 100000} + output := String{} + runConf := RunConf{ + Output: &output, + Benchmark: true, + BenchmarkTime: 10000000, + Filter: "Benchmark1", + } + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Benchmark1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Benchmark1") + c.Check(helper.calls[6], Equals, "TearDownTest") + // ... and more. + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark1\t *100\t *[12][0-9]{5} ns/op\n" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestBenchmarkBytes(c *C) { + helper := FixtureHelper{sleep: 100000} + output := String{} + runConf := RunConf{ + Output: &output, + Benchmark: true, + BenchmarkTime: 10000000, + Filter: "Benchmark2", + } + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark2\t *100\t *[12][0-9]{5} ns/op\t *[4-9]\\.[0-9]{2} MB/s\n" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestBenchmarkMem(c *C) { + helper := FixtureHelper{sleep: 100000} + output := String{} + runConf := RunConf{ + Output: &output, + Benchmark: true, + BenchmarkMem: true, + BenchmarkTime: 10000000, + Filter: "Benchmark3", + } + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark3\t *100\t *[12][0-9]{5} ns/op\t *[0-9]+ B/op\t *[1-9] allocs/op\n" + c.Assert(output.value, Matches, expected) +} diff --git a/vendor/src/github.com/go-check/check/bootstrap_test.go b/vendor/src/github.com/go-check/check/bootstrap_test.go new file mode 100644 index 0000000000000..e55f327c7be03 --- /dev/null +++ b/vendor/src/github.com/go-check/check/bootstrap_test.go @@ -0,0 +1,82 @@ +// These initial tests are for bootstrapping. They verify that we can +// basically use the testing infrastructure itself to check if the test +// system is working. +// +// These tests use will break down the test runner badly in case of +// errors because if they simply fail, we can't be sure the developer +// will ever see anything (because failing means the failing system +// somehow isn't working! :-) +// +// Do not assume *any* internal functionality works as expected besides +// what's actually tested here. + +package check_test + +import ( + "fmt" + "gopkg.in/check.v1" + "strings" +) + +type BootstrapS struct{} + +var boostrapS = check.Suite(&BootstrapS{}) + +func (s *BootstrapS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +func (s *BootstrapS) TestFailedAndFail(c *check.C) { + if c.Failed() { + critical("c.Failed() must be false first!") + } + c.Fail() + if !c.Failed() { + critical("c.Fail() didn't put the test in a failed state!") + } + c.Succeed() +} + +func (s *BootstrapS) TestFailedAndSucceed(c *check.C) { + c.Fail() + c.Succeed() + if c.Failed() { + critical("c.Succeed() didn't put the test back in a non-failed state") + } +} + +func (s *BootstrapS) TestLogAndGetTestLog(c *check.C) { + c.Log("Hello there!") + log := c.GetTestLog() + if log != "Hello there!\n" { + critical(fmt.Sprintf("Log() or GetTestLog() is not working! Got: %#v", log)) + } +} + +func (s *BootstrapS) TestLogfAndGetTestLog(c *check.C) { + c.Logf("Hello %v", "there!") + log := c.GetTestLog() + if log != "Hello there!\n" { + critical(fmt.Sprintf("Logf() or GetTestLog() is not working! Got: %#v", log)) + } +} + +func (s *BootstrapS) TestRunShowsErrors(c *check.C) { + output := String{} + check.Run(&FailHelper{}, &check.RunConf{Output: &output}) + if strings.Index(output.value, "Expected failure!") == -1 { + critical(fmt.Sprintf("RunWithWriter() output did not contain the "+ + "expected failure! Got: %#v", + output.value)) + } +} + +func (s *BootstrapS) TestRunDoesntShowSuccesses(c *check.C) { + output := String{} + check.Run(&SuccessHelper{}, &check.RunConf{Output: &output}) + if strings.Index(output.value, "Expected success!") != -1 { + critical(fmt.Sprintf("RunWithWriter() output contained a successful "+ + "test! Got: %#v", + output.value)) + } +} diff --git a/vendor/src/github.com/go-check/check/check.go b/vendor/src/github.com/go-check/check/check.go new file mode 100644 index 0000000000000..ca8c0f92deb46 --- /dev/null +++ b/vendor/src/github.com/go-check/check/check.go @@ -0,0 +1,945 @@ +// Package check is a rich testing extension for Go's testing package. +// +// For details about the project, see: +// +// http://labix.org/gocheck +// +package check + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/rand" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "runtime" + "strconv" + "strings" + "sync" + "time" +) + +// ----------------------------------------------------------------------- +// Internal type which deals with suite method calling. + +const ( + fixtureKd = iota + testKd +) + +type funcKind int + +const ( + succeededSt = iota + failedSt + skippedSt + panickedSt + fixturePanickedSt + missedSt +) + +type funcStatus int + +// A method value can't reach its own Method structure. +type methodType struct { + reflect.Value + Info reflect.Method +} + +func newMethod(receiver reflect.Value, i int) *methodType { + return &methodType{receiver.Method(i), receiver.Type().Method(i)} +} + +func (method *methodType) PC() uintptr { + return method.Info.Func.Pointer() +} + +func (method *methodType) suiteName() string { + t := method.Info.Type.In(0) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Name() +} + +func (method *methodType) String() string { + return method.suiteName() + "." + method.Info.Name +} + +func (method *methodType) matches(re *regexp.Regexp) bool { + return (re.MatchString(method.Info.Name) || + re.MatchString(method.suiteName()) || + re.MatchString(method.String())) +} + +type C struct { + method *methodType + kind funcKind + testName string + status funcStatus + logb *logger + logw io.Writer + done chan *C + reason string + mustFail bool + tempDir *tempDir + benchMem bool + startTime time.Time + timer +} + +func (c *C) stopNow() { + runtime.Goexit() +} + +// logger is a concurrency safe byte.Buffer +type logger struct { + sync.Mutex + writer bytes.Buffer +} + +func (l *logger) Write(buf []byte) (int, error) { + l.Lock() + defer l.Unlock() + return l.writer.Write(buf) +} + +func (l *logger) WriteTo(w io.Writer) (int64, error) { + l.Lock() + defer l.Unlock() + return l.writer.WriteTo(w) +} + +func (l *logger) String() string { + l.Lock() + defer l.Unlock() + return l.writer.String() +} + +// ----------------------------------------------------------------------- +// Handling of temporary files and directories. + +type tempDir struct { + sync.Mutex + path string + counter int +} + +func (td *tempDir) newPath() string { + td.Lock() + defer td.Unlock() + if td.path == "" { + var err error + for i := 0; i != 100; i++ { + path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int()) + if err = os.Mkdir(path, 0700); err == nil { + td.path = path + break + } + } + if td.path == "" { + panic("Couldn't create temporary directory: " + err.Error()) + } + } + result := filepath.Join(td.path, strconv.Itoa(td.counter)) + td.counter += 1 + return result +} + +func (td *tempDir) removeAll() { + td.Lock() + defer td.Unlock() + if td.path != "" { + err := os.RemoveAll(td.path) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error()) + } + } +} + +// Create a new temporary directory which is automatically removed after +// the suite finishes running. +func (c *C) MkDir() string { + path := c.tempDir.newPath() + if err := os.Mkdir(path, 0700); err != nil { + panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error())) + } + return path +} + +// ----------------------------------------------------------------------- +// Low-level logging functions. + +func (c *C) log(args ...interface{}) { + c.writeLog([]byte(fmt.Sprint(args...) + "\n")) +} + +func (c *C) logf(format string, args ...interface{}) { + c.writeLog([]byte(fmt.Sprintf(format+"\n", args...))) +} + +func (c *C) logNewLine() { + c.writeLog([]byte{'\n'}) +} + +func (c *C) writeLog(buf []byte) { + c.logb.Write(buf) + if c.logw != nil { + c.logw.Write(buf) + } +} + +func hasStringOrError(x interface{}) (ok bool) { + _, ok = x.(fmt.Stringer) + if ok { + return + } + _, ok = x.(error) + return +} + +func (c *C) logValue(label string, value interface{}) { + if label == "" { + if hasStringOrError(value) { + c.logf("... %#v (%q)", value, value) + } else { + c.logf("... %#v", value) + } + } else if value == nil { + c.logf("... %s = nil", label) + } else { + if hasStringOrError(value) { + fv := fmt.Sprintf("%#v", value) + qv := fmt.Sprintf("%q", value) + if fv != qv { + c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv) + return + } + } + if s, ok := value.(string); ok && isMultiLine(s) { + c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value)) + c.logMultiLine(s) + } else { + c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value) + } + } +} + +func (c *C) logMultiLine(s string) { + b := make([]byte, 0, len(s)*2) + i := 0 + n := len(s) + for i < n { + j := i + 1 + for j < n && s[j-1] != '\n' { + j++ + } + b = append(b, "... "...) + b = strconv.AppendQuote(b, s[i:j]) + if j < n { + b = append(b, " +"...) + } + b = append(b, '\n') + i = j + } + c.writeLog(b) +} + +func isMultiLine(s string) bool { + for i := 0; i+1 < len(s); i++ { + if s[i] == '\n' { + return true + } + } + return false +} + +func (c *C) logString(issue string) { + c.log("... ", issue) +} + +func (c *C) logCaller(skip int) { + // This is a bit heavier than it ought to be. + skip += 1 // Our own frame. + pc, callerFile, callerLine, ok := runtime.Caller(skip) + if !ok { + return + } + var testFile string + var testLine int + testFunc := runtime.FuncForPC(c.method.PC()) + if runtime.FuncForPC(pc) != testFunc { + for { + skip += 1 + if pc, file, line, ok := runtime.Caller(skip); ok { + // Note that the test line may be different on + // distinct calls for the same test. Showing + // the "internal" line is helpful when debugging. + if runtime.FuncForPC(pc) == testFunc { + testFile, testLine = file, line + break + } + } else { + break + } + } + } + if testFile != "" && (testFile != callerFile || testLine != callerLine) { + c.logCode(testFile, testLine) + } + c.logCode(callerFile, callerLine) +} + +func (c *C) logCode(path string, line int) { + c.logf("%s:%d:", nicePath(path), line) + code, err := printLine(path, line) + if code == "" { + code = "..." // XXX Open the file and take the raw line. + if err != nil { + code += err.Error() + } + } + c.log(indent(code, " ")) +} + +var valueGo = filepath.Join("reflect", "value.go") +var asmGo = filepath.Join("runtime", "asm_") + +func (c *C) logPanic(skip int, value interface{}) { + skip++ // Our own frame. + initialSkip := skip + for ; ; skip++ { + if pc, file, line, ok := runtime.Caller(skip); ok { + if skip == initialSkip { + c.logf("... Panic: %s (PC=0x%X)\n", value, pc) + } + name := niceFuncName(pc) + path := nicePath(file) + if strings.Contains(path, "/gopkg.in/check.v") { + continue + } + if name == "Value.call" && strings.HasSuffix(path, valueGo) { + continue + } + if name == "call16" && strings.Contains(path, asmGo) { + continue + } + c.logf("%s:%d\n in %s", nicePath(file), line, name) + } else { + break + } + } +} + +func (c *C) logSoftPanic(issue string) { + c.log("... Panic: ", issue) +} + +func (c *C) logArgPanic(method *methodType, expectedType string) { + c.logf("... Panic: %s argument should be %s", + niceFuncName(method.PC()), expectedType) +} + +// ----------------------------------------------------------------------- +// Some simple formatting helpers. + +var initWD, initWDErr = os.Getwd() + +func init() { + if initWDErr == nil { + initWD = strings.Replace(initWD, "\\", "/", -1) + "/" + } +} + +func nicePath(path string) string { + if initWDErr == nil { + if strings.HasPrefix(path, initWD) { + return path[len(initWD):] + } + } + return path +} + +func niceFuncPath(pc uintptr) string { + function := runtime.FuncForPC(pc) + if function != nil { + filename, line := function.FileLine(pc) + return fmt.Sprintf("%s:%d", nicePath(filename), line) + } + return "" +} + +func niceFuncName(pc uintptr) string { + function := runtime.FuncForPC(pc) + if function != nil { + name := path.Base(function.Name()) + if i := strings.Index(name, "."); i > 0 { + name = name[i+1:] + } + if strings.HasPrefix(name, "(*") { + if i := strings.Index(name, ")"); i > 0 { + name = name[2:i] + name[i+1:] + } + } + if i := strings.LastIndex(name, ".*"); i != -1 { + name = name[:i] + "." + name[i+2:] + } + if i := strings.LastIndex(name, "·"); i != -1 { + name = name[:i] + "." + name[i+2:] + } + return name + } + return "" +} + +// ----------------------------------------------------------------------- +// Result tracker to aggregate call results. + +type Result struct { + Succeeded int + Failed int + Skipped int + Panicked int + FixturePanicked int + ExpectedFailures int + Missed int // Not even tried to run, related to a panic in the fixture. + RunError error // Houston, we've got a problem. + WorkDir string // If KeepWorkDir is true +} + +type resultTracker struct { + result Result + _lastWasProblem bool + _waiting int + _missed int + _expectChan chan *C + _doneChan chan *C + _stopChan chan bool +} + +func newResultTracker() *resultTracker { + return &resultTracker{_expectChan: make(chan *C), // Synchronous + _doneChan: make(chan *C, 32), // Asynchronous + _stopChan: make(chan bool)} // Synchronous +} + +func (tracker *resultTracker) start() { + go tracker._loopRoutine() +} + +func (tracker *resultTracker) waitAndStop() { + <-tracker._stopChan +} + +func (tracker *resultTracker) expectCall(c *C) { + tracker._expectChan <- c +} + +func (tracker *resultTracker) callDone(c *C) { + tracker._doneChan <- c +} + +func (tracker *resultTracker) _loopRoutine() { + for { + var c *C + if tracker._waiting > 0 { + // Calls still running. Can't stop. + select { + // XXX Reindent this (not now to make diff clear) + case c = <-tracker._expectChan: + tracker._waiting += 1 + case c = <-tracker._doneChan: + tracker._waiting -= 1 + switch c.status { + case succeededSt: + if c.kind == testKd { + if c.mustFail { + tracker.result.ExpectedFailures++ + } else { + tracker.result.Succeeded++ + } + } + case failedSt: + tracker.result.Failed++ + case panickedSt: + if c.kind == fixtureKd { + tracker.result.FixturePanicked++ + } else { + tracker.result.Panicked++ + } + case fixturePanickedSt: + // Track it as missed, since the panic + // was on the fixture, not on the test. + tracker.result.Missed++ + case missedSt: + tracker.result.Missed++ + case skippedSt: + if c.kind == testKd { + tracker.result.Skipped++ + } + } + } + } else { + // No calls. Can stop, but no done calls here. + select { + case tracker._stopChan <- true: + return + case c = <-tracker._expectChan: + tracker._waiting += 1 + case c = <-tracker._doneChan: + panic("Tracker got an unexpected done call.") + } + } + } +} + +// ----------------------------------------------------------------------- +// The underlying suite runner. + +type suiteRunner struct { + suite interface{} + setUpSuite, tearDownSuite *methodType + setUpTest, tearDownTest *methodType + tests []*methodType + tracker *resultTracker + tempDir *tempDir + keepDir bool + output *outputWriter + reportedProblemLast bool + benchTime time.Duration + benchMem bool +} + +type RunConf struct { + Output io.Writer + Stream bool + Verbose bool + Filter string + Benchmark bool + BenchmarkTime time.Duration // Defaults to 1 second + BenchmarkMem bool + KeepWorkDir bool +} + +// Create a new suiteRunner able to run all methods in the given suite. +func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner { + var conf RunConf + if runConf != nil { + conf = *runConf + } + if conf.Output == nil { + conf.Output = os.Stdout + } + if conf.Benchmark { + conf.Verbose = true + } + + suiteType := reflect.TypeOf(suite) + suiteNumMethods := suiteType.NumMethod() + suiteValue := reflect.ValueOf(suite) + + runner := &suiteRunner{ + suite: suite, + output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose), + tracker: newResultTracker(), + benchTime: conf.BenchmarkTime, + benchMem: conf.BenchmarkMem, + tempDir: &tempDir{}, + keepDir: conf.KeepWorkDir, + tests: make([]*methodType, 0, suiteNumMethods), + } + if runner.benchTime == 0 { + runner.benchTime = 1 * time.Second + } + + var filterRegexp *regexp.Regexp + if conf.Filter != "" { + if regexp, err := regexp.Compile(conf.Filter); err != nil { + msg := "Bad filter expression: " + err.Error() + runner.tracker.result.RunError = errors.New(msg) + return runner + } else { + filterRegexp = regexp + } + } + + for i := 0; i != suiteNumMethods; i++ { + method := newMethod(suiteValue, i) + switch method.Info.Name { + case "SetUpSuite": + runner.setUpSuite = method + case "TearDownSuite": + runner.tearDownSuite = method + case "SetUpTest": + runner.setUpTest = method + case "TearDownTest": + runner.tearDownTest = method + default: + prefix := "Test" + if conf.Benchmark { + prefix = "Benchmark" + } + if !strings.HasPrefix(method.Info.Name, prefix) { + continue + } + if filterRegexp == nil || method.matches(filterRegexp) { + runner.tests = append(runner.tests, method) + } + } + } + return runner +} + +// Run all methods in the given suite. +func (runner *suiteRunner) run() *Result { + if runner.tracker.result.RunError == nil && len(runner.tests) > 0 { + runner.tracker.start() + if runner.checkFixtureArgs() { + c := runner.runFixture(runner.setUpSuite, "", nil) + if c == nil || c.status == succeededSt { + for i := 0; i != len(runner.tests); i++ { + c := runner.runTest(runner.tests[i]) + if c.status == fixturePanickedSt { + runner.skipTests(missedSt, runner.tests[i+1:]) + break + } + } + } else if c != nil && c.status == skippedSt { + runner.skipTests(skippedSt, runner.tests) + } else { + runner.skipTests(missedSt, runner.tests) + } + runner.runFixture(runner.tearDownSuite, "", nil) + } else { + runner.skipTests(missedSt, runner.tests) + } + runner.tracker.waitAndStop() + if runner.keepDir { + runner.tracker.result.WorkDir = runner.tempDir.path + } else { + runner.tempDir.removeAll() + } + } + return &runner.tracker.result +} + +// Create a call object with the given suite method, and fork a +// goroutine with the provided dispatcher for running it. +func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C { + var logw io.Writer + if runner.output.Stream { + logw = runner.output + } + if logb == nil { + logb = new(logger) + } + c := &C{ + method: method, + kind: kind, + testName: testName, + logb: logb, + logw: logw, + tempDir: runner.tempDir, + done: make(chan *C, 1), + timer: timer{benchTime: runner.benchTime}, + startTime: time.Now(), + benchMem: runner.benchMem, + } + runner.tracker.expectCall(c) + go (func() { + runner.reportCallStarted(c) + defer runner.callDone(c) + dispatcher(c) + })() + return c +} + +// Same as forkCall(), but wait for call to finish before returning. +func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C { + c := runner.forkCall(method, kind, testName, logb, dispatcher) + <-c.done + return c +} + +// Handle a finished call. If there were any panics, update the call status +// accordingly. Then, mark the call as done and report to the tracker. +func (runner *suiteRunner) callDone(c *C) { + value := recover() + if value != nil { + switch v := value.(type) { + case *fixturePanic: + if v.status == skippedSt { + c.status = skippedSt + } else { + c.logSoftPanic("Fixture has panicked (see related PANIC)") + c.status = fixturePanickedSt + } + default: + c.logPanic(1, value) + c.status = panickedSt + } + } + if c.mustFail { + switch c.status { + case failedSt: + c.status = succeededSt + case succeededSt: + c.status = failedSt + c.logString("Error: Test succeeded, but was expected to fail") + c.logString("Reason: " + c.reason) + } + } + + runner.reportCallDone(c) + c.done <- c +} + +// Runs a fixture call synchronously. The fixture will still be run in a +// goroutine like all suite methods, but this method will not return +// while the fixture goroutine is not done, because the fixture must be +// run in a desired order. +func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C { + if method != nil { + c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) { + c.ResetTimer() + c.StartTimer() + defer c.StopTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + }) + return c + } + return nil +} + +// Run the fixture method with runFixture(), but panic with a fixturePanic{} +// in case the fixture method panics. This makes it easier to track the +// fixture panic together with other call panics within forkTest(). +func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C { + if skipped != nil && *skipped { + return nil + } + c := runner.runFixture(method, testName, logb) + if c != nil && c.status != succeededSt { + if skipped != nil { + *skipped = c.status == skippedSt + } + panic(&fixturePanic{c.status, method}) + } + return c +} + +type fixturePanic struct { + status funcStatus + method *methodType +} + +// Run the suite test method, together with the test-specific fixture, +// asynchronously. +func (runner *suiteRunner) forkTest(method *methodType) *C { + testName := method.String() + return runner.forkCall(method, testKd, testName, nil, func(c *C) { + var skipped bool + defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped) + defer c.StopTimer() + benchN := 1 + for { + runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped) + mt := c.method.Type() + if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) { + // Rather than a plain panic, provide a more helpful message when + // the argument type is incorrect. + c.status = panickedSt + c.logArgPanic(c.method, "*check.C") + return + } + if strings.HasPrefix(c.method.Info.Name, "Test") { + c.ResetTimer() + c.StartTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + return + } + if !strings.HasPrefix(c.method.Info.Name, "Benchmark") { + panic("unexpected method prefix: " + c.method.Info.Name) + } + + runtime.GC() + c.N = benchN + c.ResetTimer() + c.StartTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + c.StopTimer() + if c.status != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 { + return + } + perOpN := int(1e9) + if c.nsPerOp() != 0 { + perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp()) + } + + // Logic taken from the stock testing package: + // - Run more iterations than we think we'll need for a second (1.5x). + // - Don't grow too fast in case we had timing errors previously. + // - Be sure to run at least one more than last time. + benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1) + benchN = roundUp(benchN) + + skipped = true // Don't run the deferred one if this panics. + runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil) + skipped = false + } + }) +} + +// Same as forkTest(), but wait for the test to finish before returning. +func (runner *suiteRunner) runTest(method *methodType) *C { + c := runner.forkTest(method) + <-c.done + return c +} + +// Helper to mark tests as skipped or missed. A bit heavy for what +// it does, but it enables homogeneous handling of tracking, including +// nice verbose output. +func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) { + for _, method := range methods { + runner.runFunc(method, testKd, "", nil, func(c *C) { + c.status = status + }) + } +} + +// Verify if the fixture arguments are *check.C. In case of errors, +// log the error as a panic in the fixture method call, and return false. +func (runner *suiteRunner) checkFixtureArgs() bool { + succeeded := true + argType := reflect.TypeOf(&C{}) + for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest} { + if method != nil { + mt := method.Type() + if mt.NumIn() != 1 || mt.In(0) != argType { + succeeded = false + runner.runFunc(method, fixtureKd, "", nil, func(c *C) { + c.logArgPanic(method, "*check.C") + c.status = panickedSt + }) + } + } + } + return succeeded +} + +func (runner *suiteRunner) reportCallStarted(c *C) { + runner.output.WriteCallStarted("START", c) +} + +func (runner *suiteRunner) reportCallDone(c *C) { + runner.tracker.callDone(c) + switch c.status { + case succeededSt: + if c.mustFail { + runner.output.WriteCallSuccess("FAIL EXPECTED", c) + } else { + runner.output.WriteCallSuccess("PASS", c) + } + case skippedSt: + runner.output.WriteCallSuccess("SKIP", c) + case failedSt: + runner.output.WriteCallProblem("FAIL", c) + case panickedSt: + runner.output.WriteCallProblem("PANIC", c) + case fixturePanickedSt: + // That's a testKd call reporting that its fixture + // has panicked. The fixture call which caused the + // panic itself was tracked above. We'll report to + // aid debugging. + runner.output.WriteCallProblem("PANIC", c) + case missedSt: + runner.output.WriteCallSuccess("MISS", c) + } +} + +// ----------------------------------------------------------------------- +// Output writer manages atomic output writing according to settings. + +type outputWriter struct { + m sync.Mutex + writer io.Writer + wroteCallProblemLast bool + Stream bool + Verbose bool +} + +func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter { + return &outputWriter{writer: writer, Stream: stream, Verbose: verbose} +} + +func (ow *outputWriter) Write(content []byte) (n int, err error) { + ow.m.Lock() + n, err = ow.writer.Write(content) + ow.m.Unlock() + return +} + +func (ow *outputWriter) WriteCallStarted(label string, c *C) { + if ow.Stream { + header := renderCallHeader(label, c, "", "\n") + ow.m.Lock() + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func (ow *outputWriter) WriteCallProblem(label string, c *C) { + var prefix string + if !ow.Stream { + prefix = "\n-----------------------------------" + + "-----------------------------------\n" + } + header := renderCallHeader(label, c, prefix, "\n\n") + ow.m.Lock() + ow.wroteCallProblemLast = true + ow.writer.Write([]byte(header)) + if !ow.Stream { + c.logb.WriteTo(ow.writer) + } + ow.m.Unlock() +} + +func (ow *outputWriter) WriteCallSuccess(label string, c *C) { + if ow.Stream || (ow.Verbose && c.kind == testKd) { + // TODO Use a buffer here. + var suffix string + if c.reason != "" { + suffix = " (" + c.reason + ")" + } + if c.status == succeededSt { + suffix += "\t" + c.timerString() + } + suffix += "\n" + if ow.Stream { + suffix += "\n" + } + header := renderCallHeader(label, c, "", suffix) + ow.m.Lock() + // Resist temptation of using line as prefix above due to race. + if !ow.Stream && ow.wroteCallProblemLast { + header = "\n-----------------------------------" + + "-----------------------------------\n" + + header + } + ow.wroteCallProblemLast = false + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func renderCallHeader(label string, c *C, prefix, suffix string) string { + pc := c.method.PC() + return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc), + niceFuncName(pc), suffix) +} diff --git a/vendor/src/github.com/go-check/check/check_test.go b/vendor/src/github.com/go-check/check/check_test.go new file mode 100644 index 0000000000000..871b325276a21 --- /dev/null +++ b/vendor/src/github.com/go-check/check/check_test.go @@ -0,0 +1,207 @@ +// This file contains just a few generic helpers which are used by the +// other test files. + +package check_test + +import ( + "flag" + "fmt" + "os" + "regexp" + "runtime" + "testing" + "time" + + "gopkg.in/check.v1" +) + +// We count the number of suites run at least to get a vague hint that the +// test suite is behaving as it should. Otherwise a bug introduced at the +// very core of the system could go unperceived. +const suitesRunExpected = 8 + +var suitesRun int = 0 + +func Test(t *testing.T) { + check.TestingT(t) + if suitesRun != suitesRunExpected && flag.Lookup("check.f").Value.String() == "" { + critical(fmt.Sprintf("Expected %d suites to run rather than %d", + suitesRunExpected, suitesRun)) + } +} + +// ----------------------------------------------------------------------- +// Helper functions. + +// Break down badly. This is used in test cases which can't yet assume +// that the fundamental bits are working. +func critical(error string) { + fmt.Fprintln(os.Stderr, "CRITICAL: "+error) + os.Exit(1) +} + +// Return the file line where it's called. +func getMyLine() int { + if _, _, line, ok := runtime.Caller(1); ok { + return line + } + return -1 +} + +// ----------------------------------------------------------------------- +// Helper type implementing a basic io.Writer for testing output. + +// Type implementing the io.Writer interface for analyzing output. +type String struct { + value string +} + +// The only function required by the io.Writer interface. Will append +// written data to the String.value string. +func (s *String) Write(p []byte) (n int, err error) { + s.value += string(p) + return len(p), nil +} + +// Trivial wrapper to test errors happening on a different file +// than the test itself. +func checkEqualWrapper(c *check.C, obtained, expected interface{}) (result bool, line int) { + return c.Check(obtained, check.Equals, expected), getMyLine() +} + +// ----------------------------------------------------------------------- +// Helper suite for testing basic fail behavior. + +type FailHelper struct { + testLine int +} + +func (s *FailHelper) TestLogAndFail(c *check.C) { + s.testLine = getMyLine() - 1 + c.Log("Expected failure!") + c.Fail() +} + +// ----------------------------------------------------------------------- +// Helper suite for testing basic success behavior. + +type SuccessHelper struct{} + +func (s *SuccessHelper) TestLogAndSucceed(c *check.C) { + c.Log("Expected success!") +} + +// ----------------------------------------------------------------------- +// Helper suite for testing ordering and behavior of fixture. + +type FixtureHelper struct { + calls []string + panicOn string + skip bool + skipOnN int + sleepOn string + sleep time.Duration + bytes int64 +} + +func (s *FixtureHelper) trace(name string, c *check.C) { + s.calls = append(s.calls, name) + if name == s.panicOn { + panic(name) + } + if s.sleep > 0 && s.sleepOn == name { + time.Sleep(s.sleep) + } + if s.skip && s.skipOnN == len(s.calls)-1 { + c.Skip("skipOnN == n") + } +} + +func (s *FixtureHelper) SetUpSuite(c *check.C) { + s.trace("SetUpSuite", c) +} + +func (s *FixtureHelper) TearDownSuite(c *check.C) { + s.trace("TearDownSuite", c) +} + +func (s *FixtureHelper) SetUpTest(c *check.C) { + s.trace("SetUpTest", c) +} + +func (s *FixtureHelper) TearDownTest(c *check.C) { + s.trace("TearDownTest", c) +} + +func (s *FixtureHelper) Test1(c *check.C) { + s.trace("Test1", c) +} + +func (s *FixtureHelper) Test2(c *check.C) { + s.trace("Test2", c) +} + +func (s *FixtureHelper) Benchmark1(c *check.C) { + s.trace("Benchmark1", c) + for i := 0; i < c.N; i++ { + time.Sleep(s.sleep) + } +} + +func (s *FixtureHelper) Benchmark2(c *check.C) { + s.trace("Benchmark2", c) + c.SetBytes(1024) + for i := 0; i < c.N; i++ { + time.Sleep(s.sleep) + } +} + +func (s *FixtureHelper) Benchmark3(c *check.C) { + var x []int64 + s.trace("Benchmark3", c) + for i := 0; i < c.N; i++ { + time.Sleep(s.sleep) + x = make([]int64, 5) + _ = x + } +} + +// ----------------------------------------------------------------------- +// Helper which checks the state of the test and ensures that it matches +// the given expectations. Depends on c.Errorf() working, so shouldn't +// be used to test this one function. + +type expectedState struct { + name string + result interface{} + failed bool + log string +} + +// Verify the state of the test. Note that since this also verifies if +// the test is supposed to be in a failed state, no other checks should +// be done in addition to what is being tested. +func checkState(c *check.C, result interface{}, expected *expectedState) { + failed := c.Failed() + c.Succeed() + log := c.GetTestLog() + matched, matchError := regexp.MatchString("^"+expected.log+"$", log) + if matchError != nil { + c.Errorf("Error in matching expression used in testing %s", + expected.name) + } else if !matched { + c.Errorf("%s logged:\n----------\n%s----------\n\nExpected:\n----------\n%s\n----------", + expected.name, log, expected.log) + } + if result != expected.result { + c.Errorf("%s returned %#v rather than %#v", + expected.name, result, expected.result) + } + if failed != expected.failed { + if failed { + c.Errorf("%s has failed when it shouldn't", expected.name) + } else { + c.Errorf("%s has not failed when it should", expected.name) + } + } +} diff --git a/vendor/src/github.com/go-check/check/checkers.go b/vendor/src/github.com/go-check/check/checkers.go new file mode 100644 index 0000000000000..bac338729c887 --- /dev/null +++ b/vendor/src/github.com/go-check/check/checkers.go @@ -0,0 +1,458 @@ +package check + +import ( + "fmt" + "reflect" + "regexp" +) + +// ----------------------------------------------------------------------- +// CommentInterface and Commentf helper, to attach extra information to checks. + +type comment struct { + format string + args []interface{} +} + +// Commentf returns an infomational value to use with Assert or Check calls. +// If the checker test fails, the provided arguments will be passed to +// fmt.Sprintf, and will be presented next to the logged failure. +// +// For example: +// +// c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i)) +// +// Note that if the comment is constant, a better option is to +// simply use a normal comment right above or next to the line, as +// it will also get printed with any errors: +// +// c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123) +// +func Commentf(format string, args ...interface{}) CommentInterface { + return &comment{format, args} +} + +// CommentInterface must be implemented by types that attach extra +// information to failed checks. See the Commentf function for details. +type CommentInterface interface { + CheckCommentString() string +} + +func (c *comment) CheckCommentString() string { + return fmt.Sprintf(c.format, c.args...) +} + +// ----------------------------------------------------------------------- +// The Checker interface. + +// The Checker interface must be provided by checkers used with +// the Assert and Check verification methods. +type Checker interface { + Info() *CheckerInfo + Check(params []interface{}, names []string) (result bool, error string) +} + +// See the Checker interface. +type CheckerInfo struct { + Name string + Params []string +} + +func (info *CheckerInfo) Info() *CheckerInfo { + return info +} + +// ----------------------------------------------------------------------- +// Not checker logic inverter. + +// The Not checker inverts the logic of the provided checker. The +// resulting checker will succeed where the original one failed, and +// vice-versa. +// +// For example: +// +// c.Assert(a, Not(Equals), b) +// +func Not(checker Checker) Checker { + return ¬Checker{checker} +} + +type notChecker struct { + sub Checker +} + +func (checker *notChecker) Info() *CheckerInfo { + info := *checker.sub.Info() + info.Name = "Not(" + info.Name + ")" + return &info +} + +func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) { + result, error = checker.sub.Check(params, names) + result = !result + return +} + +// ----------------------------------------------------------------------- +// IsNil checker. + +type isNilChecker struct { + *CheckerInfo +} + +// The IsNil checker tests whether the obtained value is nil. +// +// For example: +// +// c.Assert(err, IsNil) +// +var IsNil Checker = &isNilChecker{ + &CheckerInfo{Name: "IsNil", Params: []string{"value"}}, +} + +func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) { + return isNil(params[0]), "" +} + +func isNil(obtained interface{}) (result bool) { + if obtained == nil { + result = true + } else { + switch v := reflect.ValueOf(obtained); v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + } + } + return +} + +// ----------------------------------------------------------------------- +// NotNil checker. Alias for Not(IsNil), since it's so common. + +type notNilChecker struct { + *CheckerInfo +} + +// The NotNil checker verifies that the obtained value is not nil. +// +// For example: +// +// c.Assert(iface, NotNil) +// +// This is an alias for Not(IsNil), made available since it's a +// fairly common check. +// +var NotNil Checker = ¬NilChecker{ + &CheckerInfo{Name: "NotNil", Params: []string{"value"}}, +} + +func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) { + return !isNil(params[0]), "" +} + +// ----------------------------------------------------------------------- +// Equals checker. + +type equalsChecker struct { + *CheckerInfo +} + +// The Equals checker verifies that the obtained value is equal to +// the expected value, according to usual Go semantics for ==. +// +// For example: +// +// c.Assert(value, Equals, 42) +// +var Equals Checker = &equalsChecker{ + &CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}}, +} + +func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) { + defer func() { + if v := recover(); v != nil { + result = false + error = fmt.Sprint(v) + } + }() + return params[0] == params[1], "" +} + +// ----------------------------------------------------------------------- +// DeepEquals checker. + +type deepEqualsChecker struct { + *CheckerInfo +} + +// The DeepEquals checker verifies that the obtained value is deep-equal to +// the expected value. The check will work correctly even when facing +// slices, interfaces, and values of different types (which always fail +// the test). +// +// For example: +// +// c.Assert(value, DeepEquals, 42) +// c.Assert(array, DeepEquals, []string{"hi", "there"}) +// +var DeepEquals Checker = &deepEqualsChecker{ + &CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}}, +} + +func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) { + return reflect.DeepEqual(params[0], params[1]), "" +} + +// ----------------------------------------------------------------------- +// HasLen checker. + +type hasLenChecker struct { + *CheckerInfo +} + +// The HasLen checker verifies that the obtained value has the +// provided length. In many cases this is superior to using Equals +// in conjuction with the len function because in case the check +// fails the value itself will be printed, instead of its length, +// providing more details for figuring the problem. +// +// For example: +// +// c.Assert(list, HasLen, 5) +// +var HasLen Checker = &hasLenChecker{ + &CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}}, +} + +func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) { + n, ok := params[1].(int) + if !ok { + return false, "n must be an int" + } + value := reflect.ValueOf(params[0]) + switch value.Kind() { + case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String: + default: + return false, "obtained value type has no length" + } + return value.Len() == n, "" +} + +// ----------------------------------------------------------------------- +// ErrorMatches checker. + +type errorMatchesChecker struct { + *CheckerInfo +} + +// The ErrorMatches checker verifies that the error value +// is non nil and matches the regular expression provided. +// +// For example: +// +// c.Assert(err, ErrorMatches, "perm.*denied") +// +var ErrorMatches Checker = errorMatchesChecker{ + &CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}}, +} + +func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) { + if params[0] == nil { + return false, "Error value is nil" + } + err, ok := params[0].(error) + if !ok { + return false, "Value is not an error" + } + params[0] = err.Error() + names[0] = "error" + return matches(params[0], params[1]) +} + +// ----------------------------------------------------------------------- +// Matches checker. + +type matchesChecker struct { + *CheckerInfo +} + +// The Matches checker verifies that the string provided as the obtained +// value (or the string resulting from obtained.String()) matches the +// regular expression provided. +// +// For example: +// +// c.Assert(err, Matches, "perm.*denied") +// +var Matches Checker = &matchesChecker{ + &CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}}, +} + +func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) { + return matches(params[0], params[1]) +} + +func matches(value, regex interface{}) (result bool, error string) { + reStr, ok := regex.(string) + if !ok { + return false, "Regex must be a string" + } + valueStr, valueIsStr := value.(string) + if !valueIsStr { + if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr { + valueStr, valueIsStr = valueWithStr.String(), true + } + } + if valueIsStr { + matches, err := regexp.MatchString("^"+reStr+"$", valueStr) + if err != nil { + return false, "Can't compile regex: " + err.Error() + } + return matches, "" + } + return false, "Obtained value is not a string and has no .String()" +} + +// ----------------------------------------------------------------------- +// Panics checker. + +type panicsChecker struct { + *CheckerInfo +} + +// The Panics checker verifies that calling the provided zero-argument +// function will cause a panic which is deep-equal to the provided value. +// +// For example: +// +// c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}). +// +// +var Panics Checker = &panicsChecker{ + &CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}}, +} + +func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) { + f := reflect.ValueOf(params[0]) + if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { + return false, "Function must take zero arguments" + } + defer func() { + // If the function has not panicked, then don't do the check. + if error != "" { + return + } + params[0] = recover() + names[0] = "panic" + result = reflect.DeepEqual(params[0], params[1]) + }() + f.Call(nil) + return false, "Function has not panicked" +} + +type panicMatchesChecker struct { + *CheckerInfo +} + +// The PanicMatches checker verifies that calling the provided zero-argument +// function will cause a panic with an error value matching +// the regular expression provided. +// +// For example: +// +// c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`). +// +// +var PanicMatches Checker = &panicMatchesChecker{ + &CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}}, +} + +func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) { + f := reflect.ValueOf(params[0]) + if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { + return false, "Function must take zero arguments" + } + defer func() { + // If the function has not panicked, then don't do the check. + if errmsg != "" { + return + } + obtained := recover() + names[0] = "panic" + if e, ok := obtained.(error); ok { + params[0] = e.Error() + } else if _, ok := obtained.(string); ok { + params[0] = obtained + } else { + errmsg = "Panic value is not a string or an error" + return + } + result, errmsg = matches(params[0], params[1]) + }() + f.Call(nil) + return false, "Function has not panicked" +} + +// ----------------------------------------------------------------------- +// FitsTypeOf checker. + +type fitsTypeChecker struct { + *CheckerInfo +} + +// The FitsTypeOf checker verifies that the obtained value is +// assignable to a variable with the same type as the provided +// sample value. +// +// For example: +// +// c.Assert(value, FitsTypeOf, int64(0)) +// c.Assert(value, FitsTypeOf, os.Error(nil)) +// +var FitsTypeOf Checker = &fitsTypeChecker{ + &CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}}, +} + +func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained := reflect.ValueOf(params[0]) + sample := reflect.ValueOf(params[1]) + if !obtained.IsValid() { + return false, "" + } + if !sample.IsValid() { + return false, "Invalid sample value" + } + return obtained.Type().AssignableTo(sample.Type()), "" +} + +// ----------------------------------------------------------------------- +// Implements checker. + +type implementsChecker struct { + *CheckerInfo +} + +// The Implements checker verifies that the obtained value +// implements the interface specified via a pointer to an interface +// variable. +// +// For example: +// +// var e os.Error +// c.Assert(err, Implements, &e) +// +var Implements Checker = &implementsChecker{ + &CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}}, +} + +func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained := reflect.ValueOf(params[0]) + ifaceptr := reflect.ValueOf(params[1]) + if !obtained.IsValid() { + return false, "" + } + if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface { + return false, "ifaceptr should be a pointer to an interface variable" + } + return obtained.Type().Implements(ifaceptr.Elem().Type()), "" +} diff --git a/vendor/src/github.com/go-check/check/checkers_test.go b/vendor/src/github.com/go-check/check/checkers_test.go new file mode 100644 index 0000000000000..5c697474696b3 --- /dev/null +++ b/vendor/src/github.com/go-check/check/checkers_test.go @@ -0,0 +1,272 @@ +package check_test + +import ( + "errors" + "gopkg.in/check.v1" + "reflect" + "runtime" +) + +type CheckersS struct{} + +var _ = check.Suite(&CheckersS{}) + +func testInfo(c *check.C, checker check.Checker, name string, paramNames []string) { + info := checker.Info() + if info.Name != name { + c.Fatalf("Got name %s, expected %s", info.Name, name) + } + if !reflect.DeepEqual(info.Params, paramNames) { + c.Fatalf("Got param names %#v, expected %#v", info.Params, paramNames) + } +} + +func testCheck(c *check.C, checker check.Checker, result bool, error string, params ...interface{}) ([]interface{}, []string) { + info := checker.Info() + if len(params) != len(info.Params) { + c.Fatalf("unexpected param count in test; expected %d got %d", len(info.Params), len(params)) + } + names := append([]string{}, info.Params...) + result_, error_ := checker.Check(params, names) + if result_ != result || error_ != error { + c.Fatalf("%s.Check(%#v) returned (%#v, %#v) rather than (%#v, %#v)", + info.Name, params, result_, error_, result, error) + } + return params, names +} + +func (s *CheckersS) TestComment(c *check.C) { + bug := check.Commentf("a %d bc", 42) + comment := bug.CheckCommentString() + if comment != "a 42 bc" { + c.Fatalf("Commentf returned %#v", comment) + } +} + +func (s *CheckersS) TestIsNil(c *check.C) { + testInfo(c, check.IsNil, "IsNil", []string{"value"}) + + testCheck(c, check.IsNil, true, "", nil) + testCheck(c, check.IsNil, false, "", "a") + + testCheck(c, check.IsNil, true, "", (chan int)(nil)) + testCheck(c, check.IsNil, false, "", make(chan int)) + testCheck(c, check.IsNil, true, "", (error)(nil)) + testCheck(c, check.IsNil, false, "", errors.New("")) + testCheck(c, check.IsNil, true, "", ([]int)(nil)) + testCheck(c, check.IsNil, false, "", make([]int, 1)) + testCheck(c, check.IsNil, false, "", int(0)) +} + +func (s *CheckersS) TestNotNil(c *check.C) { + testInfo(c, check.NotNil, "NotNil", []string{"value"}) + + testCheck(c, check.NotNil, false, "", nil) + testCheck(c, check.NotNil, true, "", "a") + + testCheck(c, check.NotNil, false, "", (chan int)(nil)) + testCheck(c, check.NotNil, true, "", make(chan int)) + testCheck(c, check.NotNil, false, "", (error)(nil)) + testCheck(c, check.NotNil, true, "", errors.New("")) + testCheck(c, check.NotNil, false, "", ([]int)(nil)) + testCheck(c, check.NotNil, true, "", make([]int, 1)) +} + +func (s *CheckersS) TestNot(c *check.C) { + testInfo(c, check.Not(check.IsNil), "Not(IsNil)", []string{"value"}) + + testCheck(c, check.Not(check.IsNil), false, "", nil) + testCheck(c, check.Not(check.IsNil), true, "", "a") +} + +type simpleStruct struct { + i int +} + +func (s *CheckersS) TestEquals(c *check.C) { + testInfo(c, check.Equals, "Equals", []string{"obtained", "expected"}) + + // The simplest. + testCheck(c, check.Equals, true, "", 42, 42) + testCheck(c, check.Equals, false, "", 42, 43) + + // Different native types. + testCheck(c, check.Equals, false, "", int32(42), int64(42)) + + // With nil. + testCheck(c, check.Equals, false, "", 42, nil) + + // Slices + testCheck(c, check.Equals, false, "runtime error: comparing uncomparable type []uint8", []byte{1, 2}, []byte{1, 2}) + + // Struct values + testCheck(c, check.Equals, true, "", simpleStruct{1}, simpleStruct{1}) + testCheck(c, check.Equals, false, "", simpleStruct{1}, simpleStruct{2}) + + // Struct pointers + testCheck(c, check.Equals, false, "", &simpleStruct{1}, &simpleStruct{1}) + testCheck(c, check.Equals, false, "", &simpleStruct{1}, &simpleStruct{2}) +} + +func (s *CheckersS) TestDeepEquals(c *check.C) { + testInfo(c, check.DeepEquals, "DeepEquals", []string{"obtained", "expected"}) + + // The simplest. + testCheck(c, check.DeepEquals, true, "", 42, 42) + testCheck(c, check.DeepEquals, false, "", 42, 43) + + // Different native types. + testCheck(c, check.DeepEquals, false, "", int32(42), int64(42)) + + // With nil. + testCheck(c, check.DeepEquals, false, "", 42, nil) + + // Slices + testCheck(c, check.DeepEquals, true, "", []byte{1, 2}, []byte{1, 2}) + testCheck(c, check.DeepEquals, false, "", []byte{1, 2}, []byte{1, 3}) + + // Struct values + testCheck(c, check.DeepEquals, true, "", simpleStruct{1}, simpleStruct{1}) + testCheck(c, check.DeepEquals, false, "", simpleStruct{1}, simpleStruct{2}) + + // Struct pointers + testCheck(c, check.DeepEquals, true, "", &simpleStruct{1}, &simpleStruct{1}) + testCheck(c, check.DeepEquals, false, "", &simpleStruct{1}, &simpleStruct{2}) +} + +func (s *CheckersS) TestHasLen(c *check.C) { + testInfo(c, check.HasLen, "HasLen", []string{"obtained", "n"}) + + testCheck(c, check.HasLen, true, "", "abcd", 4) + testCheck(c, check.HasLen, true, "", []int{1, 2}, 2) + testCheck(c, check.HasLen, false, "", []int{1, 2}, 3) + + testCheck(c, check.HasLen, false, "n must be an int", []int{1, 2}, "2") + testCheck(c, check.HasLen, false, "obtained value type has no length", nil, 2) +} + +func (s *CheckersS) TestErrorMatches(c *check.C) { + testInfo(c, check.ErrorMatches, "ErrorMatches", []string{"value", "regex"}) + + testCheck(c, check.ErrorMatches, false, "Error value is nil", nil, "some error") + testCheck(c, check.ErrorMatches, false, "Value is not an error", 1, "some error") + testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "some error") + testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "so.*or") + + // Verify params mutation + params, names := testCheck(c, check.ErrorMatches, false, "", errors.New("some error"), "other error") + c.Assert(params[0], check.Equals, "some error") + c.Assert(names[0], check.Equals, "error") +} + +func (s *CheckersS) TestMatches(c *check.C) { + testInfo(c, check.Matches, "Matches", []string{"value", "regex"}) + + // Simple matching + testCheck(c, check.Matches, true, "", "abc", "abc") + testCheck(c, check.Matches, true, "", "abc", "a.c") + + // Must match fully + testCheck(c, check.Matches, false, "", "abc", "ab") + testCheck(c, check.Matches, false, "", "abc", "bc") + + // String()-enabled values accepted + testCheck(c, check.Matches, true, "", reflect.ValueOf("abc"), "a.c") + testCheck(c, check.Matches, false, "", reflect.ValueOf("abc"), "a.d") + + // Some error conditions. + testCheck(c, check.Matches, false, "Obtained value is not a string and has no .String()", 1, "a.c") + testCheck(c, check.Matches, false, "Can't compile regex: error parsing regexp: missing closing ]: `[c$`", "abc", "a[c") +} + +func (s *CheckersS) TestPanics(c *check.C) { + testInfo(c, check.Panics, "Panics", []string{"function", "expected"}) + + // Some errors. + testCheck(c, check.Panics, false, "Function has not panicked", func() bool { return false }, "BOOM") + testCheck(c, check.Panics, false, "Function must take zero arguments", 1, "BOOM") + + // Plain strings. + testCheck(c, check.Panics, true, "", func() { panic("BOOM") }, "BOOM") + testCheck(c, check.Panics, false, "", func() { panic("KABOOM") }, "BOOM") + testCheck(c, check.Panics, true, "", func() bool { panic("BOOM") }, "BOOM") + + // Error values. + testCheck(c, check.Panics, true, "", func() { panic(errors.New("BOOM")) }, errors.New("BOOM")) + testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM")) + + type deep struct{ i int } + // Deep value + testCheck(c, check.Panics, true, "", func() { panic(&deep{99}) }, &deep{99}) + + // Verify params/names mutation + params, names := testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM")) + c.Assert(params[0], check.ErrorMatches, "KABOOM") + c.Assert(names[0], check.Equals, "panic") + + // Verify a nil panic + testCheck(c, check.Panics, true, "", func() { panic(nil) }, nil) + testCheck(c, check.Panics, false, "", func() { panic(nil) }, "NOPE") +} + +func (s *CheckersS) TestPanicMatches(c *check.C) { + testInfo(c, check.PanicMatches, "PanicMatches", []string{"function", "expected"}) + + // Error matching. + testCheck(c, check.PanicMatches, true, "", func() { panic(errors.New("BOOM")) }, "BO.M") + testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BO.M") + + // Some errors. + testCheck(c, check.PanicMatches, false, "Function has not panicked", func() bool { return false }, "BOOM") + testCheck(c, check.PanicMatches, false, "Function must take zero arguments", 1, "BOOM") + + // Plain strings. + testCheck(c, check.PanicMatches, true, "", func() { panic("BOOM") }, "BO.M") + testCheck(c, check.PanicMatches, false, "", func() { panic("KABOOM") }, "BOOM") + testCheck(c, check.PanicMatches, true, "", func() bool { panic("BOOM") }, "BO.M") + + // Verify params/names mutation + params, names := testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BOOM") + c.Assert(params[0], check.Equals, "KABOOM") + c.Assert(names[0], check.Equals, "panic") + + // Verify a nil panic + testCheck(c, check.PanicMatches, false, "Panic value is not a string or an error", func() { panic(nil) }, "") +} + +func (s *CheckersS) TestFitsTypeOf(c *check.C) { + testInfo(c, check.FitsTypeOf, "FitsTypeOf", []string{"obtained", "sample"}) + + // Basic types + testCheck(c, check.FitsTypeOf, true, "", 1, 0) + testCheck(c, check.FitsTypeOf, false, "", 1, int64(0)) + + // Aliases + testCheck(c, check.FitsTypeOf, false, "", 1, errors.New("")) + testCheck(c, check.FitsTypeOf, false, "", "error", errors.New("")) + testCheck(c, check.FitsTypeOf, true, "", errors.New("error"), errors.New("")) + + // Structures + testCheck(c, check.FitsTypeOf, false, "", 1, simpleStruct{}) + testCheck(c, check.FitsTypeOf, false, "", simpleStruct{42}, &simpleStruct{}) + testCheck(c, check.FitsTypeOf, true, "", simpleStruct{42}, simpleStruct{}) + testCheck(c, check.FitsTypeOf, true, "", &simpleStruct{42}, &simpleStruct{}) + + // Some bad values + testCheck(c, check.FitsTypeOf, false, "Invalid sample value", 1, interface{}(nil)) + testCheck(c, check.FitsTypeOf, false, "", interface{}(nil), 0) +} + +func (s *CheckersS) TestImplements(c *check.C) { + testInfo(c, check.Implements, "Implements", []string{"obtained", "ifaceptr"}) + + var e error + var re runtime.Error + testCheck(c, check.Implements, true, "", errors.New(""), &e) + testCheck(c, check.Implements, false, "", errors.New(""), &re) + + // Some bad values + testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, errors.New("")) + testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, interface{}(nil)) + testCheck(c, check.Implements, false, "", interface{}(nil), &e) +} diff --git a/vendor/src/github.com/go-check/check/export_test.go b/vendor/src/github.com/go-check/check/export_test.go new file mode 100644 index 0000000000000..0e6cfe0f22d80 --- /dev/null +++ b/vendor/src/github.com/go-check/check/export_test.go @@ -0,0 +1,9 @@ +package check + +func PrintLine(filename string, line int) (string, error) { + return printLine(filename, line) +} + +func Indent(s, with string) string { + return indent(s, with) +} diff --git a/vendor/src/github.com/go-check/check/fixture_test.go b/vendor/src/github.com/go-check/check/fixture_test.go new file mode 100644 index 0000000000000..2bff9e1633173 --- /dev/null +++ b/vendor/src/github.com/go-check/check/fixture_test.go @@ -0,0 +1,484 @@ +// Tests for the behavior of the test fixture system. + +package check_test + +import ( + . "gopkg.in/check.v1" +) + +// ----------------------------------------------------------------------- +// Fixture test suite. + +type FixtureS struct{} + +var fixtureS = Suite(&FixtureS{}) + +func (s *FixtureS) TestCountSuite(c *C) { + suitesRun += 1 +} + +// ----------------------------------------------------------------------- +// Basic fixture ordering verification. + +func (s *FixtureS) TestOrder(c *C) { + helper := FixtureHelper{} + Run(&helper, nil) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) +} + +// ----------------------------------------------------------------------- +// Check the behavior when panics occur within tests and fixtures. + +func (s *FixtureS) TestPanicOnTest(c *C) { + helper := FixtureHelper{panicOn: "Test1"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: FixtureHelper.Test1\n\n" + + "\\.\\.\\. Panic: Test1 \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.Test1\n" + + "(.|\n)*$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnSetUpTest(c *C) { + helper := FixtureHelper{panicOn: "SetUpTest"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "TearDownTest") + c.Check(helper.calls[3], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 4) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper\\.SetUpTest\n\n" + + "\\.\\.\\. Panic: SetUpTest \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.SetUpTest\n" + + "(.|\n)*" + + "\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: Fixture has panicked " + + "\\(see related PANIC\\)\n$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnTearDownTest(c *C) { + helper := FixtureHelper{panicOn: "TearDownTest"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 5) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper.TearDownTest\n\n" + + "\\.\\.\\. Panic: TearDownTest \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.TearDownTest\n" + + "(.|\n)*" + + "\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: Fixture has panicked " + + "\\(see related PANIC\\)\n$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnSetUpSuite(c *C) { + helper := FixtureHelper{panicOn: "SetUpSuite"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 2) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper.SetUpSuite\n\n" + + "\\.\\.\\. Panic: SetUpSuite \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.SetUpSuite\n" + + "(.|\n)*$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnTearDownSuite(c *C) { + helper := FixtureHelper{panicOn: "TearDownSuite"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper.TearDownSuite\n\n" + + "\\.\\.\\. Panic: TearDownSuite \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.TearDownSuite\n" + + "(.|\n)*$" + + c.Check(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// A wrong argument on a test or fixture will produce a nice error. + +func (s *FixtureS) TestPanicOnWrongTestArg(c *C) { + helper := WrongTestArgHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "TearDownTest") + c.Check(helper.calls[3], Equals, "SetUpTest") + c.Check(helper.calls[4], Equals, "Test2") + c.Check(helper.calls[5], Equals, "TearDownTest") + c.Check(helper.calls[6], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 7) + + expected := "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongTestArgHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: WrongTestArgHelper\\.Test1 argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpTestArg(c *C) { + helper := WrongSetUpTestArgHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpTestArgHelper\\.SetUpTest\n\n" + + "\\.\\.\\. Panic: WrongSetUpTestArgHelper\\.SetUpTest argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpSuiteArg(c *C) { + helper := WrongSetUpSuiteArgHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpSuiteArgHelper\\.SetUpSuite\n\n" + + "\\.\\.\\. Panic: WrongSetUpSuiteArgHelper\\.SetUpSuite argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Nice errors also when tests or fixture have wrong arg count. + +func (s *FixtureS) TestPanicOnWrongTestArgCount(c *C) { + helper := WrongTestArgCountHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "TearDownTest") + c.Check(helper.calls[3], Equals, "SetUpTest") + c.Check(helper.calls[4], Equals, "Test2") + c.Check(helper.calls[5], Equals, "TearDownTest") + c.Check(helper.calls[6], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 7) + + expected := "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongTestArgCountHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: WrongTestArgCountHelper\\.Test1 argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpTestArgCount(c *C) { + helper := WrongSetUpTestArgCountHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpTestArgCountHelper\\.SetUpTest\n\n" + + "\\.\\.\\. Panic: WrongSetUpTestArgCountHelper\\.SetUpTest argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpSuiteArgCount(c *C) { + helper := WrongSetUpSuiteArgCountHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpSuiteArgCountHelper\\.SetUpSuite\n\n" + + "\\.\\.\\. Panic: WrongSetUpSuiteArgCountHelper" + + "\\.SetUpSuite argument should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Helper test suites with wrong function arguments. + +type WrongTestArgHelper struct { + FixtureHelper +} + +func (s *WrongTestArgHelper) Test1(t int) { +} + +type WrongSetUpTestArgHelper struct { + FixtureHelper +} + +func (s *WrongSetUpTestArgHelper) SetUpTest(t int) { +} + +type WrongSetUpSuiteArgHelper struct { + FixtureHelper +} + +func (s *WrongSetUpSuiteArgHelper) SetUpSuite(t int) { +} + +type WrongTestArgCountHelper struct { + FixtureHelper +} + +func (s *WrongTestArgCountHelper) Test1(c *C, i int) { +} + +type WrongSetUpTestArgCountHelper struct { + FixtureHelper +} + +func (s *WrongSetUpTestArgCountHelper) SetUpTest(c *C, i int) { +} + +type WrongSetUpSuiteArgCountHelper struct { + FixtureHelper +} + +func (s *WrongSetUpSuiteArgCountHelper) SetUpSuite(c *C, i int) { +} + +// ----------------------------------------------------------------------- +// Ensure fixture doesn't run without tests. + +type NoTestsHelper struct { + hasRun bool +} + +func (s *NoTestsHelper) SetUpSuite(c *C) { + s.hasRun = true +} + +func (s *NoTestsHelper) TearDownSuite(c *C) { + s.hasRun = true +} + +func (s *FixtureS) TestFixtureDoesntRunWithoutTests(c *C) { + helper := NoTestsHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.hasRun, Equals, false) +} + +// ----------------------------------------------------------------------- +// Verify that checks and assertions work correctly inside the fixture. + +type FixtureCheckHelper struct { + fail string + completed bool +} + +func (s *FixtureCheckHelper) SetUpSuite(c *C) { + switch s.fail { + case "SetUpSuiteAssert": + c.Assert(false, Equals, true) + case "SetUpSuiteCheck": + c.Check(false, Equals, true) + } + s.completed = true +} + +func (s *FixtureCheckHelper) SetUpTest(c *C) { + switch s.fail { + case "SetUpTestAssert": + c.Assert(false, Equals, true) + case "SetUpTestCheck": + c.Check(false, Equals, true) + } + s.completed = true +} + +func (s *FixtureCheckHelper) Test(c *C) { + // Do nothing. +} + +func (s *FixtureS) TestSetUpSuiteCheck(c *C) { + helper := FixtureCheckHelper{fail: "SetUpSuiteCheck"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Matches, + "\n---+\n"+ + "FAIL: fixture_test\\.go:[0-9]+: "+ + "FixtureCheckHelper\\.SetUpSuite\n\n"+ + "fixture_test\\.go:[0-9]+:\n"+ + " c\\.Check\\(false, Equals, true\\)\n"+ + "\\.+ obtained bool = false\n"+ + "\\.+ expected bool = true\n\n") + c.Assert(helper.completed, Equals, true) +} + +func (s *FixtureS) TestSetUpSuiteAssert(c *C) { + helper := FixtureCheckHelper{fail: "SetUpSuiteAssert"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Matches, + "\n---+\n"+ + "FAIL: fixture_test\\.go:[0-9]+: "+ + "FixtureCheckHelper\\.SetUpSuite\n\n"+ + "fixture_test\\.go:[0-9]+:\n"+ + " c\\.Assert\\(false, Equals, true\\)\n"+ + "\\.+ obtained bool = false\n"+ + "\\.+ expected bool = true\n\n") + c.Assert(helper.completed, Equals, false) +} + +// ----------------------------------------------------------------------- +// Verify that logging within SetUpTest() persists within the test log itself. + +type FixtureLogHelper struct { + c *C +} + +func (s *FixtureLogHelper) SetUpTest(c *C) { + s.c = c + c.Log("1") +} + +func (s *FixtureLogHelper) Test(c *C) { + c.Log("2") + s.c.Log("3") + c.Log("4") + c.Fail() +} + +func (s *FixtureLogHelper) TearDownTest(c *C) { + s.c.Log("5") +} + +func (s *FixtureS) TestFixtureLogging(c *C) { + helper := FixtureLogHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Matches, + "\n---+\n"+ + "FAIL: fixture_test\\.go:[0-9]+: "+ + "FixtureLogHelper\\.Test\n\n"+ + "1\n2\n3\n4\n5\n") +} + +// ----------------------------------------------------------------------- +// Skip() within fixture methods. + +func (s *FixtureS) TestSkipSuite(c *C) { + helper := FixtureHelper{skip: true, skipOnN: 0} + output := String{} + result := Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Equals, "") + c.Assert(helper.calls[0], Equals, "SetUpSuite") + c.Assert(helper.calls[1], Equals, "TearDownSuite") + c.Assert(len(helper.calls), Equals, 2) + c.Assert(result.Skipped, Equals, 2) +} + +func (s *FixtureS) TestSkipTest(c *C) { + helper := FixtureHelper{skip: true, skipOnN: 1} + output := String{} + result := Run(&helper, &RunConf{Output: &output}) + c.Assert(helper.calls[0], Equals, "SetUpSuite") + c.Assert(helper.calls[1], Equals, "SetUpTest") + c.Assert(helper.calls[2], Equals, "SetUpTest") + c.Assert(helper.calls[3], Equals, "Test2") + c.Assert(helper.calls[4], Equals, "TearDownTest") + c.Assert(helper.calls[5], Equals, "TearDownSuite") + c.Assert(len(helper.calls), Equals, 6) + c.Assert(result.Skipped, Equals, 1) +} diff --git a/vendor/src/github.com/go-check/check/foundation_test.go b/vendor/src/github.com/go-check/check/foundation_test.go new file mode 100644 index 0000000000000..8ecf7915f233a --- /dev/null +++ b/vendor/src/github.com/go-check/check/foundation_test.go @@ -0,0 +1,335 @@ +// These tests check that the foundations of gocheck are working properly. +// They already assume that fundamental failing is working already, though, +// since this was tested in bootstrap_test.go. Even then, some care may +// still have to be taken when using external functions, since they should +// of course not rely on functionality tested here. + +package check_test + +import ( + "fmt" + "gopkg.in/check.v1" + "log" + "os" + "regexp" + "strings" +) + +// ----------------------------------------------------------------------- +// Foundation test suite. + +type FoundationS struct{} + +var foundationS = check.Suite(&FoundationS{}) + +func (s *FoundationS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +func (s *FoundationS) TestErrorf(c *check.C) { + // Do not use checkState() here. It depends on Errorf() working. + expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c.Errorf(\"Error %%v!\", \"message\")\n"+ + "... Error: Error message!\n\n", + getMyLine()+1) + c.Errorf("Error %v!", "message") + failed := c.Failed() + c.Succeed() + if log := c.GetTestLog(); log != expectedLog { + c.Logf("Errorf() logged %#v rather than %#v", log, expectedLog) + c.Fail() + } + if !failed { + c.Logf("Errorf() didn't put the test in a failed state") + c.Fail() + } +} + +func (s *FoundationS) TestError(c *check.C) { + expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c\\.Error\\(\"Error \", \"message!\"\\)\n"+ + "\\.\\.\\. Error: Error message!\n\n", + getMyLine()+1) + c.Error("Error ", "message!") + checkState(c, nil, + &expectedState{ + name: "Error(`Error `, `message!`)", + failed: true, + log: expectedLog, + }) +} + +func (s *FoundationS) TestFailNow(c *check.C) { + defer (func() { + if !c.Failed() { + c.Error("FailNow() didn't fail the test") + } else { + c.Succeed() + if c.GetTestLog() != "" { + c.Error("Something got logged:\n" + c.GetTestLog()) + } + } + })() + + c.FailNow() + c.Log("FailNow() didn't stop the test") +} + +func (s *FoundationS) TestSucceedNow(c *check.C) { + defer (func() { + if c.Failed() { + c.Error("SucceedNow() didn't succeed the test") + } + if c.GetTestLog() != "" { + c.Error("Something got logged:\n" + c.GetTestLog()) + } + })() + + c.Fail() + c.SucceedNow() + c.Log("SucceedNow() didn't stop the test") +} + +func (s *FoundationS) TestFailureHeader(c *check.C) { + output := String{} + failHelper := FailHelper{} + check.Run(&failHelper, &check.RunConf{Output: &output}) + header := fmt.Sprintf(""+ + "\n-----------------------------------"+ + "-----------------------------------\n"+ + "FAIL: check_test.go:%d: FailHelper.TestLogAndFail\n", + failHelper.testLine) + if strings.Index(output.value, header) == -1 { + c.Errorf(""+ + "Failure didn't print a proper header.\n"+ + "... Got:\n%s... Expected something with:\n%s", + output.value, header) + } +} + +func (s *FoundationS) TestFatal(c *check.C) { + var line int + defer (func() { + if !c.Failed() { + c.Error("Fatal() didn't fail the test") + } else { + c.Succeed() + expected := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c.Fatal(\"Die \", \"now!\")\n"+ + "... Error: Die now!\n\n", + line) + if c.GetTestLog() != expected { + c.Error("Incorrect log:", c.GetTestLog()) + } + } + })() + + line = getMyLine() + 1 + c.Fatal("Die ", "now!") + c.Log("Fatal() didn't stop the test") +} + +func (s *FoundationS) TestFatalf(c *check.C) { + var line int + defer (func() { + if !c.Failed() { + c.Error("Fatalf() didn't fail the test") + } else { + c.Succeed() + expected := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c.Fatalf(\"Die %%s!\", \"now\")\n"+ + "... Error: Die now!\n\n", + line) + if c.GetTestLog() != expected { + c.Error("Incorrect log:", c.GetTestLog()) + } + } + })() + + line = getMyLine() + 1 + c.Fatalf("Die %s!", "now") + c.Log("Fatalf() didn't stop the test") +} + +func (s *FoundationS) TestCallerLoggingInsideTest(c *check.C) { + log := fmt.Sprintf(""+ + "foundation_test.go:%d:\n"+ + " result := c.Check\\(10, check.Equals, 20\\)\n"+ + "\\.\\.\\. obtained int = 10\n"+ + "\\.\\.\\. expected int = 20\n\n", + getMyLine()+1) + result := c.Check(10, check.Equals, 20) + checkState(c, result, + &expectedState{ + name: "Check(10, Equals, 20)", + result: false, + failed: true, + log: log, + }) +} + +func (s *FoundationS) TestCallerLoggingInDifferentFile(c *check.C) { + result, line := checkEqualWrapper(c, 10, 20) + testLine := getMyLine() - 1 + log := fmt.Sprintf(""+ + "foundation_test.go:%d:\n"+ + " result, line := checkEqualWrapper\\(c, 10, 20\\)\n"+ + "check_test.go:%d:\n"+ + " return c.Check\\(obtained, check.Equals, expected\\), getMyLine\\(\\)\n"+ + "\\.\\.\\. obtained int = 10\n"+ + "\\.\\.\\. expected int = 20\n\n", + testLine, line) + checkState(c, result, + &expectedState{ + name: "Check(10, Equals, 20)", + result: false, + failed: true, + log: log, + }) +} + +// ----------------------------------------------------------------------- +// ExpectFailure() inverts the logic of failure. + +type ExpectFailureSucceedHelper struct{} + +func (s *ExpectFailureSucceedHelper) TestSucceed(c *check.C) { + c.ExpectFailure("It booms!") + c.Error("Boom!") +} + +type ExpectFailureFailHelper struct{} + +func (s *ExpectFailureFailHelper) TestFail(c *check.C) { + c.ExpectFailure("Bug #XYZ") +} + +func (s *FoundationS) TestExpectFailureFail(c *check.C) { + helper := ExpectFailureFailHelper{} + output := String{} + result := check.Run(&helper, &check.RunConf{Output: &output}) + + expected := "" + + "^\n-+\n" + + "FAIL: foundation_test\\.go:[0-9]+:" + + " ExpectFailureFailHelper\\.TestFail\n\n" + + "\\.\\.\\. Error: Test succeeded, but was expected to fail\n" + + "\\.\\.\\. Reason: Bug #XYZ\n$" + + matched, err := regexp.MatchString(expected, output.value) + if err != nil { + c.Error("Bad expression: ", expected) + } else if !matched { + c.Error("ExpectFailure() didn't log properly:\n", output.value) + } + + c.Assert(result.ExpectedFailures, check.Equals, 0) +} + +func (s *FoundationS) TestExpectFailureSucceed(c *check.C) { + helper := ExpectFailureSucceedHelper{} + output := String{} + result := check.Run(&helper, &check.RunConf{Output: &output}) + + c.Assert(output.value, check.Equals, "") + c.Assert(result.ExpectedFailures, check.Equals, 1) +} + +func (s *FoundationS) TestExpectFailureSucceedVerbose(c *check.C) { + helper := ExpectFailureSucceedHelper{} + output := String{} + result := check.Run(&helper, &check.RunConf{Output: &output, Verbose: true}) + + expected := "" + + "FAIL EXPECTED: foundation_test\\.go:[0-9]+:" + + " ExpectFailureSucceedHelper\\.TestSucceed \\(It booms!\\)\t *[.0-9]+s\n" + + matched, err := regexp.MatchString(expected, output.value) + if err != nil { + c.Error("Bad expression: ", expected) + } else if !matched { + c.Error("ExpectFailure() didn't log properly:\n", output.value) + } + + c.Assert(result.ExpectedFailures, check.Equals, 1) +} + +// ----------------------------------------------------------------------- +// Skip() allows stopping a test without positive/negative results. + +type SkipTestHelper struct{} + +func (s *SkipTestHelper) TestFail(c *check.C) { + c.Skip("Wrong platform or whatever") + c.Error("Boom!") +} + +func (s *FoundationS) TestSkip(c *check.C) { + helper := SkipTestHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output}) + + if output.value != "" { + c.Error("Skip() logged something:\n", output.value) + } +} + +func (s *FoundationS) TestSkipVerbose(c *check.C) { + helper := SkipTestHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output, Verbose: true}) + + expected := "SKIP: foundation_test\\.go:[0-9]+: SkipTestHelper\\.TestFail" + + " \\(Wrong platform or whatever\\)" + matched, err := regexp.MatchString(expected, output.value) + if err != nil { + c.Error("Bad expression: ", expected) + } else if !matched { + c.Error("Skip() didn't log properly:\n", output.value) + } +} + +// ----------------------------------------------------------------------- +// Check minimum *log.Logger interface provided by *check.C. + +type minLogger interface { + Output(calldepth int, s string) error +} + +func (s *BootstrapS) TestMinLogger(c *check.C) { + var logger minLogger + logger = log.New(os.Stderr, "", 0) + logger = c + logger.Output(0, "Hello there") + expected := `\[LOG\] [0-9]+:[0-9][0-9]\.[0-9][0-9][0-9] +Hello there\n` + output := c.GetTestLog() + c.Assert(output, check.Matches, expected) +} + +// ----------------------------------------------------------------------- +// Ensure that suites with embedded types are working fine, including the +// the workaround for issue 906. + +type EmbeddedInternalS struct { + called bool +} + +type EmbeddedS struct { + EmbeddedInternalS +} + +var embeddedS = check.Suite(&EmbeddedS{}) + +func (s *EmbeddedS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +func (s *EmbeddedInternalS) TestMethod(c *check.C) { + c.Error("TestMethod() of the embedded type was called!?") +} + +func (s *EmbeddedS) TestMethod(c *check.C) { + // http://code.google.com/p/go/issues/detail?id=906 + c.Check(s.called, check.Equals, false) // Go issue 906 is affecting the runner? + s.called = true +} diff --git a/vendor/src/github.com/go-check/check/helpers.go b/vendor/src/github.com/go-check/check/helpers.go new file mode 100644 index 0000000000000..4b6c26da45245 --- /dev/null +++ b/vendor/src/github.com/go-check/check/helpers.go @@ -0,0 +1,231 @@ +package check + +import ( + "fmt" + "strings" + "time" +) + +// TestName returns the current test name in the form "SuiteName.TestName" +func (c *C) TestName() string { + return c.testName +} + +// ----------------------------------------------------------------------- +// Basic succeeding/failing logic. + +// Failed returns whether the currently running test has already failed. +func (c *C) Failed() bool { + return c.status == failedSt +} + +// Fail marks the currently running test as failed. +// +// Something ought to have been previously logged so the developer can tell +// what went wrong. The higher level helper functions will fail the test +// and do the logging properly. +func (c *C) Fail() { + c.status = failedSt +} + +// FailNow marks the currently running test as failed and stops running it. +// Something ought to have been previously logged so the developer can tell +// what went wrong. The higher level helper functions will fail the test +// and do the logging properly. +func (c *C) FailNow() { + c.Fail() + c.stopNow() +} + +// Succeed marks the currently running test as succeeded, undoing any +// previous failures. +func (c *C) Succeed() { + c.status = succeededSt +} + +// SucceedNow marks the currently running test as succeeded, undoing any +// previous failures, and stops running the test. +func (c *C) SucceedNow() { + c.Succeed() + c.stopNow() +} + +// ExpectFailure informs that the running test is knowingly broken for +// the provided reason. If the test does not fail, an error will be reported +// to raise attention to this fact. This method is useful to temporarily +// disable tests which cover well known problems until a better time to +// fix the problem is found, without forgetting about the fact that a +// failure still exists. +func (c *C) ExpectFailure(reason string) { + if reason == "" { + panic("Missing reason why the test is expected to fail") + } + c.mustFail = true + c.reason = reason +} + +// Skip skips the running test for the provided reason. If run from within +// SetUpTest, the individual test being set up will be skipped, and if run +// from within SetUpSuite, the whole suite is skipped. +func (c *C) Skip(reason string) { + if reason == "" { + panic("Missing reason why the test is being skipped") + } + c.reason = reason + c.status = skippedSt + c.stopNow() +} + +// ----------------------------------------------------------------------- +// Basic logging. + +// GetTestLog returns the current test error output. +func (c *C) GetTestLog() string { + return c.logb.String() +} + +// Log logs some information into the test error output. +// The provided arguments are assembled together into a string with fmt.Sprint. +func (c *C) Log(args ...interface{}) { + c.log(args...) +} + +// Log logs some information into the test error output. +// The provided arguments are assembled together into a string with fmt.Sprintf. +func (c *C) Logf(format string, args ...interface{}) { + c.logf(format, args...) +} + +// Output enables *C to be used as a logger in functions that require only +// the minimum interface of *log.Logger. +func (c *C) Output(calldepth int, s string) error { + d := time.Now().Sub(c.startTime) + msec := d / time.Millisecond + sec := d / time.Second + min := d / time.Minute + + c.Logf("[LOG] %d:%02d.%03d %s", min, sec%60, msec%1000, s) + return nil +} + +// Error logs an error into the test error output and marks the test as failed. +// The provided arguments are assembled together into a string with fmt.Sprint. +func (c *C) Error(args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...))) + c.logNewLine() + c.Fail() +} + +// Errorf logs an error into the test error output and marks the test as failed. +// The provided arguments are assembled together into a string with fmt.Sprintf. +func (c *C) Errorf(format string, args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprintf("Error: "+format, args...)) + c.logNewLine() + c.Fail() +} + +// Fatal logs an error into the test error output, marks the test as failed, and +// stops the test execution. The provided arguments are assembled together into +// a string with fmt.Sprint. +func (c *C) Fatal(args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...))) + c.logNewLine() + c.FailNow() +} + +// Fatlaf logs an error into the test error output, marks the test as failed, and +// stops the test execution. The provided arguments are assembled together into +// a string with fmt.Sprintf. +func (c *C) Fatalf(format string, args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprintf(format, args...))) + c.logNewLine() + c.FailNow() +} + +// ----------------------------------------------------------------------- +// Generic checks and assertions based on checkers. + +// Check verifies if the first value matches the expected value according +// to the provided checker. If they do not match, an error is logged, the +// test is marked as failed, and the test execution continues. +// +// Some checkers may not need the expected argument (e.g. IsNil). +// +// Extra arguments provided to the function are logged next to the reported +// problem when the matching fails. +func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool { + return c.internalCheck("Check", obtained, checker, args...) +} + +// Assert ensures that the first value matches the expected value according +// to the provided checker. If they do not match, an error is logged, the +// test is marked as failed, and the test execution stops. +// +// Some checkers may not need the expected argument (e.g. IsNil). +// +// Extra arguments provided to the function are logged next to the reported +// problem when the matching fails. +func (c *C) Assert(obtained interface{}, checker Checker, args ...interface{}) { + if !c.internalCheck("Assert", obtained, checker, args...) { + c.stopNow() + } +} + +func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker, args ...interface{}) bool { + if checker == nil { + c.logCaller(2) + c.logString(fmt.Sprintf("%s(obtained, nil!?, ...):", funcName)) + c.logString("Oops.. you've provided a nil checker!") + c.logNewLine() + c.Fail() + return false + } + + // If the last argument is a bug info, extract it out. + var comment CommentInterface + if len(args) > 0 { + if c, ok := args[len(args)-1].(CommentInterface); ok { + comment = c + args = args[:len(args)-1] + } + } + + params := append([]interface{}{obtained}, args...) + info := checker.Info() + + if len(params) != len(info.Params) { + names := append([]string{info.Params[0], info.Name}, info.Params[1:]...) + c.logCaller(2) + c.logString(fmt.Sprintf("%s(%s):", funcName, strings.Join(names, ", "))) + c.logString(fmt.Sprintf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(names), len(params)+1)) + c.logNewLine() + c.Fail() + return false + } + + // Copy since it may be mutated by Check. + names := append([]string{}, info.Params...) + + // Do the actual check. + result, error := checker.Check(params, names) + if !result || error != "" { + c.logCaller(2) + for i := 0; i != len(params); i++ { + c.logValue(names[i], params[i]) + } + if comment != nil { + c.logString(comment.CheckCommentString()) + } + if error != "" { + c.logString(error) + } + c.logNewLine() + c.Fail() + return false + } + return true +} diff --git a/vendor/src/github.com/go-check/check/helpers_test.go b/vendor/src/github.com/go-check/check/helpers_test.go new file mode 100644 index 0000000000000..4baa656ba8320 --- /dev/null +++ b/vendor/src/github.com/go-check/check/helpers_test.go @@ -0,0 +1,519 @@ +// These tests verify the inner workings of the helper methods associated +// with check.T. + +package check_test + +import ( + "gopkg.in/check.v1" + "os" + "reflect" + "runtime" + "sync" +) + +var helpersS = check.Suite(&HelpersS{}) + +type HelpersS struct{} + +func (s *HelpersS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +// ----------------------------------------------------------------------- +// Fake checker and bug info to verify the behavior of Assert() and Check(). + +type MyChecker struct { + info *check.CheckerInfo + params []interface{} + names []string + result bool + error string +} + +func (checker *MyChecker) Info() *check.CheckerInfo { + if checker.info == nil { + return &check.CheckerInfo{Name: "MyChecker", Params: []string{"myobtained", "myexpected"}} + } + return checker.info +} + +func (checker *MyChecker) Check(params []interface{}, names []string) (bool, string) { + rparams := checker.params + rnames := checker.names + checker.params = append([]interface{}{}, params...) + checker.names = append([]string{}, names...) + if rparams != nil { + copy(params, rparams) + } + if rnames != nil { + copy(names, rnames) + } + return checker.result, checker.error +} + +type myCommentType string + +func (c myCommentType) CheckCommentString() string { + return string(c) +} + +func myComment(s string) myCommentType { + return myCommentType(s) +} + +// ----------------------------------------------------------------------- +// Ensure a real checker actually works fine. + +func (s *HelpersS) TestCheckerInterface(c *check.C) { + testHelperSuccess(c, "Check(1, Equals, 1)", true, func() interface{} { + return c.Check(1, check.Equals, 1) + }) +} + +// ----------------------------------------------------------------------- +// Tests for Check(), mostly the same as for Assert() following these. + +func (s *HelpersS) TestCheckSucceedWithExpected(c *check.C) { + checker := &MyChecker{result: true} + testHelperSuccess(c, "Check(1, checker, 2)", true, func() interface{} { + return c.Check(1, checker, 2) + }) + if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestCheckSucceedWithoutExpected(c *check.C) { + checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + testHelperSuccess(c, "Check(1, checker)", true, func() interface{} { + return c.Check(1, checker) + }) + if !reflect.DeepEqual(checker.params, []interface{}{1}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestCheckFailWithExpected(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n\n" + testHelperFailure(c, "Check(1, checker, 2)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2) + }) +} + +func (s *HelpersS) TestCheckFailWithExpectedAndComment(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2, myComment("Hello world!")) + }) +} + +func (s *HelpersS) TestCheckFailWithExpectedAndStaticComment(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " // Nice leading comment\\.\n" + + " return c\\.Check\\(1, checker, 2\\) // Hello there\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n\n" + testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log, + func() interface{} { + // Nice leading comment. + return c.Check(1, checker, 2) // Hello there + }) +} + +func (s *HelpersS) TestCheckFailWithoutExpected(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker\\)\n" + + "\\.+ myvalue int = 1\n\n" + testHelperFailure(c, "Check(1, checker)", false, false, log, + func() interface{} { + return c.Check(1, checker) + }) +} + +func (s *HelpersS) TestCheckFailWithoutExpectedAndMessage(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myvalue int = 1\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Check(1, checker, msg)", false, false, log, + func() interface{} { + return c.Check(1, checker, myComment("Hello world!")) + }) +} + +func (s *HelpersS) TestCheckWithMissingExpected(c *check.C) { + checker := &MyChecker{result: true} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker\\)\n" + + "\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" + + "\\.+ Wrong number of parameters for MyChecker: " + + "want 3, got 2\n\n" + testHelperFailure(c, "Check(1, checker, !?)", false, false, log, + func() interface{} { + return c.Check(1, checker) + }) +} + +func (s *HelpersS) TestCheckWithTooManyExpected(c *check.C) { + checker := &MyChecker{result: true} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2, 3\\)\n" + + "\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" + + "\\.+ Wrong number of parameters for MyChecker: " + + "want 3, got 4\n\n" + testHelperFailure(c, "Check(1, checker, 2, 3)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2, 3) + }) +} + +func (s *HelpersS) TestCheckWithError(c *check.C) { + checker := &MyChecker{result: false, error: "Some not so cool data provided!"} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Some not so cool data provided!\n\n" + testHelperFailure(c, "Check(1, checker, 2)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2) + }) +} + +func (s *HelpersS) TestCheckWithNilChecker(c *check.C) { + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, nil\\)\n" + + "\\.+ Check\\(obtained, nil!\\?, \\.\\.\\.\\):\n" + + "\\.+ Oops\\.\\. you've provided a nil checker!\n\n" + testHelperFailure(c, "Check(obtained, nil)", false, false, log, + func() interface{} { + return c.Check(1, nil) + }) +} + +func (s *HelpersS) TestCheckWithParamsAndNamesMutation(c *check.C) { + checker := &MyChecker{result: false, params: []interface{}{3, 4}, names: []string{"newobtained", "newexpected"}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2\\)\n" + + "\\.+ newobtained int = 3\n" + + "\\.+ newexpected int = 4\n\n" + testHelperFailure(c, "Check(1, checker, 2) with mutation", false, false, log, + func() interface{} { + return c.Check(1, checker, 2) + }) +} + +// ----------------------------------------------------------------------- +// Tests for Assert(), mostly the same as for Check() above. + +func (s *HelpersS) TestAssertSucceedWithExpected(c *check.C) { + checker := &MyChecker{result: true} + testHelperSuccess(c, "Assert(1, checker, 2)", nil, func() interface{} { + c.Assert(1, checker, 2) + return nil + }) + if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestAssertSucceedWithoutExpected(c *check.C) { + checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + testHelperSuccess(c, "Assert(1, checker)", nil, func() interface{} { + c.Assert(1, checker) + return nil + }) + if !reflect.DeepEqual(checker.params, []interface{}{1}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestAssertFailWithExpected(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n\n" + testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log, + func() interface{} { + c.Assert(1, checker, 2) + return nil + }) +} + +func (s *HelpersS) TestAssertFailWithExpectedAndMessage(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Assert(1, checker, 2, msg)", nil, true, log, + func() interface{} { + c.Assert(1, checker, 2, myComment("Hello world!")) + return nil + }) +} + +func (s *HelpersS) TestAssertFailWithoutExpected(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker\\)\n" + + "\\.+ myvalue int = 1\n\n" + testHelperFailure(c, "Assert(1, checker)", nil, true, log, + func() interface{} { + c.Assert(1, checker) + return nil + }) +} + +func (s *HelpersS) TestAssertFailWithoutExpectedAndMessage(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myvalue int = 1\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Assert(1, checker, msg)", nil, true, log, + func() interface{} { + c.Assert(1, checker, myComment("Hello world!")) + return nil + }) +} + +func (s *HelpersS) TestAssertWithMissingExpected(c *check.C) { + checker := &MyChecker{result: true} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker\\)\n" + + "\\.+ Assert\\(myobtained, MyChecker, myexpected\\):\n" + + "\\.+ Wrong number of parameters for MyChecker: " + + "want 3, got 2\n\n" + testHelperFailure(c, "Assert(1, checker, !?)", nil, true, log, + func() interface{} { + c.Assert(1, checker) + return nil + }) +} + +func (s *HelpersS) TestAssertWithError(c *check.C) { + checker := &MyChecker{result: false, error: "Some not so cool data provided!"} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Some not so cool data provided!\n\n" + testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log, + func() interface{} { + c.Assert(1, checker, 2) + return nil + }) +} + +func (s *HelpersS) TestAssertWithNilChecker(c *check.C) { + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, nil\\)\n" + + "\\.+ Assert\\(obtained, nil!\\?, \\.\\.\\.\\):\n" + + "\\.+ Oops\\.\\. you've provided a nil checker!\n\n" + testHelperFailure(c, "Assert(obtained, nil)", nil, true, log, + func() interface{} { + c.Assert(1, nil) + return nil + }) +} + +// ----------------------------------------------------------------------- +// Ensure that values logged work properly in some interesting cases. + +func (s *HelpersS) TestValueLoggingWithArrays(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" + + " return c\\.Check\\(\\[\\]byte{1, 2}, checker, \\[\\]byte{1, 3}\\)\n" + + "\\.+ myobtained \\[\\]uint8 = \\[\\]byte{0x1, 0x2}\n" + + "\\.+ myexpected \\[\\]uint8 = \\[\\]byte{0x1, 0x3}\n\n" + testHelperFailure(c, "Check([]byte{1}, chk, []byte{3})", false, false, log, + func() interface{} { + return c.Check([]byte{1, 2}, checker, []byte{1, 3}) + }) +} + +func (s *HelpersS) TestValueLoggingWithMultiLine(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" + + " return c\\.Check\\(\"a\\\\nb\\\\n\", checker, \"a\\\\nb\\\\nc\"\\)\n" + + "\\.+ myobtained string = \"\" \\+\n" + + "\\.+ \"a\\\\n\" \\+\n" + + "\\.+ \"b\\\\n\"\n" + + "\\.+ myexpected string = \"\" \\+\n" + + "\\.+ \"a\\\\n\" \\+\n" + + "\\.+ \"b\\\\n\" \\+\n" + + "\\.+ \"c\"\n\n" + testHelperFailure(c, `Check("a\nb\n", chk, "a\nb\nc")`, false, false, log, + func() interface{} { + return c.Check("a\nb\n", checker, "a\nb\nc") + }) +} + +func (s *HelpersS) TestValueLoggingWithMultiLineException(c *check.C) { + // If the newline is at the end of the string, don't log as multi-line. + checker := &MyChecker{result: false} + log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" + + " return c\\.Check\\(\"a b\\\\n\", checker, \"a\\\\nb\"\\)\n" + + "\\.+ myobtained string = \"a b\\\\n\"\n" + + "\\.+ myexpected string = \"\" \\+\n" + + "\\.+ \"a\\\\n\" \\+\n" + + "\\.+ \"b\"\n\n" + testHelperFailure(c, `Check("a b\n", chk, "a\nb")`, false, false, log, + func() interface{} { + return c.Check("a b\n", checker, "a\nb") + }) +} + +// ----------------------------------------------------------------------- +// MakeDir() tests. + +type MkDirHelper struct { + path1 string + path2 string + isDir1 bool + isDir2 bool + isDir3 bool + isDir4 bool +} + +func (s *MkDirHelper) SetUpSuite(c *check.C) { + s.path1 = c.MkDir() + s.isDir1 = isDir(s.path1) +} + +func (s *MkDirHelper) Test(c *check.C) { + s.path2 = c.MkDir() + s.isDir2 = isDir(s.path2) +} + +func (s *MkDirHelper) TearDownSuite(c *check.C) { + s.isDir3 = isDir(s.path1) + s.isDir4 = isDir(s.path2) +} + +func (s *HelpersS) TestMkDir(c *check.C) { + helper := MkDirHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output}) + c.Assert(output.value, check.Equals, "") + c.Check(helper.isDir1, check.Equals, true) + c.Check(helper.isDir2, check.Equals, true) + c.Check(helper.isDir3, check.Equals, true) + c.Check(helper.isDir4, check.Equals, true) + c.Check(helper.path1, check.Not(check.Equals), + helper.path2) + c.Check(isDir(helper.path1), check.Equals, false) + c.Check(isDir(helper.path2), check.Equals, false) +} + +func isDir(path string) bool { + if stat, err := os.Stat(path); err == nil { + return stat.IsDir() + } + return false +} + +// Concurrent logging should not corrupt the underling buffer. +// Use go test -race to detect the race in this test. +func (s *HelpersS) TestConcurrentLogging(c *check.C) { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) + var start, stop sync.WaitGroup + start.Add(1) + for i, n := 0, runtime.NumCPU()*2; i < n; i++ { + stop.Add(1) + go func(i int) { + start.Wait() + for j := 0; j < 30; j++ { + c.Logf("Worker %d: line %d", i, j) + } + stop.Done() + }(i) + } + start.Done() + stop.Wait() +} + +// ----------------------------------------------------------------------- +// Test the TestName function + +type TestNameHelper struct { + name1 string + name2 string + name3 string + name4 string + name5 string +} + +func (s *TestNameHelper) SetUpSuite(c *check.C) { s.name1 = c.TestName() } +func (s *TestNameHelper) SetUpTest(c *check.C) { s.name2 = c.TestName() } +func (s *TestNameHelper) Test(c *check.C) { s.name3 = c.TestName() } +func (s *TestNameHelper) TearDownTest(c *check.C) { s.name4 = c.TestName() } +func (s *TestNameHelper) TearDownSuite(c *check.C) { s.name5 = c.TestName() } + +func (s *HelpersS) TestTestName(c *check.C) { + helper := TestNameHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output}) + c.Check(helper.name1, check.Equals, "") + c.Check(helper.name2, check.Equals, "TestNameHelper.Test") + c.Check(helper.name3, check.Equals, "TestNameHelper.Test") + c.Check(helper.name4, check.Equals, "TestNameHelper.Test") + c.Check(helper.name5, check.Equals, "") +} + +// ----------------------------------------------------------------------- +// A couple of helper functions to test helper functions. :-) + +func testHelperSuccess(c *check.C, name string, expectedResult interface{}, closure func() interface{}) { + var result interface{} + defer (func() { + if err := recover(); err != nil { + panic(err) + } + checkState(c, result, + &expectedState{ + name: name, + result: expectedResult, + failed: false, + log: "", + }) + })() + result = closure() +} + +func testHelperFailure(c *check.C, name string, expectedResult interface{}, shouldStop bool, log string, closure func() interface{}) { + var result interface{} + defer (func() { + if err := recover(); err != nil { + panic(err) + } + checkState(c, result, + &expectedState{ + name: name, + result: expectedResult, + failed: true, + log: log, + }) + })() + result = closure() + if shouldStop { + c.Logf("%s didn't stop when it should", name) + } +} diff --git a/vendor/src/github.com/go-check/check/printer.go b/vendor/src/github.com/go-check/check/printer.go new file mode 100644 index 0000000000000..e0f7557b5cc76 --- /dev/null +++ b/vendor/src/github.com/go-check/check/printer.go @@ -0,0 +1,168 @@ +package check + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" +) + +func indent(s, with string) (r string) { + eol := true + for i := 0; i != len(s); i++ { + c := s[i] + switch { + case eol && c == '\n' || c == '\r': + case c == '\n' || c == '\r': + eol = true + case eol: + eol = false + s = s[:i] + with + s[i:] + i += len(with) + } + } + return s +} + +func printLine(filename string, line int) (string, error) { + fset := token.NewFileSet() + file, err := os.Open(filename) + if err != nil { + return "", err + } + fnode, err := parser.ParseFile(fset, filename, file, parser.ParseComments) + if err != nil { + return "", err + } + config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4} + lp := &linePrinter{fset: fset, fnode: fnode, line: line, config: config} + ast.Walk(lp, fnode) + result := lp.output.Bytes() + // Comments leave \n at the end. + n := len(result) + for n > 0 && result[n-1] == '\n' { + n-- + } + return string(result[:n]), nil +} + +type linePrinter struct { + config *printer.Config + fset *token.FileSet + fnode *ast.File + line int + output bytes.Buffer + stmt ast.Stmt +} + +func (lp *linePrinter) emit() bool { + if lp.stmt != nil { + lp.trim(lp.stmt) + lp.printWithComments(lp.stmt) + lp.stmt = nil + return true + } + return false +} + +func (lp *linePrinter) printWithComments(n ast.Node) { + nfirst := lp.fset.Position(n.Pos()).Line + nlast := lp.fset.Position(n.End()).Line + for _, g := range lp.fnode.Comments { + cfirst := lp.fset.Position(g.Pos()).Line + clast := lp.fset.Position(g.End()).Line + if clast == nfirst-1 && lp.fset.Position(n.Pos()).Column == lp.fset.Position(g.Pos()).Column { + for _, c := range g.List { + lp.output.WriteString(c.Text) + lp.output.WriteByte('\n') + } + } + if cfirst >= nfirst && cfirst <= nlast && n.End() <= g.List[0].Slash { + // The printer will not include the comment if it starts past + // the node itself. Trick it into printing by overlapping the + // slash with the end of the statement. + g.List[0].Slash = n.End() - 1 + } + } + node := &printer.CommentedNode{n, lp.fnode.Comments} + lp.config.Fprint(&lp.output, lp.fset, node) +} + +func (lp *linePrinter) Visit(n ast.Node) (w ast.Visitor) { + if n == nil { + if lp.output.Len() == 0 { + lp.emit() + } + return nil + } + first := lp.fset.Position(n.Pos()).Line + last := lp.fset.Position(n.End()).Line + if first <= lp.line && last >= lp.line { + // Print the innermost statement containing the line. + if stmt, ok := n.(ast.Stmt); ok { + if _, ok := n.(*ast.BlockStmt); !ok { + lp.stmt = stmt + } + } + if first == lp.line && lp.emit() { + return nil + } + return lp + } + return nil +} + +func (lp *linePrinter) trim(n ast.Node) bool { + stmt, ok := n.(ast.Stmt) + if !ok { + return true + } + line := lp.fset.Position(n.Pos()).Line + if line != lp.line { + return false + } + switch stmt := stmt.(type) { + case *ast.IfStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.SwitchStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.TypeSwitchStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.CaseClause: + stmt.Body = lp.trimList(stmt.Body) + case *ast.CommClause: + stmt.Body = lp.trimList(stmt.Body) + case *ast.BlockStmt: + stmt.List = lp.trimList(stmt.List) + } + return true +} + +func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt { + if !lp.trim(stmt) { + return lp.emptyBlock(stmt) + } + stmt.Rbrace = stmt.Lbrace + return stmt +} + +func (lp *linePrinter) trimList(stmts []ast.Stmt) []ast.Stmt { + for i := 0; i != len(stmts); i++ { + if !lp.trim(stmts[i]) { + stmts[i] = lp.emptyStmt(stmts[i]) + break + } + } + return stmts +} + +func (lp *linePrinter) emptyStmt(n ast.Node) *ast.ExprStmt { + return &ast.ExprStmt{&ast.Ellipsis{n.Pos(), nil}} +} + +func (lp *linePrinter) emptyBlock(n ast.Node) *ast.BlockStmt { + p := n.Pos() + return &ast.BlockStmt{p, []ast.Stmt{lp.emptyStmt(n)}, p} +} diff --git a/vendor/src/github.com/go-check/check/printer_test.go b/vendor/src/github.com/go-check/check/printer_test.go new file mode 100644 index 0000000000000..538b2d52e3b7e --- /dev/null +++ b/vendor/src/github.com/go-check/check/printer_test.go @@ -0,0 +1,104 @@ +package check_test + +import ( + . "gopkg.in/check.v1" +) + +var _ = Suite(&PrinterS{}) + +type PrinterS struct{} + +func (s *PrinterS) TestCountSuite(c *C) { + suitesRun += 1 +} + +var printTestFuncLine int + +func init() { + printTestFuncLine = getMyLine() + 3 +} + +func printTestFunc() { + println(1) // Comment1 + if 2 == 2 { // Comment2 + println(3) // Comment3 + } + switch 5 { + case 6: println(6) // Comment6 + println(7) + } + switch interface{}(9).(type) {// Comment9 + case int: println(10) + println(11) + } + select { + case <-(chan bool)(nil): println(14) + println(15) + default: println(16) + println(17) + } + println(19, + 20) + _ = func() { println(21) + println(22) + } + println(24, func() { + println(25) + }) + // Leading comment + // with multiple lines. + println(29) // Comment29 +} + +var printLineTests = []struct { + line int + output string +}{ + {1, "println(1) // Comment1"}, + {2, "if 2 == 2 { // Comment2\n ...\n}"}, + {3, "println(3) // Comment3"}, + {5, "switch 5 {\n...\n}"}, + {6, "case 6:\n println(6) // Comment6\n ..."}, + {7, "println(7)"}, + {9, "switch interface{}(9).(type) { // Comment9\n...\n}"}, + {10, "case int:\n println(10)\n ..."}, + {14, "case <-(chan bool)(nil):\n println(14)\n ..."}, + {15, "println(15)"}, + {16, "default:\n println(16)\n ..."}, + {17, "println(17)"}, + {19, "println(19,\n 20)"}, + {20, "println(19,\n 20)"}, + {21, "_ = func() {\n println(21)\n println(22)\n}"}, + {22, "println(22)"}, + {24, "println(24, func() {\n println(25)\n})"}, + {25, "println(25)"}, + {26, "println(24, func() {\n println(25)\n})"}, + {29, "// Leading comment\n// with multiple lines.\nprintln(29) // Comment29"}, +} + +func (s *PrinterS) TestPrintLine(c *C) { + for _, test := range printLineTests { + output, err := PrintLine("printer_test.go", printTestFuncLine+test.line) + c.Assert(err, IsNil) + c.Assert(output, Equals, test.output) + } +} + +var indentTests = []struct { + in, out string +}{ + {"", ""}, + {"\n", "\n"}, + {"a", ">>>a"}, + {"a\n", ">>>a\n"}, + {"a\nb", ">>>a\n>>>b"}, + {" ", ">>> "}, +} + +func (s *PrinterS) TestIndent(c *C) { + for _, test := range indentTests { + out := Indent(test.in, ">>>") + c.Assert(out, Equals, test.out) + } + +} diff --git a/vendor/src/github.com/go-check/check/run.go b/vendor/src/github.com/go-check/check/run.go new file mode 100644 index 0000000000000..da8fd79872997 --- /dev/null +++ b/vendor/src/github.com/go-check/check/run.go @@ -0,0 +1,175 @@ +package check + +import ( + "bufio" + "flag" + "fmt" + "os" + "testing" + "time" +) + +// ----------------------------------------------------------------------- +// Test suite registry. + +var allSuites []interface{} + +// Suite registers the given value as a test suite to be run. Any methods +// starting with the Test prefix in the given value will be considered as +// a test method. +func Suite(suite interface{}) interface{} { + allSuites = append(allSuites, suite) + return suite +} + +// ----------------------------------------------------------------------- +// Public running interface. + +var ( + oldFilterFlag = flag.String("gocheck.f", "", "Regular expression selecting which tests and/or suites to run") + oldVerboseFlag = flag.Bool("gocheck.v", false, "Verbose mode") + oldStreamFlag = flag.Bool("gocheck.vv", false, "Super verbose mode (disables output caching)") + oldBenchFlag = flag.Bool("gocheck.b", false, "Run benchmarks") + oldBenchTime = flag.Duration("gocheck.btime", 1*time.Second, "approximate run time for each benchmark") + oldListFlag = flag.Bool("gocheck.list", false, "List the names of all tests that will be run") + oldWorkFlag = flag.Bool("gocheck.work", false, "Display and do not remove the test working directory") + + newFilterFlag = flag.String("check.f", "", "Regular expression selecting which tests and/or suites to run") + newVerboseFlag = flag.Bool("check.v", false, "Verbose mode") + newStreamFlag = flag.Bool("check.vv", false, "Super verbose mode (disables output caching)") + newBenchFlag = flag.Bool("check.b", false, "Run benchmarks") + newBenchTime = flag.Duration("check.btime", 1*time.Second, "approximate run time for each benchmark") + newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks") + newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run") + newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory") +) + +// TestingT runs all test suites registered with the Suite function, +// printing results to stdout, and reporting any failures back to +// the "testing" package. +func TestingT(testingT *testing.T) { + benchTime := *newBenchTime + if benchTime == 1*time.Second { + benchTime = *oldBenchTime + } + conf := &RunConf{ + Filter: *oldFilterFlag + *newFilterFlag, + Verbose: *oldVerboseFlag || *newVerboseFlag, + Stream: *oldStreamFlag || *newStreamFlag, + Benchmark: *oldBenchFlag || *newBenchFlag, + BenchmarkTime: benchTime, + BenchmarkMem: *newBenchMem, + KeepWorkDir: *oldWorkFlag || *newWorkFlag, + } + if *oldListFlag || *newListFlag { + w := bufio.NewWriter(os.Stdout) + for _, name := range ListAll(conf) { + fmt.Fprintln(w, name) + } + w.Flush() + return + } + result := RunAll(conf) + println(result.String()) + if !result.Passed() { + testingT.Fail() + } +} + +// RunAll runs all test suites registered with the Suite function, using the +// provided run configuration. +func RunAll(runConf *RunConf) *Result { + result := Result{} + for _, suite := range allSuites { + result.Add(Run(suite, runConf)) + } + return &result +} + +// Run runs the provided test suite using the provided run configuration. +func Run(suite interface{}, runConf *RunConf) *Result { + runner := newSuiteRunner(suite, runConf) + return runner.run() +} + +// ListAll returns the names of all the test functions registered with the +// Suite function that will be run with the provided run configuration. +func ListAll(runConf *RunConf) []string { + var names []string + for _, suite := range allSuites { + names = append(names, List(suite, runConf)...) + } + return names +} + +// List returns the names of the test functions in the given +// suite that will be run with the provided run configuration. +func List(suite interface{}, runConf *RunConf) []string { + var names []string + runner := newSuiteRunner(suite, runConf) + for _, t := range runner.tests { + names = append(names, t.String()) + } + return names +} + +// ----------------------------------------------------------------------- +// Result methods. + +func (r *Result) Add(other *Result) { + r.Succeeded += other.Succeeded + r.Skipped += other.Skipped + r.Failed += other.Failed + r.Panicked += other.Panicked + r.FixturePanicked += other.FixturePanicked + r.ExpectedFailures += other.ExpectedFailures + r.Missed += other.Missed + if r.WorkDir != "" && other.WorkDir != "" { + r.WorkDir += ":" + other.WorkDir + } else if other.WorkDir != "" { + r.WorkDir = other.WorkDir + } +} + +func (r *Result) Passed() bool { + return (r.Failed == 0 && r.Panicked == 0 && + r.FixturePanicked == 0 && r.Missed == 0 && + r.RunError == nil) +} + +func (r *Result) String() string { + if r.RunError != nil { + return "ERROR: " + r.RunError.Error() + } + + var value string + if r.Failed == 0 && r.Panicked == 0 && r.FixturePanicked == 0 && + r.Missed == 0 { + value = "OK: " + } else { + value = "OOPS: " + } + value += fmt.Sprintf("%d passed", r.Succeeded) + if r.Skipped != 0 { + value += fmt.Sprintf(", %d skipped", r.Skipped) + } + if r.ExpectedFailures != 0 { + value += fmt.Sprintf(", %d expected failures", r.ExpectedFailures) + } + if r.Failed != 0 { + value += fmt.Sprintf(", %d FAILED", r.Failed) + } + if r.Panicked != 0 { + value += fmt.Sprintf(", %d PANICKED", r.Panicked) + } + if r.FixturePanicked != 0 { + value += fmt.Sprintf(", %d FIXTURE-PANICKED", r.FixturePanicked) + } + if r.Missed != 0 { + value += fmt.Sprintf(", %d MISSED", r.Missed) + } + if r.WorkDir != "" { + value += "\nWORK=" + r.WorkDir + } + return value +} diff --git a/vendor/src/github.com/go-check/check/run_test.go b/vendor/src/github.com/go-check/check/run_test.go new file mode 100644 index 0000000000000..f41fffc3f5b56 --- /dev/null +++ b/vendor/src/github.com/go-check/check/run_test.go @@ -0,0 +1,419 @@ +// These tests verify the test running logic. + +package check_test + +import ( + "errors" + . "gopkg.in/check.v1" + "os" + "sync" +) + +var runnerS = Suite(&RunS{}) + +type RunS struct{} + +func (s *RunS) TestCountSuite(c *C) { + suitesRun += 1 +} + +// ----------------------------------------------------------------------- +// Tests ensuring result counting works properly. + +func (s *RunS) TestSuccess(c *C) { + output := String{} + result := Run(&SuccessHelper{}, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 1) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestFailure(c *C) { + output := String{} + result := Run(&FailHelper{}, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 0) + c.Check(result.Failed, Equals, 1) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestFixture(c *C) { + output := String{} + result := Run(&FixtureHelper{}, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 2) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestPanicOnTest(c *C) { + output := String{} + helper := &FixtureHelper{panicOn: "Test1"} + result := Run(helper, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 1) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 1) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestPanicOnSetUpTest(c *C) { + output := String{} + helper := &FixtureHelper{panicOn: "SetUpTest"} + result := Run(helper, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 0) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 1) + c.Check(result.Missed, Equals, 2) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestPanicOnSetUpSuite(c *C) { + output := String{} + helper := &FixtureHelper{panicOn: "SetUpSuite"} + result := Run(helper, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 0) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 1) + c.Check(result.Missed, Equals, 2) + c.Check(result.RunError, IsNil) +} + +// ----------------------------------------------------------------------- +// Check result aggregation. + +func (s *RunS) TestAdd(c *C) { + result := &Result{ + Succeeded: 1, + Skipped: 2, + Failed: 3, + Panicked: 4, + FixturePanicked: 5, + Missed: 6, + ExpectedFailures: 7, + } + result.Add(&Result{ + Succeeded: 10, + Skipped: 20, + Failed: 30, + Panicked: 40, + FixturePanicked: 50, + Missed: 60, + ExpectedFailures: 70, + }) + c.Check(result.Succeeded, Equals, 11) + c.Check(result.Skipped, Equals, 22) + c.Check(result.Failed, Equals, 33) + c.Check(result.Panicked, Equals, 44) + c.Check(result.FixturePanicked, Equals, 55) + c.Check(result.Missed, Equals, 66) + c.Check(result.ExpectedFailures, Equals, 77) + c.Check(result.RunError, IsNil) +} + +// ----------------------------------------------------------------------- +// Check the Passed() method. + +func (s *RunS) TestPassed(c *C) { + c.Assert((&Result{}).Passed(), Equals, true) + c.Assert((&Result{Succeeded: 1}).Passed(), Equals, true) + c.Assert((&Result{Skipped: 1}).Passed(), Equals, true) + c.Assert((&Result{Failed: 1}).Passed(), Equals, false) + c.Assert((&Result{Panicked: 1}).Passed(), Equals, false) + c.Assert((&Result{FixturePanicked: 1}).Passed(), Equals, false) + c.Assert((&Result{Missed: 1}).Passed(), Equals, false) + c.Assert((&Result{RunError: errors.New("!")}).Passed(), Equals, false) +} + +// ----------------------------------------------------------------------- +// Check that result printing is working correctly. + +func (s *RunS) TestPrintSuccess(c *C) { + result := &Result{Succeeded: 5} + c.Check(result.String(), Equals, "OK: 5 passed") +} + +func (s *RunS) TestPrintFailure(c *C) { + result := &Result{Failed: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FAILED") +} + +func (s *RunS) TestPrintSkipped(c *C) { + result := &Result{Skipped: 5} + c.Check(result.String(), Equals, "OK: 0 passed, 5 skipped") +} + +func (s *RunS) TestPrintExpectedFailures(c *C) { + result := &Result{ExpectedFailures: 5} + c.Check(result.String(), Equals, "OK: 0 passed, 5 expected failures") +} + +func (s *RunS) TestPrintPanicked(c *C) { + result := &Result{Panicked: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 PANICKED") +} + +func (s *RunS) TestPrintFixturePanicked(c *C) { + result := &Result{FixturePanicked: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FIXTURE-PANICKED") +} + +func (s *RunS) TestPrintMissed(c *C) { + result := &Result{Missed: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 MISSED") +} + +func (s *RunS) TestPrintAll(c *C) { + result := &Result{Succeeded: 1, Skipped: 2, ExpectedFailures: 3, + Panicked: 4, FixturePanicked: 5, Missed: 6} + c.Check(result.String(), Equals, + "OOPS: 1 passed, 2 skipped, 3 expected failures, 4 PANICKED, "+ + "5 FIXTURE-PANICKED, 6 MISSED") +} + +func (s *RunS) TestPrintRunError(c *C) { + result := &Result{Succeeded: 1, Failed: 1, + RunError: errors.New("Kaboom!")} + c.Check(result.String(), Equals, "ERROR: Kaboom!") +} + +// ----------------------------------------------------------------------- +// Verify that the method pattern flag works correctly. + +func (s *RunS) TestFilterTestName(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "Test[91]"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 5) +} + +func (s *RunS) TestFilterTestNameWithAll(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: ".*"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) +} + +func (s *RunS) TestFilterSuiteName(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "FixtureHelper"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) +} + +func (s *RunS) TestFilterSuiteNameAndTestName(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "FixtureHelper\\.Test2"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test2") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 5) +} + +func (s *RunS) TestFilterAllOut(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "NotFound"} + Run(&helper, &runConf) + c.Check(len(helper.calls), Equals, 0) +} + +func (s *RunS) TestRequirePartialMatch(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "est"} + Run(&helper, &runConf) + c.Check(len(helper.calls), Equals, 8) +} + +func (s *RunS) TestFilterError(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "]["} + result := Run(&helper, &runConf) + c.Check(result.String(), Equals, + "ERROR: Bad filter expression: error parsing regexp: missing closing ]: `[`") + c.Check(len(helper.calls), Equals, 0) +} + +// ----------------------------------------------------------------------- +// Verify that List works correctly. + +func (s *RunS) TestListFiltered(c *C) { + names := List(&FixtureHelper{}, &RunConf{Filter: "1"}) + c.Assert(names, DeepEquals, []string{ + "FixtureHelper.Test1", + }) +} + +func (s *RunS) TestList(c *C) { + names := List(&FixtureHelper{}, &RunConf{}) + c.Assert(names, DeepEquals, []string{ + "FixtureHelper.Test1", + "FixtureHelper.Test2", + }) +} + +// ----------------------------------------------------------------------- +// Verify that verbose mode prints tests which pass as well. + +func (s *RunS) TestVerboseMode(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Verbose: true} + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t *[.0-9]+s\n" + + "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n" + + c.Assert(output.value, Matches, expected) +} + +func (s *RunS) TestVerboseModeWithFailBeforePass(c *C) { + helper := FixtureHelper{panicOn: "Test1"} + output := String{} + runConf := RunConf{Output: &output, Verbose: true} + Run(&helper, &runConf) + + expected := "(?s).*PANIC.*\n-+\n" + // Should have an extra line. + "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n" + + c.Assert(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Verify the stream output mode. In this mode there's no output caching. + +type StreamHelper struct { + l2 sync.Mutex + l3 sync.Mutex +} + +func (s *StreamHelper) SetUpSuite(c *C) { + c.Log("0") +} + +func (s *StreamHelper) Test1(c *C) { + c.Log("1") + s.l2.Lock() + s.l3.Lock() + go func() { + s.l2.Lock() // Wait for "2". + c.Log("3") + s.l3.Unlock() + }() +} + +func (s *StreamHelper) Test2(c *C) { + c.Log("2") + s.l2.Unlock() + s.l3.Lock() // Wait for "3". + c.Fail() + c.Log("4") +} + +func (s *RunS) TestStreamMode(c *C) { + helper := &StreamHelper{} + output := String{} + runConf := RunConf{Output: &output, Stream: true} + Run(helper, &runConf) + + expected := "START: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\n0\n" + + "PASS: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\t *[.0-9]+s\n\n" + + "START: run_test\\.go:[0-9]+: StreamHelper\\.Test1\n1\n" + + "PASS: run_test\\.go:[0-9]+: StreamHelper\\.Test1\t *[.0-9]+s\n\n" + + "START: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n2\n3\n4\n" + + "FAIL: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n\n" + + c.Assert(output.value, Matches, expected) +} + +type StreamMissHelper struct{} + +func (s *StreamMissHelper) SetUpSuite(c *C) { + c.Log("0") + c.Fail() +} + +func (s *StreamMissHelper) Test1(c *C) { + c.Log("1") +} + +func (s *RunS) TestStreamModeWithMiss(c *C) { + helper := &StreamMissHelper{} + output := String{} + runConf := RunConf{Output: &output, Stream: true} + Run(helper, &runConf) + + expected := "START: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n0\n" + + "FAIL: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n\n" + + "START: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n" + + "MISS: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n\n" + + c.Assert(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Verify that that the keep work dir request indeed does so. + +type WorkDirSuite struct {} + +func (s *WorkDirSuite) Test(c *C) { + c.MkDir() +} + +func (s *RunS) TestKeepWorkDir(c *C) { + output := String{} + runConf := RunConf{Output: &output, Verbose: true, KeepWorkDir: true} + result := Run(&WorkDirSuite{}, &runConf) + + c.Assert(result.String(), Matches, ".*\nWORK=" + result.WorkDir) + + stat, err := os.Stat(result.WorkDir) + c.Assert(err, IsNil) + c.Assert(stat.IsDir(), Equals, true) +} From dc944ea7e48d11a2906e751d3e61daf08faee054 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Sat, 18 Apr 2015 09:46:47 -0700 Subject: [PATCH 213/332] Use suite for integration-cli It prints test name and duration for each test. Also performs deleteAllContainers after each test. Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 34 + integration-cli/docker_api_attach_test.go | 22 +- integration-cli/docker_api_containers_test.go | 369 ++-- .../docker_api_exec_resize_test.go | 14 +- integration-cli/docker_api_exec_test.go | 12 +- integration-cli/docker_api_images_test.go | 63 +- integration-cli/docker_api_info_test.go | 2 - integration-cli/docker_api_inspect_test.go | 19 +- integration-cli/docker_api_logs_test.go | 25 +- integration-cli/docker_api_resize_test.go | 36 +- integration-cli/docker_api_version_test.go | 10 +- integration-cli/docker_cli_attach_test.go | 73 +- .../docker_cli_attach_unix_test.go | 104 +- integration-cli/docker_cli_build_test.go | 1836 ++++++++--------- integration-cli/docker_cli_by_digest_test.go | 296 ++- integration-cli/docker_cli_commit_test.go | 115 +- integration-cli/docker_cli_config_test.go | 12 +- integration-cli/docker_cli_cp_test.go | 290 ++- integration-cli/docker_cli_create_test.go | 155 +- integration-cli/docker_cli_daemon_test.go | 449 ++-- integration-cli/docker_cli_diff_test.go | 37 +- integration-cli/docker_cli_events_test.go | 208 +- .../docker_cli_events_unix_test.go | 26 +- integration-cli/docker_cli_exec_test.go | 306 ++- .../docker_cli_export_import_test.go | 31 +- integration-cli/docker_cli_help_test.go | 35 +- integration-cli/docker_cli_history_test.go | 35 +- integration-cli/docker_cli_images_test.go | 87 +- integration-cli/docker_cli_import_test.go | 16 +- integration-cli/docker_cli_info_test.go | 10 +- integration-cli/docker_cli_inspect_test.go | 10 +- integration-cli/docker_cli_kill_test.go | 29 +- integration-cli/docker_cli_links_test.go | 189 +- integration-cli/docker_cli_login_test.go | 8 +- integration-cli/docker_cli_logs_test.go | 113 +- integration-cli/docker_cli_nat_test.go | 25 +- integration-cli/docker_cli_pause_test.go | 51 +- integration-cli/docker_cli_port_test.go | 97 +- integration-cli/docker_cli_proxy_test.go | 27 +- integration-cli/docker_cli_ps_test.go | 257 ++- integration-cli/docker_cli_pull_test.go | 56 +- integration-cli/docker_cli_push_test.go | 79 +- integration-cli/docker_cli_rename_test.go | 53 +- integration-cli/docker_cli_restart_test.go | 109 +- integration-cli/docker_cli_rm_test.go | 76 +- integration-cli/docker_cli_rmi_test.go | 121 +- integration-cli/docker_cli_run_test.go | 1819 ++++++---------- integration-cli/docker_cli_run_unix_test.go | 121 +- integration-cli/docker_cli_save_load_test.go | 121 +- .../docker_cli_save_load_unix_test.go | 31 +- integration-cli/docker_cli_search_test.go | 44 +- integration-cli/docker_cli_start_test.go | 125 +- integration-cli/docker_cli_tag_test.go | 62 +- integration-cli/docker_cli_top_test.go | 46 +- integration-cli/docker_cli_version_test.go | 10 +- integration-cli/docker_cli_wait_test.go | 43 +- integration-cli/docker_utils.go | 86 +- integration-cli/registry.go | 7 +- integration-cli/requirements.go | 7 +- integration-cli/utils.go | 4 - 60 files changed, 3746 insertions(+), 4807 deletions(-) create mode 100644 integration-cli/check_test.go diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go new file mode 100644 index 0000000000000..330bc373b5d32 --- /dev/null +++ b/integration-cli/check_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/go-check/check" +) + +func Test(t *testing.T) { check.TestingT(t) } + +type TimerSuite struct { + start time.Time +} + +func (s *TimerSuite) SetUpTest(c *check.C) { + s.start = time.Now() +} + +func (s *TimerSuite) TearDownTest(c *check.C) { + fmt.Printf("%-60s%.2f\n", c.TestName(), time.Since(s.start).Seconds()) +} + +type DockerSuite struct { + TimerSuite +} + +func (s *DockerSuite) TearDownTest(c *check.C) { + deleteAllContainers() + s.TimerSuite.TearDownTest(c) +} + +var _ = check.Suite(&DockerSuite{}) diff --git a/integration-cli/docker_api_attach_test.go b/integration-cli/docker_api_attach_test.go index 3257798c563f5..ce7f85d306814 100644 --- a/integration-cli/docker_api_attach_test.go +++ b/integration-cli/docker_api_attach_test.go @@ -4,23 +4,23 @@ import ( "bytes" "os/exec" "strings" - "testing" "time" + "github.com/go-check/check" + "code.google.com/p/go.net/websocket" ) -func TestGetContainersAttachWebsocket(t *testing.T) { +func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-dit", "busybox", "cat") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() rwc, err := sockConn(time.Duration(10 * time.Second)) if err != nil { - t.Fatal(err) + c.Fatal(err) } cleanedContainerID := strings.TrimSpace(out) @@ -29,12 +29,12 @@ func TestGetContainersAttachWebsocket(t *testing.T) { "http://localhost", ) if err != nil { - t.Fatal(err) + c.Fatal(err) } ws, err := websocket.NewClient(config, rwc) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ws.Close() @@ -43,7 +43,7 @@ func TestGetContainersAttachWebsocket(t *testing.T) { outChan := make(chan string) go func() { if _, err := ws.Read(actual); err != nil { - t.Fatal(err) + c.Fatal(err) } outChan <- "done" }() @@ -51,7 +51,7 @@ func TestGetContainersAttachWebsocket(t *testing.T) { inChan := make(chan string) go func() { if _, err := ws.Write(expected); err != nil { - t.Fatal(err) + c.Fatal(err) } inChan <- "done" }() @@ -60,8 +60,6 @@ func TestGetContainersAttachWebsocket(t *testing.T) { <-outChan if !bytes.Equal(expected, actual) { - t.Fatal("Expected output on websocket to match input") + c.Fatal("Expected output on websocket to match input") } - - logDone("container attach websocket - can echo input via cat") } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 1dea478452571..db4093733e6cd 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -7,65 +7,59 @@ import ( "net/http" "os/exec" "strings" - "testing" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" + "github.com/go-check/check" ) -func TestContainerApiGetAll(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestContainerApiGetAll(c *check.C) { startCount, err := getContainerCount() if err != nil { - t.Fatalf("Cannot query container count: %v", err) + c.Fatalf("Cannot query container count: %v", err) } name := "getall" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } _, body, err := sockRequest("GET", "/containers/json?all=1", nil) if err != nil { - t.Fatalf("GET all containers sockRequest failed: %v", err) + c.Fatalf("GET all containers sockRequest failed: %v", err) } var inspectJSON []struct { Names []string } if err = json.Unmarshal(body, &inspectJSON); err != nil { - t.Fatalf("unable to unmarshal response body: %v", err) + c.Fatalf("unable to unmarshal response body: %v", err) } if len(inspectJSON) != startCount+1 { - t.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount) + c.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount) } if actual := inspectJSON[0].Names[0]; actual != "/"+name { - t.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual) + c.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual) } - - logDone("container REST API - check GET json/all=1") } -func TestContainerApiGetExport(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestContainerApiGetExport(c *check.C) { name := "exportcontainer" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } _, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) if err != nil { - t.Fatalf("GET containers/export sockRequest failed: %v", err) + c.Fatalf("GET containers/export sockRequest failed: %v", err) } found := false @@ -75,7 +69,7 @@ func TestContainerApiGetExport(t *testing.T) { if err == io.EOF { break } - t.Fatal(err) + c.Fatal(err) } if h.Name == "test" { found = true @@ -84,25 +78,21 @@ func TestContainerApiGetExport(t *testing.T) { } if !found { - t.Fatalf("The created test file has not been found in the exported image") + c.Fatalf("The created test file has not been found in the exported image") } - - logDone("container REST API - check GET containers/export") } -func TestContainerApiGetChanges(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestContainerApiGetChanges(c *check.C) { name := "changescontainer" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "rm", "/etc/passwd") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } _, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) if err != nil { - t.Fatalf("GET containers/changes sockRequest failed: %v", err) + c.Fatalf("GET containers/changes sockRequest failed: %v", err) } changes := []struct { @@ -110,7 +100,7 @@ func TestContainerApiGetChanges(t *testing.T) { Path string }{} if err = json.Unmarshal(body, &changes); err != nil { - t.Fatalf("unable to unmarshal response body: %v", err) + c.Fatalf("unable to unmarshal response body: %v", err) } // Check the changelog for removal of /etc/passwd @@ -121,14 +111,11 @@ func TestContainerApiGetChanges(t *testing.T) { } } if !success { - t.Fatalf("/etc/passwd has been removed but is not present in the diff") + c.Fatalf("/etc/passwd has been removed but is not present in the diff") } - - logDone("container REST API - check GET containers/changes") } -func TestContainerApiStartVolumeBinds(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiStartVolumeBinds(c *check.C) { name := "testing" config := map[string]interface{}{ "Image": "busybox", @@ -136,7 +123,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } bindPath := randomUnixTmpDirPath("test") @@ -144,24 +131,21 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { "Binds": []string{bindPath + ":/tmp"}, } if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } pth, err := inspectFieldMap(name, "Volumes", "/tmp") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pth != bindPath { - t.Fatalf("expected volume host path to be %s, got %s", bindPath, pth) + c.Fatalf("expected volume host path to be %s, got %s", bindPath, pth) } - - logDone("container REST API - check volume binds on start") } // Test for GH#10618 -func TestContainerApiStartDupVolumeBinds(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) { name := "testdups" config := map[string]interface{}{ "Image": "busybox", @@ -169,7 +153,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } bindPath1 := randomUnixTmpDirPath("test1") @@ -179,22 +163,19 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { "Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"}, } if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { - t.Fatal("expected container start to fail when duplicate volume binds to same container path") + c.Fatal("expected container start to fail when duplicate volume binds to same container path") } else { if !strings.Contains(string(body), "Duplicate volume") { - t.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) + c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) } } - - logDone("container REST API - check for duplicate volume binds error on start") } -func TestContainerApiStartVolumesFrom(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { volName := "voltst" volPath := "/tmp" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } name := "testing" @@ -204,41 +185,38 @@ func TestContainerApiStartVolumesFrom(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } config = map[string]interface{}{ "VolumesFrom": []string{volName}, } if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } pth2, err := inspectFieldMap(volName, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if pth != pth2 { - t.Fatalf("expected volume host path to be %s, got %s", pth, pth2) + c.Fatalf("expected volume host path to be %s, got %s", pth, pth2) } - - logDone("container REST API - check VolumesFrom on start") } // Ensure that volumes-from has priority over binds/anything else // This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start -func TestVolumesFromHasPriority(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestVolumesFromHasPriority(c *check.C) { volName := "voltst2" volPath := "/tmp" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } name := "testing" @@ -248,7 +226,7 @@ func TestVolumesFromHasPriority(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } bindPath := randomUnixTmpDirPath("test") @@ -257,34 +235,31 @@ func TestVolumesFromHasPriority(t *testing.T) { "Binds": []string{bindPath + ":/tmp"}, } if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } pth2, err := inspectFieldMap(volName, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if pth != pth2 { - t.Fatalf("expected volume host path to be %s, got %s", pth, pth2) + c.Fatalf("expected volume host path to be %s, got %s", pth, pth2) } - - logDone("container REST API - check VolumesFrom has priority") } -func TestGetContainerStats(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestGetContainerStats(c *check.C) { var ( name = "statscontainer" runCmd = exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top") ) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } type b struct { body []byte @@ -299,38 +274,36 @@ func TestGetContainerStats(t *testing.T) { // allow some time to stream the stats from the container time.Sleep(4 * time.Second) if _, err := runCommand(exec.Command(dockerBinary, "rm", "-f", name)); err != nil { - t.Fatal(err) + c.Fatal(err) } // collect the results from the stats stream or timeout and fail // if the stream was not disconnected. select { case <-time.After(2 * time.Second): - t.Fatal("stream was not closed after container was removed") + c.Fatal("stream was not closed after container was removed") case sr := <-bc: if sr.err != nil { - t.Fatal(sr.err) + c.Fatal(sr.err) } dec := json.NewDecoder(bytes.NewBuffer(sr.body)) var s *types.Stats // decode only one object from the stream if err := dec.Decode(&s); err != nil { - t.Fatal(err) + c.Fatal(err) } } - logDone("container REST API - check GET containers/stats") } -func TestGetStoppedContainerStats(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) { var ( name = "statscontainer" runCmd = exec.Command(dockerBinary, "create", "--name", name, "busybox", "top") ) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } go func() { @@ -338,17 +311,15 @@ func TestGetStoppedContainerStats(t *testing.T) { // just send request and see if panic or error would happen on daemon side. _, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } }() // allow some time to send request and let daemon deal with it time.Sleep(1 * time.Second) - - logDone("container REST API - check GET stopped containers/stats") } -func TestBuildApiDockerfilePath(t *testing.T) { +func (s *DockerSuite) TestBuildApiDockerfilePath(c *check.C) { // Test to make sure we stop people from trying to leave the // build context when specifying the path to the dockerfile buffer := new(bytes.Buffer) @@ -360,33 +331,31 @@ func TestBuildApiDockerfilePath(t *testing.T) { Name: "Dockerfile", Size: int64(len(dockerfile)), }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write(dockerfile); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } _, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") if err == nil { out, _ := readBody(body) - t.Fatalf("Build was supposed to fail: %s", out) + c.Fatalf("Build was supposed to fail: %s", out) } out, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(string(out), "must be within the build context") { - t.Fatalf("Didn't complain about leaving build context: %s", out) + c.Fatalf("Didn't complain about leaving build context: %s", out) } - - logDone("container REST API - check build w/bad Dockerfile path") } -func TestBuildApiDockerFileRemote(t *testing.T) { +func (s *DockerSuite) TestBuildApiDockerFileRemote(c *check.C) { server, err := fakeStorage(map[string]string{ "testD": `FROM busybox COPY * /tmp/ @@ -394,17 +363,17 @@ RUN find / -name ba* RUN find /tmp/`, }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") if err != nil { - t.Fatalf("Build failed: %s", err) + c.Fatalf("Build failed: %s", err) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Make sure Dockerfile exists. @@ -412,41 +381,37 @@ RUN find /tmp/`, out := string(buf) if !strings.Contains(out, "/tmp/Dockerfile") || strings.Contains(out, "baz") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build with -f from remote") } -func TestBuildApiLowerDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildApiLowerDockerfile(c *check.C) { git, err := fakeGIT("repo", map[string]string{ "dockerfile": `FROM busybox RUN echo from dockerfile`, }, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { buf, _ := readBody(body) - t.Fatalf("Build failed: %s\n%q", err, buf) + c.Fatalf("Build failed: %s\n%q", err, buf) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } out := string(buf) if !strings.Contains(out, "from dockerfile") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build with lower dockerfile") } -func TestBuildApiBuildGitWithF(t *testing.T) { +func (s *DockerSuite) TestBuildApiBuildGitWithF(c *check.C) { git, err := fakeGIT("repo", map[string]string{ "baz": `FROM busybox RUN echo from baz`, @@ -454,7 +419,7 @@ RUN echo from baz`, RUN echo from Dockerfile`, }, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() @@ -462,23 +427,21 @@ RUN echo from Dockerfile`, _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") if err != nil { buf, _ := readBody(body) - t.Fatalf("Build failed: %s\n%q", err, buf) + c.Fatalf("Build failed: %s\n%q", err, buf) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } out := string(buf) if !strings.Contains(out, "from baz") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build from git w/F") } -func TestBuildApiDoubleDockerfile(t *testing.T) { - testRequires(t, UnixCli) // dockerfile overwrites Dockerfile on Windows +func (s *DockerSuite) TestBuildApiDoubleDockerfile(c *check.C) { + testRequires(c, UnixCli) // dockerfile overwrites Dockerfile on Windows git, err := fakeGIT("repo", map[string]string{ "Dockerfile": `FROM busybox RUN echo from Dockerfile`, @@ -486,29 +449,27 @@ RUN echo from Dockerfile`, RUN echo from dockerfile`, }, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() // Make sure it tries to 'dockerfile' query param value _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { - t.Fatalf("Build failed: %s", err) + c.Fatalf("Build failed: %s", err) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } out := string(buf) if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build with two dockerfiles") } -func TestBuildApiDockerfileSymlink(t *testing.T) { +func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) { // Test to make sure we stop people from trying to leave the // build context when specifying a symlink as the path to the dockerfile buffer := new(bytes.Buffer) @@ -520,20 +481,20 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { Typeflag: tar.TypeSymlink, Linkname: "/etc/passwd", }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } _, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") if err == nil { out, _ := readBody(body) - t.Fatalf("Build was supposed to fail: %s", out) + c.Fatalf("Build was supposed to fail: %s", out) } out, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } // The reason the error is "Cannot locate specified Dockerfile" is because @@ -541,96 +502,89 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { // Dockerfile -> /etc/passwd becomes etc/passwd from the context which is // a nonexistent file. if !strings.Contains(string(out), "Cannot locate specified Dockerfile: Dockerfile") { - t.Fatalf("Didn't complain about leaving build context: %s", out) + c.Fatalf("Didn't complain about leaving build context: %s", out) } - - logDone("container REST API - check build w/bad Dockerfile symlink path") } // #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume -func TestPostContainerBindNormalVolume(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } fooDir, err := inspectFieldMap("one", "Volumes", "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} if status, _, err := sockRequest("POST", "/containers/two/start", bindSpec); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } fooDir2, err := inspectFieldMap("two", "Volumes", "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } if fooDir2 != fooDir { - t.Fatalf("expected volume path to be %s, got: %s", fooDir, fooDir2) + c.Fatalf("expected volume path to be %s, got: %s", fooDir, fooDir2) } - - logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume") } -func TestContainerApiPause(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiPause(c *check.C) { defer unpauseAllContainers() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "30") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } ContainerID := strings.TrimSpace(out) if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && status != http.StatusNoContent { - t.Fatalf("POST a container pause: sockRequest failed: %v", err) + c.Fatalf("POST a container pause: sockRequest failed: %v", err) } pausedContainers, err := getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] { - t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) + c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && status != http.StatusNoContent { - t.Fatalf("POST a container pause: sockRequest failed: %v", err) + c.Fatalf("POST a container pause: sockRequest failed: %v", err) } pausedContainers, err = getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if pausedContainers != nil { - t.Fatalf("There should be no paused container.") + c.Fatalf("There should be no paused container.") } - - logDone("container REST API - check POST containers/pause and unpause") } -func TestContainerApiTop(t *testing.T) { - defer deleteAllContainers() - out, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") - id := strings.TrimSpace(out) +func (s *DockerSuite) TestContainerApiTop(c *check.C) { + out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "top").CombinedOutput() + if err != nil { + c.Fatal(err, out) + } + id := strings.TrimSpace(string(out)) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } type topResp struct { @@ -640,40 +594,41 @@ func TestContainerApiTop(t *testing.T) { var top topResp _, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := json.Unmarshal(b, &top); err != nil { - t.Fatal(err) + c.Fatal(err) } if len(top.Titles) != 11 { - t.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles) + c.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles) } if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { - t.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) + c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) } if len(top.Processes) != 2 { - t.Fatalf("expeted 2 processes, found %d: %v", len(top.Processes), top.Processes) + c.Fatalf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes) } - if top.Processes[0][10] != "/bin/sh -c cat" { - t.Fatalf("expected `/bin/sh -c cat`, found: %s", top.Processes[0][10]) + if top.Processes[0][10] != "/bin/sh -c top" { + c.Fatalf("expected `/bin/sh -c top`, found: %s", top.Processes[0][10]) } - if top.Processes[1][10] != "cat" { - t.Fatalf("expected `cat`, found: %s", top.Processes[1][10]) + if top.Processes[1][10] != "top" { + c.Fatalf("expected `top`, found: %s", top.Processes[1][10]) } - - logDone("containers REST API - GET /containers//top") } -func TestContainerApiCommit(t *testing.T) { - out, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") - id := strings.TrimSpace(out) +func (s *DockerSuite) TestContainerApiCommit(c *check.C) { + out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput() + if err != nil { + c.Fatal(err, out) + } + id := strings.TrimSpace(string(out)) name := "testcommit" _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - t.Fatal(err) + c.Fatal(err) } type resp struct { @@ -681,22 +636,25 @@ func TestContainerApiCommit(t *testing.T) { } var img resp if err := json.Unmarshal(b, &img); err != nil { - t.Fatal(err) + c.Fatal(err) } defer deleteImages(img.Id) - out, err = inspectField(img.Id, "Config.Cmd") - if out != "[/bin/sh -c touch /test]" { - t.Fatalf("got wrong Cmd from commit: %q", out) + cmd, err := inspectField(img.Id, "Config.Cmd") + if err != nil { + c.Fatal(err) + } + if cmd != "[/bin/sh -c touch /test]" { + c.Fatalf("got wrong Cmd from commit: %q", cmd) } // sanity check, make sure the image is what we think it is - dockerCmd(t, "run", img.Id, "ls", "/test") - - logDone("containers REST API - POST /commit") + out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput() + if err != nil { + c.Fatal(out, err) + } } -func TestContainerApiCreate(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiCreate(c *check.C) { config := map[string]interface{}{ "Image": "busybox", "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"}, @@ -704,26 +662,26 @@ func TestContainerApiCreate(t *testing.T) { _, b, err := sockRequest("POST", "/containers/create", config) if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - t.Fatal(err) + c.Fatal(err) } type createResp struct { Id string } var container createResp if err := json.Unmarshal(b, &container); err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _ := dockerCmd(t, "start", "-a", container.Id) - if strings.TrimSpace(out) != "/test" { - t.Fatalf("expected output `/test`, got %q", out) + out, err := exec.Command(dockerBinary, "start", "-a", container.Id).CombinedOutput() + if err != nil { + c.Fatal(out, err) + } + if strings.TrimSpace(string(out)) != "/test" { + c.Fatalf("expected output `/test`, got %q", out) } - - logDone("containers REST API - POST /containers/create") } -func TestContainerApiVerifyHeader(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { config := map[string]interface{}{ "Image": "busybox", } @@ -731,7 +689,7 @@ func TestContainerApiVerifyHeader(t *testing.T) { create := func(ct string) (int, io.ReadCloser, error) { jsonData := bytes.NewBuffer(nil) if err := json.NewEncoder(jsonData).Encode(config); err != nil { - t.Fatal(err) + c.Fatal(err) } return sockRequestRaw("POST", "/containers/create", jsonData, ct) } @@ -740,14 +698,14 @@ func TestContainerApiVerifyHeader(t *testing.T) { _, body, err := create("") if err == nil { b, _ := readBody(body) - t.Fatalf("expected error when content-type is not set: %q", string(b)) + c.Fatalf("expected error when content-type is not set: %q", string(b)) } body.Close() // Try with wrong content-type _, body, err = create("application/xml") if err == nil { b, _ := readBody(body) - t.Fatalf("expected error when content-type is not set: %q", string(b)) + c.Fatalf("expected error when content-type is not set: %q", string(b)) } body.Close() @@ -755,16 +713,14 @@ func TestContainerApiVerifyHeader(t *testing.T) { _, body, err = create("application/json") if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { b, _ := readBody(body) - t.Fatalf("%v - %q", err, string(b)) + c.Fatalf("%v - %q", err, string(b)) } body.Close() - - logDone("containers REST API - verify create header") } // Issue 7941 - test to make sure a "null" in JSON is just ignored. // W/o this fix a null in JSON would be parsed into a string var as "null" -func TestContainerApiPostCreateNull(t *testing.T) { +func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { config := `{ "Hostname":"", "Domainname":"", @@ -792,33 +748,31 @@ func TestContainerApiPostCreateNull(t *testing.T) { _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { b, _ := readBody(body) - t.Fatal(err, string(b)) + c.Fatal(err, string(b)) } b, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } type createResp struct { Id string } var container createResp if err := json.Unmarshal(b, &container); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err := inspectField(container.Id, "HostConfig.CpusetCpus") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if out != "" { - t.Fatalf("expected empty string, got %q", out) + c.Fatalf("expected empty string, got %q", out) } - - logDone("containers REST API - Create Null") } -func TestCreateWithTooLowMemoryLimit(t *testing.T) { +func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { defer deleteAllContainers() config := `{ "Image": "busybox", @@ -831,22 +785,21 @@ func TestCreateWithTooLowMemoryLimit(t *testing.T) { _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { - t.Fatal(err2) + c.Fatal(err2) } if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") } - logDone("container REST API - create can't set too low memory limit") } -func TestStartWithTooLowMemoryLimit(t *testing.T) { +func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { defer deleteAllContainers() out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } containerID := strings.TrimSpace(out) @@ -859,12 +812,10 @@ func TestStartWithTooLowMemoryLimit(t *testing.T) { _, body, err := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { - t.Fatal(err2) + c.Fatal(err2) } if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") } - - logDone("container REST API - start can't set too low memory limit") } diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go index d4290408e1a91..09a13bad21bed 100644 --- a/integration-cli/docker_api_exec_resize_test.go +++ b/integration-cli/docker_api_exec_resize_test.go @@ -4,26 +4,24 @@ import ( "net/http" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestExecResizeApiHeightWidthNoInt(t *testing.T) { +func (s *DockerSuite) TestExecResizeApiHeightWidthNoInt(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) if err == nil { - t.Fatal("Expected exec resize Request to fail") + c.Fatal("Expected exec resize Request to fail") } if status != http.StatusInternalServerError { - t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) } - - logDone("container exec resize - height, width no int fail") } diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go index f898250a11470..4299f00ec9c73 100644 --- a/integration-cli/docker_api_exec_test.go +++ b/integration-cli/docker_api_exec_test.go @@ -6,22 +6,20 @@ import ( "bytes" "fmt" "os/exec" - "testing" + + "github.com/go-check/check" ) // Regression test for #9414 -func TestExecApiCreateNoCmd(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecApiCreateNoCmd(c *check.C) { name := "exec_test" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } _, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) if err == nil || !bytes.Contains(body, []byte("No exec command specified")) { - t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) + c.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) } - - logDone("exec create API - returns error when missing Cmd") } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 1774e6b74d4fb..e22f67d2b50de 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -3,50 +3,50 @@ package main import ( "encoding/json" "net/url" + "os/exec" "strings" - "testing" "github.com/docker/docker/api/types" + "github.com/go-check/check" ) -func TestLegacyImages(t *testing.T) { +func (s *DockerSuite) TestLegacyImages(c *check.C) { _, body, err := sockRequest("GET", "/v1.6/images/json", nil) if err != nil { - t.Fatalf("Error on GET: %s", err) + c.Fatalf("Error on GET: %s", err) } images := []types.LegacyImage{} if err = json.Unmarshal(body, &images); err != nil { - t.Fatalf("Error on unmarshal: %s", err) + c.Fatalf("Error on unmarshal: %s", err) } if len(images) == 0 || images[0].Tag == "" || images[0].Repository == "" { - t.Fatalf("Bad data: %q", images) + c.Fatalf("Bad data: %q", images) } - - logDone("images - checking legacy json") } -func TestApiImagesFilter(t *testing.T) { +func (s *DockerSuite) TestApiImagesFilter(c *check.C) { name := "utest:tag1" name2 := "utest/docker:tag2" name3 := "utest:5000/docker:tag3" defer deleteImages(name, name2, name3) - dockerCmd(t, "tag", "busybox", name) - dockerCmd(t, "tag", "busybox", name2) - dockerCmd(t, "tag", "busybox", name3) - + for _, n := range []string{name, name2, name3} { + if out, err := exec.Command(dockerBinary, "tag", "busybox", n).CombinedOutput(); err != nil { + c.Fatal(err, out) + } + } type image struct{ RepoTags []string } getImages := func(filter string) []image { v := url.Values{} v.Set("filter", filter) _, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } var images []image if err := json.Unmarshal(b, &images); err != nil { - t.Fatal(err) + c.Fatal(err) } return images @@ -54,48 +54,49 @@ func TestApiImagesFilter(t *testing.T) { errMsg := "incorrect number of matches returned" if images := getImages("utest*/*"); len(images[0].RepoTags) != 2 { - t.Fatal(errMsg) + c.Fatal(errMsg) } if images := getImages("utest"); len(images[0].RepoTags) != 1 { - t.Fatal(errMsg) + c.Fatal(errMsg) } if images := getImages("utest*"); len(images[0].RepoTags) != 1 { - t.Fatal(errMsg) + c.Fatal(errMsg) } if images := getImages("*5000*/*"); len(images[0].RepoTags) != 1 { - t.Fatal(errMsg) + c.Fatal(errMsg) } - - logDone("images - filter param is applied") } -func TestApiImagesSaveAndLoad(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { + testRequires(c, Network) out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) defer deleteImages("saveandload") _, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer body.Close() - dockerCmd(t, "rmi", id) + if out, err := exec.Command(dockerBinary, "rmi", id).CombinedOutput(); err != nil { + c.Fatal(err, out) + } _, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer loadBody.Close() - out, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) - if strings.TrimSpace(out) != id { - t.Fatal("load did not work properly") + inspectOut, err := exec.Command(dockerBinary, "inspect", "--format='{{ .Id }}'", id).CombinedOutput() + if err != nil { + c.Fatal(err, inspectOut) + } + if strings.TrimSpace(string(inspectOut)) != id { + c.Fatal("load did not work properly") } - - logDone("images API - save and load") } diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go index a934ed6ccd3b6..5f7c9515c9b9d 100644 --- a/integration-cli/docker_api_info_test.go +++ b/integration-cli/docker_api_info_test.go @@ -33,6 +33,4 @@ func TestInfoApi(t *testing.T) { t.Errorf("couldn't find string %v in output", linePrefix) } } - - logDone("container REST API - check GET /info") } diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index 43144f9165243..982962261390d 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -4,16 +4,15 @@ import ( "encoding/json" "os/exec" "strings" - "testing" -) -func TestInspectApiContainerResponse(t *testing.T) { - defer deleteAllContainers() + "github.com/go-check/check" +) +func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -29,12 +28,12 @@ func TestInspectApiContainerResponse(t *testing.T) { } _, body, err := sockRequest("GET", endpoint, nil) if err != nil { - t.Fatalf("sockRequest failed for %s version: %v", testVersion, err) + c.Fatalf("sockRequest failed for %s version: %v", testVersion, err) } var inspectJSON map[string]interface{} if err = json.Unmarshal(body, &inspectJSON); err != nil { - t.Fatalf("unable to unmarshal body for %s version: %v", testVersion, err) + c.Fatalf("unable to unmarshal body for %s version: %v", testVersion, err) } keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW"} @@ -47,14 +46,12 @@ func TestInspectApiContainerResponse(t *testing.T) { for _, key := range keys { if _, ok := inspectJSON[key]; !ok { - t.Fatalf("%s does not exist in response for %s version", key, testVersion) + c.Fatalf("%s does not exist in response for %s version", key, testVersion) } } //Issue #6830: type not properly converted to JSON/back if _, ok := inspectJSON["Path"].(bool); ok { - t.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling") + c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling") } } - - logDone("container json - check keys in container json response") } diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index 27eb31c339be8..bbf9d17cbd971 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -5,49 +5,44 @@ import ( "fmt" "net/http" "os/exec" - "testing" + + "github.com/go-check/check" ) -func TestLogsApiWithStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { name := "logs_test" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name) if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) if err != nil || statusCode != http.StatusOK { - t.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) + c.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) } if !bytes.Contains(body, []byte(name)) { - t.Fatalf("Expected %s, got %s", name, string(body[:])) + c.Fatalf("Expected %s, got %s", name, string(body[:])) } - - logDone("logs API - with stdout ok") } -func TestLogsApiNoStdoutNorStderr(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLogsApiNoStdoutNorStderr(c *check.C) { name := "logs_test" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) if err == nil || statusCode != http.StatusBadRequest { - t.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) + c.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) } expected := "Bad parameters: you must choose at least one stream" if !bytes.Contains(body, []byte(expected)) { - t.Fatalf("Expected %s, got %s", expected, string(body[:])) + c.Fatalf("Expected %s, got %s", expected, string(body[:])) } - - logDone("logs API - returns error when no stdout nor stderr specified") } diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 8c7b87eb4f219..6f5019b6dad17 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -4,72 +4,64 @@ import ( "net/http" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestResizeApiResponse(t *testing.T) { +func (s *DockerSuite) TestResizeApiResponse(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" _, _, err = sockRequest("POST", endpoint, nil) if err != nil { - t.Fatalf("resize Request failed %v", err) + c.Fatalf("resize Request failed %v", err) } - - logDone("container resize - when started") } -func TestResizeApiHeightWidthNoInt(t *testing.T) { +func (s *DockerSuite) TestResizeApiHeightWidthNoInt(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) if err == nil { - t.Fatal("Expected resize Request to fail") + c.Fatal("Expected resize Request to fail") } if status != http.StatusInternalServerError { - t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) } - - logDone("container resize - height, width no int fail") } -func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { +func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) // make sure the exited container is not running runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" _, body, err := sockRequest("POST", endpoint, nil) if err == nil { - t.Fatalf("resize should fail when container is not started") + c.Fatalf("resize should fail when container is not started") } if !strings.Contains(string(body), "Cannot resize container") && !strings.Contains(string(body), cleanedContainerID) { - t.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body)) + c.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body)) } - - logDone("container resize - when not started should not resize") } diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go index 2846fb1d398ce..d1ea6b9f896c8 100644 --- a/integration-cli/docker_api_version_test.go +++ b/integration-cli/docker_api_version_test.go @@ -2,23 +2,23 @@ package main import ( "encoding/json" - "testing" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + "github.com/go-check/check" ) -func TestGetVersion(t *testing.T) { +func (s *DockerSuite) TestGetVersion(c *check.C) { _, body, err := sockRequest("GET", "/version", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } var v types.Version if err := json.Unmarshal(body, &v); err != nil { - t.Fatal(err) + c.Fatal(err) } if v.Version != dockerversion.VERSION { - t.Fatal("Version mismatch") + c.Fatal("Version mismatch") } } diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 08b109f883260..11ae1584ad2b2 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -6,14 +6,14 @@ import ( "os/exec" "strings" "sync" - "testing" "time" + + "github.com/go-check/check" ) const attachWait = 5 * time.Second -func TestAttachMultipleAndRestart(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachMultipleAndRestart(c *check.C) { endGroup := &sync.WaitGroup{} startGroup := &sync.WaitGroup{} @@ -21,7 +21,7 @@ func TestAttachMultipleAndRestart(t *testing.T) { startGroup.Add(3) if err := waitForContainer("attacher", "-d", "busybox", "/bin/sh", "-c", "while true; do sleep 1; echo hello; done"); err != nil { - t.Fatal(err) + c.Fatal(err) } startDone := make(chan struct{}) @@ -39,32 +39,32 @@ func TestAttachMultipleAndRestart(t *testing.T) { for i := 0; i < 3; i++ { go func() { - c := exec.Command(dockerBinary, "attach", "attacher") + cmd := exec.Command(dockerBinary, "attach", "attacher") defer func() { - c.Wait() + cmd.Wait() endGroup.Done() }() - out, err := c.StdoutPipe() + out, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } - if err := c.Start(); err != nil { - t.Fatal(err) + if err := cmd.Start(); err != nil { + c.Fatal(err) } buf := make([]byte, 1024) if _, err := out.Read(buf); err != nil && err != io.EOF { - t.Fatal(err) + c.Fatal(err) } startGroup.Done() if !strings.Contains(string(buf), "hello") { - t.Fatalf("unexpected output %s expected hello\n", string(buf)) + c.Fatalf("unexpected output %s expected hello\n", string(buf)) } }() } @@ -72,41 +72,39 @@ func TestAttachMultipleAndRestart(t *testing.T) { select { case <-startDone: case <-time.After(attachWait): - t.Fatalf("Attaches did not initialize properly") + c.Fatalf("Attaches did not initialize properly") } cmd := exec.Command(dockerBinary, "kill", "attacher") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } select { case <-endDone: case <-time.After(attachWait): - t.Fatalf("Attaches did not finish properly") + c.Fatalf("Attaches did not finish properly") } - logDone("attach - multiple attach") } -func TestAttachTtyWithoutStdin(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachTtyWithoutStdin(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to start container: %v (%v)", out, err) + c.Fatalf("failed to start container: %v (%v)", out, err) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } defer func() { cmd := exec.Command(dockerBinary, "kill", id) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to kill container: %v (%v)", out, err) + c.Fatalf("failed to kill container: %v (%v)", out, err) } }() @@ -116,70 +114,67 @@ func TestAttachTtyWithoutStdin(t *testing.T) { cmd := exec.Command(dockerBinary, "attach", id) if _, err := cmd.StdinPipe(); err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("attach should have failed") + c.Fatal("attach should have failed") } else if !strings.Contains(out, expected) { - t.Fatalf("attach failed with error %q: expected %q", out, expected) + c.Fatalf("attach failed with error %q: expected %q", out, expected) } }() select { case <-done: case <-time.After(attachWait): - t.Fatal("attach is running but should have failed") + c.Fatal("attach is running but should have failed") } - logDone("attach - forbid piped stdin to tty enabled container") } -func TestAttachDisconnect(t *testing.T) { - defer deleteAllContainers() - out, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") +func (s *DockerSuite) TestAttachDisconnect(c *check.C) { + out, _ := dockerCmd(c, "run", "-di", "busybox", "/bin/cat") id := strings.TrimSpace(out) cmd := exec.Command(dockerBinary, "attach", id) stdin, err := cmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdin.Close() stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdout.Close() if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer cmd.Process.Kill() if _, err := stdin.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } if err := stdin.Close(); err != nil { - t.Fatal(err) + c.Fatal(err) } // Expect container to still be running after stdin is closed running, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } - logDone("attach - disconnect") } diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index e17256cf74d37..5567c92a0b943 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -6,26 +6,25 @@ import ( "bufio" "os/exec" "strings" - "testing" "time" "github.com/docker/docker/pkg/stringid" + "github.com/go-check/check" "github.com/kr/pty" ) // #9860 -func TestAttachClosedOnContainerStop(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-dti", "busybox", "sleep", "2") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to start container: %v (%v)", out, err) + c.Fatalf("failed to start container: %v (%v)", out, err) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } done := make(chan struct{}) @@ -35,7 +34,7 @@ func TestAttachClosedOnContainerStop(t *testing.T) { _, tty, err := pty.Open() if err != nil { - t.Fatalf("could not open pty: %v", err) + c.Fatalf("could not open pty: %v", err) } attachCmd := exec.Command(dockerBinary, "attach", id) attachCmd.Stdin = tty @@ -43,31 +42,29 @@ func TestAttachClosedOnContainerStop(t *testing.T) { attachCmd.Stderr = tty if err := attachCmd.Run(); err != nil { - t.Fatalf("attach returned error %s", err) + c.Fatalf("attach returned error %s", err) } }() waitCmd := exec.Command(dockerBinary, "wait", id) if out, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } select { case <-done: case <-time.After(attachWait): - t.Fatal("timed out without attach returning") + c.Fatal("timed out without attach returning") } - logDone("attach - return after container finished") } -func TestAttachAfterDetach(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { name := "detachtest" cpty, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command(dockerBinary, "run", "-ti", "--name", name, "busybox") cmd.Stdin = tty @@ -77,14 +74,14 @@ func TestAttachAfterDetach(t *testing.T) { detached := make(chan struct{}) go func() { if err := cmd.Run(); err != nil { - t.Fatalf("attach returned error %s", err) + c.Fatalf("attach returned error %s", err) } close(detached) }() time.Sleep(500 * time.Millisecond) if err := waitRun(name); err != nil { - t.Fatal(err) + c.Fatal(err) } cpty.Write([]byte{16}) time.Sleep(100 * time.Millisecond) @@ -94,7 +91,7 @@ func TestAttachAfterDetach(t *testing.T) { cpty, tty, err = pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd = exec.Command(dockerBinary, "attach", name) @@ -103,7 +100,7 @@ func TestAttachAfterDetach(t *testing.T) { cmd.Stderr = tty if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } bytes := make([]byte, 10) @@ -123,34 +120,33 @@ func TestAttachAfterDetach(t *testing.T) { select { case err := <-readErr: if err != nil { - t.Fatal(err) + c.Fatal(err) } case <-time.After(2 * time.Second): - t.Fatal("timeout waiting for attach read") + c.Fatal("timeout waiting for attach read") } if err := cmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(string(bytes[:nBytes]), "/ #") { - t.Fatalf("failed to get a new prompt. got %s", string(bytes[:nBytes])) + c.Fatalf("failed to get a new prompt. got %s", string(bytes[:nBytes])) } - logDone("attach - reconnect after detaching") } // TestAttachDetach checks that attach in tty mode can be detached using the long container ID -func TestAttachDetach(t *testing.T) { - out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") +func (s *DockerSuite) TestAttachDetach(c *check.C) { + out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat") id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } cpty, tty, err := pty.Open() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer cpty.Close() @@ -158,34 +154,34 @@ func TestAttachDetach(t *testing.T) { cmd.Stdin = tty stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdout.Close() if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := waitRun(id); err != nil { - t.Fatalf("error waiting for container to start: %v", err) + c.Fatalf("error waiting for container to start: %v", err) } if _, err := cpty.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } // escape sequence if _, err := cpty.Write([]byte{16}); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(100 * time.Millisecond) if _, err := cpty.Write([]byte{17}); err != nil { - t.Fatal(err) + c.Fatal(err) } ch := make(chan struct{}) @@ -196,36 +192,35 @@ func TestAttachDetach(t *testing.T) { running, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } go func() { - dockerCmd(t, "kill", id) + dockerCmd(c, "kill", id) }() select { case <-ch: case <-time.After(10 * time.Millisecond): - t.Fatal("timed out waiting for container to exit") + c.Fatal("timed out waiting for container to exit") } - logDone("attach - detach") } // TestAttachDetachTruncatedID checks that attach in tty mode can be detached -func TestAttachDetachTruncatedID(t *testing.T) { - out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") +func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) { + out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat") id := stringid.TruncateID(strings.TrimSpace(out)) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } cpty, tty, err := pty.Open() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer cpty.Close() @@ -233,31 +228,31 @@ func TestAttachDetachTruncatedID(t *testing.T) { cmd.Stdin = tty stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdout.Close() if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := cpty.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } // escape sequence if _, err := cpty.Write([]byte{16}); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(100 * time.Millisecond) if _, err := cpty.Write([]byte{17}); err != nil { - t.Fatal(err) + c.Fatal(err) } ch := make(chan struct{}) @@ -268,21 +263,20 @@ func TestAttachDetachTruncatedID(t *testing.T) { running, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } go func() { - dockerCmd(t, "kill", id) + dockerCmd(c, "kill", id) }() select { case <-ch: case <-time.After(10 * time.Millisecond): - t.Fatal("timed out waiting for container to exit") + c.Fatal("timed out waiting for container to exit") } - logDone("attach - detach truncated ID") } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 44370d4b9ff9a..a4ccd18ea4ed9 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -16,16 +16,16 @@ import ( "strconv" "strings" "sync" - "testing" "text/template" "time" "github.com/docker/docker/builder/command" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/stringutils" + "github.com/go-check/check" ) -func TestBuildJSONEmptyRun(t *testing.T) { +func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) { name := "testbuildjsonemptyrun" defer deleteImages(name) @@ -38,13 +38,12 @@ func TestBuildJSONEmptyRun(t *testing.T) { true) if err != nil { - t.Fatal("error when dealing with a RUN statement with empty JSON array") + c.Fatal("error when dealing with a RUN statement with empty JSON array") } - logDone("build - RUN with an empty array should not panic") } -func TestBuildEmptyWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyWhitespace(c *check.C) { name := "testbuildemptywhitespace" defer deleteImages(name) @@ -59,13 +58,12 @@ func TestBuildEmptyWhitespace(t *testing.T) { true) if err == nil { - t.Fatal("no error when dealing with a COPY statement with no content on the same line") + c.Fatal("no error when dealing with a COPY statement with no content on the same line") } - logDone("build - statements with whitespace and no content should generate a parse error") } -func TestBuildShCmdJSONEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) { name := "testbuildshcmdjsonentrypoint" defer deleteImages(name) @@ -79,7 +77,7 @@ func TestBuildShCmdJSONEntrypoint(t *testing.T) { true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput( @@ -90,17 +88,16 @@ func TestBuildShCmdJSONEntrypoint(t *testing.T) { name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "/bin/sh -c echo test" { - t.Fatal("CMD did not contain /bin/sh -c") + c.Fatal("CMD did not contain /bin/sh -c") } - logDone("build - CMD should always contain /bin/sh -c when specified without JSON") } -func TestBuildEnvironmentReplacementUser(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -110,22 +107,21 @@ func TestBuildEnvironmentReplacementUser(t *testing.T) { USER ${user} `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.User") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != `"foo"` { - t.Fatal("User foo from environment not in Config.User on image") + c.Fatal("User foo from environment not in Config.User on image") } - logDone("build - user environment replacement") } -func TestBuildEnvironmentReplacementVolume(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -135,28 +131,27 @@ func TestBuildEnvironmentReplacementVolume(t *testing.T) { VOLUME ${volume} `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } var volumes map[string]interface{} if err := json.Unmarshal([]byte(res), &volumes); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := volumes["/quux"]; !ok { - t.Fatal("Volume /quux from environment not in Config.Volumes on image") + c.Fatal("Volume /quux from environment not in Config.Volumes on image") } - logDone("build - volume environment replacement") } -func TestBuildEnvironmentReplacementExpose(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -166,28 +161,27 @@ func TestBuildEnvironmentReplacementExpose(t *testing.T) { EXPOSE ${port} `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } var exposedPorts map[string]interface{} if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := exposedPorts["80/tcp"]; !ok { - t.Fatal("Exposed port 80 from environment not in Config.ExposedPorts on image") + c.Fatal("Exposed port 80 from environment not in Config.ExposedPorts on image") } - logDone("build - expose environment replacement") } -func TestBuildEnvironmentReplacementWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -199,13 +193,12 @@ func TestBuildEnvironmentReplacementWorkdir(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - workdir environment replacement") } -func TestBuildEnvironmentReplacementAddCopy(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -230,18 +223,17 @@ func TestBuildEnvironmentReplacementAddCopy(t *testing.T) { }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add/copy environment replacement") } -func TestBuildEnvironmentReplacementEnv(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -263,18 +255,18 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Env") if err != nil { - t.Fatal(err) + c.Fatal(err) } envResult := []string{} if err = unmarshalJSON([]byte(res), &envResult); err != nil { - t.Fatal(err) + c.Fatal(err) } found := false @@ -285,33 +277,32 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { if parts[0] == "bar" { found = true if parts[1] != "zzz" { - t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1]) + c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1]) } } else if strings.HasPrefix(parts[0], "env") { envCount++ if parts[1] != "zzz" { - t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) + c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) } } else if strings.HasPrefix(parts[0], "env") { envCount++ if parts[1] != "foo" { - t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) + c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) } } } if !found { - t.Fatal("Never found the `bar` env variable") + c.Fatal("Never found the `bar` env variable") } if envCount != 4 { - t.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult) + c.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult) } - logDone("build - env environment replacement") } -func TestBuildHandleEscapes(t *testing.T) { +func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { name := "testbuildhandleescapes" defer deleteImages(name) @@ -324,22 +315,22 @@ func TestBuildHandleEscapes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } var result map[string]map[string]struct{} res, err := inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } if err = unmarshalJSON([]byte(res), &result); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := result["bar"]; !ok { - t.Fatal("Could not find volume bar set from env foo in volumes table") + c.Fatal("Could not find volume bar set from env foo in volumes table") } deleteImages(name) @@ -352,20 +343,20 @@ func TestBuildHandleEscapes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err = inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } if err = unmarshalJSON([]byte(res), &result); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := result["${FOO}"]; !ok { - t.Fatal("Could not find volume ${FOO} set from env foo in volumes table") + c.Fatal("Could not find volume ${FOO} set from env foo in volumes table") } deleteImages(name) @@ -382,26 +373,25 @@ func TestBuildHandleEscapes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err = inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } if err = unmarshalJSON([]byte(res), &result); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := result[`\\\${FOO}`]; !ok { - t.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result) + c.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result) } - logDone("build - handle escapes") } -func TestBuildOnBuildLowercase(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) { name := "testbuildonbuildlowercase" name2 := "testbuildonbuildlowercase2" @@ -414,7 +404,7 @@ func TestBuildOnBuildLowercase(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, out, err := buildImageWithOut(name2, fmt.Sprintf(` @@ -422,24 +412,22 @@ func TestBuildOnBuildLowercase(t *testing.T) { `, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "quux") { - t.Fatalf("Did not receive the expected echo text, got %s", out) + c.Fatalf("Did not receive the expected echo text, got %s", out) } if strings.Contains(out, "ONBUILD ONBUILD") { - t.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", out) + c.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", out) } - logDone("build - handle case-insensitive onbuild statement") } -func TestBuildEnvEscapes(t *testing.T) { +func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) { name := "testbuildenvescapes" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, ` FROM busybox @@ -451,20 +439,18 @@ func TestBuildEnvEscapes(t *testing.T) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "$" { - t.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) + c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) } - logDone("build - env should handle \\$ properly") } -func TestBuildEnvOverwrite(t *testing.T) { +func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) { name := "testbuildenvoverwrite" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, ` @@ -475,32 +461,30 @@ func TestBuildEnvOverwrite(t *testing.T) { true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-e", "TEST=bar", "-t", name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "bar" { - t.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) + c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) } - logDone("build - env should overwrite builder ENV during run") } -func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenmaintainerinsourceimage" defer deleteImages("onbuild") defer deleteImages(name) - defer deleteAllContainers() createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -508,7 +492,7 @@ func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) { commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"MAINTAINER docker.io\"]}", cleanedContainerID, "onbuild") if _, err := runCommand(commitCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -516,25 +500,23 @@ func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "maintainer isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about MAINTAINER and ONBUILD in source image", err) + c.Fatalf("Wrong error %v, must be about MAINTAINER and ONBUILD in source image", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden maintainer in source image") } -func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenfrominsourceimage" defer deleteImages("onbuild") defer deleteImages(name) - defer deleteAllContainers() createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -542,7 +524,7 @@ func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) { commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"FROM busybox\"]}", cleanedContainerID, "onbuild") if _, err := runCommand(commitCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -550,25 +532,23 @@ func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "from isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about FROM and ONBUILD in source image", err) + c.Fatalf("Wrong error %v, must be about FROM and ONBUILD in source image", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden from in source image") } -func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenChainedInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenchainedinsourceimage" defer deleteImages("onbuild") defer deleteImages(name) - defer deleteAllContainers() createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -576,7 +556,7 @@ func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) { commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"ONBUILD RUN ls\"]}", cleanedContainerID, "onbuild") if _, err := runCommand(commitCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -584,22 +564,20 @@ func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") { - t.Fatalf("Wrong error %v, must be about chaining ONBUILD in source image", err) + c.Fatalf("Wrong error %v, must be about chaining ONBUILD in source image", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden chained in source image") } -func TestBuildOnBuildCmdEntrypointJSON(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildCmdEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" defer deleteImages(name2) defer deleteImages(name1) - defer deleteAllContainers() _, err := buildImage(name1, ` FROM busybox @@ -609,34 +587,32 @@ ONBUILD RUN ["true"]`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name2, fmt.Sprintf(`FROM %s`, name1), false) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { - t.Fatal("did not get echo output from onbuild", out) + c.Fatal("did not get echo output from onbuild", out) } - logDone("build - onbuild with json entrypoint/cmd") } -func TestBuildOnBuildEntrypointJSON(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" defer deleteImages(name2) defer deleteImages(name1) - defer deleteAllContainers() _, err := buildImage(name1, ` FROM busybox @@ -644,28 +620,27 @@ ONBUILD ENTRYPOINT ["echo"]`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name2, fmt.Sprintf("FROM %s\nCMD [\"hello world\"]\n", name1), false) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { - t.Fatal("got malformed output from onbuild", out) + c.Fatal("got malformed output from onbuild", out) } - logDone("build - onbuild with json entrypoint") } -func TestBuildCacheADD(t *testing.T) { +func (s *DockerSuite) TestBuildCacheADD(c *check.C) { name := "testbuildtwoimageswithadd" defer deleteImages(name) server, err := fakeStorage(map[string]string{ @@ -673,7 +648,7 @@ func TestBuildCacheADD(t *testing.T) { "index.html": "world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -681,10 +656,10 @@ func TestBuildCacheADD(t *testing.T) { fmt.Sprintf(`FROM scratch ADD %s/robots.txt /`, server.URL()), true); err != nil { - t.Fatal(err) + c.Fatal(err) } if err != nil { - t.Fatal(err) + c.Fatal(err) } deleteImages(name) _, out, err := buildImageWithOut(name, @@ -692,16 +667,15 @@ func TestBuildCacheADD(t *testing.T) { ADD %s/index.html /`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.Contains(out, "Using cache") { - t.Fatal("2nd build used cache on ADD, it shouldn't") + c.Fatal("2nd build used cache on ADD, it shouldn't") } - logDone("build - build two images with remote ADD") } -func TestBuildLastModified(t *testing.T) { +func (s *DockerSuite) TestBuildLastModified(c *check.C) { name := "testbuildlastmodified" defer deleteImages(name) @@ -709,7 +683,7 @@ func TestBuildLastModified(t *testing.T) { "file": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -722,13 +696,13 @@ RUN ls -le /file` dockerfile := fmt.Sprintf(dFmt, server.URL()) if _, out, err = buildImageWithOut(name, dockerfile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } originMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out) // Make sure our regexp is correct if strings.Index(originMTime, "/file") < 0 { - t.Fatalf("Missing ls info on 'file':\n%s", out) + c.Fatalf("Missing ls info on 'file':\n%s", out) } // Build it again and make sure the mtime of the file didn't change. @@ -736,12 +710,12 @@ RUN ls -le /file` time.Sleep(2 * time.Second) if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } newMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out2) if newMTime != originMTime { - t.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", originMTime, newMTime) + c.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", originMTime, newMTime) } // Now 'touch' the file and make sure the timestamp DID change this time @@ -750,25 +724,24 @@ RUN ls -le /file` "file": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() dockerfile = fmt.Sprintf(dFmt, server.URL()) if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } newMTime = regexp.MustCompile(`root.*/file.*\n`).FindString(out2) if newMTime == originMTime { - t.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", originMTime, newMTime) + c.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", originMTime, newMTime) } - logDone("build - use Last-Modified header") } -func TestBuildSixtySteps(t *testing.T) { +func (s *DockerSuite) TestBuildSixtySteps(c *check.C) { name := "foobuildsixtysteps" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\n"+strings.Repeat("ADD foo /\n", 60), @@ -776,17 +749,16 @@ func TestBuildSixtySteps(t *testing.T) { "foo": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - build an image with sixty build steps") } -func TestBuildAddSingleFileToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToRoot(c *check.C) { name := "testaddimg" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -802,18 +774,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add single file to root") } // Issue #3960: "ADD src ." hangs -func TestBuildAddSingleFileToWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToWorkdir(c *check.C) { name := "testaddsinglefiletoworkdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -822,26 +793,25 @@ ADD test_file .`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() done := make(chan struct{}) go func() { if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } close(done) }() select { case <-time.After(5 * time.Second): - t.Fatal("Build with adding to workdir timed out") + c.Fatal("Build with adding to workdir timed out") case <-done: } - logDone("build - add single file to workdir") } -func TestBuildAddSingleFileToExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToExistDir(c *check.C) { name := "testaddsinglefiletoexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -858,22 +828,21 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add single file to existing dir") } -func TestBuildCopyAddMultipleFiles(t *testing.T) { +func (s *DockerSuite) TestBuildCopyAddMultipleFiles(c *check.C) { server, err := fakeStorage(map[string]string{ "robots.txt": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -905,16 +874,15 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - multiple file copy/add tests") } -func TestBuildAddMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildAddMultipleFilesToFile(c *check.C) { name := "testaddmultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -926,18 +894,17 @@ func TestBuildAddMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file") } -func TestBuildJSONAddMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFile(c *check.C) { name := "testjsonaddmultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -949,18 +916,17 @@ func TestBuildJSONAddMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file json syntax") } -func TestBuildAddMultipleFilesToFileWild(t *testing.T) { +func (s *DockerSuite) TestBuildAddMultipleFilesToFileWild(c *check.C) { name := "testaddmultiplefilestofilewild" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -972,18 +938,17 @@ func TestBuildAddMultipleFilesToFileWild(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file wild") } -func TestBuildJSONAddMultipleFilesToFileWild(t *testing.T) { +func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFileWild(c *check.C) { name := "testjsonaddmultiplefilestofilewild" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -995,18 +960,17 @@ func TestBuildJSONAddMultipleFilesToFileWild(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file wild json syntax") } -func TestBuildCopyMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildCopyMultipleFilesToFile(c *check.C) { name := "testcopymultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1018,18 +982,17 @@ func TestBuildCopyMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using COPY with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple copy files to file") } -func TestBuildJSONCopyMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildJSONCopyMultipleFilesToFile(c *check.C) { name := "testjsoncopymultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1041,18 +1004,17 @@ func TestBuildJSONCopyMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using COPY with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple copy files to file json syntax") } -func TestBuildAddFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildAddFileWithWhitespace(c *check.C) { name := "testaddfilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1080,16 +1042,15 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add file with whitespace") } -func TestBuildCopyFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildCopyFileWithWhitespace(c *check.C) { name := "testcopyfilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1117,16 +1078,15 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy file with whitespace") } -func TestBuildAddMultipleFilesToFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildAddMultipleFilesToFileWithWhitespace(c *check.C) { name := "testaddmultiplefilestofilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1138,18 +1098,17 @@ func TestBuildAddMultipleFilesToFileWithWhitespace(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file with whitespace") } -func TestBuildCopyMultipleFilesToFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildCopyMultipleFilesToFileWithWhitespace(c *check.C) { name := "testcopymultiplefilestofilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1161,18 +1120,17 @@ func TestBuildCopyMultipleFilesToFileWithWhitespace(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using COPY with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple copy files to file with whitespace") } -func TestBuildCopyWildcard(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { name := "testcopywildcard" defer deleteImages(name) server, err := fakeStorage(map[string]string{ @@ -1180,7 +1138,7 @@ func TestBuildCopyWildcard(t *testing.T) { "index.html": "world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -1203,28 +1161,27 @@ func TestBuildCopyWildcard(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Now make sure we use a cache the 2nd time id2, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("didn't use the cache") + c.Fatal("didn't use the cache") } - logDone("build - copy wild card") } -func TestBuildCopyWildcardNoFind(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) { name := "testcopywildcardnofind" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1232,21 +1189,20 @@ func TestBuildCopyWildcardNoFind(t *testing.T) { `, nil) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImageFromContext(name, ctx, true) if err == nil { - t.Fatal("should have failed to find a file") + c.Fatal("should have failed to find a file") } if !strings.Contains(err.Error(), "No source files were specified") { - t.Fatalf("Wrong error %v, must be about no source files", err) + c.Fatalf("Wrong error %v, must be about no source files", err) } - logDone("build - copy wild card no find") } -func TestBuildCopyWildcardCache(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { name := "testcopywildcardcache" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1256,12 +1212,12 @@ func TestBuildCopyWildcardCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Now make sure we use a cache the 2nd time even with wild cards. @@ -1271,17 +1227,16 @@ func TestBuildCopyWildcardCache(t *testing.T) { id2, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("didn't use the cache") + c.Fatal("didn't use the cache") } - logDone("build - copy wild card cache") } -func TestBuildAddSingleFileToNonExistingDir(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToNonExistingDir(c *check.C) { name := "testaddsinglefiletononexistingdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1297,18 +1252,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add single file to non-existing dir") } -func TestBuildAddDirContentToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddDirContentToRoot(c *check.C) { name := "testadddircontenttoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1323,17 +1277,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add directory contents to root") } -func TestBuildAddDirContentToExistingDir(t *testing.T) { +func (s *DockerSuite) TestBuildAddDirContentToExistingDir(c *check.C) { name := "testadddircontenttoexistingdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1350,17 +1303,16 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add directory contents to existing dir") } -func TestBuildAddWholeDirToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddWholeDirToRoot(c *check.C) { name := "testaddwholedirtoroot" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -1378,18 +1330,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add whole directory to root") } // Testing #5941 -func TestBuildAddEtcToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddEtcToRoot(c *check.C) { name := "testaddetctoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1398,18 +1349,17 @@ ADD . /`, "etc/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add etc directory to root") } // Testing #9401 -func TestBuildAddPreservesFilesSpecialBits(t *testing.T) { +func (s *DockerSuite) TestBuildAddPreservesFilesSpecialBits(c *check.C) { name := "testaddpreservesfilesspecialbits" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1423,17 +1373,16 @@ RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`, "/data/usr/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add preserves files special bits") } -func TestBuildCopySingleFileToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToRoot(c *check.C) { name := "testcopysinglefiletoroot" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -1449,18 +1398,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy single file to root") } // Issue #3960: "ADD src ." hangs - adapted for COPY -func TestBuildCopySingleFileToWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToWorkdir(c *check.C) { name := "testcopysinglefiletoworkdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1469,26 +1417,25 @@ COPY test_file .`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() done := make(chan struct{}) go func() { if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } close(done) }() select { case <-time.After(5 * time.Second): - t.Fatal("Build with adding to workdir timed out") + c.Fatal("Build with adding to workdir timed out") case <-done: } - logDone("build - copy single file to workdir") } -func TestBuildCopySingleFileToExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToExistDir(c *check.C) { name := "testcopysinglefiletoexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1505,17 +1452,16 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy single file to existing dir") } -func TestBuildCopySingleFileToNonExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToNonExistDir(c *check.C) { name := "testcopysinglefiletononexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1531,17 +1477,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy single file to non-existing dir") } -func TestBuildCopyDirContentToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDirContentToRoot(c *check.C) { name := "testcopydircontenttoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1556,17 +1501,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy directory contents to root") } -func TestBuildCopyDirContentToExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDirContentToExistDir(c *check.C) { name := "testcopydircontenttoexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1583,17 +1527,16 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy directory contents to existing dir") } -func TestBuildCopyWholeDirToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWholeDirToRoot(c *check.C) { name := "testcopywholedirtoroot" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -1611,17 +1554,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy whole directory to root") } -func TestBuildCopyEtcToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopyEtcToRoot(c *check.C) { name := "testcopyetctoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1630,29 +1572,27 @@ COPY . /`, "etc/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy etc directory to root") } -func TestBuildCopyDisallowRemote(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDisallowRemote(c *check.C) { name := "testcopydisallowremote" defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch COPY https://index.docker.io/robots.txt /`, true) if err == nil || !strings.Contains(out, "Source can't be a URL for COPY") { - t.Fatalf("Error should be about disallowed remote source, got err: %s, out: %q", err, out) + c.Fatalf("Error should be about disallowed remote source, got err: %s, out: %q", err, out) } - logDone("build - copy - disallow copy from remote") } -func TestBuildAddBadLinks(t *testing.T) { +func (s *DockerSuite) TestBuildAddBadLinks(c *check.C) { const ( dockerfile = ` FROM scratch @@ -1667,13 +1607,13 @@ func TestBuildAddBadLinks(t *testing.T) { defer deleteImages(name) ctx, err := fakeContext(dockerfile, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() tempDir, err := ioutil.TempDir("", "test-link-absolute-temp-") if err != nil { - t.Fatalf("failed to create temporary directory: %s", tempDir) + c.Fatalf("failed to create temporary directory: %s", tempDir) } defer os.RemoveAll(tempDir) @@ -1681,7 +1621,7 @@ func TestBuildAddBadLinks(t *testing.T) { if runtime.GOOS == "windows" { var driveLetter string if abs, err := filepath.Abs(tempDir); err != nil { - t.Fatal(err) + c.Fatal(err) } else { driveLetter = abs[:1] } @@ -1697,7 +1637,7 @@ func TestBuildAddBadLinks(t *testing.T) { tarOut, err := os.Create(tarPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } tarWriter := tar.NewWriter(tarOut) @@ -1713,7 +1653,7 @@ func TestBuildAddBadLinks(t *testing.T) { err = tarWriter.WriteHeader(header) if err != nil { - t.Fatal(err) + c.Fatal(err) } tarWriter.Close() @@ -1721,26 +1661,25 @@ func TestBuildAddBadLinks(t *testing.T) { foo, err := os.Create(fooPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer foo.Close() if _, err := foo.WriteString("test"); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) { - t.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) + c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) } - logDone("build - ADD must add files in container") } -func TestBuildAddBadLinksVolume(t *testing.T) { +func (s *DockerSuite) TestBuildAddBadLinksVolume(c *check.C) { const ( dockerfileTemplate = ` FROM busybox @@ -1757,7 +1696,7 @@ func TestBuildAddBadLinksVolume(t *testing.T) { tempDir, err := ioutil.TempDir("", "test-link-absolute-volume-temp-") if err != nil { - t.Fatalf("failed to create temporary directory: %s", tempDir) + c.Fatalf("failed to create temporary directory: %s", tempDir) } defer os.RemoveAll(tempDir) @@ -1766,68 +1705,67 @@ func TestBuildAddBadLinksVolume(t *testing.T) { ctx, err := fakeContext(dockerfile, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() fooPath := filepath.Join(ctx.Dir, targetFile) foo, err := os.Create(fooPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer foo.Close() if _, err := foo.WriteString("test"); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) { - t.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) + c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) } - logDone("build - ADD should add files in volume") } // 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) { - testRequires(t, UnixCli) // test uses chown/chmod: not available on windows +func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { + testRequires(c, UnixCli) // test uses chown/chmod: not available on windows { name := "testbuildinaccessiblefiles" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"fileWithoutReadAccess": "foo"}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() // This is used to ensure we detect inaccessible files early during build in the cli client pathToFileWithoutReadAccess := filepath.Join(ctx.Dir, "fileWithoutReadAccess") if err = os.Chown(pathToFileWithoutReadAccess, 0, 0); err != nil { - t.Fatalf("failed to chown file to root: %s", err) + c.Fatalf("failed to chown file to root: %s", err) } if err = os.Chmod(pathToFileWithoutReadAccess, 0700); err != nil { - t.Fatalf("failed to chmod file to 700: %s", err) + c.Fatalf("failed to chmod file to 700: %s", err) } buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err == nil { - t.Fatalf("build should have failed: %s %s", err, out) + c.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 but contained: %s", out) + c.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") { - t.Fatalf("output should've contained the string: Error checking context is accessible") + c.Fatalf("output should've contained the string: Error checking context is accessible") } } { @@ -1835,7 +1773,7 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"directoryWeCantStat/bar": "foo"}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() // This is used to ensure we detect inaccessible directories early during build in the cli client @@ -1843,29 +1781,29 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { - t.Fatalf("failed to chown directory to root: %s", err) + c.Fatalf("failed to chown directory to root: %s", err) } if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil { - t.Fatalf("failed to chmod directory to 444: %s", err) + c.Fatalf("failed to chmod directory to 444: %s", err) } if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil { - t.Fatalf("failed to chmod file to 700: %s", err) + c.Fatalf("failed to chmod file to 700: %s", err) } buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err == nil { - t.Fatalf("build should have failed: %s %s", err, out) + c.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) + c.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") + c.Fatalf("output should've contained the string: Error checking context is accessible") } } @@ -1874,19 +1812,19 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() target := "../../../../../../../../../../../../../../../../../../../azA" if err := os.Symlink(filepath.Join(ctx.Dir, "g"), target); err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.Remove(target) // 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 if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } } { @@ -1898,61 +1836,59 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { ".dockerignore": "directoryWeCantStat", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() // This is used to ensure we don't try to add inaccessible files when they are ignored by a .dockerignore pattern pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat") pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { - t.Fatalf("failed to chown directory to root: %s", err) + c.Fatalf("failed to chown directory to root: %s", err) } if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil { - t.Fatalf("failed to chmod directory to 755: %s", err) + c.Fatalf("failed to chmod directory to 755: %s", err) } if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil { - t.Fatalf("failed to chmod file to 444: %s", err) + c.Fatalf("failed to chmod file to 444: %s", err) } buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) buildCmd.Dir = ctx.Dir if out, _, err := runCommandWithOutput(buildCmd); err != nil { - t.Fatalf("build should have worked: %s %s", err, out) + c.Fatalf("build should have worked: %s %s", err, out) } } - logDone("build - ADD from context with inaccessible files must not pass") } -func TestBuildForceRm(t *testing.T) { +func (s *DockerSuite) TestBuildForceRm(c *check.C) { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } name := "testbuildforcerm" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nRUN true\nRUN thiswillfail", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", name, "--force-rm", ".") buildCmd.Dir = ctx.Dir if out, _, err := runCommandWithOutput(buildCmd); err == nil { - t.Fatalf("failed to build the image: %s, %v", out, err) + c.Fatalf("failed to build the image: %s, %v", out, err) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { - t.Fatalf("--force-rm shouldn't have left containers behind") + c.Fatalf("--force-rm shouldn't have left containers behind") } - logDone("build - ensure --force-rm doesn't leave containers behind") } // Test that an infinite sleep during a build is killed if the client disconnects. @@ -1962,18 +1898,17 @@ func TestBuildForceRm(t *testing.T) { // * Run a 1-year-long sleep from a docker build. // * When docker events sees container start, close the "docker build" command // * Wait for docker events to emit a dying event. -func TestBuildCancelationKillsSleep(t *testing.T) { +func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { var wg sync.WaitGroup defer wg.Wait() name := "testbuildcancelation" defer deleteImages(name) - defer deleteAllContainers() // (Note: one year, will never finish) ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() @@ -1984,7 +1919,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { eventDie := make(chan struct{}) containerID := make(chan string) - startEpoch := daemonTime(t).Unix() + startEpoch := daemonTime(c).Unix() wg.Add(1) // Goroutine responsible for watching start/die events from `docker events` @@ -1997,7 +1932,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { stdout, err := eventsCmd.StdoutPipe() err = eventsCmd.Start() if err != nil { - t.Fatalf("failed to start 'docker events': %s", err) + c.Fatalf("failed to start 'docker events': %s", err) } go func() { @@ -2025,7 +1960,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { err = eventsCmd.Wait() if err != nil && !IsKilled(err) { - t.Fatalf("docker events had bad exit status: %s", err) + c.Fatalf("docker events had bad exit status: %s", err) } }() @@ -2035,7 +1970,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { stdoutBuild, err := buildCmd.StdoutPipe() err = buildCmd.Start() if err != nil { - t.Fatalf("failed to run build: %s", err) + c.Fatalf("failed to run build: %s", err) } matchCID := regexp.MustCompile("Running in ") @@ -2050,7 +1985,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe build container start in timely fashion") + c.Fatal("failed to observe build container start in timely fashion") case <-eventStart: // Proceeds from here when we see the container fly past in the // output of "docker events". @@ -2061,54 +1996,53 @@ func TestBuildCancelationKillsSleep(t *testing.T) { // Causes the underlying build to be cancelled due to socket close. err = buildCmd.Process.Kill() if err != nil { - t.Fatalf("error killing build command: %s", err) + c.Fatalf("error killing build command: %s", err) } // Get the exit status of `docker build`, check it exited because killed. err = buildCmd.Wait() if err != nil && !IsKilled(err) { - t.Fatalf("wait failed during build run: %T %s", err, err) + c.Fatalf("wait failed during build run: %T %s", err, err) } select { case <-time.After(5 * time.Second): // If we don't get here in a timely fashion, it wasn't killed. - t.Fatal("container cancel did not succeed") + c.Fatal("container cancel did not succeed") case <-eventDie: // We saw the container shut down in the `docker events` stream, // as expected. } - logDone("build - ensure canceled job finishes immediately") } -func TestBuildRm(t *testing.T) { +func (s *DockerSuite) TestBuildRm(c *check.C) { name := "testbuildrm" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD foo /\nADD foo /", map[string]string{"foo": "bar"}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "--rm", "-t", name, ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm", "-t", name, ".") if err != nil { - t.Fatal("failed to build the image", out) + c.Fatal("failed to build the image", out) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { - t.Fatalf("-rm shouldn't have left containers behind") + c.Fatalf("-rm shouldn't have left containers behind") } deleteImages(name) } @@ -2116,22 +2050,22 @@ func TestBuildRm(t *testing.T) { { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", name, ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name, ".") if err != nil { - t.Fatal("failed to build the image", out) + c.Fatal("failed to build the image", out) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { - t.Fatalf("--rm shouldn't have left containers behind") + c.Fatalf("--rm shouldn't have left containers behind") } deleteImages(name) } @@ -2139,32 +2073,31 @@ func TestBuildRm(t *testing.T) { { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "--rm=false", "-t", name, ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm=false", "-t", name, ".") if err != nil { - t.Fatal("failed to build the image", out) + c.Fatal("failed to build the image", out) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore == containerCountAfter { - t.Fatalf("--rm=false should have left containers behind") + c.Fatalf("--rm=false should have left containers behind") } deleteAllContainers() deleteImages(name) } - logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default") } -func TestBuildWithVolumes(t *testing.T) { +func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { var ( result map[string]map[string]struct{} name = "testbuildvolumes" @@ -2191,28 +2124,27 @@ func TestBuildWithVolumes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } err = unmarshalJSON([]byte(res), &result) if err != nil { - t.Fatal(err) + c.Fatal(err) } equal := reflect.DeepEqual(&result, &expected) if !equal { - t.Fatalf("Volumes %s, expected %s", result, expected) + c.Fatalf("Volumes %s, expected %s", result, expected) } - logDone("build - with volumes") } -func TestBuildMaintainer(t *testing.T) { +func (s *DockerSuite) TestBuildMaintainer(c *check.C) { name := "testbuildmaintainer" expected := "dockerio" defer deleteImages(name) @@ -2221,19 +2153,18 @@ func TestBuildMaintainer(t *testing.T) { MAINTAINER dockerio`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Author") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Maintainer %s, expected %s", res, expected) + c.Fatalf("Maintainer %s, expected %s", res, expected) } - logDone("build - maintainer") } -func TestBuildUser(t *testing.T) { +func (s *DockerSuite) TestBuildUser(c *check.C) { name := "testbuilduser" expected := "dockerio" defer deleteImages(name) @@ -2244,19 +2175,18 @@ func TestBuildUser(t *testing.T) { RUN [ $(whoami) = 'dockerio' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.User") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("User %s, expected %s", res, expected) + c.Fatalf("User %s, expected %s", res, expected) } - logDone("build - user") } -func TestBuildRelativeWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) { name := "testbuildrelativeworkdir" expected := "/test2/test3" defer deleteImages(name) @@ -2271,19 +2201,18 @@ func TestBuildRelativeWorkdir(t *testing.T) { RUN [ "$PWD" = '/test2/test3' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.WorkingDir") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Workdir %s, expected %s", res, expected) + c.Fatalf("Workdir %s, expected %s", res, expected) } - logDone("build - relative workdir") } -func TestBuildWorkdirWithEnvVariables(t *testing.T) { +func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) { name := "testbuildworkdirwithenvvariables" expected := "/test1/test2" defer deleteImages(name) @@ -2295,19 +2224,18 @@ func TestBuildWorkdirWithEnvVariables(t *testing.T) { WORKDIR $SUBDIRNAME/$MISSING_VAR`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.WorkingDir") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Workdir %s, expected %s", res, expected) + c.Fatalf("Workdir %s, expected %s", res, expected) } - logDone("build - workdir with env variables") } -func TestBuildRelativeCopy(t *testing.T) { +func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) { name := "testbuildrelativecopy" defer deleteImages(name) dockerfile := ` @@ -2338,16 +2266,15 @@ func TestBuildRelativeCopy(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImageFromContext(name, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - relative copy/add") } -func TestBuildEnv(t *testing.T) { +func (s *DockerSuite) TestBuildEnv(c *check.C) { name := "testbuildenv" expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" defer deleteImages(name) @@ -2358,74 +2285,70 @@ func TestBuildEnv(t *testing.T) { RUN [ $(env | grep PORT) = 'PORT=2375' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Env") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Env %s, expected %s", res, expected) + c.Fatalf("Env %s, expected %s", res, expected) } - logDone("build - env") } -func TestBuildContextCleanup(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestBuildContextCleanup(c *check.C) { + testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" defer deleteImages(name) entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } _, err = buildImage(name, `FROM scratch ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } if err = compareDirectoryEntries(entries, entriesFinal); err != nil { - t.Fatalf("context should have been deleted, but wasn't") + c.Fatalf("context should have been deleted, but wasn't") } - logDone("build - verify context cleanup works properly") } -func TestBuildContextCleanupFailedBuild(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { + testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" defer deleteImages(name) - defer deleteAllContainers() entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } _, err = buildImage(name, `FROM scratch RUN /non/existing/command`, true) if err == nil { - t.Fatalf("expected build to fail, but it didn't") + c.Fatalf("expected build to fail, but it didn't") } entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } if err = compareDirectoryEntries(entries, entriesFinal); err != nil { - t.Fatalf("context should have been deleted, but wasn't") + c.Fatalf("context should have been deleted, but wasn't") } - logDone("build - verify context cleanup works properly after an unsuccessful build") } -func TestBuildCmd(t *testing.T) { +func (s *DockerSuite) TestBuildCmd(c *check.C) { name := "testbuildcmd" expected := "[/bin/echo Hello World]" defer deleteImages(name) @@ -2434,19 +2357,18 @@ func TestBuildCmd(t *testing.T) { CMD ["/bin/echo", "Hello World"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Cmd %s, expected %s", res, expected) + c.Fatalf("Cmd %s, expected %s", res, expected) } - logDone("build - cmd") } -func TestBuildExpose(t *testing.T) { +func (s *DockerSuite) TestBuildExpose(c *check.C) { name := "testbuildexpose" expected := "map[2375/tcp:map[]]" defer deleteImages(name) @@ -2455,19 +2377,18 @@ func TestBuildExpose(t *testing.T) { EXPOSE 2375`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Exposed ports %s, expected %s", res, expected) + c.Fatalf("Exposed ports %s, expected %s", res, expected) } - logDone("build - expose") } -func TestBuildExposeMorePorts(t *testing.T) { +func (s *DockerSuite) TestBuildExposeMorePorts(c *check.C) { // start building docker file with a large number of ports portList := make([]string, 50) line := make([]string, 100) @@ -2496,43 +2417,42 @@ func TestBuildExposeMorePorts(t *testing.T) { defer deleteImages(name) _, err := buildImage(name, buf.String(), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // check if all the ports are saved inside Config.ExposedPorts res, err := inspectFieldJSON(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } var exposedPorts map[string]interface{} if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil { - t.Fatal(err) + c.Fatal(err) } for _, p := range expectedPorts { ep := fmt.Sprintf("%d/tcp", p) if _, ok := exposedPorts[ep]; !ok { - t.Errorf("Port(%s) is not exposed", ep) + c.Errorf("Port(%s) is not exposed", ep) } else { delete(exposedPorts, ep) } } if len(exposedPorts) != 0 { - t.Errorf("Unexpected extra exposed ports %v", exposedPorts) + c.Errorf("Unexpected extra exposed ports %v", exposedPorts) } - logDone("build - expose large number of ports") } -func TestBuildExposeOrder(t *testing.T) { +func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { buildID := func(name, exposed string) string { _, err := buildImage(name, fmt.Sprintf(`FROM scratch EXPOSE %s`, exposed), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id, err := inspectField(name, "Id") if err != nil { - t.Fatal(err) + c.Fatal(err) } return id } @@ -2541,12 +2461,11 @@ func TestBuildExposeOrder(t *testing.T) { id2 := buildID("testbuildexpose2", "2375 80") defer deleteImages("testbuildexpose1", "testbuildexpose2") if id1 != id2 { - t.Errorf("EXPOSE should invalidate the cache only when ports actually changed") + c.Errorf("EXPOSE should invalidate the cache only when ports actually changed") } - logDone("build - expose order") } -func TestBuildExposeUpperCaseProto(t *testing.T) { +func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { name := "testbuildexposeuppercaseproto" expected := "map[5678/udp:map[]]" defer deleteImages(name) @@ -2555,19 +2474,18 @@ func TestBuildExposeUpperCaseProto(t *testing.T) { EXPOSE 5678/UDP`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Exposed ports %s, expected %s", res, expected) + c.Fatalf("Exposed ports %s, expected %s", res, expected) } - logDone("build - expose port with upper case proto") } -func TestBuildExposeHostPort(t *testing.T) { +func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { // start building docker file with ip:hostPort:containerPort name := "testbuildexpose" expected := "map[5678/tcp:map[]]" @@ -2577,24 +2495,23 @@ func TestBuildExposeHostPort(t *testing.T) { EXPOSE 192.168.1.2:2375:5678`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "to map host ports to container ports (ip:hostPort:containerPort) is deprecated.") { - t.Fatal("Missing warning message") + c.Fatal("Missing warning message") } res, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Exposed ports %s, expected %s", res, expected) + c.Fatalf("Exposed ports %s, expected %s", res, expected) } - logDone("build - ignore exposing host's port") } -func TestBuildEmptyEntrypointInheritance(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { name := "testbuildentrypointinheritance" name2 := "testbuildentrypointinheritance2" defer deleteImages(name, name2) @@ -2604,16 +2521,16 @@ func TestBuildEmptyEntrypointInheritance(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "[/bin/echo]" if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } _, err = buildImage(name2, @@ -2621,23 +2538,22 @@ func TestBuildEmptyEntrypointInheritance(t *testing.T) { ENTRYPOINT []`, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err = inspectField(name2, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected = "[]" if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - empty entrypoint inheritance") } -func TestBuildEmptyEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { name := "testbuildentrypoint" defer deleteImages(name) expected := "[]" @@ -2647,20 +2563,19 @@ func TestBuildEmptyEntrypoint(t *testing.T) { ENTRYPOINT []`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - empty entrypoint") } -func TestBuildEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypoint(c *check.C) { name := "testbuildentrypoint" expected := "[/bin/echo]" defer deleteImages(name) @@ -2669,21 +2584,20 @@ func TestBuildEntrypoint(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - entrypoint") } // #6445 ensure ONBUILD triggers aren't committed to grandchildren -func TestBuildOnBuildLimitedInheritence(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { var ( out2, out3 string ) @@ -2696,13 +2610,13 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { ` ctx, err := fakeContext(dockerfile1, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() - out1, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", name1, ".") + out1, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name1, ".") if err != nil { - t.Fatalf("build failed to complete: %s, %v", out1, err) + c.Fatalf("build failed to complete: %s, %v", out1, err) } defer deleteImages(name1) } @@ -2713,13 +2627,13 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { ` ctx, err := fakeContext(dockerfile2, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() - out2, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-t", name2, ".") + out2, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name2, ".") if err != nil { - t.Fatalf("build failed to complete: %s, %v", out2, err) + c.Fatalf("build failed to complete: %s, %v", out2, err) } defer deleteImages(name2) } @@ -2730,13 +2644,13 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { ` ctx, err := fakeContext(dockerfile3, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() - out3, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-t", name3, ".") + out3, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name3, ".") if err != nil { - t.Fatalf("build failed to complete: %s, %v", out3, err) + c.Fatalf("build failed to complete: %s, %v", out3, err) } defer deleteImages(name3) @@ -2744,18 +2658,17 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { // ONBUILD should be run in second build. if !strings.Contains(out2, "ONBUILD PARENT") { - t.Fatalf("ONBUILD instruction did not run in child of ONBUILD parent") + c.Fatalf("ONBUILD instruction did not run in child of ONBUILD parent") } // ONBUILD should *not* be run in third build. if strings.Contains(out3, "ONBUILD PARENT") { - t.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent") + c.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent") } - logDone("build - onbuild") } -func TestBuildWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildWithCache(c *check.C) { name := "testbuildwithcache" defer deleteImages(name) id1, err := buildImage(name, @@ -2765,7 +2678,7 @@ func TestBuildWithCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name, `FROM scratch @@ -2774,15 +2687,14 @@ func TestBuildWithCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - with cache") } -func TestBuildWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildWithoutCache(c *check.C) { name := "testbuildwithoutcache" name2 := "testbuildwithoutcache2" defer deleteImages(name, name2) @@ -2793,7 +2705,7 @@ func TestBuildWithoutCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name2, @@ -2803,15 +2715,14 @@ func TestBuildWithoutCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - without cache") } -func TestBuildConditionalCache(t *testing.T) { +func (s *DockerSuite) TestBuildConditionalCache(c *check.C) { name := "testbuildconditionalcache" name2 := "testbuildconditionalcache2" defer deleteImages(name, name2) @@ -2823,39 +2734,38 @@ func TestBuildConditionalCache(t *testing.T) { "foo": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatalf("Error building #1: %s", err) + c.Fatalf("Error building #1: %s", err) } if err := ctx.Add("foo", "bye"); err != nil { - t.Fatalf("Error modifying foo: %s", err) + c.Fatalf("Error modifying foo: %s", err) } id2, err := buildImageFromContext(name, ctx, false) if err != nil { - t.Fatalf("Error building #2: %s", err) + c.Fatalf("Error building #2: %s", err) } if id2 == id1 { - t.Fatal("Should not have used the cache") + c.Fatal("Should not have used the cache") } id3, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatalf("Error building #3: %s", err) + c.Fatalf("Error building #3: %s", err) } if id3 != id2 { - t.Fatal("Should have used the cache") + c.Fatal("Should have used the cache") } - logDone("build - conditional cache") } -func TestBuildADDLocalFileWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalFileWithCache(c *check.C) { name := "testbuildaddlocalfilewithcache" name2 := "testbuildaddlocalfilewithcache2" defer deleteImages(name, name2) @@ -2869,23 +2779,22 @@ func TestBuildADDLocalFileWithCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add local file with cache") } -func TestBuildADDMultipleLocalFileWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDMultipleLocalFileWithCache(c *check.C) { name := "testbuildaddmultiplelocalfilewithcache" name2 := "testbuildaddmultiplelocalfilewithcache2" defer deleteImages(name, name2) @@ -2899,23 +2808,22 @@ func TestBuildADDMultipleLocalFileWithCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add multiple local files with cache") } -func TestBuildADDLocalFileWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalFileWithoutCache(c *check.C) { name := "testbuildaddlocalfilewithoutcache" name2 := "testbuildaddlocalfilewithoutcache2" defer deleteImages(name, name2) @@ -2929,23 +2837,22 @@ func TestBuildADDLocalFileWithoutCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add local file without cache") } -func TestBuildCopyDirButNotFile(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDirButNotFile(c *check.C) { name := "testbuildcopydirbutnotfile" name2 := "testbuildcopydirbutnotfile2" defer deleteImages(name, name2) @@ -2957,27 +2864,26 @@ func TestBuildCopyDirButNotFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Check that adding file with similar name doesn't mess with cache if err := ctx.Add("dir_file", "hello2"); err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but wasn't") + c.Fatal("The cache should have been used but wasn't") } - logDone("build - add current directory but not file") } -func TestBuildADDCurrentDirWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDCurrentDirWithCache(c *check.C) { name := "testbuildaddcurrentdirwithcache" name2 := name + "2" name3 := name + "3" @@ -2993,57 +2899,56 @@ func TestBuildADDCurrentDirWithCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Check that adding file invalidate cache of "ADD ." if err := ctx.Add("bar", "hello2"); err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.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) + c.Fatal(err) } id3, err := buildImageFromContext(name3, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id2 == id3 { - t.Fatal("The cache should have been invalided but hasn't.") + c.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) + c.Fatal(err) } id4, err := buildImageFromContext(name4, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id3 == id4 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } id5, err := buildImageFromContext(name5, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id4 != id5 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add current directory with cache") } -func TestBuildADDCurrentDirWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDCurrentDirWithoutCache(c *check.C) { name := "testbuildaddcurrentdirwithoutcache" name2 := "testbuildaddcurrentdirwithoutcache2" defer deleteImages(name, name2) @@ -3056,30 +2961,29 @@ func TestBuildADDCurrentDirWithoutCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add current directory without cache") } -func TestBuildADDRemoteFileWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDRemoteFileWithCache(c *check.C) { name := "testbuildaddremotefilewithcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3089,7 +2993,7 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name, fmt.Sprintf(`FROM scratch @@ -3097,15 +3001,14 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add remote file with cache") } -func TestBuildADDRemoteFileWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDRemoteFileWithoutCache(c *check.C) { name := "testbuildaddremotefilewithoutcache" name2 := "testbuildaddremotefilewithoutcache2" defer deleteImages(name, name2) @@ -3113,7 +3016,7 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3123,7 +3026,7 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name2, fmt.Sprintf(`FROM scratch @@ -3131,15 +3034,14 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add remote file without cache") } -func TestBuildADDRemoteFileMTime(t *testing.T) { +func (s *DockerSuite) TestBuildADDRemoteFileMTime(c *check.C) { name := "testbuildaddremotefilemtime" name2 := name + "2" name3 := name + "3" @@ -3150,7 +3052,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { files := map[string]string{"baz": "hello"} server, err := fakeStorage(files) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3158,21 +3060,21 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server.URL()), nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but wasn't - #1") + c.Fatal("The cache should have been used but wasn't - #1") } // Now create a different server withsame contents (causes different mtim) @@ -3183,7 +3085,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { server2, err := fakeStorage(files) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server2.Close() @@ -3191,36 +3093,35 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server2.URL()), nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx2.Close() id3, err := buildImageFromContext(name3, ctx2, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id3 { - t.Fatal("The cache should not have been used but was") + c.Fatal("The cache should not have been used but was") } // And for good measure do it again and make sure cache is used this time id4, err := buildImageFromContext(name4, ctx2, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id3 != id4 { - t.Fatal("The cache should have been used but wasn't - #2") + c.Fatal("The cache should have been used but wasn't - #2") } - logDone("build - add remote file testing mtime") } -func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithCache(c *check.C) { name := "testbuildaddlocalandremotefilewithcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3232,24 +3133,23 @@ func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { "foo": "hello world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add local and remote file with cache") } -func testContextTar(t *testing.T, compression archive.Compression) { +func testContextTar(c *check.C, compression archive.Compression) { ctx, err := fakeContext( `FROM busybox ADD foo /foo @@ -3260,11 +3160,11 @@ CMD ["cat", "/foo"]`, ) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } context, err := archive.Tar(ctx.Dir, compression) if err != nil { - t.Fatalf("failed to build context tar: %v", err) + c.Fatalf("failed to build context tar: %v", err) } name := "contexttar" buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") @@ -3272,38 +3172,35 @@ CMD ["cat", "/foo"]`, buildCmd.Stdin = context if out, _, err := runCommandWithOutput(buildCmd); err != nil { - t.Fatalf("build failed to complete: %v %v", out, err) + c.Fatalf("build failed to complete: %v %v", out, err) } } -func TestBuildContextTarGzip(t *testing.T) { - testContextTar(t, archive.Gzip) - logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", archive.Gzip)) +func (s *DockerSuite) TestBuildContextTarGzip(c *check.C) { + testContextTar(c, archive.Gzip) } -func TestBuildContextTarNoCompression(t *testing.T) { - testContextTar(t, archive.Uncompressed) - logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", archive.Uncompressed)) +func (s *DockerSuite) TestBuildContextTarNoCompression(c *check.C) { + testContextTar(c, archive.Uncompressed) } -func TestBuildNoContext(t *testing.T) { +func (s *DockerSuite) TestBuildNoContext(c *check.C) { buildCmd := exec.Command(dockerBinary, "build", "-t", "nocontext", "-") buildCmd.Stdin = strings.NewReader("FROM busybox\nCMD echo ok\n") if out, _, err := runCommandWithOutput(buildCmd); err != nil { - t.Fatalf("build failed to complete: %v %v", out, err) + c.Fatalf("build failed to complete: %v %v", out, err) } - if out, _ := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" { - t.Fatalf("run produced invalid output: %q, expected %q", out, "ok") + if out, _ := dockerCmd(c, "run", "--rm", "nocontext"); out != "ok\n" { + c.Fatalf("run produced invalid output: %q, expected %q", out, "ok") } deleteImages("nocontext") - logDone("build - build an image with no context") } // TODO: TestCaching -func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithoutCache(c *check.C) { name := "testbuildaddlocalandremotefilewithoutcache" name2 := "testbuildaddlocalandremotefilewithoutcache2" defer deleteImages(name, name2) @@ -3311,7 +3208,7 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3323,24 +3220,23 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { "foo": "hello world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add local and remote file without cache") } -func TestBuildWithVolumeOwnership(t *testing.T) { +func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) { name := "testbuildimg" defer deleteImages(name) @@ -3351,36 +3247,35 @@ func TestBuildWithVolumeOwnership(t *testing.T) { true) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--rm", "testbuildimg", "ls", "-la", "/test") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if expected := "drw-------"; !strings.Contains(out, expected) { - t.Fatalf("expected %s received %s", expected, out) + c.Fatalf("expected %s received %s", expected, out) } if expected := "daemon daemon"; !strings.Contains(out, expected) { - t.Fatalf("expected %s received %s", expected, out) + c.Fatalf("expected %s received %s", expected, out) } - logDone("build - volume ownership") } // testing #1405 - config.Cmd does not get cleaned up if // utilizing cache -func TestBuildEntrypointRunCleanup(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { name := "testbuildcmdcleanup" defer deleteImages(name) if _, err := buildImage(name, `FROM busybox RUN echo "hello"`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } ctx, err := fakeContext(`FROM busybox @@ -3392,23 +3287,22 @@ func TestBuildEntrypointRunCleanup(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } // Cmd must be cleaned up if expected := ""; res != expected { - t.Fatalf("Cmd %s, expected %s", res, expected) + c.Fatalf("Cmd %s, expected %s", res, expected) } - logDone("build - cleanup cmd after RUN") } -func TestBuildForbiddenContextPath(t *testing.T) { +func (s *DockerSuite) TestBuildForbiddenContextPath(c *check.C) { name := "testbuildforbidpath" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -3420,18 +3314,17 @@ func TestBuildForbiddenContextPath(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "Forbidden path outside the build context: ../../ " if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain \"%s\") got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain \"%s\") got:\n%v", expected, err) } - logDone("build - forbidden context path") } -func TestBuildADDFileNotFound(t *testing.T) { +func (s *DockerSuite) TestBuildADDFileNotFound(c *check.C) { name := "testbuildaddnotfound" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -3439,19 +3332,18 @@ func TestBuildADDFileNotFound(t *testing.T) { map[string]string{"bar": "hello"}) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { if !strings.Contains(err.Error(), "foo: no such file or directory") { - t.Fatalf("Wrong error %v, must be about missing foo file or directory", err) + c.Fatalf("Wrong error %v, must be about missing foo file or directory", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - add file not found") } -func TestBuildInheritance(t *testing.T) { +func (s *DockerSuite) TestBuildInheritance(c *check.C) { name := "testbuildinheritance" defer deleteImages(name) @@ -3460,11 +3352,11 @@ func TestBuildInheritance(t *testing.T) { EXPOSE 2375`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } ports1, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -3472,59 +3364,55 @@ func TestBuildInheritance(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if expected := "[/bin/echo]"; res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } ports2, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if ports1 != ports2 { - t.Fatalf("Ports must be same: %s != %s", ports1, ports2) + c.Fatalf("Ports must be same: %s != %s", ports1, ports2) } - logDone("build - inheritance") } -func TestBuildFails(t *testing.T) { +func (s *DockerSuite) TestBuildFails(c *check.C) { name := "testbuildfails" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox RUN sh -c "exit 23"`, true) if err != nil { if !strings.Contains(err.Error(), "returned a non-zero code: 23") { - t.Fatalf("Wrong error %v, must be about non-zero code 23", err) + c.Fatalf("Wrong error %v, must be about non-zero code 23", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - unsuccessful") } -func TestBuildFailsDockerfileEmpty(t *testing.T) { +func (s *DockerSuite) TestBuildFailsDockerfileEmpty(c *check.C) { name := "testbuildfails" defer deleteImages(name) _, err := buildImage(name, ``, true) if err != nil { if !strings.Contains(err.Error(), "The Dockerfile (Dockerfile) cannot be empty") { - t.Fatalf("Wrong error %v, must be about empty Dockerfile", err) + c.Fatalf("Wrong error %v, must be about empty Dockerfile", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - unsuccessful with empty dockerfile") } -func TestBuildOnBuild(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuild(c *check.C) { name := "testbuildonbuild" defer deleteImages(name) _, err := buildImage(name, @@ -3532,19 +3420,18 @@ func TestBuildOnBuild(t *testing.T) { ONBUILD RUN touch foobar`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, fmt.Sprintf(`FROM %s RUN [ -f foobar ]`, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - onbuild") } -func TestBuildOnBuildForbiddenChained(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenChained(c *check.C) { name := "testbuildonbuildforbiddenchained" defer deleteImages(name) _, err := buildImage(name, @@ -3553,15 +3440,14 @@ func TestBuildOnBuildForbiddenChained(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") { - t.Fatalf("Wrong error %v, must be about chaining ONBUILD", err) + c.Fatalf("Wrong error %v, must be about chaining ONBUILD", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden chained") } -func TestBuildOnBuildForbiddenFrom(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenFrom(c *check.C) { name := "testbuildonbuildforbiddenfrom" defer deleteImages(name) _, err := buildImage(name, @@ -3570,15 +3456,14 @@ func TestBuildOnBuildForbiddenFrom(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "FROM isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about FROM forbidden", err) + c.Fatalf("Wrong error %v, must be about FROM forbidden", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden from") } -func TestBuildOnBuildForbiddenMaintainer(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainer(c *check.C) { name := "testbuildonbuildforbiddenmaintainer" defer deleteImages(name) _, err := buildImage(name, @@ -3587,16 +3472,15 @@ func TestBuildOnBuildForbiddenMaintainer(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "MAINTAINER isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about MAINTAINER forbidden", err) + c.Fatalf("Wrong error %v, must be about MAINTAINER forbidden", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden maintainer") } // gh #2446 -func TestBuildAddToSymlinkDest(t *testing.T) { +func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) { name := "testbuildaddtosymlinkdest" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -3609,16 +3493,15 @@ func TestBuildAddToSymlinkDest(t *testing.T) { "foo": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add to symlink destination") } -func TestBuildEscapeWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildEscapeWhitespace(c *check.C) { name := "testbuildescaping" defer deleteImages(name) @@ -3632,17 +3515,16 @@ docker.com>" res, err := inspectField(name, "Author") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "\"Docker IO \"" { - t.Fatalf("Parsed string did not match the escaped string. Got: %q", res) + c.Fatalf("Parsed string did not match the escaped string. Got: %q", res) } - logDone("build - validate escaping whitespace") } -func TestBuildVerifyIntString(t *testing.T) { +func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { // Verify that strings that look like ints are still passed as strings name := "testbuildstringing" defer deleteImages(name) @@ -3654,17 +3536,16 @@ func TestBuildVerifyIntString(t *testing.T) { out, rc, err := runCommandWithOutput(exec.Command(dockerBinary, "inspect", name)) if rc != 0 || err != nil { - t.Fatalf("Unexcepted error from inspect: rc: %v err: %v", rc, err) + c.Fatalf("Unexcepted error from inspect: rc: %v err: %v", rc, err) } if !strings.Contains(out, "\"123\"") { - t.Fatalf("Output does not contain the int as a string:\n%s", out) + c.Fatalf("Output does not contain the int as a string:\n%s", out) } - logDone("build - verify int/strings as strings") } -func TestBuildDockerignore(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignore(c *check.C) { name := "testbuilddockerignore" defer deleteImages(name) dockerfile := ` @@ -3687,15 +3568,14 @@ func TestBuildDockerignore(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - test .dockerignore") } -func TestBuildDockerignoreCleanPaths(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { name := "testbuilddockerignorecleanpaths" defer deleteImages(name) dockerfile := ` @@ -3709,16 +3589,15 @@ func TestBuildDockerignoreCleanPaths(t *testing.T) { ".dockerignore": "./foo\ndir1//foo\n./dir1/../foo2", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - test .dockerignore with clean paths") } -func TestBuildDockerignoringDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" defer deleteImages(name) dockerfile := ` @@ -3731,24 +3610,23 @@ func TestBuildDockerignoringDockerfile(t *testing.T) { ".dockerignore": "Dockerfile\n", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore Dockerfile correctly:%s", err) + c.Fatalf("Didn't ignore Dockerfile correctly:%s", err) } // now try it with ./Dockerfile ctx.Add(".dockerignore", "./Dockerfile\n") if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore ./Dockerfile correctly:%s", err) + c.Fatalf("Didn't ignore ./Dockerfile correctly:%s", err) } - logDone("build - test .dockerignore of Dockerfile") } -func TestBuildDockerignoringRenamedDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" defer deleteImages(name) dockerfile := ` @@ -3763,24 +3641,23 @@ func TestBuildDockerignoringRenamedDockerfile(t *testing.T) { ".dockerignore": "MyDockerfile\n", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore MyDockerfile correctly:%s", err) + c.Fatalf("Didn't ignore MyDockerfile correctly:%s", err) } // now try it with ./MyDockerfile ctx.Add(".dockerignore", "./MyDockerfile\n") if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err) + c.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err) } - logDone("build - test .dockerignore of renamed Dockerfile") } -func TestBuildDockerignoringDockerignore(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringDockerignore(c *check.C) { name := "testbuilddockerignoredockerignore" defer deleteImages(name) dockerfile := ` @@ -3794,15 +3671,14 @@ func TestBuildDockerignoringDockerignore(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore .dockerignore correctly:%s", err) + c.Fatalf("Didn't ignore .dockerignore correctly:%s", err) } - logDone("build - test .dockerignore of .dockerignore") } -func TestBuildDockerignoreTouchDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) { var id1 string var id2 string @@ -3817,46 +3693,45 @@ func TestBuildDockerignoreTouchDockerfile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id2, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id1 != id2 { - t.Fatalf("Didn't use the cache - 1") + c.Fatalf("Didn't use the cache - 1") } // Now make sure touching Dockerfile doesn't invalidate the cache if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { - t.Fatalf("Didn't add Dockerfile: %s", err) + c.Fatalf("Didn't add Dockerfile: %s", err) } if id2, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id1 != id2 { - t.Fatalf("Didn't use the cache - 2") + c.Fatalf("Didn't use the cache - 2") } // One more time but just 'touch' it instead of changing the content if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { - t.Fatalf("Didn't add Dockerfile: %s", err) + c.Fatalf("Didn't add Dockerfile: %s", err) } if id2, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id1 != id2 { - t.Fatalf("Didn't use the cache - 3") + c.Fatalf("Didn't use the cache - 3") } - logDone("build - test .dockerignore touch dockerfile") } -func TestBuildDockerignoringWholeDir(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { name := "testbuilddockerignorewholedir" defer deleteImages(name) dockerfile := ` @@ -3871,15 +3746,14 @@ func TestBuildDockerignoringWholeDir(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - test .dockerignore whole dir with .*") } -func TestBuildLineBreak(t *testing.T) { +func (s *DockerSuite) TestBuildLineBreak(c *check.C) { name := "testbuildlinebreak" defer deleteImages(name) _, err := buildImage(name, @@ -3891,12 +3765,11 @@ RUN [ "$(cat /tmp/passwd)" = "root:testpass" ] RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - line break with \\") } -func TestBuildEOLInLine(t *testing.T) { +func (s *DockerSuite) TestBuildEOLInLine(c *check.C) { name := "testbuildeolinline" defer deleteImages(name) _, err := buildImage(name, @@ -3908,12 +3781,11 @@ RUN [ "$(cat /tmp/passwd)" = "root:testpass" ] RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - end of line in dockerfile instruction") } -func TestBuildCommentsShebangs(t *testing.T) { +func (s *DockerSuite) TestBuildCommentsShebangs(c *check.C) { name := "testbuildcomments" defer deleteImages(name) _, err := buildImage(name, @@ -3928,12 +3800,11 @@ RUN [ "$(cat /hello.sh)" = $'#!/bin/sh\necho hello world' ] RUN [ "$(/hello.sh)" = "hello world" ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - comments and shebangs") } -func TestBuildUsersAndGroups(t *testing.T) { +func (s *DockerSuite) TestBuildUsersAndGroups(c *check.C) { name := "testbuildusers" defer deleteImages(name) _, err := buildImage(name, @@ -3992,12 +3863,11 @@ USER 1042:1043 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - users and groups") } -func TestBuildEnvUsage(t *testing.T) { +func (s *DockerSuite) TestBuildEnvUsage(c *check.C) { name := "testbuildenvusage" defer deleteImages(name) dockerfile := `FROM busybox @@ -4023,18 +3893,17 @@ RUN [ "$ghi" = "def" ] "hello/docker/world": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() _, err = buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - environment variables usage") } -func TestBuildEnvUsage2(t *testing.T) { +func (s *DockerSuite) TestBuildEnvUsage2(c *check.C) { name := "testbuildenvusage2" defer deleteImages(name) dockerfile := `FROM busybox @@ -4127,18 +3996,17 @@ RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ] "hello/docker/world": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() _, err = buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - environment variables usage2") } -func TestBuildAddScript(t *testing.T) { +func (s *DockerSuite) TestBuildAddScript(c *check.C) { name := "testbuildaddscript" defer deleteImages(name) dockerfile := ` @@ -4151,18 +4019,17 @@ RUN [ "$(cat /testfile)" = 'test!' ]` "test": "#!/bin/sh\necho 'test!' > /testfile", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() _, err = buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add and run script") } -func TestBuildAddTar(t *testing.T) { +func (s *DockerSuite) TestBuildAddTar(c *check.C) { name := "testbuildaddtar" defer deleteImages(name) @@ -4185,7 +4052,7 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi` tmpDir, err := ioutil.TempDir("", "fake-context") testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) if err != nil { - t.Fatalf("failed to create test.tar archive: %v", err) + c.Fatalf("failed to create test.tar archive: %v", err) } defer testTar.Close() @@ -4195,30 +4062,29 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi` Name: "test/foo", Size: 2, }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write([]byte("Hi")); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil { - t.Fatalf("failed to open destination dockerfile: %v", err) + c.Fatalf("failed to open destination dockerfile: %v", err) } return fakeContextFromDir(tmpDir) }() defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("build failed to complete for TestBuildAddTar: %v", err) + c.Fatalf("build failed to complete for TestBuildAddTar: %v", err) } - logDone("build - ADD tar") } -func TestBuildAddTarXz(t *testing.T) { +func (s *DockerSuite) TestBuildAddTarXz(c *check.C) { name := "testbuildaddtarxz" defer deleteImages(name) @@ -4230,7 +4096,7 @@ func TestBuildAddTarXz(t *testing.T) { tmpDir, err := ioutil.TempDir("", "fake-context") testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) if err != nil { - t.Fatalf("failed to create test.tar archive: %v", err) + c.Fatalf("failed to create test.tar archive: %v", err) } defer testTar.Close() @@ -4240,23 +4106,23 @@ func TestBuildAddTarXz(t *testing.T) { Name: "test/foo", Size: 2, }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write([]byte("Hi")); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } xzCompressCmd := exec.Command("xz", "-k", "test.tar") xzCompressCmd.Dir = tmpDir out, _, err := runCommandWithOutput(xzCompressCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil { - t.Fatalf("failed to open destination dockerfile: %v", err) + c.Fatalf("failed to open destination dockerfile: %v", err) } return fakeContextFromDir(tmpDir) }() @@ -4264,13 +4130,12 @@ func TestBuildAddTarXz(t *testing.T) { defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) + c.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) } - logDone("build - ADD tar.xz") } -func TestBuildAddTarXzGz(t *testing.T) { +func (s *DockerSuite) TestBuildAddTarXzGz(c *check.C) { name := "testbuildaddtarxzgz" defer deleteImages(name) @@ -4282,7 +4147,7 @@ func TestBuildAddTarXzGz(t *testing.T) { tmpDir, err := ioutil.TempDir("", "fake-context") testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) if err != nil { - t.Fatalf("failed to create test.tar archive: %v", err) + c.Fatalf("failed to create test.tar archive: %v", err) } defer testTar.Close() @@ -4292,31 +4157,31 @@ func TestBuildAddTarXzGz(t *testing.T) { Name: "test/foo", Size: 2, }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write([]byte("Hi")); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } xzCompressCmd := exec.Command("xz", "-k", "test.tar") xzCompressCmd.Dir = tmpDir out, _, err := runCommandWithOutput(xzCompressCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } gzipCompressCmd := exec.Command("gzip", "test.tar.xz") gzipCompressCmd.Dir = tmpDir out, _, err = runCommandWithOutput(gzipCompressCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil { - t.Fatalf("failed to open destination dockerfile: %v", err) + c.Fatalf("failed to open destination dockerfile: %v", err) } return fakeContextFromDir(tmpDir) }() @@ -4324,13 +4189,12 @@ func TestBuildAddTarXzGz(t *testing.T) { defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) + c.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) } - logDone("build - ADD tar.xz.gz") } -func TestBuildFromGIT(t *testing.T) { +func (s *DockerSuite) TestBuildFromGIT(c *check.C) { name := "testbuildfromgit" defer deleteImages(name) git, err := fakeGIT("repo", map[string]string{ @@ -4341,25 +4205,24 @@ func TestBuildFromGIT(t *testing.T) { "first": "test git data", }, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() _, err = buildImageFromPath(name, git.RepoURL, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Author") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "docker" { - t.Fatalf("Maintainer should be docker, got %s", res) + c.Fatalf("Maintainer should be docker, got %s", res) } - logDone("build - build from GIT") } -func TestBuildCleanupCmdOnEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { name := "testbuildcmdcleanuponentrypoint" defer deleteImages(name) if _, err := buildImage(name, @@ -4367,32 +4230,31 @@ func TestBuildCleanupCmdOnEntrypoint(t *testing.T) { CMD ["test"] ENTRYPOINT ["echo"]`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImage(name, fmt.Sprintf(`FROM %s ENTRYPOINT ["cat"]`, name), true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if expected := ""; res != expected { - t.Fatalf("Cmd %s, expected %s", res, expected) + c.Fatalf("Cmd %s, expected %s", res, expected) } res, err = inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if expected := "[cat]"; res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - cleanup cmd on ENTRYPOINT") } -func TestBuildClearCmd(t *testing.T) { +func (s *DockerSuite) TestBuildClearCmd(c *check.C) { name := "testbuildclearcmd" defer deleteImages(name) _, err := buildImage(name, @@ -4401,39 +4263,37 @@ func TestBuildClearCmd(t *testing.T) { CMD []`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "[]" { - t.Fatalf("Cmd %s, expected %s", res, "[]") + c.Fatalf("Cmd %s, expected %s", res, "[]") } - logDone("build - clearcmd") } -func TestBuildEmptyCmd(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyCmd(c *check.C) { name := "testbuildemptycmd" defer deleteImages(name) if _, err := buildImage(name, "FROM scratch\nMAINTAINER quux\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "null" { - t.Fatalf("Cmd %s, expected %s", res, "null") + c.Fatalf("Cmd %s, expected %s", res, "null") } - logDone("build - empty cmd") } -func TestBuildOnBuildOutput(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildOutput(c *check.C) { name := "testbuildonbuildparent" defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nONBUILD RUN echo foo\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } childname := "testbuildonbuildchild" @@ -4441,50 +4301,47 @@ func TestBuildOnBuildOutput(t *testing.T) { _, out, err := buildImageWithOut(name, "FROM "+name+"\nMAINTAINER quux\n", true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "Trigger 0, RUN echo foo") { - t.Fatal("failed to find the ONBUILD output", out) + c.Fatal("failed to find the ONBUILD output", out) } - logDone("build - onbuild output") } -func TestBuildInvalidTag(t *testing.T) { +func (s *DockerSuite) TestBuildInvalidTag(c *check.C) { name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200) defer deleteImages(name) _, out, err := buildImageWithOut(name, "FROM scratch\nMAINTAINER quux\n", true) // if the error doesnt check for illegal tag name, or the image is built // then this should fail if !strings.Contains(out, "Illegal tag name") || strings.Contains(out, "Sending build context to Docker daemon") { - t.Fatalf("failed to stop before building. Error: %s, Output: %s", err, out) + c.Fatalf("failed to stop before building. Error: %s, Output: %s", err, out) } - logDone("build - invalid tag") } -func TestBuildCmdShDashC(t *testing.T) { +func (s *DockerSuite) TestBuildCmdShDashC(c *check.C) { name := "testbuildcmdshc" defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD echo cmd\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err, res) + c.Fatal(err, res) } expected := `["/bin/sh","-c","echo cmd"]` if res != expected { - t.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) + c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) } - logDone("build - cmd should have sh -c for non-json") } -func TestBuildCmdSpaces(t *testing.T) { +func (s *DockerSuite) TestBuildCmdSpaces(c *check.C) { // Test to make sure that when we strcat arrays we take into account // the arg separator to make sure ["echo","hi"] and ["echo hi"] don't // look the same @@ -4495,100 +4352,95 @@ func TestBuildCmdSpaces(t *testing.T) { var err error if id1, err = buildImage(name, "FROM busybox\nCMD [\"echo hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id2, err = buildImage(name, "FROM busybox\nCMD [\"echo\", \"hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("Should not have resulted in the same CMD") + c.Fatal("Should not have resulted in the same CMD") } // Now do the same with ENTRYPOINT if id1, err = buildImage(name, "FROM busybox\nENTRYPOINT [\"echo hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id2, err = buildImage(name, "FROM busybox\nENTRYPOINT [\"echo\", \"hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("Should not have resulted in the same ENTRYPOINT") + c.Fatal("Should not have resulted in the same ENTRYPOINT") } - logDone("build - cmd with spaces") } -func TestBuildCmdJSONNoShDashC(t *testing.T) { +func (s *DockerSuite) TestBuildCmdJSONNoShDashC(c *check.C) { name := "testbuildcmdjson" defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD [\"echo\", \"cmd\"]", true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err, res) + c.Fatal(err, res) } expected := `["echo","cmd"]` if res != expected { - t.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) + c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) } - logDone("build - cmd should not have /bin/sh -c for json") } -func TestBuildErrorInvalidInstruction(t *testing.T) { +func (s *DockerSuite) TestBuildErrorInvalidInstruction(c *check.C) { name := "testbuildignoreinvalidinstruction" defer deleteImages(name) out, _, err := buildImageWithOut(name, "FROM busybox\nfoo bar", true) if err == nil { - t.Fatalf("Should have failed: %s", out) + c.Fatalf("Should have failed: %s", out) } - logDone("build - error invalid Dockerfile instruction") } -func TestBuildEntrypointInheritance(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypointInheritance(c *check.C) { defer deleteImages("parent", "child") - defer deleteAllContainers() if _, err := buildImage("parent", ` FROM busybox ENTRYPOINT exit 130 `, true); err != nil { - t.Fatal(err) + c.Fatal(err) } status, _ := runCommand(exec.Command(dockerBinary, "run", "parent")) if status != 130 { - t.Fatalf("expected exit code 130 but received %d", status) + c.Fatalf("expected exit code 130 but received %d", status) } if _, err := buildImage("child", ` FROM parent ENTRYPOINT exit 5 `, true); err != nil { - t.Fatal(err) + c.Fatal(err) } status, _ = runCommand(exec.Command(dockerBinary, "run", "child")) if status != 5 { - t.Fatalf("expected exit code 5 but received %d", status) + c.Fatalf("expected exit code 5 but received %d", status) } - logDone("build - clear entrypoint") } -func TestBuildEntrypointInheritanceInspect(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypointInheritanceInspect(c *check.C) { var ( name = "testbuildepinherit" name2 = "testbuildepinherit2" @@ -4596,40 +4448,38 @@ func TestBuildEntrypointInheritanceInspect(t *testing.T) { ) defer deleteImages(name, name2) - defer deleteAllContainers() if _, err := buildImage(name, "FROM busybox\nENTRYPOINT /foo/bar", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImage(name2, fmt.Sprintf("FROM %s\nENTRYPOINT echo quux", name), true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name2, "Config.Entrypoint") if err != nil { - t.Fatal(err, res) + c.Fatal(err, res) } if res != expected { - t.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res) + c.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2)) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } expected = "quux" if strings.TrimSpace(out) != expected { - t.Fatalf("Expected output is %s, got %s", expected, out) + c.Fatalf("Expected output is %s, got %s", expected, out) } - logDone("build - entrypoint override inheritance properly") } -func TestBuildRunShEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildRunShEntrypoint(c *check.C) { name := "testbuildentrypoint" defer deleteImages(name) _, err := buildImage(name, @@ -4637,19 +4487,18 @@ func TestBuildRunShEntrypoint(t *testing.T) { ENTRYPOINT /bin/echo`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name)) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("build - entrypoint with /bin/echo running successfully") } -func TestBuildExoticShellInterpolation(t *testing.T) { +func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) { name := "testbuildexoticshellinterpolation" defer deleteImages(name) @@ -4673,13 +4522,12 @@ func TestBuildExoticShellInterpolation(t *testing.T) { RUN [ "${SOME_UNSET_VAR:-${SOME_VAR:-d.e.f}}" = 'a.b.c' ] `, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - exotic shell interpolation") } -func TestBuildVerifySingleQuoteFails(t *testing.T) { +func (s *DockerSuite) TestBuildVerifySingleQuoteFails(c *check.C) { // This testcase is supposed to generate an error because the // JSON array we're passing in on the CMD uses single quotes instead // of double quotes (per the JSON spec). This means we interpret it @@ -4687,7 +4535,6 @@ func TestBuildVerifySingleQuoteFails(t *testing.T) { // it should barf on it. name := "testbuildsinglequotefails" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox @@ -4696,13 +4543,12 @@ func TestBuildVerifySingleQuoteFails(t *testing.T) { _, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name)) if err == nil { - t.Fatal("The image was not supposed to be able to run") + c.Fatal("The image was not supposed to be able to run") } - logDone("build - verify single quotes break the build") } -func TestBuildVerboseOut(t *testing.T) { +func (s *DockerSuite) TestBuildVerboseOut(c *check.C) { name := "testbuildverboseout" defer deleteImages(name) @@ -4712,36 +4558,34 @@ RUN echo 123`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "\n123\n") { - t.Fatalf("Output should contain %q: %q", "123", out) + c.Fatalf("Output should contain %q: %q", "123", out) } - logDone("build - verbose output from commands") } -func TestBuildWithTabs(t *testing.T) { +func (s *DockerSuite) TestBuildWithTabs(c *check.C) { name := "testbuildwithtabs" defer deleteImages(name) _, err := buildImage(name, "FROM busybox\nRUN echo\tone\t\ttwo", true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "ContainerConfig.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected1 := `["/bin/sh","-c","echo\tone\t\ttwo"]` expected2 := `["/bin/sh","-c","echo\u0009one\u0009\u0009two"]` // syntactically equivalent, and what Go 1.3 generates if res != expected1 && res != expected2 { - t.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2) + c.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2) } - logDone("build - with tabs") } -func TestBuildLabels(t *testing.T) { +func (s *DockerSuite) TestBuildLabels(c *check.C) { name := "testbuildlabel" expected := `{"License":"GPL","Vendor":"Acme"}` defer deleteImages(name) @@ -4751,19 +4595,18 @@ func TestBuildLabels(t *testing.T) { LABEL License GPL`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Labels") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Labels %s, expected %s", res, expected) + c.Fatalf("Labels %s, expected %s", res, expected) } - logDone("build - label") } -func TestBuildLabelsCache(t *testing.T) { +func (s *DockerSuite) TestBuildLabelsCache(c *check.C) { name := "testbuildlabelcache" defer deleteImages(name) @@ -4771,28 +4614,28 @@ func TestBuildLabelsCache(t *testing.T) { `FROM busybox LABEL Vendor=Acme`, false) if err != nil { - t.Fatalf("Build 1 should have worked: %v", err) + c.Fatalf("Build 1 should have worked: %v", err) } id2, err := buildImage(name, `FROM busybox LABEL Vendor=Acme`, true) if err != nil || id1 != id2 { - t.Fatalf("Build 2 should have worked & used cache(%s,%s): %v", id1, id2, err) + c.Fatalf("Build 2 should have worked & used cache(%s,%s): %v", id1, id2, err) } id2, err = buildImage(name, `FROM busybox LABEL Vendor=Acme1`, true) if err != nil || id1 == id2 { - t.Fatalf("Build 3 should have worked & NOT used cache(%s,%s): %v", id1, id2, err) + c.Fatalf("Build 3 should have worked & NOT used cache(%s,%s): %v", id1, id2, err) } id2, err = buildImage(name, `FROM busybox LABEL Vendor Acme`, true) // Note: " " and "=" should be same if err != nil || id1 != id2 { - t.Fatalf("Build 4 should have worked & used cache(%s,%s): %v", id1, id2, err) + c.Fatalf("Build 4 should have worked & used cache(%s,%s): %v", id1, id2, err) } // Now make sure the cache isn't used by mistake @@ -4800,20 +4643,19 @@ func TestBuildLabelsCache(t *testing.T) { `FROM busybox LABEL f1=b1 f2=b2`, false) if err != nil { - t.Fatalf("Build 5 should have worked: %q", err) + c.Fatalf("Build 5 should have worked: %q", err) } id2, err = buildImage(name, `FROM busybox LABEL f1="b1 f2=b2"`, true) if err != nil || id1 == id2 { - t.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s): %q", id1, id2, err) + c.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s): %q", id1, id2, err) } - logDone("build - label cache") } -func TestBuildStderr(t *testing.T) { +func (s *DockerSuite) TestBuildStderr(c *check.C) { // This test just makes sure that no non-error output goes // to stderr name := "testbuildstderr" @@ -4821,7 +4663,7 @@ func TestBuildStderr(t *testing.T) { _, _, stderr, err := buildImageWithStdoutStderr(name, "FROM busybox\nRUN echo one", true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if runtime.GOOS == "windows" { @@ -4829,19 +4671,18 @@ func TestBuildStderr(t *testing.T) { lines := strings.Split(stderr, "\n") for _, v := range lines { if v != "" && !strings.Contains(v, "SECURITY WARNING:") { - t.Fatalf("Stderr contains unexpected output line: %q", v) + c.Fatalf("Stderr contains unexpected output line: %q", v) } } } else { if stderr != "" { - t.Fatalf("Stderr should have been empty, instead its: %q", stderr) + c.Fatalf("Stderr should have been empty, instead its: %q", stderr) } } - logDone("build - testing stderr") } -func TestBuildChownSingleFile(t *testing.T) { - testRequires(t, UnixCli) // test uses chown: not available on windows +func (s *DockerSuite) TestBuildChownSingleFile(c *check.C) { + testRequires(c, UnixCli) // test uses chown: not available on windows name := "testbuildchownsinglefile" defer deleteImages(name) @@ -4855,46 +4696,45 @@ RUN [ $(ls -l /test | awk '{print $3":"$4}') = 'root:root' ] "test": "test", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if err := os.Chown(filepath.Join(ctx.Dir, "test"), 4242, 4242); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - change permission on single file") } -func TestBuildSymlinkBreakout(t *testing.T) { +func (s *DockerSuite) TestBuildSymlinkBreakout(c *check.C) { name := "testbuildsymlinkbreakout" tmpdir, err := ioutil.TempDir("", name) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) ctx := filepath.Join(tmpdir, "context") if err := os.MkdirAll(ctx, 0755); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte(` from busybox add symlink.tar / add inject /symlink/ `), 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } inject := filepath.Join(ctx, "inject") if err := ioutil.WriteFile(inject, nil, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } f, err := os.Create(filepath.Join(ctx, "symlink.tar")) if err != nil { - t.Fatal(err) + c.Fatal(err) } w := tar.NewWriter(f) w.WriteHeader(&tar.Header{ @@ -4914,17 +4754,16 @@ func TestBuildSymlinkBreakout(t *testing.T) { w.Close() f.Close() if _, err := buildImageFromContext(name, fakeContextFromDir(ctx), false); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Lstat(filepath.Join(tmpdir, "inject")); err == nil { - t.Fatal("symlink breakout - inject") + c.Fatal("symlink breakout - inject") } else if !os.IsNotExist(err) { - t.Fatalf("unexpected error: %v", err) + c.Fatalf("unexpected error: %v", err) } - logDone("build - symlink breakout") } -func TestBuildXZHost(t *testing.T) { +func (s *DockerSuite) TestBuildXZHost(c *check.C) { name := "testbuildxzhost" defer deleteImages(name) @@ -4942,18 +4781,17 @@ RUN [ ! -e /injected ]`, }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - xz host is being used") } -func TestBuildVolumesRetainContents(t *testing.T) { +func (s *DockerSuite) TestBuildVolumesRetainContents(c *check.C) { var ( name = "testbuildvolumescontent" expected = "some text" @@ -4968,27 +4806,25 @@ CMD cat /foo/file`, "content": expected, }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, false); err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if out != expected { - t.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out) + c.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out) } - logDone("build - volumes retain contents in build") } -func TestBuildRenamedDockerfile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestBuildRenamedDockerfile(c *check.C) { ctx, err := fakeContext(`FROM busybox RUN echo from Dockerfile`, @@ -5001,99 +4837,98 @@ func TestBuildRenamedDockerfile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("test1 should have used Dockerfile, output:%s", out) + c.Fatalf("test1 should have used Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-f", filepath.Join("files", "Dockerfile"), "-t", "test2", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-f", filepath.Join("files", "Dockerfile"), "-t", "test2", ".") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "from files/Dockerfile") { - t.Fatalf("test2 should have used files/Dockerfile, output:%s", out) + c.Fatalf("test2 should have used files/Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", fmt.Sprintf("--file=%s", filepath.Join("files", "dFile")), "-t", "test3", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", fmt.Sprintf("--file=%s", filepath.Join("files", "dFile")), "-t", "test3", ".") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "from files/dFile") { - t.Fatalf("test3 should have used files/dFile, output:%s", out) + c.Fatalf("test3 should have used files/dFile, output:%s", out) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "--file=dFile", "-t", "test4", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", "--file=dFile", "-t", "test4", ".") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "from dFile") { - t.Fatalf("test4 should have used dFile, output:%s", out) + c.Fatalf("test4 should have used dFile, output:%s", out) } dirWithNoDockerfile, _ := ioutil.TempDir(os.TempDir(), "test5") nonDockerfileFile := filepath.Join(dirWithNoDockerfile, "notDockerfile") if _, err = os.Create(nonDockerfileFile); err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", fmt.Sprintf("--file=%s", nonDockerfileFile), "-t", "test5", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", fmt.Sprintf("--file=%s", nonDockerfileFile), "-t", "test5", ".") if err == nil { - t.Fatalf("test5 was supposed to fail to find passwd") + c.Fatalf("test5 was supposed to fail to find passwd") } if expected := fmt.Sprintf("The Dockerfile (%s) must be within the build context (.)", strings.Replace(nonDockerfileFile, `\`, `\\`, -1)); !strings.Contains(out, expected) { - t.Fatalf("wrong error messsage:%v\nexpected to contain=%v", out, expected) + c.Fatalf("wrong error messsage:%v\nexpected to contain=%v", out, expected) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test6", "..") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test6", "..") if err != nil { - t.Fatalf("test6 failed: %s", err) + c.Fatalf("test6 failed: %s", err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("test6 should have used root Dockerfile, output:%s", out) + c.Fatalf("test6 should have used root Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join(ctx.Dir, "files", "Dockerfile"), "-t", "test7", "..") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join(ctx.Dir, "files", "Dockerfile"), "-t", "test7", "..") if err != nil { - t.Fatalf("test7 failed: %s", err) + c.Fatalf("test7 failed: %s", err) } if !strings.Contains(out, "from files/Dockerfile") { - t.Fatalf("test7 should have used files Dockerfile, output:%s", out) + c.Fatalf("test7 should have used files Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test8", ".") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test8", ".") if err == nil || !strings.Contains(out, "must be within the build context") { - t.Fatalf("test8 should have failed with Dockerfile out of context: %s", err) + c.Fatalf("test8 should have failed with Dockerfile out of context: %s", err) } tmpDir := os.TempDir() - out, _, err = dockerCmdInDir(t, tmpDir, "build", "-t", "test9", ctx.Dir) + out, _, err = dockerCmdInDir(c, tmpDir, "build", "-t", "test9", ctx.Dir) if err != nil { - t.Fatalf("test9 - failed: %s", err) + c.Fatalf("test9 - failed: %s", err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("test9 should have used root Dockerfile, output:%s", out) + c.Fatalf("test9 should have used root Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", "dFile2", "-t", "test10", ".") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", "dFile2", "-t", "test10", ".") if err != nil { - t.Fatalf("test10 should have worked: %s", err) + c.Fatalf("test10 should have worked: %s", err) } if !strings.Contains(out, "from files/dFile2") { - t.Fatalf("test10 should have used files/dFile2, output:%s", out) + c.Fatalf("test10 should have used files/dFile2, output:%s", out) } - logDone("build - rename dockerfile") } -func TestBuildFromMixedcaseDockerfile(t *testing.T) { - testRequires(t, UnixCli) // Dockerfile overwrites dockerfile on windows +func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) { + testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox @@ -5103,23 +4938,22 @@ func TestBuildFromMixedcaseDockerfile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - mixedcase Dockerfile") } -func TestBuildWithTwoDockerfiles(t *testing.T) { - testRequires(t, UnixCli) // Dockerfile overwrites dockerfile on windows +func (s *DockerSuite) TestBuildWithTwoDockerfiles(c *check.C) { + testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox @@ -5129,22 +4963,21 @@ RUN echo from Dockerfile`, }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - two Dockerfiles") } -func TestBuildFromURLWithF(t *testing.T) { +func (s *DockerSuite) TestBuildFromURLWithF(c *check.C) { defer deleteImages("test1") server, err := fakeStorage(map[string]string{"baz": `FROM busybox @@ -5152,7 +4985,7 @@ RUN echo from baz COPY * /tmp/ RUN find /tmp/`}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -5161,26 +4994,25 @@ RUN echo from Dockerfile`, map[string]string{}) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } // Make sure that -f is ignored and that we don't use the Dockerfile // that's in the current dir - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from baz") || strings.Contains(out, "/tmp/baz") || !strings.Contains(out, "/tmp/Dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - from URL with -f") } -func TestBuildFromStdinWithF(t *testing.T) { +func (s *DockerSuite) TestBuildFromStdinWithF(c *check.C) { defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox @@ -5188,7 +5020,7 @@ RUN echo from Dockerfile`, map[string]string{}) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } // Make sure that -f is ignored and that we don't use the Dockerfile @@ -5201,19 +5033,18 @@ COPY * /tmp/ RUN find /tmp/`) out, status, err := runCommandWithOutput(dockerCommand) if err != nil || status != 0 { - t.Fatalf("Error building: %s", err) + c.Fatalf("Error building: %s", err) } if !strings.Contains(out, "from baz") || strings.Contains(out, "/tmp/baz") || !strings.Contains(out, "/tmp/Dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - from stdin with -f") } -func TestBuildFromOfficialNames(t *testing.T) { +func (s *DockerSuite) TestBuildFromOfficialNames(c *check.C) { name := "testbuildfromofficial" fromNames := []string{ "busybox", @@ -5227,45 +5058,44 @@ func TestBuildFromOfficialNames(t *testing.T) { imgName := fmt.Sprintf("%s%d", name, idx) _, err := buildImage(imgName, "FROM "+fromName, true) if err != nil { - t.Errorf("Build failed using FROM %s: %s", fromName, err) + c.Errorf("Build failed using FROM %s: %s", fromName, err) } deleteImages(imgName) } - logDone("build - from official names") } -func TestBuildDockerfileOutsideContext(t *testing.T) { - testRequires(t, UnixCli) // uses os.Symlink: not implemented in windows at the time of writing (go-1.4.2) +func (s *DockerSuite) TestBuildDockerfileOutsideContext(c *check.C) { + testRequires(c, UnixCli) // uses os.Symlink: not implemented in windows at the time of writing (go-1.4.2) name := "testbuilddockerfileoutsidecontext" tmpdir, err := ioutil.TempDir("", name) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) ctx := filepath.Join(tmpdir, "context") if err := os.MkdirAll(ctx, 0755); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte("FROM scratch\nENV X Y"), 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } wd, err := os.Getwd() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.Chdir(wd) if err := os.Chdir(ctx); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(tmpdir, "outsideDockerfile"), []byte("FROM scratch\nENV x y"), 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.Symlink(filepath.Join("..", "outsideDockerfile"), filepath.Join(ctx, "dockerfile1")); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.Symlink(filepath.Join(tmpdir, "outsideDockerfile"), filepath.Join(ctx, "dockerfile2")); err != nil { - t.Fatal(err) + c.Fatal(err) } for _, dockerfilePath := range []string{ @@ -5275,10 +5105,10 @@ func TestBuildDockerfileOutsideContext(t *testing.T) { } { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", dockerfilePath, ".")) if err == nil { - t.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out) + c.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out) } if !strings.Contains(out, "must be within the build context") && !strings.Contains(out, "Cannot locate Dockerfile") { - t.Fatalf("Unexpected error with %s. Out: %s", dockerfilePath, out) + c.Fatalf("Unexpected error with %s. Out: %s", dockerfilePath, out) } deleteImages(name) } @@ -5289,14 +5119,13 @@ func TestBuildDockerfileOutsideContext(t *testing.T) { // There is a Dockerfile in the context, but since there is no Dockerfile in the current directory, the following should fail out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", "Dockerfile", ctx)) if err == nil { - t.Fatalf("Expected error. Out: %s", out) + c.Fatalf("Expected error. Out: %s", out) } deleteImages(name) - logDone("build - Dockerfile outside context") } -func TestBuildSpaces(t *testing.T) { +func (s *DockerSuite) TestBuildSpaces(c *check.C) { // Test to make sure that leading/trailing spaces on a command // doesn't change the error msg we get var ( @@ -5311,17 +5140,17 @@ func TestBuildSpaces(t *testing.T) { "Dockerfile": "FROM busybox\nCOPY\n", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err1 = buildImageFromContext(name, ctx, false); err1 == nil { - t.Fatal("Build 1 was supposed to fail, but didn't") + c.Fatal("Build 1 was supposed to fail, but didn't") } ctx.Add("Dockerfile", "FROM busybox\nCOPY ") if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil { - t.Fatal("Build 2 was supposed to fail, but didn't") + c.Fatal("Build 2 was supposed to fail, but didn't") } removeLogTimestamps := func(s string) string { @@ -5334,12 +5163,12 @@ func TestBuildSpaces(t *testing.T) { // Ignore whitespace since that's what were verifying doesn't change stuff if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { - t.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", err1, err2) + c.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", err1, err2) } ctx.Add("Dockerfile", "FROM busybox\n COPY") if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil { - t.Fatal("Build 3 was supposed to fail, but didn't") + c.Fatal("Build 3 was supposed to fail, but didn't") } // Skip over the times @@ -5348,12 +5177,12 @@ func TestBuildSpaces(t *testing.T) { // Ignore whitespace since that's what were verifying doesn't change stuff if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { - t.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", err1, err2) + c.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", err1, err2) } ctx.Add("Dockerfile", "FROM busybox\n COPY ") if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil { - t.Fatal("Build 4 was supposed to fail, but didn't") + c.Fatal("Build 4 was supposed to fail, but didn't") } // Skip over the times @@ -5362,13 +5191,12 @@ func TestBuildSpaces(t *testing.T) { // Ignore whitespace since that's what were verifying doesn't change stuff if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { - t.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", err1, err2) + c.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", err1, err2) } - logDone("build - test spaces") } -func TestBuildSpacesWithQuotes(t *testing.T) { +func (s *DockerSuite) TestBuildSpacesWithQuotes(c *check.C) { // Test to make sure that spaces in quotes aren't lost name := "testspacesquotes" defer deleteImages(name) @@ -5379,19 +5207,18 @@ RUN echo " \ _, out, err := buildImageWithOut(name, dockerfile, false) if err != nil { - t.Fatal("Build failed:", err) + c.Fatal("Build failed:", err) } expecting := "\n foo \n" if !strings.Contains(out, expecting) { - t.Fatalf("Bad output: %q expecting to contian %q", out, expecting) + c.Fatalf("Bad output: %q expecting to contian %q", out, expecting) } - logDone("build - test spaces with quotes") } // #4393 -func TestBuildVolumeFileExistsinContainer(t *testing.T) { +func (s *DockerSuite) TestBuildVolumeFileExistsinContainer(c *check.C) { buildCmd := exec.Command(dockerBinary, "build", "-t", "docker-test-errcreatevolumewithfile", "-") buildCmd.Stdin = strings.NewReader(` FROM busybox @@ -5401,13 +5228,12 @@ func TestBuildVolumeFileExistsinContainer(t *testing.T) { out, _, err := runCommandWithOutput(buildCmd) if err == nil || !strings.Contains(out, "file exists") { - t.Fatalf("expected build to fail when file exists in container at requested volume path") + c.Fatalf("expected build to fail when file exists in container at requested volume path") } - logDone("build - errors when volume is specified where a file exists") } -func TestBuildMissingArgs(t *testing.T) { +func (s *DockerSuite) TestBuildMissingArgs(c *check.C) { // Test to make sure that all Dockerfile commands (except the ones listed // in skipCmds) will generate an error if no args are provided. // Note: INSERT is deprecated so we exclude it because of that. @@ -5418,8 +5244,6 @@ func TestBuildMissingArgs(t *testing.T) { "INSERT": {}, } - defer deleteAllContainers() - for cmd := range command.Commands { cmd = strings.ToUpper(cmd) if _, ok := skipCmds[cmd]; ok { @@ -5436,57 +5260,53 @@ func TestBuildMissingArgs(t *testing.T) { ctx, err := fakeContext(dockerfile, map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() var out string if out, err = buildImageFromContext("args", ctx, true); err == nil { - t.Fatalf("%s was supposed to fail. Out:%s", cmd, out) + c.Fatalf("%s was supposed to fail. Out:%s", cmd, out) } if !strings.Contains(err.Error(), cmd+" requires") { - t.Fatalf("%s returned the wrong type of error:%s", cmd, err) + c.Fatalf("%s returned the wrong type of error:%s", cmd, err) } } - logDone("build - verify missing args") } -func TestBuildEmptyScratch(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) { defer deleteImages("sc") _, out, err := buildImageWithOut("sc", "FROM scratch", true) if err == nil { - t.Fatalf("Build was supposed to fail") + c.Fatalf("Build was supposed to fail") } if !strings.Contains(out, "No image was generated") { - t.Fatalf("Wrong error message: %v", out) + c.Fatalf("Wrong error message: %v", out) } - logDone("build - empty scratch Dockerfile") } -func TestBuildDotDotFile(t *testing.T) { +func (s *DockerSuite) TestBuildDotDotFile(c *check.C) { defer deleteImages("sc") ctx, err := fakeContext("FROM busybox\n", map[string]string{ "..gitme": "", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err = buildImageFromContext("sc", ctx, false); err != nil { - t.Fatalf("Build was supposed to work: %s", err) + c.Fatalf("Build was supposed to work: %s", err) } - logDone("build - ..file") } -func TestBuildNotVerbose(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestBuildNotVerbose(c *check.C) { defer deleteImages("verbose") ctx, err := fakeContext("FROM busybox\nENV abc=hi\nRUN echo $abc there", map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() @@ -5495,10 +5315,10 @@ func TestBuildNotVerbose(t *testing.T) { buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err != nil { - t.Fatalf("failed to build the image w/o -q: %s, %v", out, err) + c.Fatalf("failed to build the image w/o -q: %s, %v", out, err) } if !strings.Contains(out, "hi there") { - t.Fatalf("missing output:%s\n", out) + c.Fatalf("missing output:%s\n", out) } // Now do it w/o verbose @@ -5506,25 +5326,23 @@ func TestBuildNotVerbose(t *testing.T) { buildCmd.Dir = ctx.Dir out, _, err = runCommandWithOutput(buildCmd) if err != nil { - t.Fatalf("failed to build the image w/ -q: %s, %v", out, err) + c.Fatalf("failed to build the image w/ -q: %s, %v", out, err) } if strings.Contains(out, "hi there") { - t.Fatalf("Bad output, should not contain 'hi there':%s", out) + c.Fatalf("Bad output, should not contain 'hi there':%s", out) } - logDone("build - not verbose") } -func TestBuildRUNoneJSON(t *testing.T) { +func (s *DockerSuite) TestBuildRUNoneJSON(c *check.C) { name := "testbuildrunonejson" - defer deleteAllContainers() defer deleteImages(name, "hello-world") ctx, err := fakeContext(`FROM hello-world:frozen RUN [ "/hello" ]`, map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() @@ -5532,19 +5350,17 @@ RUN [ "/hello" ]`, map[string]string{}) buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err != nil { - t.Fatalf("failed to build the image: %s, %v", out, err) + c.Fatalf("failed to build the image: %s, %v", out, err) } if !strings.Contains(out, "Hello from Docker") { - t.Fatalf("bad output: %s", out) + c.Fatalf("bad output: %s", out) } - logDone("build - RUN with one JSON arg") } -func TestBuildResourceConstraintsAreUsed(t *testing.T) { +func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { name := "testbuildresourceconstraints" - defer deleteAllContainers() defer deleteImages(name, "hello-world") ctx, err := fakeContext(` @@ -5552,7 +5368,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { RUN ["/hello"] `, map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "-t", name, ".") @@ -5560,9 +5376,9 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - out, _ = dockerCmd(t, "ps", "-lq") + out, _ = dockerCmd(c, "ps", "-lq") cID := strings.TrimSpace(out) @@ -5576,40 +5392,39 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { cfg, err := inspectFieldJSON(cID, "HostConfig") if err != nil { - t.Fatal(err) + c.Fatal(err) } var c1 hostConfig if err := json.Unmarshal([]byte(cfg), &c1); err != nil { - t.Fatal(err, cfg) + c.Fatal(err, cfg) } mem := int64(c1.Memory) if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { - t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", mem, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) } // Make sure constraints aren't saved to image - _, _ = dockerCmd(t, "run", "--name=test", name) + _, _ = dockerCmd(c, "run", "--name=test", name) cfg, err = inspectFieldJSON("test", "HostConfig") if err != nil { - t.Fatal(err) + c.Fatal(err) } var c2 hostConfig if err := json.Unmarshal([]byte(cfg), &c2); err != nil { - t.Fatal(err, cfg) + c.Fatal(err, cfg) } mem = int64(c2.Memory) if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { - t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", mem, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) } - logDone("build - resource constraints applied") } -func TestBuildEmptyStringVolume(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) { name := "testbuildemptystringvolume" defer deleteImages(name) @@ -5619,8 +5434,7 @@ func TestBuildEmptyStringVolume(t *testing.T) { VOLUME $foo `, false) if err == nil { - t.Fatal("Should have failed to build") + c.Fatal("Should have failed to build") } - logDone("build - empty string volume") } diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index 24ebf0cf70b7c..fc8c4600b9cee 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -5,9 +5,9 @@ import ( "os/exec" "regexp" "strings" - "testing" "github.com/docker/docker/utils" + "github.com/go-check/check" ) var ( @@ -22,15 +22,15 @@ func setupImage() (string, error) { func setupImageWithTag(tag string) (string, error) { containerName := "busyboxbydigest" - c := exec.Command(dockerBinary, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox") - if _, err := runCommand(c); err != nil { + cmd := exec.Command(dockerBinary, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox") + if _, err := runCommand(cmd); err != nil { return "", err } // tag the image to upload it to the private registry repoAndTag := utils.ImageReference(repoName, tag) - c = exec.Command(dockerBinary, "commit", containerName, repoAndTag) - if out, _, err := runCommandWithOutput(c); err != nil { + cmd = exec.Command(dockerBinary, "commit", containerName, repoAndTag) + if out, _, err := runCommandWithOutput(cmd); err != nil { return "", fmt.Errorf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoAndTag) @@ -41,15 +41,15 @@ func setupImageWithTag(tag string) (string, error) { } // push the image - c = exec.Command(dockerBinary, "push", repoAndTag) - out, _, err := runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "push", repoAndTag) + out, _, err := runCommandWithOutput(cmd) if err != nil { return "", fmt.Errorf("pushing the image to the private registry has failed: %s, %v", out, err) } // delete our local repo that we previously tagged - c = exec.Command(dockerBinary, "rmi", repoAndTag) - if out, _, err := runCommandWithOutput(c); err != nil { + cmd = exec.Command(dockerBinary, "rmi", repoAndTag) + if out, _, err := runCommandWithOutput(cmd); err != nil { return "", fmt.Errorf("error deleting images prior to real test: %s, %v", out, err) } @@ -63,194 +63,189 @@ func setupImageWithTag(tag string) (string, error) { return pushDigest, nil } -func TestPullByTagDisplaysDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } // pull from the registry using the tag - c := exec.Command(dockerBinary, "pull", repoName) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", repoName) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by tag: %s, %v", out, err) + c.Fatalf("error pulling by tag: %s, %v", out, err) } defer deleteImages(repoName) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) if len(matches) != 2 { - t.Fatalf("unable to parse digest from pull output: %s", out) + c.Fatalf("unable to parse digest from pull output: %s", out) } pullDigest := matches[1] // make sure the pushed and pull digests match if pushDigest != pullDigest { - t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) + c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - logDone("by_digest - pull by tag displays digest") } -func TestPullByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPullByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } // pull from the registry using the @ reference imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } defer deleteImages(imageReference) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) if len(matches) != 2 { - t.Fatalf("unable to parse digest from pull output: %s", out) + c.Fatalf("unable to parse digest from pull output: %s", out) } pullDigest := matches[1] // make sure the pushed and pull digests match if pushDigest != pullDigest { - t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) + c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - logDone("by_digest - pull by digest") } -func TestCreateByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestCreateByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) containerName := "createByDigest" - c := exec.Command(dockerBinary, "create", "--name", containerName, imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "create", "--name", containerName, imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error creating by digest: %s, %v", out, err) + c.Fatalf("error creating by digest: %s, %v", out, err) } defer deleteContainer(containerName) res, err := inspectField(containerName, "Config.Image") if err != nil { - t.Fatalf("failed to get Config.Image: %s, %v", out, err) + c.Fatalf("failed to get Config.Image: %s, %v", out, err) } if res != imageReference { - t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) + c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - logDone("by_digest - create by digest") } -func TestRunByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestRunByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) containerName := "runByDigest" - c := exec.Command(dockerBinary, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest") - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest") + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error run by digest: %s, %v", out, err) + c.Fatalf("error run by digest: %s, %v", out, err) } defer deleteContainer(containerName) foundRegex := regexp.MustCompile("found=([^\n]+)") matches := foundRegex.FindStringSubmatch(out) if len(matches) != 2 { - t.Fatalf("error locating expected 'found=1' output: %s", out) + c.Fatalf("error locating expected 'found=1' output: %s", out) } if matches[1] != "1" { - t.Fatalf("Expected %q, got %q", "1", matches[1]) + c.Fatalf("Expected %q, got %q", "1", matches[1]) } res, err := inspectField(containerName, "Config.Image") if err != nil { - t.Fatalf("failed to get Config.Image: %s, %v", out, err) + c.Fatalf("failed to get Config.Image: %s, %v", out, err) } if res != imageReference { - t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) + c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - logDone("by_digest - run by digest") } -func TestRemoveImageByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestRemoveImageByDigest(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // make sure inspect runs ok if _, err := inspectField(imageReference, "Id"); err != nil { - t.Fatalf("failed to inspect image: %v", err) + c.Fatalf("failed to inspect image: %v", err) } // do the delete if err := deleteImages(imageReference); err != nil { - t.Fatalf("unexpected error deleting image: %v", err) + c.Fatalf("unexpected error deleting image: %v", err) } // try to inspect again - it should error this time if _, err := inspectField(imageReference, "Id"); err == nil { - t.Fatalf("unexpected nil err trying to inspect what should be a non-existent image") + c.Fatalf("unexpected nil err trying to inspect what should be a non-existent image") } else if !strings.Contains(err.Error(), "No such image") { - t.Fatalf("expected 'No such image' output, got %v", err) + c.Fatalf("expected 'No such image' output, got %v", err) } - logDone("by_digest - remove image by digest") } -func TestBuildByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestBuildByDigest(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // get the image id imageID, err := inspectField(imageReference, "Id") if err != nil { - t.Fatalf("error getting image id: %v", err) + c.Fatalf("error getting image id: %v", err) } // do the build @@ -261,275 +256,270 @@ func TestBuildByDigest(t *testing.T) { CMD ["/bin/echo", "Hello World"]`, imageReference), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // get the build's image id res, err := inspectField(name, "Config.Image") if err != nil { - t.Fatal(err) + c.Fatal(err) } // make sure they match if res != imageID { - t.Fatalf("Image %s, expected %s", res, imageID) + c.Fatalf("Image %s, expected %s", res, imageID) } - logDone("by_digest - build by digest") } -func TestTagByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestTagByDigest(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // tag it tag := "tagbydigest" - c = exec.Command(dockerBinary, "tag", imageReference, tag) - if _, err := runCommand(c); err != nil { - t.Fatalf("unexpected error tagging: %v", err) + cmd = exec.Command(dockerBinary, "tag", imageReference, tag) + if _, err := runCommand(cmd); err != nil { + c.Fatalf("unexpected error tagging: %v", err) } expectedID, err := inspectField(imageReference, "Id") if err != nil { - t.Fatalf("error getting original image id: %v", err) + c.Fatalf("error getting original image id: %v", err) } tagID, err := inspectField(tag, "Id") if err != nil { - t.Fatalf("error getting tagged image id: %v", err) + c.Fatalf("error getting tagged image id: %v", err) } if tagID != expectedID { - t.Fatalf("expected image id %q, got %q", expectedID, tagID) + c.Fatalf("expected image id %q, got %q", expectedID, tagID) } - logDone("by_digest - tag by digest") } -func TestListImagesWithoutDigests(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } - c = exec.Command(dockerBinary, "images") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } if strings.Contains(out, "DIGEST") { - t.Fatalf("list output should not have contained DIGEST header: %s", out) + c.Fatalf("list output should not have contained DIGEST header: %s", out) } - logDone("by_digest - list images - digest header not displayed by default") } -func TestListImagesWithDigests(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { + defer setupRegistry(c)() defer deleteImages(repoName+":tag1", repoName+":tag2") // setup image1 digest1, err := setupImageWithTag("tag1") if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1) defer deleteImages(imageReference1) - t.Logf("imageReference1 = %s", imageReference1) + c.Logf("imageReference1 = %s", imageReference1) // pull image1 by digest - c := exec.Command(dockerBinary, "pull", imageReference1) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference1) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure repo shown, tag=, digest = $digest1 re1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`) if !re1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // setup image2 digest2, err := setupImageWithTag("tag2") if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2) defer deleteImages(imageReference2) - t.Logf("imageReference2 = %s", imageReference2) + c.Logf("imageReference2 = %s", imageReference2) // pull image1 by digest - c = exec.Command(dockerBinary, "pull", imageReference1) - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", imageReference1) + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // pull image2 by digest - c = exec.Command(dockerBinary, "pull", imageReference2) - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", imageReference2) + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure repo shown, tag=, digest = $digest1 if !re1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // make sure repo shown, tag=, digest = $digest2 re2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`) if !re2.MatchString(out) { - t.Fatalf("expected %q: %s", re2.String(), out) + c.Fatalf("expected %q: %s", re2.String(), out) } // pull tag1 - c = exec.Command(dockerBinary, "pull", repoName+":tag1") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", repoName+":tag1") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling tag1: %s, %v", out, err) + c.Fatalf("error pulling tag1: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure image 1 has repo, tag, AND repo, , digest reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*\s`) reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`) if !reWithTag1.MatchString(out) { - t.Fatalf("expected %q: %s", reWithTag1.String(), out) + c.Fatalf("expected %q: %s", reWithTag1.String(), out) } if !reWithDigest1.MatchString(out) { - t.Fatalf("expected %q: %s", reWithDigest1.String(), out) + c.Fatalf("expected %q: %s", reWithDigest1.String(), out) } // make sure image 2 has repo, , digest if !re2.MatchString(out) { - t.Fatalf("expected %q: %s", re2.String(), out) + c.Fatalf("expected %q: %s", re2.String(), out) } // pull tag 2 - c = exec.Command(dockerBinary, "pull", repoName+":tag2") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", repoName+":tag2") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling tag2: %s, %v", out, err) + c.Fatalf("error pulling tag2: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure image 1 has repo, tag, digest if !reWithTag1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // make sure image 2 has repo, tag, digest reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*\s`) reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`) if !reWithTag2.MatchString(out) { - t.Fatalf("expected %q: %s", reWithTag2.String(), out) + c.Fatalf("expected %q: %s", reWithTag2.String(), out) } if !reWithDigest2.MatchString(out) { - t.Fatalf("expected %q: %s", reWithDigest2.String(), out) + c.Fatalf("expected %q: %s", reWithDigest2.String(), out) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure image 1 has repo, tag, digest if !reWithTag1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // make sure image 2 has repo, tag, digest if !reWithTag2.MatchString(out) { - t.Fatalf("expected %q: %s", re2.String(), out) + c.Fatalf("expected %q: %s", re2.String(), out) } // make sure busybox has tag, but not digest busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*\s`) if !busyboxRe.MatchString(out) { - t.Fatalf("expected %q: %s", busyboxRe.String(), out) + c.Fatalf("expected %q: %s", busyboxRe.String(), out) } - logDone("by_digest - list images with digests") } -func TestDeleteImageByIDOnlyPulledByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } // pull from the registry using the @ reference imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // just in case... defer deleteImages(imageReference) imageID, err := inspectField(imageReference, ".Id") if err != nil { - t.Fatalf("error inspecting image id: %v", err) + c.Fatalf("error inspecting image id: %v", err) } - c = exec.Command(dockerBinary, "rmi", imageID) - if _, err := runCommand(c); err != nil { - t.Fatalf("error deleting image by id: %v", err) + cmd = exec.Command(dockerBinary, "rmi", imageID) + if _, err := runCommand(cmd); err != nil { + c.Fatalf("error deleting image by id: %v", err) } - logDone("by_digest - delete image by id only pulled by digest") } diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 5d4288192350d..9bbff09ccd0eb 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -3,96 +3,94 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestCommitAfterContainerIsDone(t *testing.T) { +func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("failed to inspect image: %s, %v", out, err) + c.Fatalf("failed to inspect image: %s, %v", out, err) } deleteContainer(cleanedContainerID) deleteImages(cleanedImageID) - logDone("commit - echo foo and commit the image") } -func TestCommitWithoutPause(t *testing.T) { +func (s *DockerSuite) TestCommitWithoutPause(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", "-p=false", cleanedContainerID) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("failed to inspect image: %s, %v", out, err) + c.Fatalf("failed to inspect image: %s, %v", out, err) } deleteContainer(cleanedContainerID) deleteImages(cleanedImageID) - logDone("commit - echo foo and commit the image with --pause=false") } //test commit a paused container should not unpause it after commit -func TestCommitPausedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitPausedContainer(c *check.C) { defer unpauseAllContainers() cmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox") out, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cleanedContainerID := strings.TrimSpace(out) cmd = exec.Command(dockerBinary, "pause", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to pause container: %v, output: %q", err, out) + c.Fatalf("failed to pause container: %v, output: %q", err, out) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) defer deleteImages(cleanedImageID) @@ -100,28 +98,26 @@ func TestCommitPausedContainer(t *testing.T) { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Paused}}", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to inspect container: %v, output: %q", err, out) + c.Fatalf("failed to inspect container: %v, output: %q", err, out) } if !strings.Contains(out, "true") { - t.Fatalf("commit should not unpause a paused container") + c.Fatalf("commit should not unpause a paused container") } - logDone("commit - commit a paused container will not unpause it") } -func TestCommitNewFile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitNewFile(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "commiter", "busybox", "/bin/sh", "-c", "echo koye > /foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", "commiter") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } imageID = strings.Trim(imageID, "\r\n") defer deleteImages(imageID) @@ -130,22 +126,20 @@ func TestCommitNewFile(t *testing.T) { out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "koye" { - t.Fatalf("expected output koye received %q", actual) + c.Fatalf("expected output koye received %q", actual) } - logDone("commit - commit file and read") } -func TestCommitHardlink(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitHardlink(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "--name", "hardlinks", "busybox", "sh", "-c", "touch file1 && ln file1 file2 && ls -di file1 file2") firstOuput, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } chunks := strings.Split(strings.TrimSpace(firstOuput), " ") @@ -158,13 +152,13 @@ func TestCommitHardlink(t *testing.T) { } } if !found { - t.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) + c.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) } cmd = exec.Command(dockerBinary, "commit", "hardlinks", "hardlinks") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(imageID, err) + c.Fatal(imageID, err) } imageID = strings.Trim(imageID, "\r\n") defer deleteImages(imageID) @@ -172,7 +166,7 @@ func TestCommitHardlink(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "-t", "hardlinks", "ls", "-di", "file1", "file2") secondOuput, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } chunks = strings.Split(strings.TrimSpace(secondOuput), " ") @@ -185,48 +179,44 @@ func TestCommitHardlink(t *testing.T) { } } if !found { - t.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) + c.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) } - logDone("commit - commit hardlinks") } -func TestCommitTTY(t *testing.T) { +func (s *DockerSuite) TestCommitTTY(c *check.C) { defer deleteImages("ttytest") - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-t", "--name", "tty", "busybox", "/bin/ls") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", "tty", "ttytest") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.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) + c.Fatal(err) } - logDone("commit - commit tty") } -func TestCommitWithHostBindMount(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "bind-commit", "-v", "/dev/null:/winning", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", "bind-commit", "bindtest") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(imageID, err) + c.Fatal(imageID, err) } imageID = strings.Trim(imageID, "\r\n") @@ -235,18 +225,16 @@ func TestCommitWithHostBindMount(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "bindtest", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("commit - commit bind mounted file") } -func TestCommitChange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitChange(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", @@ -257,7 +245,7 @@ func TestCommitChange(t *testing.T) { "test", "test-commit") imageId, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(imageId, err) + c.Fatal(imageId, err) } imageId = strings.Trim(imageId, "\r\n") defer deleteImages(imageId) @@ -270,29 +258,27 @@ func TestCommitChange(t *testing.T) { for conf, value := range expected { res, err := inspectField(imageId, conf) if err != nil { - t.Errorf("failed to get value %s, error: %s", conf, err) + c.Errorf("failed to get value %s, error: %s", conf, err) } if res != value { - t.Errorf("%s('%s'), expected %s", conf, res, value) + c.Errorf("%s('%s'), expected %s", conf, res, value) } } - logDone("commit - commit --change") } // TODO: commit --run is deprecated, remove this once --run is removed -func TestCommitMergeConfigRun(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) { name := "commit-test" - out, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") + out, _ := dockerCmd(c, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") id := strings.TrimSpace(out) - dockerCmd(t, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") + dockerCmd(c, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") defer deleteImages("commit-test") - out, _ = dockerCmd(t, "run", "--name", name, "commit-test") + out, _ = dockerCmd(c, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { - t.Fatal("run config in commited container was not merged") + c.Fatal("run config in commited container was not merged") } type cfg struct { @@ -301,11 +287,11 @@ func TestCommitMergeConfigRun(t *testing.T) { } config1 := cfg{} if err := inspectFieldAndMarshall(id, "Config", &config1); err != nil { - t.Fatal(err) + c.Fatal(err) } config2 := cfg{} if err := inspectFieldAndMarshall(name, "Config", &config2); err != nil { - t.Fatal(err) + c.Fatal(err) } // Env has at least PATH loaded as well here, so let's just grab the FOO one @@ -324,8 +310,7 @@ func TestCommitMergeConfigRun(t *testing.T) { } if len(config1.Env) != len(config2.Env) || env1 != env2 && env2 != "" { - t.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env) + c.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env) } - logDone("commit - configs are merged with --run") } diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go index 23ad700698bc1..5ccd7af10e0cb 100644 --- a/integration-cli/docker_cli_config_test.go +++ b/integration-cli/docker_cli_config_test.go @@ -7,13 +7,13 @@ import ( "os" "os/exec" "path/filepath" - "testing" "github.com/docker/docker/pkg/homedir" + "github.com/go-check/check" ) -func TestConfigHttpHeader(t *testing.T) { - testRequires(t, UnixCli) // Can't set/unset HOME on windows right now +func (s *DockerSuite) TestConfigHttpHeader(c *check.C) { + testRequires(c, UnixCli) // Can't set/unset HOME on windows right now // We either need a level of Go that supports Unsetenv (for cases // when HOME/USERPROFILE isn't set), or we need to be able to use // os/user but user.Current() only works if we aren't statically compiling @@ -44,15 +44,13 @@ func TestConfigHttpHeader(t *testing.T) { err := ioutil.WriteFile(tmpCfg, []byte(data), 0600) if err != nil { - t.Fatalf("Err creating file(%s): %v", tmpCfg, err) + c.Fatalf("Err creating file(%s): %v", tmpCfg, err) } cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "ps") out, _, _ := runCommandWithOutput(cmd) if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { - t.Fatalf("Missing/bad header: %q\nout:%v", headers, out) + c.Fatalf("Missing/bad header: %q\nout:%v", headers, out) } - - logDone("config - add new http headers") } diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index b577e82982608..022b3cc9ea05e 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -9,7 +9,8 @@ import ( "path" "path/filepath" "strings" - "testing" + + "github.com/go-check/check" ) const ( @@ -24,27 +25,27 @@ const ( // Test for #5656 // Check that garbage paths don't escape the container's rootfs -func TestCpGarbagePath(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) +func (s *DockerSuite) TestCpGarbagePath(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -53,7 +54,7 @@ func TestCpGarbagePath(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -61,49 +62,48 @@ func TestCpGarbagePath(t *testing.T) { path := path.Join("../../../../../../../../../../../../", cpFullPath) - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- garbage path can escape container rootfs") + c.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") + c.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 := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) +func (s *DockerSuite) TestCpRelativePath(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -113,7 +113,7 @@ func TestCpRelativePath(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -125,52 +125,51 @@ func TestCpRelativePath(t *testing.T) { // get this unix-path manipulation on windows with filepath. relPath = cpFullPath[1:] } else { - t.Fatalf("path %s was assumed to be an absolute path", cpFullPath) + c.Fatalf("path %s was assumed to be an absolute path", cpFullPath) } - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+relPath, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- relative path can escape container rootfs") + c.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") + c.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 := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) +func (s *DockerSuite) TestCpAbsolutePath(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -180,7 +179,7 @@ func TestCpAbsolutePath(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -188,50 +187,49 @@ func TestCpAbsolutePath(t *testing.T) { path := cpFullPath - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- absolute path can escape container rootfs") + c.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") + c.Errorf("output doesn't match the input for absolute path") } - 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 := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") +func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -241,7 +239,7 @@ func TestCpAbsoluteSymlink(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -249,50 +247,49 @@ func TestCpAbsoluteSymlink(t *testing.T) { path := path.Join("/", "container_path") - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- absolute symlink can escape container rootfs") + c.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") + c.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 := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") +func (s *DockerSuite) TestCpSymlinkComponent(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -302,7 +299,7 @@ func TestCpSymlinkComponent(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -310,268 +307,263 @@ func TestCpSymlinkComponent(t *testing.T) { path := path.Join("/", "container_path", cpTestName) - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- symlink path component can escape container rootfs") + c.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") + c.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) { - testRequires(t, UnixCli) // uses chmod/su: not available on windows +func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) { + testRequires(c, UnixCli) // uses chmod/su: not available on windows - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) if err = os.Chmod(tmpdir, 0777); err != nil { - t.Fatal(err) + c.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) + c.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err) } - logDone("cp - unprivileged user") } -func TestCpSpecialFiles(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCpSpecialFiles(c *check.C) { + testRequires(c, SameHostDaemon) outDir, err := ioutil.TempDir("", "cp-test-special-files") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(outDir) - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } // Copy actual /etc/resolv.conf - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf") actual, err := ioutil.ReadFile(outDir + "/resolv.conf") if !bytes.Equal(actual, expected) { - t.Fatalf("Expected copied file to be duplicate of the container resolvconf") + c.Fatalf("Expected copied file to be duplicate of the container resolvconf") } // Copy actual /etc/hosts - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hosts", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hosts", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts") actual, err = ioutil.ReadFile(outDir + "/hosts") if !bytes.Equal(actual, expected) { - t.Fatalf("Expected copied file to be duplicate of the container hosts") + c.Fatalf("Expected copied file to be duplicate of the container hosts") } // Copy actual /etc/resolv.conf - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hostname", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hostname", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname") actual, err = ioutil.ReadFile(outDir + "/hostname") if !bytes.Equal(actual, expected) { - t.Fatalf("Expected copied file to be duplicate of the container resolvconf") + c.Fatalf("Expected copied file to be duplicate of the container resolvconf") } - logDone("cp - special files (resolv.conf, hosts, hostname)") } -func TestCpVolumePath(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCpVolumePath(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir("", "cp-test-volumepath") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) outDir, err := ioutil.TempDir("", "cp-test-volumepath-out") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(outDir) _, err = os.Create(tmpDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, exitCode := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") + out, exitCode := dockerCmd(c, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) - defer dockerCmd(t, "rm", "-fv", cleanedContainerID) + defer dockerCmd(c, "rm", "-fv", cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } // Copy actual volume path - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo", outDir) stat, err := os.Stat(outDir + "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !stat.IsDir() { - t.Fatal("expected copied content to be dir") + c.Fatal("expected copied content to be dir") } stat, err = os.Stat(outDir + "/foo/bar") if err != nil { - t.Fatal(err) + c.Fatal(err) } if stat.IsDir() { - t.Fatal("Expected file `bar` to be a file") + c.Fatal("Expected file `bar` to be a file") } // Copy file nested in volume - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo/bar", outDir) stat, err = os.Stat(outDir + "/bar") if err != nil { - t.Fatal(err) + c.Fatal(err) } if stat.IsDir() { - t.Fatal("Expected file `bar` to be a file") + c.Fatal("Expected file `bar` to be a file") } // Copy Bind-mounted dir - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz", outDir) stat, err = os.Stat(outDir + "/baz") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !stat.IsDir() { - t.Fatal("Expected `baz` to be a dir") + c.Fatal("Expected `baz` to be a dir") } // Copy file nested in bind-mounted dir - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz/test", outDir) fb, err := ioutil.ReadFile(outDir + "/baz/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } fb2, err := ioutil.ReadFile(tmpDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(fb, fb2) { - t.Fatalf("Expected copied file to be duplicate of bind-mounted file") + c.Fatalf("Expected copied file to be duplicate of bind-mounted file") } // Copy bind-mounted file - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", outDir) fb, err = ioutil.ReadFile(outDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } fb2, err = ioutil.ReadFile(tmpDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(fb, fb2) { - t.Fatalf("Expected copied file to be duplicate of bind-mounted file") + c.Fatalf("Expected copied file to be duplicate of bind-mounted file") } - logDone("cp - volume path") } -func TestCpToDot(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") +func (s *DockerSuite) TestCpToDot(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) cwd, err := os.Getwd() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.Chdir(cwd) if err := os.Chdir(tmpdir); err != nil { - t.Fatal(err) + c.Fatal(err) } - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", ".") + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", ".") content, err := ioutil.ReadFile("./test") if string(content) != "lololol\n" { - t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") + c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") } - logDone("cp - to dot path") } -func TestCpToStdout(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") +func (s *DockerSuite) TestCpToStdout(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") if exitCode != 0 { - t.Fatalf("failed to create a container:%s\n", out) + c.Fatalf("failed to create a container:%s\n", out) } cID := strings.TrimSpace(out) defer deleteContainer(cID) - out, _ = dockerCmd(t, "wait", cID) + out, _ = dockerCmd(c, "wait", cID) if strings.TrimSpace(out) != "0" { - t.Fatalf("failed to set up container:%s\n", out) + c.Fatalf("failed to set up container:%s\n", out) } out, _, err := runCommandPipelineWithOutput( @@ -579,40 +571,38 @@ func TestCpToStdout(t *testing.T) { exec.Command("tar", "-vtf", "-")) if err != nil { - t.Fatalf("Failed to run commands: %s", err) + c.Fatalf("Failed to run commands: %s", err) } if !strings.Contains(out, "test") || !strings.Contains(out, "-rw") { - t.Fatalf("Missing file from tar TOC:\n%s", out) + c.Fatalf("Missing file from tar TOC:\n%s", out) } - logDone("cp - to stdout") } -func TestCpNameHasColon(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCpNameHasColon(c *check.C) { + testRequires(c, SameHostDaemon) - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/te:s:t", tmpdir) content, err := ioutil.ReadFile(tmpdir + "/te:s:t") if string(content) != "lololol\n" { - t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") + c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") } - logDone("cp - copy filename has ':'") } diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index a8cc09106ea4b..5fbd50b6da8d0 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -6,20 +6,19 @@ import ( "os/exec" "reflect" "strings" - "testing" "time" "github.com/docker/docker/nat" + "github.com/go-check/check" ) // Make sure we can create a simple container with some args -func TestCreateArgs(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateArgs(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "busybox", "command", "arg1", "arg2", "arg with space") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -27,7 +26,7 @@ func TestCreateArgs(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -38,40 +37,38 @@ func TestCreateArgs(t *testing.T) { Image string }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.Path != "command" { - t.Fatalf("Unexpected container path. Expected command, received: %s", c.Path) + cont := containers[0] + if cont.Path != "command" { + c.Fatalf("Unexpected container path. Expected command, received: %s", cont.Path) } b := false expected := []string{"arg1", "arg2", "arg with space"} for i, arg := range expected { - if arg != c.Args[i] { + if arg != cont.Args[i] { b = true break } } - if len(c.Args) != len(expected) || b { - t.Fatalf("Unexpected args. Expected %v, received: %v", expected, c.Args) + if len(cont.Args) != len(expected) || b { + c.Fatalf("Unexpected args. Expected %v, received: %v", expected, cont.Args) } - logDone("create - args") } // Make sure we can set hostconfig options too -func TestCreateHostConfig(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateHostConfig(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "-P", "busybox", "echo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -79,7 +76,7 @@ func TestCreateHostConfig(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -88,31 +85,29 @@ func TestCreateHostConfig(t *testing.T) { } }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.HostConfig == nil { - t.Fatalf("Expected HostConfig, got none") + cont := containers[0] + if cont.HostConfig == nil { + c.Fatalf("Expected HostConfig, got none") } - if !c.HostConfig.PublishAllPorts { - t.Fatalf("Expected PublishAllPorts, got false") + if !cont.HostConfig.PublishAllPorts { + c.Fatalf("Expected PublishAllPorts, got false") } - logDone("create - hostconfig") } -func TestCreateWithPortRange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateWithPortRange(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "-p", "3300-3303:3300-3303/tcp", "busybox", "echo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -120,7 +115,7 @@ func TestCreateWithPortRange(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -129,39 +124,37 @@ func TestCreateWithPortRange(t *testing.T) { } }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.HostConfig == nil { - t.Fatalf("Expected HostConfig, got none") + cont := containers[0] + if cont.HostConfig == nil { + c.Fatalf("Expected HostConfig, got none") } - if len(c.HostConfig.PortBindings) != 4 { - t.Fatalf("Expected 4 ports bindings, got %d", len(c.HostConfig.PortBindings)) + if len(cont.HostConfig.PortBindings) != 4 { + c.Fatalf("Expected 4 ports bindings, got %d", len(cont.HostConfig.PortBindings)) } - for k, v := range c.HostConfig.PortBindings { + for k, v := range cont.HostConfig.PortBindings { if len(v) != 1 { - t.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) + c.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) } if k.Port() != v[0].HostPort { - t.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) + c.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) } } - logDone("create - port range") } -func TestCreateWithiLargePortRange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateWithiLargePortRange(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "-p", "1-65535:1-65535/tcp", "busybox", "echo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -169,7 +162,7 @@ func TestCreateWithiLargePortRange(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -178,40 +171,38 @@ func TestCreateWithiLargePortRange(t *testing.T) { } }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.HostConfig == nil { - t.Fatalf("Expected HostConfig, got none") + cont := containers[0] + if cont.HostConfig == nil { + c.Fatalf("Expected HostConfig, got none") } - if len(c.HostConfig.PortBindings) != 65535 { - t.Fatalf("Expected 65535 ports bindings, got %d", len(c.HostConfig.PortBindings)) + if len(cont.HostConfig.PortBindings) != 65535 { + c.Fatalf("Expected 65535 ports bindings, got %d", len(cont.HostConfig.PortBindings)) } - for k, v := range c.HostConfig.PortBindings { + for k, v := range cont.HostConfig.PortBindings { if len(v) != 1 { - t.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) + c.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) } if k.Port() != v[0].HostPort { - t.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) + c.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) } } - logDone("create - large port range") } // "test123" should be printed by docker create + start -func TestCreateEchoStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateEchoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "busybox", "echo", "test123") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -219,62 +210,58 @@ func TestCreateEchoStdout(t *testing.T) { runCmd = exec.Command(dockerBinary, "start", "-ai", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "test123\n" { - t.Errorf("container should've printed 'test123', got %q", out) + c.Errorf("container should've printed 'test123', got %q", out) } - logDone("create - echo test123") } -func TestCreateVolumesCreated(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestCreateVolumesCreated(c *check.C) { + testRequires(c, SameHostDaemon) name := "test_create_volume" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-v", "/foo", "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } dir, err := inspectFieldMap(name, "Volumes", "/foo") if err != nil { - t.Fatalf("Error getting volume host path: %q", err) + c.Fatalf("Error getting volume host path: %q", err) } if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { - t.Fatalf("Volume was not created") + c.Fatalf("Volume was not created") } if err != nil { - t.Fatalf("Error statting volume host path: %q", err) + c.Fatalf("Error statting volume host path: %q", err) } - logDone("create - volumes are created") } -func TestCreateLabels(t *testing.T) { +func (s *DockerSuite) TestCreateLabels(c *check.C) { name := "test_create_labels" expected := map[string]string{"k1": "v1", "k2": "v2"} if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k1=v1", "--label", "k2=v2", "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } actual := make(map[string]string) err := inspectFieldAndMarshall(name, "Config.Labels", &actual) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !reflect.DeepEqual(expected, actual) { - t.Fatalf("Expected %s got %s", expected, actual) + c.Fatalf("Expected %s got %s", expected, actual) } deleteAllContainers() - logDone("create - labels") } -func TestCreateLabelFromImage(t *testing.T) { +func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { imageName := "testcreatebuildlabel" defer deleteImages(imageName) _, err := buildImage(imageName, @@ -282,34 +269,32 @@ func TestCreateLabelFromImage(t *testing.T) { LABEL k1=v1 k2=v2`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } name := "test_create_labels_from_image" expected := map[string]string{"k2": "x", "k3": "v3", "k1": "v1"} if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k2=x", "--label", "k3=v3", imageName)); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } actual := make(map[string]string) err = inspectFieldAndMarshall(name, "Config.Labels", &actual) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !reflect.DeepEqual(expected, actual) { - t.Fatalf("Expected %s got %s", expected, actual) + c.Fatalf("Expected %s got %s", expected, actual) } deleteAllContainers() - logDone("create - labels from image") } -func TestCreateHostnameWithNumber(t *testing.T) { - out, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") +func (s *DockerSuite) TestCreateHostnameWithNumber(c *check.C) { + out, _ := dockerCmd(c, "run", "-h", "web.0", "busybox", "hostname") if strings.TrimSpace(out) != "web.0" { - t.Fatalf("hostname not set, expected `web.0`, got: %s", out) + c.Fatalf("hostname not set, expected `web.0`, got: %s", out) } - logDone("create - use hostname with number") } diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index d81ad3c807c9b..81cf0ab0c3f1b 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -10,41 +10,41 @@ import ( "os/exec" "path/filepath" "strings" - "testing" "time" "github.com/docker/libtrust" + "github.com/go-check/check" ) -func TestDaemonRestartWithRunningContainersPorts(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top1: err=%v\n%s", err, out) + c.Fatalf("Could not run top1: err=%v\n%s", err, out) } // --restart=no by default if out, err := d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top2: err=%v\n%s", err, out) + c.Fatalf("Could not run top2: err=%v\n%s", err, out) } testRun := func(m map[string]bool, prefix string) { var format string - for c, shouldRun := range m { + for cont, shouldRun := range m { out, err := d.Cmd("ps") if err != nil { - t.Fatalf("Could not run ps: err=%v\n%q", err, out) + c.Fatalf("Could not run ps: err=%v\n%q", err, out) } if shouldRun { format = "%scontainer %q is not running" } else { format = "%scontainer %q is running" } - if shouldRun != strings.Contains(out, c) { - t.Fatalf(format, prefix, c) + if shouldRun != strings.Contains(out, cont) { + c.Fatalf(format, prefix, cont) } } } @@ -52,102 +52,97 @@ func TestDaemonRestartWithRunningContainersPorts(t *testing.T) { testRun(map[string]bool{"top1": true, "top2": true}, "") if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ") - logDone("daemon - running containers on daemon restart") } -func TestDaemonRestartWithVolumesRefs(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Restart(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("rm", "-fv", "volrestarttest2"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } v, err := d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1") if err != nil { - t.Fatal(err) + c.Fatal(err) } volumes := make(map[string]string) json.Unmarshal([]byte(v), &volumes) if _, err := os.Stat(volumes["/foo"]); err != nil { - t.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err) + c.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err) } - logDone("daemon - volume refs are restored") } -func TestDaemonStartIptablesFalse(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonStartIptablesFalse(c *check.C) { + d := NewDaemon(c) if err := d.Start("--iptables=false"); err != nil { - t.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err) + c.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err) } d.Stop() - logDone("daemon - started daemon with iptables=false") } // Issue #8444: If docker0 bridge is modified (intentionally or unintentionally) and // no longer has an IP associated, we should gracefully handle that case and associate // an IP with it rather than fail daemon start -func TestDaemonStartBridgeWithoutIPAssociation(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { + d := NewDaemon(c) // rather than depending on brctl commands to verify docker0 is created and up // let's start the daemon and stop it, and then make a modification to run the // actual test if err := d.Start(); err != nil { - t.Fatalf("Could not start daemon: %v", err) + c.Fatalf("Could not start daemon: %v", err) } if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } // now we will remove the ip from docker0 and then try starting the daemon ipCmd := exec.Command("ip", "addr", "flush", "dev", "docker0") stdout, stderr, _, err := runCommandWithStdoutStderr(ipCmd) if err != nil { - t.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr) + c.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr) } if err := d.Start(); err != nil { warning := "**WARNING: Docker bridge network in bad state--delete docker0 bridge interface to fix" - t.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning) + c.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning) } // cleanup - stop the daemon if test passed if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } - logDone("daemon - successful daemon start when bridge has no IP association") } -func TestDaemonIptablesClean(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top: %s, %v", out, err) + c.Fatalf("Could not run top: %s, %v", out, err) } // get output from iptables with container running @@ -155,42 +150,40 @@ func TestDaemonIptablesClean(t *testing.T) { ipTablesCmd := exec.Command("iptables", "-nvL") out, _, err := runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if !strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } // get output from iptables after restart ipTablesCmd = exec.Command("iptables", "-nvL") out, _, err = runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out) } - logDone("daemon - run,iptables - iptables rules cleaned after daemon restart") } -func TestDaemonIptablesCreate(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top: %s, %v", out, err) + c.Fatalf("Could not run top: %s, %v", out, err) } // get output from iptables with container running @@ -198,101 +191,99 @@ func TestDaemonIptablesCreate(t *testing.T) { ipTablesCmd := exec.Command("iptables", "-nvL") out, _, err := runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if !strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } // make sure the container is not running runningOut, err := d.Cmd("inspect", "--format='{{.State.Running}}'", "top") if err != nil { - t.Fatalf("Could not inspect on container: %s, %v", out, err) + c.Fatalf("Could not inspect on container: %s, %v", out, err) } if strings.TrimSpace(runningOut) != "true" { - t.Fatalf("Container should have been restarted after daemon restart. Status running should have been true but was: %q", strings.TrimSpace(runningOut)) + c.Fatalf("Container should have been restarted after daemon restart. Status running should have been true but was: %q", strings.TrimSpace(runningOut)) } // get output from iptables after restart ipTablesCmd = exec.Command("iptables", "-nvL") out, _, err = runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if !strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out) } - logDone("daemon - run,iptables - iptables rules for always restarted container created after daemon restart") } -func TestDaemonLoggingLevel(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingLevel(c *check.C) { + d := NewDaemon(c) if err := d.Start("--log-level=bogus"); err == nil { - t.Fatal("Daemon should not have been able to start") + c.Fatal("Daemon should not have been able to start") } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--log-level=debug"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ := ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--log-level=fatal"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content)) + c.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("-D"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--debug"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--debug", "--log-level=fatal"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) } - logDone("daemon - Logging Level") } -func TestDaemonAllocatesListeningPort(t *testing.T) { +func (s *DockerSuite) TestDaemonAllocatesListeningPort(c *check.C) { listeningPorts := [][]string{ {"0.0.0.0", "0.0.0.0", "5678"}, {"127.0.0.1", "127.0.0.1", "1234"}, @@ -304,120 +295,116 @@ func TestDaemonAllocatesListeningPort(t *testing.T) { cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2])) } - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(cmdArgs...); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() for _, hostDirective := range listeningPorts { output, err := d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true") if err == nil { - t.Fatalf("Container should not start, expected port already allocated error: %q", output) + c.Fatalf("Container should not start, expected port already allocated error: %q", output) } else if !strings.Contains(output, "port is already allocated") { - t.Fatalf("Expected port is already allocated error: %q", output) + c.Fatalf("Expected port is already allocated error: %q", output) } } - logDone("daemon - daemon listening port is allocated") } // #9629 -func TestDaemonVolumesBindsRefs(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonVolumesBindsRefs(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() tmp, err := ioutil.TempDir(os.TempDir(), "") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmp) if err := ioutil.WriteFile(tmp+"/test", []byte("testing"), 0655); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Restart(); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("daemon - bind refs in data-containers survive daemon restart") } -func TestDaemonKeyGeneration(t *testing.T) { +func (s *DockerSuite) TestDaemonKeyGeneration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start(); err != nil { - t.Fatalf("Could not start daemon: %v", err) + c.Fatalf("Could not start daemon: %v", err) } d.Stop() k, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { - t.Fatalf("Error opening key file") + c.Fatalf("Error opening key file") } kid := k.KeyID() // Test Key ID is a valid fingerprint (e.g. QQXN:JY5W:TBXI:MK3X:GX6P:PD5D:F56N:NHCS:LVRZ:JA46:R24J:XEFF) if len(kid) != 59 { - t.Fatalf("Bad key ID: %s", kid) + c.Fatalf("Bad key ID: %s", kid) } - logDone("daemon - key generation") } -func TestDaemonKeyMigration(t *testing.T) { +func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") k1, err := libtrust.GenerateECP256PrivateKey() if err != nil { - t.Fatalf("Error generating private key: %s", err) + c.Fatalf("Error generating private key: %s", err) } if err := os.MkdirAll(filepath.Join(os.Getenv("HOME"), ".docker"), 0755); err != nil { - t.Fatalf("Error creating .docker directory: %s", err) + c.Fatalf("Error creating .docker directory: %s", err) } if err := libtrust.SaveKey(filepath.Join(os.Getenv("HOME"), ".docker", "key.json"), k1); err != nil { - t.Fatalf("Error saving private key: %s", err) + c.Fatalf("Error saving private key: %s", err) } - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start(); err != nil { - t.Fatalf("Could not start daemon: %v", err) + c.Fatalf("Could not start daemon: %v", err) } d.Stop() k2, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { - t.Fatalf("Error opening key file") + c.Fatalf("Error opening key file") } if k1.KeyID() != k2.KeyID() { - t.Fatalf("Key not migrated") + c.Fatalf("Key not migrated") } - logDone("daemon - key migration") } // Simulate an older daemon (pre 1.3) coming up with volumes specified in containers // without corresponding volume json -func TestDaemonUpgradeWithVolumes(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { + d := NewDaemon(c) graphDir := filepath.Join(os.TempDir(), "docker-test") defer os.RemoveAll(graphDir) if err := d.StartWithBusybox("-g", graphDir); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() @@ -425,199 +412,195 @@ func TestDaemonUpgradeWithVolumes(t *testing.T) { defer os.RemoveAll(tmpDir) if out, err := d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Stop(); err != nil { - t.Fatal(err) + c.Fatal(err) } // Remove this since we're expecting the daemon to re-create it too if err := os.RemoveAll(tmpDir); err != nil { - t.Fatal(err) + c.Fatal(err) } configDir := filepath.Join(graphDir, "volumes") if err := os.RemoveAll(configDir); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := d.Start("-g", graphDir); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(tmpDir); os.IsNotExist(err) { - t.Fatalf("expected volume path %s to exist but it does not", tmpDir) + c.Fatalf("expected volume path %s to exist but it does not", tmpDir) } dir, err := ioutil.ReadDir(configDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(dir) == 0 { - t.Fatalf("expected volumes config dir to contain data for new volume") + c.Fatalf("expected volumes config dir to contain data for new volume") } // Now with just removing the volume config and not the volume data if err := d.Stop(); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.RemoveAll(configDir); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := d.Start("-g", graphDir); err != nil { - t.Fatal(err) + c.Fatal(err) } dir, err = ioutil.ReadDir(configDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(dir) == 0 { - t.Fatalf("expected volumes config dir to contain data for new volume") + c.Fatalf("expected volumes config dir to contain data for new volume") } - logDone("daemon - volumes from old(pre 1.3) daemon work") } // GH#11320 - verify that the daemon exits on failure properly // Note that this explicitly tests the conflict of {-b,--bridge} and {--bip} options as the means // to get a daemon init failure; no other tests for -b/--bip conflict are therefore required -func TestDaemonExitOnFailure(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonExitOnFailure(c *check.C) { + d := NewDaemon(c) defer d.Stop() //attempt to start daemon with incorrect flags (we know -b and --bip conflict) if err := d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil { //verify we got the right error if !strings.Contains(err.Error(), "Daemon exited and never started") { - t.Fatalf("Expected daemon not to start, got %v", err) + c.Fatalf("Expected daemon not to start, got %v", err) } // look in the log and make sure we got the message that daemon is shutting down runCmd := exec.Command("grep", "Error starting daemon", d.LogfileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) + c.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) } } else { //if we didn't get an error and the daemon is running, this is a failure d.Stop() - t.Fatal("Conflicting options should cause the daemon to error out with a failure") + c.Fatal("Conflicting options should cause the daemon to error out with a failure") } - logDone("daemon - verify no start on daemon init errors") } -func TestDaemonUlimitDefaults(t *testing.T) { - testRequires(t, NativeExecDriver) - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { + testRequires(c, NativeExecDriver) + d := NewDaemon(c) if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } outArr := strings.Split(out, "\n") if len(outArr) < 2 { - t.Fatalf("got unexpected output: %s", out) + c.Fatalf("got unexpected output: %s", out) } nofile := strings.TrimSpace(outArr[0]) nproc := strings.TrimSpace(outArr[1]) if nofile != "42" { - t.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile) + c.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile) } if nproc != "2048" { - t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) + c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) } // Now restart daemon with a new default if err := d.Restart("--default-ulimit", "nofile=43"); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = d.Cmd("start", "-a", "test") if err != nil { - t.Fatal(err) + c.Fatal(err) } outArr = strings.Split(out, "\n") if len(outArr) < 2 { - t.Fatalf("got unexpected output: %s", out) + c.Fatalf("got unexpected output: %s", out) } nofile = strings.TrimSpace(outArr[0]) nproc = strings.TrimSpace(outArr[1]) if nofile != "43" { - t.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile) + c.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile) } if nproc != "2048" { - t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) + c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) } - logDone("daemon - default ulimits are applied") } // #11315 -func TestDaemonRestartRenameContainer(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartRenameContainer(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() if out, err := d.Cmd("run", "--name=test", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if out, err := d.Cmd("rename", "test", "test2"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Restart(); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("start", "test2"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("daemon - rename persists through daemon restart") } -func TestDaemonLoggingDriverDefault(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverDefault(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { - t.Fatal(err) + c.Fatal(err) } f, err := os.Open(logPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } var res struct { Log string `json:"log"` @@ -625,95 +608,92 @@ func TestDaemonLoggingDriverDefault(t *testing.T) { Time time.Time `json:"time"` } if err := json.NewDecoder(f).Decode(&res); err != nil { - t.Fatal(err) + c.Fatal(err) } if res.Log != "testline\n" { - t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") + c.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") } if res.Stream != "stdout" { - t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") + c.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") } if !time.Now().After(res.Time) { - t.Fatalf("Log time %v in future", res.Time) + c.Fatalf("Log time %v in future", res.Time) } - logDone("daemon - default 'json-file' logging driver") } -func TestDaemonLoggingDriverDefaultOverride(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { - t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) + c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } - logDone("daemon - default logging driver override in run") } -func TestDaemonLoggingDriverNone(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverNone(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox("--log-driver=none"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { - t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) + c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } - logDone("daemon - 'none' logging driver") } -func TestDaemonLoggingDriverNoneOverride(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox("--log-driver=none"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { - t.Fatal(err) + c.Fatal(err) } f, err := os.Open(logPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } var res struct { Log string `json:"log"` @@ -721,63 +701,60 @@ func TestDaemonLoggingDriverNoneOverride(t *testing.T) { Time time.Time `json:"time"` } if err := json.NewDecoder(f).Decode(&res); err != nil { - t.Fatal(err) + c.Fatal(err) } if res.Log != "testline\n" { - t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") + c.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") } if res.Stream != "stdout" { - t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") + c.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") } if !time.Now().After(res.Time) { - t.Fatalf("Log time %v in future", res.Time) + c.Fatalf("Log time %v in future", res.Time) } - logDone("daemon - 'none' logging driver override in run") } -func TestDaemonLoggingDriverNoneLogsError(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox("--log-driver=none"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) out, err = d.Cmd("logs", id) if err == nil { - t.Fatalf("Logs should fail with \"none\" driver") + c.Fatalf("Logs should fail with \"none\" driver") } if !strings.Contains(out, `\"logs\" command is supported only for \"json-file\" logging driver`) { - t.Fatalf("There should be error about non-json-file driver, got %s", out) + c.Fatalf("There should be error about non-json-file driver, got %s", out) } - logDone("daemon - logs not available for non-json-file drivers") } -func TestDaemonDots(t *testing.T) { - defer deleteAllContainers() - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonDots(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() // Now create 4 containers if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } d.Stop() @@ -786,56 +763,54 @@ func TestDaemonDots(t *testing.T) { d.Stop() content, _ := ioutil.ReadFile(d.logFile.Name()) if strings.Contains(string(content), "....") { - t.Fatalf("Debug level should not have ....\n%s", string(content)) + c.Fatalf("Debug level should not have ....\n%s", string(content)) } d.Start("--log-level=error") d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if strings.Contains(string(content), "....") { - t.Fatalf("Error level should not have ....\n%s", string(content)) + c.Fatalf("Error level should not have ....\n%s", string(content)) } d.Start("--log-level=info") d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), "....") { - t.Fatalf("Info level should have ....\n%s", string(content)) + c.Fatalf("Info level should have ....\n%s", string(content)) } - logDone("daemon - test dots on INFO") } -func TestDaemonUnixSockCleanedUp(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonUnixSockCleanedUp(c *check.C) { + d := NewDaemon(c) dir, err := ioutil.TempDir("", "socket-cleanup-test") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(dir) sockPath := filepath.Join(dir, "docker.sock") if err := d.Start("--host", "unix://"+sockPath); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() if _, err := os.Stat(sockPath); err != nil { - t.Fatal("socket does not exist") + c.Fatal("socket does not exist") } if err := d.Stop(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(sockPath); err == nil || !os.IsNotExist(err) { - t.Fatal("unix socket is not cleaned up") + c.Fatal("unix socket is not cleaned up") } - logDone("daemon - unix socket is cleaned up") } -func TestDaemonwithwrongkey(t *testing.T) { +func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { type Config struct { Crv string `json:"crv"` D string `json:"d"` @@ -846,24 +821,24 @@ func TestDaemonwithwrongkey(t *testing.T) { } os.Remove("/etc/docker/key.json") - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start(); err != nil { - t.Fatalf("Failed to start daemon: %v", err) + c.Fatalf("Failed to start daemon: %v", err) } if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } config := &Config{} bytes, err := ioutil.ReadFile("/etc/docker/key.json") if err != nil { - t.Fatalf("Error reading key.json file: %s", err) + c.Fatalf("Error reading key.json file: %s", err) } // byte[] to Data-Struct if err := json.Unmarshal(bytes, &config); err != nil { - t.Fatalf("Error Unmarshal: %s", err) + c.Fatalf("Error Unmarshal: %s", err) } //replace config.Kid with the fake value @@ -872,50 +847,49 @@ func TestDaemonwithwrongkey(t *testing.T) { // NEW Data-Struct to byte[] newBytes, err := json.Marshal(&config) if err != nil { - t.Fatalf("Error Marshal: %s", err) + c.Fatalf("Error Marshal: %s", err) } // write back if err := ioutil.WriteFile("/etc/docker/key.json", newBytes, 0400); err != nil { - t.Fatalf("Error ioutil.WriteFile: %s", err) + c.Fatalf("Error ioutil.WriteFile: %s", err) } - d1 := NewDaemon(t) + d1 := NewDaemon(c) defer os.Remove("/etc/docker/key.json") if err := d1.Start(); err == nil { d1.Stop() - t.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) + c.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) } content, _ := ioutil.ReadFile(d1.logFile.Name()) if !strings.Contains(string(content), "Public Key ID does not match") { - t.Fatal("Missing KeyID message from daemon logs") + c.Fatal("Missing KeyID message from daemon logs") } - logDone("daemon - it should be failed to start daemon with wrong key") } -func TestDaemonRestartKillWait(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() out, err := d.Cmd("run", "-id", "busybox", "/bin/cat") if err != nil { - t.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) + c.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) } containerID := strings.TrimSpace(out) if out, err := d.Cmd("kill", containerID); err != nil { - t.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) + c.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) } if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } errchan := make(chan error) @@ -928,12 +902,11 @@ func TestDaemonRestartKillWait(t *testing.T) { select { case <-time.After(5 * time.Second): - t.Fatal("Waiting on a stopped (killed) container timed out") + c.Fatal("Waiting on a stopped (killed) container timed out") case err := <-errchan: if err != nil { - t.Fatal(err) + c.Fatal(err) } } - logDone("wait - wait on a stopped container doesn't timeout") } diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go index f7f8cd7a9d356..332b128ed8f4c 100644 --- a/integration-cli/docker_cli_diff_test.go +++ b/integration-cli/docker_cli_diff_test.go @@ -3,16 +3,17 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // ensure that an added file shows up in docker diff -func TestDiffFilenameShownInOutput(t *testing.T) { +func (s *DockerSuite) TestDiffFilenameShownInOutput(c *check.C) { containerCmd := `echo foo > /root/bar` runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanCID := strings.TrimSpace(out) @@ -20,7 +21,7 @@ func TestDiffFilenameShownInOutput(t *testing.T) { diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) if err != nil { - t.Fatalf("failed to run diff: %s %v", out, err) + c.Fatalf("failed to run diff: %s %v", out, err) } found := false @@ -31,15 +32,12 @@ func TestDiffFilenameShownInOutput(t *testing.T) { } } if !found { - t.Errorf("couldn't find the new file in docker diff's output: %v", out) + c.Errorf("couldn't find the new file in docker diff's output: %v", out) } - deleteContainer(cleanCID) - - logDone("diff - check if created file shows up") } // test to ensure GH #3840 doesn't occur any more -func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { +func (s *DockerSuite) TestDiffEnsureDockerinitFilesAreIgnored(c *check.C) { // this is a list of files which shouldn't show up in `docker diff` dockerinitFiles := []string{"/etc/resolv.conf", "/etc/hostname", "/etc/hosts", "/.dockerinit", "/.dockerenv"} @@ -49,7 +47,7 @@ func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanCID := strings.TrimSpace(out) @@ -57,26 +55,22 @@ func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) if err != nil { - t.Fatalf("failed to run diff: %s, %v", out, err) + c.Fatalf("failed to run diff: %s, %v", out, err) } - deleteContainer(cleanCID) - for _, filename := range dockerinitFiles { if strings.Contains(out, filename) { - t.Errorf("found file which should've been ignored %v in diff output", filename) + c.Errorf("found file which should've been ignored %v in diff output", filename) } } } - - logDone("diff - check if ignored files show up in diff") } -func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { +func (s *DockerSuite) TestDiffEnsureOnlyKmsgAndPtmx(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "0") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanCID := strings.TrimSpace(out) @@ -84,9 +78,8 @@ func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) if err != nil { - t.Fatalf("failed to run diff: %s, %v", out, err) + c.Fatalf("failed to run diff: %s, %v", out, err) } - deleteContainer(cleanCID) expected := map[string]bool{ "C /dev": true, @@ -109,9 +102,7 @@ func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { for _, line := range strings.Split(out, "\n") { if line != "" && !expected[line] { - t.Errorf("%q is shown in the diff but shouldn't", line) + c.Errorf("%q is shown in the diff but shouldn't", line) } } - - logDone("diff - ensure that only kmsg and ptmx in diff") } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 3e4c005b2cc95..b8e24260a72f7 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -7,20 +7,21 @@ import ( "regexp" "strconv" "strings" - "testing" "time" + + "github.com/go-check/check" ) -func TestEventsUntag(t *testing.T) { +func (s *DockerSuite) TestEventsUntag(c *check.C) { image := "busybox" - dockerCmd(t, "tag", image, "utest:tag1") - dockerCmd(t, "tag", image, "utest:tag2") - dockerCmd(t, "rmi", "utest:tag1") - dockerCmd(t, "rmi", "utest:tag2") + dockerCmd(c, "tag", image, "utest:tag1") + dockerCmd(c, "tag", image, "utest:tag2") + dockerCmd(c, "rmi", "utest:tag1") + dockerCmd(c, "rmi", "utest:tag2") eventsCmd := exec.Command(dockerBinary, "events", "--since=1") out, exitCode, _, err := runCommandWithOutputForDuration(eventsCmd, time.Duration(time.Millisecond*200)) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events - exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events - exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") nEvents := len(events) @@ -29,126 +30,119 @@ func TestEventsUntag(t *testing.T) { // looking for. for _, v := range events[nEvents-3 : nEvents-1] { if !strings.Contains(v, "untag") { - t.Fatalf("event should be untag, not %#v", v) + c.Fatalf("event should be untag, not %#v", v) } } - logDone("events - untags are logged") } -func TestEventsContainerFailStartDie(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) { - out, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(c, "images", "-q") image := strings.Split(out, "\n")[0] eventsCmd := exec.Command(dockerBinary, "run", "--name", "testeventdie", image, "blerg") _, _, err := runCommandWithOutput(eventsCmd) if err == nil { - t.Fatalf("Container run with command blerg should have failed, but it did not") + c.Fatalf("Container run with command blerg should have failed, but it did not") } - eventsCmd = exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd = exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") if len(events) <= 1 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } startEvent := strings.Fields(events[len(events)-3]) dieEvent := strings.Fields(events[len(events)-2]) if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("event should be start, not %#v", startEvent) + c.Fatalf("event should be start, not %#v", startEvent) } if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) + c.Fatalf("event should be die, not %#v", dieEvent) } - logDone("events - container unwilling to start logs die") } -func TestEventsLimit(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestEventsLimit(c *check.C) { for i := 0; i < 30; i++ { - dockerCmd(t, "run", "busybox", "echo", strconv.Itoa(i)) + dockerCmd(c, "run", "busybox", "echo", strconv.Itoa(i)) } - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ := runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") nEvents := len(events) - 1 if nEvents != 64 { - t.Fatalf("events should be limited to 64, but received %d", nEvents) + c.Fatalf("events should be limited to 64, but received %d", nEvents) } - logDone("events - limited to 64 entries") } -func TestEventsContainerEvents(t *testing.T) { - dockerCmd(t, "run", "--rm", "busybox", "true") - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) +func (s *DockerSuite) TestEventsContainerEvents(c *check.C) { + dockerCmd(c, "run", "--rm", "busybox", "true") + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, exitCode, err := runCommandWithOutput(eventsCmd) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) < 4 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } createEvent := strings.Fields(events[len(events)-4]) startEvent := strings.Fields(events[len(events)-3]) dieEvent := strings.Fields(events[len(events)-2]) destroyEvent := strings.Fields(events[len(events)-1]) if createEvent[len(createEvent)-1] != "create" { - t.Fatalf("event should be create, not %#v", createEvent) + c.Fatalf("event should be create, not %#v", createEvent) } if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("event should be start, not %#v", startEvent) + c.Fatalf("event should be start, not %#v", startEvent) } if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) + c.Fatalf("event should be die, not %#v", dieEvent) } if destroyEvent[len(destroyEvent)-1] != "destroy" { - t.Fatalf("event should be destroy, not %#v", destroyEvent) + c.Fatalf("event should be destroy, not %#v", destroyEvent) } - logDone("events - container create, start, die, destroy is logged") } -func TestEventsContainerEventsSinceUnixEpoch(t *testing.T) { - dockerCmd(t, "run", "--rm", "busybox", "true") +func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) { + dockerCmd(c, "run", "--rm", "busybox", "true") timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano) timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1) eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since='%s'", timeBeginning), - fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, exitCode, err := runCommandWithOutput(eventsCmd) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) < 4 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } createEvent := strings.Fields(events[len(events)-4]) startEvent := strings.Fields(events[len(events)-3]) dieEvent := strings.Fields(events[len(events)-2]) destroyEvent := strings.Fields(events[len(events)-1]) if createEvent[len(createEvent)-1] != "create" { - t.Fatalf("event should be create, not %#v", createEvent) + c.Fatalf("event should be create, not %#v", createEvent) } if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("event should be start, not %#v", startEvent) + c.Fatalf("event should be start, not %#v", startEvent) } if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) + c.Fatalf("event should be die, not %#v", dieEvent) } if destroyEvent[len(destroyEvent)-1] != "destroy" { - t.Fatalf("event should be destroy, not %#v", destroyEvent) + c.Fatalf("event should be destroy, not %#v", destroyEvent) } - logDone("events - container create, start, die, destroy since Unix Epoch time") } -func TestEventsImageUntagDelete(t *testing.T) { +func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) { name := "testimageevents" defer deleteImages(name) _, err := buildImage(name, @@ -156,67 +150,64 @@ func TestEventsImageUntagDelete(t *testing.T) { MAINTAINER "docker"`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := deleteImages(name); err != nil { - t.Fatal(err) + c.Fatal(err) } - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, exitCode, err := runCommandWithOutput(eventsCmd) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) < 2 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } untagEvent := strings.Fields(events[len(events)-2]) deleteEvent := strings.Fields(events[len(events)-1]) if untagEvent[len(untagEvent)-1] != "untag" { - t.Fatalf("untag should be untag, not %#v", untagEvent) + c.Fatalf("untag should be untag, not %#v", untagEvent) } if deleteEvent[len(deleteEvent)-1] != "delete" { - t.Fatalf("delete should be delete, not %#v", deleteEvent) + c.Fatalf("delete should be delete, not %#v", deleteEvent) } - logDone("events - image untag, delete is logged") } -func TestEventsImagePull(t *testing.T) { - since := daemonTime(t).Unix() - testRequires(t, Network) +func (s *DockerSuite) TestEventsImagePull(c *check.C) { + since := daemonTime(c).Unix() + testRequires(c, Network) defer deleteImages("hello-world") pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { - t.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err) + c.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err) } eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), - fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ := runCommandWithOutput(eventsCmd) events := strings.Split(strings.TrimSpace(out), "\n") event := strings.TrimSpace(events[len(events)-1]) if !strings.HasSuffix(event, "hello-world:latest: pull") { - t.Fatalf("Missing pull event - got:%q", event) + c.Fatalf("Missing pull event - got:%q", event) } - logDone("events - image pull is logged") } -func TestEventsImageImport(t *testing.T) { - defer deleteAllContainers() - since := daemonTime(t).Unix() +func (s *DockerSuite) TestEventsImageImport(c *check.C) { + since := daemonTime(c).Unix() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -225,25 +216,24 @@ func TestEventsImageImport(t *testing.T) { exec.Command(dockerBinary, "import", "-"), ) if err != nil { - t.Errorf("import failed with errors: %v, output: %q", err, out) + c.Errorf("import failed with errors: %v, output: %q", err, out) } eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), - fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(strings.TrimSpace(out), "\n") event := strings.TrimSpace(events[len(events)-1]) if !strings.HasSuffix(event, ": import") { - t.Fatalf("Missing import event - got:%q", event) + c.Fatalf("Missing import event - got:%q", event) } - logDone("events - image import is logged") } -func TestEventsFilters(t *testing.T) { +func (s *DockerSuite) TestEventsFilters(c *check.C) { parseEvents := func(out, match string) { events := strings.Split(out, "\n") events = events[:len(events)-1] @@ -251,67 +241,65 @@ func TestEventsFilters(t *testing.T) { eventFields := strings.Fields(event) eventName := eventFields[len(eventFields)-1] if ok, err := regexp.MatchString(match, eventName); err != nil || !ok { - t.Fatalf("event should match %s, got %#v, err: %v", match, eventFields, err) + c.Fatalf("event should match %s, got %#v, err: %v", match, eventFields, err) } } } - since := daemonTime(t).Unix() + since := daemonTime(c).Unix() out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", "busybox", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", "busybox", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", "event=die")) + out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die")) if err != nil { - t.Fatalf("Failed to get events: %s", err) + c.Fatalf("Failed to get events: %s", err) } parseEvents(out, "die") - out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", "event=die", "--filter", "event=start")) + out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die", "--filter", "event=start")) if err != nil { - t.Fatalf("Failed to get events: %s", err) + c.Fatalf("Failed to get events: %s", err) } parseEvents(out, "((die)|(start))") // make sure we at least got 2 start events count := strings.Count(out, "start") if count < 2 { - t.Fatalf("should have had 2 start events but had %d, out: %s", count, out) + c.Fatalf("should have had 2 start events but had %d, out: %s", count, out) } - logDone("events - filters") } -func TestEventsFilterImageName(t *testing.T) { - since := daemonTime(t).Unix() - defer deleteAllContainers() +func (s *DockerSuite) TestEventsFilterImageName(c *check.C) { + since := daemonTime(c).Unix() out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "-d", "busybox:latest", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } container1 := strings.TrimSpace(out) out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_2", "-d", "busybox", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } container2 := strings.TrimSpace(out) - s := "busybox" - eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("image=%s", s)) + name := "busybox" + eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", fmt.Sprintf("image=%s", name)) out, _, err = runCommandWithOutput(eventsCmd) if err != nil { - t.Fatalf("Failed to get events, error: %s(%s)", err, out) + c.Fatalf("Failed to get events, error: %s(%s)", err, out) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) == 0 { - t.Fatalf("Expected events but found none for the image busybox:latest") + c.Fatalf("Expected events but found none for the image busybox:latest") } count1 := 0 count2 := 0 @@ -324,27 +312,25 @@ func TestEventsFilterImageName(t *testing.T) { } } if count1 == 0 || count2 == 0 { - t.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2) + c.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2) } - logDone("events - filters using image") } -func TestEventsFilterContainer(t *testing.T) { - defer deleteAllContainers() - since := fmt.Sprintf("%d", daemonTime(t).Unix()) +func (s *DockerSuite) TestEventsFilterContainer(c *check.C) { + since := fmt.Sprintf("%d", daemonTime(c).Unix()) nameID := make(map[string]string) for _, name := range []string{"container_1", "container_2"} { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "true")) if err != nil { - t.Fatal(err) + c.Fatal(err) } nameID[name] = strings.TrimSpace(out) waitInspect(name, "{{.State.Runing }}", "false", 5) } - until := fmt.Sprintf("%d", daemonTime(t).Unix()) + until := fmt.Sprintf("%d", daemonTime(c).Unix()) checkEvents := func(id string, events []string) error { if len(events) != 3 { // create, start, die @@ -370,32 +356,31 @@ func TestEventsFilterContainer(t *testing.T) { eventsCmd := exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+name) out, _, err := runCommandWithOutput(eventsCmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } events := strings.Split(strings.TrimSuffix(out, "\n"), "\n") if err := checkEvents(ID, events); err != nil { - t.Fatal(err) + c.Fatal(err) } // filter by ID's eventsCmd = exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+ID) out, _, err = runCommandWithOutput(eventsCmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } events = strings.Split(strings.TrimSuffix(out, "\n"), "\n") if err := checkEvents(ID, events); err != nil { - t.Fatal(err) + c.Fatal(err) } } - logDone("events - filters using container name") } -func TestEventsStreaming(t *testing.T) { - start := daemonTime(t).Unix() +func (s *DockerSuite) TestEventsStreaming(c *check.C) { + start := daemonTime(c).Unix() finish := make(chan struct{}) defer close(finish) @@ -409,11 +394,11 @@ func TestEventsStreaming(t *testing.T) { eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10)) stdout, err := eventsCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } err = eventsCmd.Start() if err != nil { - t.Fatalf("failed to start 'docker events': %s", err) + c.Fatalf("failed to start 'docker events': %s", err) } go func() { @@ -444,35 +429,35 @@ func TestEventsStreaming(t *testing.T) { err = eventsCmd.Wait() if err != nil && !IsKilled(err) { - t.Fatalf("docker events had bad exit status: %s", err) + c.Fatalf("docker events had bad exit status: %s", err) } }() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox:latest", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) id <- cleanedContainerID select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container create in timely fashion") + c.Fatal("failed to observe container create in timely fashion") case <-eventCreate: // ignore, done } select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container start in timely fashion") + c.Fatal("failed to observe container start in timely fashion") case <-eventStart: // ignore, done } select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container die in timely fashion") + c.Fatal("failed to observe container die in timely fashion") case <-eventDie: // ignore, done } @@ -480,15 +465,14 @@ func TestEventsStreaming(t *testing.T) { rmCmd := exec.Command(dockerBinary, "rm", cleanedContainerID) out, _, err = runCommandWithOutput(rmCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container destroy in timely fashion") + c.Fatal("failed to observe container destroy in timely fashion") case <-eventDestroy: // ignore, done } - logDone("events - streamed to stdout") } diff --git a/integration-cli/docker_cli_events_unix_test.go b/integration-cli/docker_cli_events_unix_test.go index 4e54283501213..1a08f2b3c0182 100644 --- a/integration-cli/docker_cli_events_unix_test.go +++ b/integration-cli/docker_cli_events_unix_test.go @@ -8,48 +8,46 @@ import ( "io/ioutil" "os" "os/exec" - "testing" "unicode" + "github.com/go-check/check" "github.com/kr/pty" ) // #5979 -func TestEventsRedirectStdout(t *testing.T) { - since := daemonTime(t).Unix() - dockerCmd(t, "run", "busybox", "true") - defer deleteAllContainers() +func (s *DockerSuite) TestEventsRedirectStdout(c *check.C) { + since := daemonTime(c).Unix() + dockerCmd(c, "run", "busybox", "true") file, err := ioutil.TempFile("", "") if err != nil { - t.Fatalf("could not create temp file: %v", err) + c.Fatalf("could not create temp file: %v", err) } defer os.Remove(file.Name()) - command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, daemonTime(t).Unix(), file.Name()) + command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, daemonTime(c).Unix(), file.Name()) _, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command("sh", "-c", command) cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty if err := cmd.Run(); err != nil { - t.Fatalf("run err for command %q: %v", command, err) + c.Fatalf("run err for command %q: %v", command, err) } scanner := bufio.NewScanner(file) for scanner.Scan() { - for _, c := range scanner.Text() { - if unicode.IsControl(c) { - t.Fatalf("found control character %v", []byte(string(c))) + for _, ch := range scanner.Text() { + if unicode.IsControl(ch) { + c.Fatalf("found control character %v", []byte(string(ch))) } } } if err := scanner.Err(); err != nil { - t.Fatalf("Scan err for command %q: %v", command, err) + c.Fatalf("Scan err for command %q: %v", command, err) } - logDone("events - redirect stdout") } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 6a0999eefbfce..7fc3d4f25c916 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -12,38 +12,36 @@ import ( "sort" "strings" "sync" - "testing" "time" + + "github.com/go-check/check" ) -func TestExec(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExec(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/tmp/file") out, _, err := runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if expected := "test"; out != expected { - t.Errorf("container exec should've printed %q but printed %q", expected, out) + c.Errorf("container exec should've printed %q but printed %q", expected, out) } - logDone("exec - basic test") } -func TestExecInteractiveStdinClose(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat")) if err != nil { - t.Fatal(err) + c.Fatal(err) } contId := strings.TrimSpace(out) @@ -55,16 +53,16 @@ func TestExecInteractiveStdinClose(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "-i", contId, "/bin/ls", "/") cmd.Stdin = os.Stdin if err != nil { - t.Fatal(err) + c.Fatal(err) } out, err := cmd.CombinedOutput() if err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } if string(out) == "" { - t.Fatalf("Output was empty, likely blocked by standard input") + c.Fatalf("Output was empty, likely blocked by standard input") } returnchan <- struct{}{} @@ -73,163 +71,153 @@ func TestExecInteractiveStdinClose(t *testing.T) { select { case <-returnchan: case <-time.After(10 * time.Second): - t.Fatal("timed out running docker exec") + c.Fatal("timed out running docker exec") } - logDone("exec - interactive mode closes stdin after execution") } -func TestExecInteractive(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecInteractive(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") stdin, err := execCmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } stdout, err := execCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := execCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := stdin.Write([]byte("cat /tmp/file\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } r := bufio.NewReader(stdout) line, err := r.ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } line = strings.TrimSpace(line) if line != "test" { - t.Fatalf("Output should be 'test', got '%q'", line) + c.Fatalf("Output should be 'test', got '%q'", line) } if err := stdin.Close(); err != nil { - t.Fatal(err) + c.Fatal(err) } finish := make(chan struct{}) go func() { if err := execCmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } close(finish) }() select { case <-finish: case <-time.After(1 * time.Second): - t.Fatal("docker exec failed to exit on stdin close") + c.Fatal("docker exec failed to exit on stdin close") } - logDone("exec - Interactive test") } -func TestExecAfterContainerRestart(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "exec", cleanedContainerID, "echo", "hello") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } outStr := strings.TrimSpace(out) if outStr != "hello" { - t.Errorf("container should've printed hello, instead printed %q", outStr) + c.Errorf("container should've printed hello, instead printed %q", outStr) } - logDone("exec - exec running container after container restart") } -func TestExecAfterDaemonRestart(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestExecAfterDaemonRestart(c *check.C) { + testRequires(c, SameHostDaemon) - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top: err=%v\n%s", err, out) + c.Fatalf("Could not run top: err=%v\n%s", err, out) } if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } if out, err := d.Cmd("start", "top"); err != nil { - t.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) + c.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) } out, err := d.Cmd("exec", "top", "echo", "hello") if err != nil { - t.Fatalf("Could not exec on container top: err=%v\n%s", err, out) + c.Fatalf("Could not exec on container top: err=%v\n%s", err, out) } outStr := strings.TrimSpace(string(out)) if outStr != "hello" { - t.Errorf("container should've printed hello, instead printed %q", outStr) + c.Errorf("container should've printed hello, instead printed %q", outStr) } - logDone("exec - exec running container after daemon restart") } // Regression test for #9155, #9044 -func TestExecEnv(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecEnv(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-e", "LALA=value1", "-e", "LALA=value2", "-d", "--name", "testing", "busybox", "top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "testing", "env") out, _, err := runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if strings.Contains(out, "LALA=value1") || !strings.Contains(out, "LALA=value2") || !strings.Contains(out, "HOME=/root") { - t.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root") + c.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root") } - logDone("exec - exec inherits correct env") } -func TestExecExitStatus(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecExitStatus(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // Test normal (non-detached) case first @@ -237,20 +225,18 @@ func TestExecExitStatus(t *testing.T) { ec, _ := runCommand(cmd) if ec != 23 { - t.Fatalf("Should have had an ExitCode of 23, not: %d", ec) + c.Fatalf("Should have had an ExitCode of 23, not: %d", ec) } - logDone("exec - exec non-zero ExitStatus") } -func TestExecPausedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecPausedContainer(c *check.C) { defer unpauseAllContainers() runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ContainerID := strings.TrimSpace(out) @@ -258,82 +244,78 @@ func TestExecPausedContainer(t *testing.T) { pausedCmd := exec.Command(dockerBinary, "pause", "testing") out, _, _, err = runCommandWithStdoutStderr(pausedCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "-i", "-t", ContainerID, "echo", "hello") out, _, err = runCommandWithOutput(execCmd) if err == nil { - t.Fatal("container should fail to exec new command if it is paused") + c.Fatal("container should fail to exec new command if it is paused") } expected := ContainerID + " is paused, unpause the container before exec" if !strings.Contains(out, expected) { - t.Fatal("container should not exec new command if it is paused") + c.Fatal("container should not exec new command if it is paused") } - logDone("exec - exec should not exec a pause container") } // regression test for #9476 -func TestExecTtyCloseStdin(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecTtyCloseStdin(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd = exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") stdinRw, err := cmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } stdinRw.Write([]byte("test")) stdinRw.Close() if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd = exec.Command(dockerBinary, "top", "exec_tty_stdin") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } outArr := strings.Split(out, "\n") if len(outArr) > 3 || strings.Contains(out, "nsenter-exec") { // This is the really bad part if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rm", "-f", "exec_tty_stdin")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - t.Fatalf("exec process left running\n\t %s", out) + c.Fatalf("exec process left running\n\t %s", out) } - logDone("exec - stdin is closed properly with tty enabled") } -func TestExecTtyWithoutStdin(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecTtyWithoutStdin(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to start container: %v (%v)", out, err) + c.Fatalf("failed to start container: %v (%v)", out, err) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } defer func() { cmd := exec.Command(dockerBinary, "kill", id) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to kill container: %v (%v)", out, err) + c.Fatalf("failed to kill container: %v (%v)", out, err) } }() @@ -343,86 +325,80 @@ func TestExecTtyWithoutStdin(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") if _, err := cmd.StdinPipe(); err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("exec should have failed") + c.Fatal("exec should have failed") } else if !strings.Contains(out, expected) { - t.Fatalf("exec failed with error %q: expected %q", out, expected) + c.Fatalf("exec failed with error %q: expected %q", out, expected) } }() select { case <-done: case <-time.After(3 * time.Second): - t.Fatal("exec is running but should have failed") + c.Fatal("exec is running but should have failed") } - logDone("exec - forbid piped stdin to tty enabled container") } -func TestExecParseError(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecParseError(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // Test normal (non-detached) case first cmd := exec.Command(dockerBinary, "exec", "top") if _, stderr, code, err := runCommandWithStdoutStderr(cmd); err == nil || !strings.Contains(stderr, "See '"+dockerBinary+" exec --help'") || code == 0 { - t.Fatalf("Should have thrown error & point to help: %s", stderr) + c.Fatalf("Should have thrown error & point to help: %s", stderr) } - logDone("exec - error on parseExec should point to help") } -func TestExecStopNotHanging(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { if out, err := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top").CombinedOutput(); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if err := exec.Command(dockerBinary, "exec", "testing", "top").Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } wait := make(chan struct{}) go func() { if out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput(); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } close(wait) }() select { case <-time.After(3 * time.Second): - t.Fatal("Container stop timed out") + c.Fatal("Container stop timed out") case <-wait: } - logDone("exec - container with exec not hanging on stop") } -func TestExecCgroup(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecCgroup(c *check.C) { var cmd *exec.Cmd cmd = exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top") _, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/1/cgroup") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerCgroups := sort.StringSlice(strings.Split(string(out), "\n")) var wg sync.WaitGroup - var s sync.Mutex + var mu sync.Mutex execCgroups := []sort.StringSlice{} // exec a few times concurrently to get consistent failure for i := 0; i < 5; i++ { @@ -431,13 +407,13 @@ func TestExecCgroup(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/self/cgroup") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cg := sort.StringSlice(strings.Split(string(out), "\n")) - s.Lock() + mu.Lock() execCgroups = append(execCgroups, cg) - s.Unlock() + mu.Unlock() wg.Done() }() } @@ -454,86 +430,81 @@ func TestExecCgroup(t *testing.T) { for _, name := range containerCgroups { fmt.Printf(" %s\n", name) } - t.Fatal("cgroups mismatched") + c.Fatal("cgroups mismatched") } } - logDone("exec - exec has the container cgroups") } -func TestInspectExecID(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestInspectExecID(c *check.C) { out, exitCode, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "top")) if exitCode != 0 || err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } id := strings.TrimSuffix(out, "\n") out, err = inspectField(id, "ExecIDs") if err != nil { - t.Fatalf("failed to inspect container: %s, %v", out, err) + c.Fatalf("failed to inspect container: %s, %v", out, err) } if out != "" { - t.Fatalf("ExecIDs should be empty, got: %s", out) + c.Fatalf("ExecIDs should be empty, got: %s", out) } exitCode, err = runCommand(exec.Command(dockerBinary, "exec", "-d", id, "ls", "/")) if exitCode != 0 || err != nil { - t.Fatalf("failed to exec in container: %s, %v", out, err) + c.Fatalf("failed to exec in container: %s, %v", out, err) } out, err = inspectField(id, "ExecIDs") if err != nil { - t.Fatalf("failed to inspect container: %s, %v", out, err) + c.Fatalf("failed to inspect container: %s, %v", out, err) } out = strings.TrimSuffix(out, "\n") if out == "[]" || out == "" { - t.Fatalf("ExecIDs should not be empty, got: %s", out) + c.Fatalf("ExecIDs should not be empty, got: %s", out) } - logDone("inspect - inspect a container with ExecIDs") } -func TestLinksPingLinkedContainersOnRename(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { var out string - out, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) if idA == "" { - t.Fatal(out, "id should not be nil") + c.Fatal(out, "id should not be nil") } - out, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) if idB == "" { - t.Fatal(out, "id should not be nil") + c.Fatal(out, "id should not be nil") } execCmd := exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") out, _, err := runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - dockerCmd(t, "rename", "container1", "container_new") + dockerCmd(c, "rename", "container1", "container_new") execCmd = exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") out, _, err = runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("links - ping linked container upon rename") } -func TestRunExecDir(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunExecDir(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) execDir := filepath.Join(execDriverPath, id) @@ -542,92 +513,90 @@ func TestRunExecDir(t *testing.T) { { fi, err := os.Stat(execDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !fi.IsDir() { - t.Fatalf("%q must be a directory", execDir) + c.Fatalf("%q must be a directory", execDir) } fi, err = os.Stat(stateFile) if err != nil { - t.Fatal(err) + c.Fatal(err) } } stopCmd := exec.Command(dockerBinary, "stop", id) out, _, err = runCommandWithOutput(stopCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } { _, err := os.Stat(execDir) if err == nil { - t.Fatal(err) + c.Fatal(err) } if err == nil { - t.Fatalf("Exec directory %q exists for removed container!", execDir) + c.Fatalf("Exec directory %q exists for removed container!", execDir) } if !os.IsNotExist(err) { - t.Fatalf("Error should be about non-existing, got %s", err) + c.Fatalf("Error should be about non-existing, got %s", err) } } startCmd := exec.Command(dockerBinary, "start", id) out, _, err = runCommandWithOutput(startCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } { fi, err := os.Stat(execDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !fi.IsDir() { - t.Fatalf("%q must be a directory", execDir) + c.Fatalf("%q must be a directory", execDir) } fi, err = os.Stat(stateFile) if err != nil { - t.Fatal(err) + c.Fatal(err) } } rmCmd := exec.Command(dockerBinary, "rm", "-f", id) out, _, err = runCommandWithOutput(rmCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } { _, err := os.Stat(execDir) if err == nil { - t.Fatal(err) + c.Fatal(err) } if err == nil { - t.Fatalf("Exec directory %q is exists for removed container!", execDir) + c.Fatalf("Exec directory %q is exists for removed container!", execDir) } if !os.IsNotExist(err) { - t.Fatalf("Error should be about non-existing, got %s", err) + c.Fatalf("Error should be about non-existing, got %s", err) } } - logDone("run - check execdriver dir behavior") } -func TestRunMutableNetworkFiles(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { + testRequires(c, SameHostDaemon) for _, fn := range []string{"resolv.conf", "hosts"} { deleteAllContainers() content, err := runCommandAndReadContainerFile(fn, exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn))) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(string(content)) != "success" { - t.Fatal("Content was not what was modified in the container", string(content)) + c.Fatal("Content was not what was modified in the container", string(content)) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "c2", "busybox", "top")) if err != nil { - t.Fatal(err) + c.Fatal(err) } contID := strings.TrimSpace(out) @@ -636,88 +605,83 @@ func TestRunMutableNetworkFiles(t *testing.T) { f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := f.Seek(0, 0); err != nil { f.Close() - t.Fatal(err) + c.Fatal(err) } if err := f.Truncate(0); err != nil { f.Close() - t.Fatal(err) + c.Fatal(err) } if _, err := f.Write([]byte("success2\n")); err != nil { f.Close() - t.Fatal(err) + c.Fatal(err) } f.Close() res, err := exec.Command(dockerBinary, "exec", contID, "cat", "/etc/"+fn).CombinedOutput() if err != nil { - t.Fatalf("Output: %s, error: %s", res, err) + c.Fatalf("Output: %s, error: %s", res, err) } if string(res) != "success2\n" { - t.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res) + c.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res) } } - logDone("run - mutable network files") } -func TestExecWithUser(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecWithUser(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd := exec.Command(dockerBinary, "exec", "-u", "1", "parent", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { - t.Fatalf("exec with user by id expected daemon user got %s", out) + c.Fatalf("exec with user by id expected daemon user got %s", out) } cmd = exec.Command(dockerBinary, "exec", "-u", "root", "parent", "id") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("exec with user by root expected root user got %s", out) + c.Fatalf("exec with user by root expected root user got %s", out) } - logDone("exec - with user") } -func TestExecWithPrivileged(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sda b 8 0") out, _, err := runCommandWithOutput(cmd) if err == nil || !strings.Contains(out, "Operation not permitted") { - t.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") + c.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") } cmd = exec.Command(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.TrimSpace(out); actual != "ok" { - t.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) + c.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) } - logDone("exec - exec command in a container with privileged") } diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 3df8d60f86855..59a5106303aa4 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -4,11 +4,12 @@ import ( "os" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // export an image and try to import it into a new one -func TestExportContainerAndImportImage(t *testing.T) { +func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { containerID := "testexportcontainerandimportimage" defer deleteImages("repo/testexp:v1") @@ -17,39 +18,38 @@ func TestExportContainerAndImportImage(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", containerID, err) + c.Fatalf("output should've been a container id: %s %s ", containerID, err) } exportCmd := exec.Command(dockerBinary, "export", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { - t.Fatalf("failed to export container: %s, %v", out, err) + c.Fatalf("failed to export container: %s, %v", out, err) } importCmd := exec.Command(dockerBinary, "import", "-", "repo/testexp:v1") importCmd.Stdin = strings.NewReader(out) out, _, err = runCommandWithOutput(importCmd) if err != nil { - t.Fatalf("failed to import image: %s, %v", out, err) + c.Fatalf("failed to import image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been an image id: %s, %v", out, err) + c.Fatalf("output should've been an image id: %s, %v", out, err) } - logDone("export - export/import a container/image") } // Used to test output flag in the export command -func TestExportContainerWithOutputAndImportImage(t *testing.T) { +func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) { containerID := "testexportcontainerwithoutputandimportimage" defer deleteImages("repo/testexp:v1") @@ -58,40 +58,39 @@ func TestExportContainerWithOutputAndImportImage(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", containerID, err) + c.Fatalf("output should've been a container id: %s %s ", containerID, err) } defer os.Remove("testexp.tar") exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { - t.Fatalf("failed to export container: %s, %v", out, err) + c.Fatalf("failed to export container: %s, %v", out, err) } out, _, err = runCommandWithOutput(exec.Command("cat", "testexp.tar")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } importCmd := exec.Command(dockerBinary, "import", "-", "repo/testexp:v1") importCmd.Stdin = strings.NewReader(out) out, _, err = runCommandWithOutput(importCmd) if err != nil { - t.Fatalf("failed to import image: %s, %v", out, err) + c.Fatalf("failed to import image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been an image id: %s, %v", out, err) + c.Fatalf("output should've been an image id: %s, %v", out, err) } - logDone("export - export/import a container/image with output flag") } diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go index 8fc5cd1aab4eb..d6903e4fb9a3f 100644 --- a/integration-cli/docker_cli_help_test.go +++ b/integration-cli/docker_cli_help_test.go @@ -5,13 +5,13 @@ import ( "os/exec" "runtime" "strings" - "testing" "unicode" "github.com/docker/docker/pkg/homedir" + "github.com/go-check/check" ) -func TestHelpTextVerify(t *testing.T) { +func (s *DockerSuite) TestHelpTextVerify(c *check.C) { // Make sure main help text fits within 80 chars and that // on non-windows system we use ~ when possible (to shorten things). // Test for HOME set to its default value and set to "/" on linux @@ -51,26 +51,26 @@ func TestHelpTextVerify(t *testing.T) { helpCmd.Env = newEnvs out, ec, err := runCommandWithOutput(helpCmd) if err != nil || ec != 0 { - t.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) + c.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) } lines := strings.Split(out, "\n") for _, line := range lines { if len(line) > 80 { - t.Fatalf("Line is too long(%d chars):\n%s", len(line), line) + c.Fatalf("Line is too long(%d chars):\n%s", len(line), line) } // All lines should not end with a space if strings.HasSuffix(line, " ") { - t.Fatalf("Line should not end with a space: %s", line) + c.Fatalf("Line should not end with a space: %s", line) } if scanForHome && strings.Contains(line, `=`+home) { - t.Fatalf("Line should use '%q' instead of %q:\n%s", homedir.GetShortcutString(), home, line) + c.Fatalf("Line should use '%q' instead of %q:\n%s", homedir.GetShortcutString(), home, line) } if runtime.GOOS != "windows" { i := strings.Index(line, homedir.GetShortcutString()) if i >= 0 && i != len(line)-1 && line[i+1] != '/' { - t.Fatalf("Main help should not have used home shortcut:\n%s", line) + c.Fatalf("Main help should not have used home shortcut:\n%s", line) } } } @@ -82,11 +82,11 @@ func TestHelpTextVerify(t *testing.T) { helpCmd.Env = newEnvs out, ec, err = runCommandWithOutput(helpCmd) if err != nil || ec != 0 { - t.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) + c.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) } i := strings.Index(out, "Commands:") if i < 0 { - t.Fatalf("Missing 'Commands:' in:\n%s", out) + c.Fatalf("Missing 'Commands:' in:\n%s", out) } // Grab all chars starting at "Commands:" @@ -106,39 +106,39 @@ func TestHelpTextVerify(t *testing.T) { helpCmd.Env = newEnvs out, ec, err := runCommandWithOutput(helpCmd) if err != nil || ec != 0 { - t.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec) + c.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec) } lines := strings.Split(out, "\n") for _, line := range lines { if len(line) > 80 { - t.Fatalf("Help for %q is too long(%d chars):\n%s", cmd, + c.Fatalf("Help for %q is too long(%d chars):\n%s", cmd, len(line), line) } if scanForHome && strings.Contains(line, `"`+home) { - t.Fatalf("Help for %q should use ~ instead of %q on:\n%s", + c.Fatalf("Help for %q should use ~ instead of %q on:\n%s", cmd, home, line) } i := strings.Index(line, "~") if i >= 0 && i != len(line)-1 && line[i+1] != '/' { - t.Fatalf("Help for %q should not have used ~:\n%s", cmd, line) + c.Fatalf("Help for %q should not have used ~:\n%s", cmd, line) } // If a line starts with 4 spaces then assume someone // added a multi-line description for an option and we need // to flag it if strings.HasPrefix(line, " ") { - t.Fatalf("Help for %q should not have a multi-line option: %s", cmd, line) + c.Fatalf("Help for %q should not have a multi-line option: %s", cmd, line) } // Options should NOT end with a period if strings.HasPrefix(line, " -") && strings.HasSuffix(line, ".") { - t.Fatalf("Help for %q should not end with a period: %s", cmd, line) + c.Fatalf("Help for %q should not end with a period: %s", cmd, line) } // Options should NOT end with a space if strings.HasSuffix(line, " ") { - t.Fatalf("Help for %q should not end with a space: %s", cmd, line) + c.Fatalf("Help for %q should not end with a space: %s", cmd, line) } } @@ -146,10 +146,9 @@ func TestHelpTextVerify(t *testing.T) { expected := 39 if len(cmds) != expected { - t.Fatalf("Wrong # of cmds(%d), it should be: %d\nThe list:\n%q", + c.Fatalf("Wrong # of cmds(%d), it should be: %d\nThe list:\n%q", len(cmds), expected, cmds) } } - logDone("help - verify text") } diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 9fd2180d3e303..a3ba18abd861a 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -4,12 +4,13 @@ import ( "fmt" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // This is a heisen-test. Because the created timestamp of images and the behavior of // sort is not predictable it doesn't always fail. -func TestBuildHistory(t *testing.T) { +func (s *DockerSuite) TestBuildHistory(c *check.C) { name := "testbuildhistory" defer deleteImages(name) _, err := buildImage(name, `FROM busybox @@ -42,12 +43,12 @@ RUN echo "Z"`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, exitCode, err := runCommandWithOutput(exec.Command(dockerBinary, "history", "testbuildhistory")) if err != nil || exitCode != 0 { - t.Fatalf("failed to get image history: %s, %v", out, err) + c.Fatalf("failed to get image history: %s, %v", out, err) } actualValues := strings.Split(out, "\n")[1:27] @@ -58,32 +59,29 @@ RUN echo "Z"`, actualValue := actualValues[i] if !strings.Contains(actualValue, echoValue) { - t.Fatalf("Expected layer \"%s\", but was: %s", expectedValues[i], actualValue) + c.Fatalf("Expected layer \"%s\", but was: %s", expectedValues[i], actualValue) } } - logDone("history - build history") } -func TestHistoryExistentImage(t *testing.T) { +func (s *DockerSuite) TestHistoryExistentImage(c *check.C) { historyCmd := exec.Command(dockerBinary, "history", "busybox") _, exitCode, err := runCommandWithOutput(historyCmd) if err != nil || exitCode != 0 { - t.Fatal("failed to get image history") + c.Fatal("failed to get image history") } - logDone("history - history on existent image must pass") } -func TestHistoryNonExistentImage(t *testing.T) { +func (s *DockerSuite) TestHistoryNonExistentImage(c *check.C) { historyCmd := exec.Command(dockerBinary, "history", "testHistoryNonExistentImage") _, exitCode, err := runCommandWithOutput(historyCmd) if err == nil || exitCode == 0 { - t.Fatal("history on a non-existent image didn't result in a non-zero exit status") + c.Fatal("history on a non-existent image didn't result in a non-zero exit status") } - logDone("history - history on non-existent image must pass") } -func TestHistoryImageWithComment(t *testing.T) { +func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { name := "testhistoryimagewithcomment" defer deleteContainer(name) defer deleteImages(name) @@ -93,26 +91,26 @@ func TestHistoryImageWithComment(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } waitCmd := exec.Command(dockerBinary, "wait", name) if out, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } comment := "This_is_a_comment" commitCmd := exec.Command(dockerBinary, "commit", "-m="+comment, name, name) if out, _, err := runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } // test docker history to check comment messages historyCmd := exec.Command(dockerBinary, "history", name) out, exitCode, err := runCommandWithOutput(historyCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to get image history: %s, %v", out, err) + c.Fatalf("failed to get image history: %s, %v", out, err) } outputTabs := strings.Fields(strings.Split(out, "\n")[1]) @@ -120,8 +118,7 @@ func TestHistoryImageWithComment(t *testing.T) { actualValue := outputTabs[len(outputTabs)-1] if !strings.Contains(actualValue, comment) { - t.Fatalf("Expected comments %q, but found %q", comment, actualValue) + c.Fatalf("Expected comments %q, but found %q", comment, actualValue) } - logDone("history - history on image with comment") } diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index 28b091efdf82c..cf219e88b85a9 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -6,27 +6,26 @@ import ( "reflect" "sort" "strings" - "testing" "time" "github.com/docker/docker/pkg/stringid" + "github.com/go-check/check" ) -func TestImagesEnsureImageIsListed(t *testing.T) { +func (s *DockerSuite) TestImagesEnsureImageIsListed(c *check.C) { imagesCmd := exec.Command(dockerBinary, "images") out, _, err := runCommandWithOutput(imagesCmd) if err != nil { - t.Fatalf("listing images failed with errors: %s, %v", out, err) + c.Fatalf("listing images failed with errors: %s, %v", out, err) } if !strings.Contains(out, "busybox") { - t.Fatal("images should've listed busybox") + c.Fatal("images should've listed busybox") } - logDone("images - busybox should be listed") } -func TestImagesOrderedByCreationDate(t *testing.T) { +func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) { defer deleteImages("order:test_a") defer deleteImages("order:test_c") defer deleteImages("order:test_b") @@ -34,56 +33,53 @@ func TestImagesOrderedByCreationDate(t *testing.T) { `FROM scratch MAINTAINER dockerio1`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second) id2, err := buildImage("order:test_c", `FROM scratch MAINTAINER dockerio2`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second) id3, err := buildImage("order:test_b", `FROM scratch MAINTAINER dockerio3`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc")) if err != nil { - t.Fatalf("listing images failed with errors: %s, %v", out, err) + c.Fatalf("listing images failed with errors: %s, %v", out, err) } imgs := strings.Split(out, "\n") if imgs[0] != id3 { - t.Fatalf("First image must be %s, got %s", id3, imgs[0]) + c.Fatalf("First image must be %s, got %s", id3, imgs[0]) } if imgs[1] != id2 { - t.Fatalf("Second image must be %s, got %s", id2, imgs[1]) + c.Fatalf("Second image must be %s, got %s", id2, imgs[1]) } if imgs[2] != id1 { - t.Fatalf("Third image must be %s, got %s", id1, imgs[2]) + c.Fatalf("Third image must be %s, got %s", id1, imgs[2]) } - logDone("images - ordering by creation date") } -func TestImagesErrorWithInvalidFilterNameTest(t *testing.T) { +func (s *DockerSuite) TestImagesErrorWithInvalidFilterNameTest(c *check.C) { imagesCmd := exec.Command(dockerBinary, "images", "-f", "FOO=123") out, _, err := runCommandWithOutput(imagesCmd) if !strings.Contains(out, "Invalid filter") { - t.Fatalf("error should occur when listing images with invalid filter name FOO, %s, %v", out, err) + c.Fatalf("error should occur when listing images with invalid filter name FOO, %s, %v", out, err) } - logDone("images - invalid filter name check working") } -func TestImagesFilterLabel(t *testing.T) { +func (s *DockerSuite) TestImagesFilterLabel(c *check.C) { imageName1 := "images_filter_test1" imageName2 := "images_filter_test2" imageName3 := "images_filter_test3" - defer deleteAllContainers() defer deleteImages(imageName1) defer deleteImages(imageName2) defer deleteImages(imageName3) @@ -91,51 +87,49 @@ func TestImagesFilterLabel(t *testing.T) { `FROM scratch LABEL match me`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } image2ID, err := buildImage(imageName2, `FROM scratch LABEL match="me too"`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } image3ID, err := buildImage(imageName3, `FROM scratch LABEL nomatch me`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if (!strings.Contains(out, image1ID) && !strings.Contains(out, image2ID)) || strings.Contains(out, image3ID) { - t.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out) + c.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out) } cmd = exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match=me too") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if out != image2ID { - t.Fatalf("Expected %s got %s", image2ID, out) + c.Fatalf("Expected %s got %s", image2ID, out) } - logDone("images - filter label") } -func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) { +func (s *DockerSuite) TestImagesFilterSpaceTrimCase(c *check.C) { imageName := "images_filter_test" - defer deleteAllContainers() defer deleteImages(imageName) buildImage(imageName, `FROM scratch @@ -156,7 +150,7 @@ func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) { cmd := exec.Command(dockerBinary, "images", "-q", "-f", filter) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } listing := strings.Split(out, "\n") sort.Strings(listing) @@ -172,50 +166,47 @@ func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) { } fmt.Print("") } - t.Fatalf("All output must be the same") + c.Fatalf("All output must be the same") } } - logDone("images - white space trimming and lower casing") } -func TestImagesEnsureDanglingImageOnlyListedOnce(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) { // create container 1 - c := exec.Command(dockerBinary, "run", "-d", "busybox", "true") - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error running busybox: %s, %v", out, err) + c.Fatalf("error running busybox: %s, %v", out, err) } containerId1 := strings.TrimSpace(out) // tag as foobox - c = exec.Command(dockerBinary, "commit", containerId1, "foobox") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "commit", containerId1, "foobox") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error tagging foobox: %s", err) + c.Fatalf("error tagging foobox: %s", err) } imageId := stringid.TruncateID(strings.TrimSpace(out)) defer deleteImages(imageId) // overwrite the tag, making the previous image dangling - c = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error tagging foobox: %s", err) + c.Fatalf("error tagging foobox: %s", err) } defer deleteImages("foobox") - c = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("listing images failed with errors: %s, %v", out, err) + c.Fatalf("listing images failed with errors: %s, %v", out, err) } if e, a := 1, strings.Count(out, imageId); e != a { - t.Fatalf("expected 1 dangling image, got %d: %s", a, out) + c.Fatalf("expected 1 dangling image, got %d: %s", a, out) } - logDone("images - dangling image only listed once") } diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go index 087d08bd55b59..dd06ef822265e 100644 --- a/integration-cli/docker_cli_import_test.go +++ b/integration-cli/docker_cli_import_test.go @@ -3,14 +3,15 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestImportDisplay(t *testing.T) { +func (s *DockerSuite) TestImportDisplay(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) @@ -20,11 +21,11 @@ func TestImportDisplay(t *testing.T) { exec.Command(dockerBinary, "import", "-"), ) if err != nil { - t.Errorf("import failed with errors: %v, output: %q", err, out) + c.Errorf("import failed with errors: %v, output: %q", err, out) } if n := strings.Count(out, "\n"); n != 1 { - t.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out) + c.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out) } image := strings.TrimSpace(out) defer deleteImages(image) @@ -32,12 +33,11 @@ func TestImportDisplay(t *testing.T) { runCmd = exec.Command(dockerBinary, "run", "--rm", image, "true") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } if out != "" { - t.Fatalf("command output should've been nothing, was %q", out) + c.Fatalf("command output should've been nothing, was %q", out) } - logDone("import - display is fine, imported image runs") } diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index e6b79f01f769a..a7a931e8524a3 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -3,15 +3,16 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // ensure docker info succeeds -func TestInfoEnsureSucceeds(t *testing.T) { +func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) { versionCmd := exec.Command(dockerBinary, "info") out, exitCode, err := runCommandWithOutput(versionCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to execute docker info: %s, %v", out, err) + c.Fatalf("failed to execute docker info: %s, %v", out, err) } // always shown fields @@ -29,9 +30,8 @@ func TestInfoEnsureSucceeds(t *testing.T) { for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { - t.Errorf("couldn't find string %v in output", linePrefix) + c.Errorf("couldn't find string %v in output", linePrefix) } } - logDone("info - verify that it works") } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index cf42217ac882a..73eb7c8af692c 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -3,21 +3,21 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestInspectImage(t *testing.T) { +func (s *DockerSuite) TestInspectImage(c *check.C) { imageTest := "emptyfs" imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158" imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Id}}'", imageTest) out, exitCode, err := runCommandWithOutput(imagesCmd) if exitCode != 0 || err != nil { - t.Fatalf("failed to inspect image: %s, %v", out, err) + c.Fatalf("failed to inspect image: %s, %v", out, err) } if id := strings.TrimSuffix(out, "\n"); id != imageTestID { - t.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id) + c.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id) } - logDone("inspect - inspect an image") } diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go index cd86c0c567667..d08709671da30 100644 --- a/integration-cli/docker_cli_kill_test.go +++ b/integration-cli/docker_cli_kill_test.go @@ -3,73 +3,72 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestKillContainer(t *testing.T) { +func (s *DockerSuite) TestKillContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } listRunningContainersCmd := exec.Command(dockerBinary, "ps", "-q") out, _, err = runCommandWithOutput(listRunningContainersCmd) if err != nil { - t.Fatalf("failed to list running containers: %s, %v", out, err) + c.Fatalf("failed to list running containers: %s, %v", out, err) } if strings.Contains(out, cleanedContainerID) { - t.Fatal("killed container is still running") + c.Fatal("killed container is still running") } deleteContainer(cleanedContainerID) - logDone("kill - kill container running top") } -func TestKillDifferentUserContainer(t *testing.T) { +func (s *DockerSuite) TestKillDifferentUserContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-u", "daemon", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } listRunningContainersCmd := exec.Command(dockerBinary, "ps", "-q") out, _, err = runCommandWithOutput(listRunningContainersCmd) if err != nil { - t.Fatalf("failed to list running containers: %s, %v", out, err) + c.Fatalf("failed to list running containers: %s, %v", out, err) } if strings.Contains(out, cleanedContainerID) { - t.Fatal("killed container is still running") + c.Fatal("killed container is still running") } deleteContainer(cleanedContainerID) - logDone("kill - kill container running top from a different user") } diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 0f48cbb1e2057..95b340be2dc4f 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -8,89 +8,81 @@ import ( "reflect" "regexp" "strings" - "testing" "time" "github.com/docker/docker/pkg/iptables" + "github.com/go-check/check" ) -func TestLinksEtcHostsRegularFile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksEtcHostsRegularFile(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "ls", "-la", "/etc/hosts") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !strings.HasPrefix(out, "-") { - t.Errorf("/etc/hosts should be a regular file") + c.Errorf("/etc/hosts should be a regular file") } - logDone("link - /etc/hosts is a regular file") } -func TestLinksEtcHostsContentMatch(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestLinksEtcHostsContentMatch(c *check.C) { + testRequires(c, SameHostDaemon) runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "cat", "/etc/hosts") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } hosts, err := ioutil.ReadFile("/etc/hosts") if os.IsNotExist(err) { - t.Skip("/etc/hosts does not exist, skip this test") + c.Skip("/etc/hosts does not exist, skip this test") } if out != string(hosts) { - t.Errorf("container") + c.Errorf("container") } - logDone("link - /etc/hosts matches hosts copy") } -func TestLinksPingUnlinkedContainers(t *testing.T) { +func (s *DockerSuite) TestLinksPingUnlinkedContainers(c *check.C) { 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) if exitCode == 0 { - t.Fatal("run ping did not fail") + c.Fatal("run ping did not fail") } else if exitCode != 1 { - t.Fatalf("run ping failed with errors: %v", err) + c.Fatalf("run ping failed with errors: %v", err) } - logDone("links - ping unlinked container") } // Test for appropriate error when calling --link with an invalid target container -func TestLinksInvalidContainerTarget(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksInvalidContainerTarget(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--link", "bogus:alias", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("an invalid container target should produce an error") + c.Fatal("an invalid container target should produce an error") } if !strings.Contains(out, "Could not get container") { - t.Fatalf("error output expected 'Could not get container', but got %q instead; err: %v", out, err) + c.Fatalf("error output expected 'Could not get container', but got %q instead; err: %v", out, err) } - logDone("links - linking to non-existent container should not work") } -func TestLinksPingLinkedContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksPingLinkedContainers(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "container1", "--hostname", "fred", "busybox", "top") if _, err := runCommand(runCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "-d", "--name", "container2", "--hostname", "wilma", "busybox", "top") if _, err := runCommand(runCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } runArgs := []string{"run", "--rm", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "sh", "-c"} @@ -98,74 +90,68 @@ func TestLinksPingLinkedContainers(t *testing.T) { // test ping by alias, ping by name, and ping by hostname // 1. Ping by alias - dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "alias1", "alias2"))...) + dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "alias1", "alias2"))...) // 2. Ping by container name - dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "container1", "container2"))...) + dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "container1", "container2"))...) // 3. Ping by hostname - dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "fred", "wilma"))...) + dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "fred", "wilma"))...) - logDone("links - ping linked container") } -func TestLinksPingLinkedContainersAfterRename(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksPingLinkedContainersAfterRename(c *check.C) { - out, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ := dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) - out, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) - dockerCmd(t, "rename", "container1", "container_new") - dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") - dockerCmd(t, "kill", idA) - dockerCmd(t, "kill", idB) + dockerCmd(c, "rename", "container1", "container_new") + dockerCmd(c, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") + dockerCmd(c, "kill", idA) + dockerCmd(c, "kill", idB) - logDone("links - ping linked container after rename") } -func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestLinksIpTablesRulesWhenLinkAndUnlink(c *check.C) { + testRequires(c, SameHostDaemon) - dockerCmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top") - childIP := findContainerIP(t, "child") - parentIP := findContainerIP(t, "parent") + childIP := findContainerIP(c, "child") + parentIP := findContainerIP(c, "parent") sourceRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"} destinationRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"} if !iptables.Exists("filter", "DOCKER", sourceRule...) || !iptables.Exists("filter", "DOCKER", destinationRule...) { - t.Fatal("Iptables rules not found") + c.Fatal("Iptables rules not found") } - dockerCmd(t, "rm", "--link", "parent/http") + dockerCmd(c, "rm", "--link", "parent/http") if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) { - t.Fatal("Iptables rules should be removed when unlink") + c.Fatal("Iptables rules should be removed when unlink") } - dockerCmd(t, "kill", "child") - dockerCmd(t, "kill", "parent") + dockerCmd(c, "kill", "child") + dockerCmd(c, "kill", "parent") - logDone("link - verify iptables when link and unlink") } -func TestLinksInspectLinksStarted(t *testing.T) { +func (s *DockerSuite) TestLinksInspectLinksStarted(c *check.C) { var ( expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}} result []string ) - defer deleteAllContainers() - dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "top") links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links") if err != nil { - t.Fatal(err) + c.Fatal(err) } err = unmarshalJSON([]byte(links), &result) if err != nil { - t.Fatal(err) + c.Fatal(err) } output := convertSliceOfStringsToMap(result) @@ -173,28 +159,26 @@ func TestLinksInspectLinksStarted(t *testing.T) { equal := reflect.DeepEqual(output, expected) if !equal { - t.Fatalf("Links %s, expected %s", result, expected) + c.Fatalf("Links %s, expected %s", result, expected) } - logDone("link - links in started container inspect") } -func TestLinksInspectLinksStopped(t *testing.T) { +func (s *DockerSuite) TestLinksInspectLinksStopped(c *check.C) { var ( expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}} result []string ) - defer deleteAllContainers() - dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true") + dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true") links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links") if err != nil { - t.Fatal(err) + c.Fatal(err) } err = unmarshalJSON([]byte(links), &result) if err != nil { - t.Fatal(err) + c.Fatal(err) } output := convertSliceOfStringsToMap(result) @@ -202,47 +186,42 @@ func TestLinksInspectLinksStopped(t *testing.T) { equal := reflect.DeepEqual(output, expected) if !equal { - t.Fatalf("Links %s, but expected %s", result, expected) + c.Fatalf("Links %s, but expected %s", result, expected) } - logDone("link - links in stopped container inspect") } -func TestLinksNotStartedParentNotFail(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksNotStartedParentNotFail(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "--name=first", "busybox", "top") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "create", "--name=second", "--link=first:first", "busybox", "top") out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "start", "first") out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("link - container start successfully updating stopped parent links") } -func TestLinksHostsFilesInject(t *testing.T) { - testRequires(t, SameHostDaemon, ExecSupport) - - defer deleteAllContainers() +func (s *DockerSuite) TestLinksHostsFilesInject(c *check.C) { + testRequires(c, SameHostDaemon, ExecSupport) out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "--name", "one", "busybox", "top")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } idOne := strings.TrimSpace(out) out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "--name", "two", "--link", "one:onetwo", "busybox", "top")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } idTwo := strings.TrimSpace(out) @@ -251,89 +230,83 @@ func TestLinksHostsFilesInject(t *testing.T) { contentOne, err := readContainerFileWithExec(idOne, "/etc/hosts") if err != nil { - t.Fatal(err, string(contentOne)) + c.Fatal(err, string(contentOne)) } contentTwo, err := readContainerFileWithExec(idTwo, "/etc/hosts") if err != nil { - t.Fatal(err, string(contentTwo)) + c.Fatal(err, string(contentTwo)) } if !strings.Contains(string(contentTwo), "onetwo") { - t.Fatal("Host is not present in updated hosts file", string(contentTwo)) + c.Fatal("Host is not present in updated hosts file", string(contentTwo)) } - logDone("link - ensure containers hosts files are updated with the link alias.") } -func TestLinksNetworkHostContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksNetworkHostContainer(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--net", "host", "--name", "host_container", "busybox", "top")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "should_fail", "--link", "host_container:tester", "busybox", "true")) if err == nil || !strings.Contains(out, "--net=host can't be used with links. This would result in undefined behavior.") { - t.Fatalf("Running container linking to a container with --net host should have failed: %s", out) + c.Fatalf("Running container linking to a container with --net host should have failed: %s", out) } - logDone("link - error thrown when linking to container with --net host") } -func TestLinksUpdateOnRestart(t *testing.T) { - testRequires(t, SameHostDaemon, ExecSupport) - - defer deleteAllContainers() +func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { + testRequires(c, SameHostDaemon, ExecSupport) if out, err := exec.Command(dockerBinary, "run", "-d", "--name", "one", "busybox", "top").CombinedOutput(); err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } out, err := exec.Command(dockerBinary, "run", "-d", "--name", "two", "--link", "one:onetwo", "--link", "one:one", "busybox", "top").CombinedOutput() if err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } id := strings.TrimSpace(string(out)) realIP, err := inspectField("one", "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } content, err := readContainerFileWithExec(id, "/etc/hosts") if err != nil { - t.Fatal(err, string(content)) + c.Fatal(err, string(content)) } getIP := func(hosts []byte, hostname string) string { re := regexp.MustCompile(fmt.Sprintf(`(\S*)\t%s`, regexp.QuoteMeta(hostname))) matches := re.FindSubmatch(hosts) if matches == nil { - t.Fatalf("Hostname %s have no matches in hosts", hostname) + c.Fatalf("Hostname %s have no matches in hosts", hostname) } return string(matches[1]) } if ip := getIP(content, "one"); ip != realIP { - t.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) } if ip := getIP(content, "onetwo"); ip != realIP { - t.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) } if out, err := exec.Command(dockerBinary, "restart", "one").CombinedOutput(); err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } realIP, err = inspectField("one", "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } content, err = readContainerFileWithExec(id, "/etc/hosts") if err != nil { - t.Fatal(err, string(content)) + c.Fatal(err, string(content)) } if ip := getIP(content, "one"); ip != realIP { - t.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) } if ip := getIP(content, "onetwo"); ip != realIP { - t.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) } - logDone("link - ensure containers hosts files are updated on restart") } diff --git a/integration-cli/docker_cli_login_test.go b/integration-cli/docker_cli_login_test.go index 9bf90f3adcd0b..3b4431d2d2fbc 100644 --- a/integration-cli/docker_cli_login_test.go +++ b/integration-cli/docker_cli_login_test.go @@ -3,10 +3,11 @@ package main import ( "bytes" "os/exec" - "testing" + + "github.com/go-check/check" ) -func TestLoginWithoutTTY(t *testing.T) { +func (s *DockerSuite) TestLoginWithoutTTY(c *check.C) { cmd := exec.Command(dockerBinary, "login") // Send to stdin so the process does not get the TTY @@ -14,8 +15,7 @@ func TestLoginWithoutTTY(t *testing.T) { // run the command and block until it's done if err := cmd.Run(); err == nil { - t.Fatal("Expected non nil err when loginning in & TTY not available") + c.Fatal("Expected non nil err when loginning in & TTY not available") } - logDone("login - login without TTY") } diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index c236ef0859df8..7f04a328b7535 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -5,19 +5,19 @@ import ( "os/exec" "regexp" "strings" - "testing" "time" "github.com/docker/docker/pkg/timeutils" + "github.com/go-check/check" ) // This used to work, it test a log of PageSize-1 (gh#4851) -func TestLogsContainerSmallerThanPage(t *testing.T) { +func (s *DockerSuite) TestLogsContainerSmallerThanPage(c *check.C) { testLen := 32767 runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -26,25 +26,24 @@ func TestLogsContainerSmallerThanPage(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if len(out) != testLen+1 { - t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } deleteContainer(cleanedContainerID) - logDone("logs - logs container running echo smaller than page size") } // Regression test: When going over the PageSize, it used to panic (gh#4851) -func TestLogsContainerBiggerThanPage(t *testing.T) { +func (s *DockerSuite) TestLogsContainerBiggerThanPage(c *check.C) { testLen := 32768 runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -53,25 +52,24 @@ func TestLogsContainerBiggerThanPage(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if len(out) != testLen+1 { - t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } deleteContainer(cleanedContainerID) - logDone("logs - logs container running echo bigger than page size") } // Regression test: When going much over the PageSize, it used to block (gh#4851) -func TestLogsContainerMuchBiggerThanPage(t *testing.T) { +func (s *DockerSuite) TestLogsContainerMuchBiggerThanPage(c *check.C) { testLen := 33000 runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -80,25 +78,24 @@ func TestLogsContainerMuchBiggerThanPage(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if len(out) != testLen+1 { - t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } deleteContainer(cleanedContainerID) - logDone("logs - logs container running echo much bigger than page size") } -func TestLogsTimestamps(t *testing.T) { +func (s *DockerSuite) TestLogsTimestamps(c *check.C) { testLen := 100 runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo =; done;", testLen)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -107,13 +104,13 @@ func TestLogsTimestamps(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", "-t", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines := strings.Split(out, "\n") if len(lines) != testLen+1 { - t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) } ts := regexp.MustCompile(`^.* `) @@ -122,26 +119,25 @@ func TestLogsTimestamps(t *testing.T) { if l != "" { _, err := time.Parse(timeutils.RFC3339NanoFixed+" ", ts.FindString(l)) if err != nil { - t.Fatalf("Failed to parse timestamp from %v: %v", l, err) + c.Fatalf("Failed to parse timestamp from %v: %v", l, err) } if l[29] != 'Z' { // ensure we have padded 0's - t.Fatalf("Timestamp isn't padded properly: %s", l) + c.Fatalf("Timestamp isn't padded properly: %s", l) } } } deleteContainer(cleanedContainerID) - logDone("logs - logs with timestamps") } -func TestLogsSeparateStderr(t *testing.T) { +func (s *DockerSuite) TestLogsSeparateStderr(c *check.C) { msg := "stderr_log" runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("echo %s 1>&2", msg)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -150,30 +146,29 @@ func TestLogsSeparateStderr(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) stdout, stderr, _, err := runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if stdout != "" { - t.Fatalf("Expected empty stdout stream, got %v", stdout) + c.Fatalf("Expected empty stdout stream, got %v", stdout) } stderr = strings.TrimSpace(stderr) if stderr != msg { - t.Fatalf("Expected %v in stderr stream, got %v", msg, stderr) + c.Fatalf("Expected %v in stderr stream, got %v", msg, stderr) } deleteContainer(cleanedContainerID) - logDone("logs - separate stderr (without pseudo-tty)") } -func TestLogsStderrInStdout(t *testing.T) { +func (s *DockerSuite) TestLogsStderrInStdout(c *check.C) { msg := "stderr_log" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "busybox", "sh", "-c", fmt.Sprintf("echo %s 1>&2", msg)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -182,30 +177,29 @@ func TestLogsStderrInStdout(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) stdout, stderr, _, err := runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if stderr != "" { - t.Fatalf("Expected empty stderr stream, got %v", stdout) + c.Fatalf("Expected empty stderr stream, got %v", stdout) } stdout = strings.TrimSpace(stdout) if stdout != msg { - t.Fatalf("Expected %v in stdout stream, got %v", msg, stdout) + c.Fatalf("Expected %v in stdout stream, got %v", msg, stdout) } deleteContainer(cleanedContainerID) - logDone("logs - stderr in stdout (with pseudo-tty)") } -func TestLogsTail(t *testing.T) { +func (s *DockerSuite) TestLogsTail(c *check.C) { testLen := 100 runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo =; done;", testLen)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -214,49 +208,48 @@ func TestLogsTail(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", "--tail", "5", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines := strings.Split(out, "\n") if len(lines) != 6 { - t.Fatalf("Expected log %d lines, received %d\n", 6, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", 6, len(lines)) } logsCmd = exec.Command(dockerBinary, "logs", "--tail", "all", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines = strings.Split(out, "\n") if len(lines) != testLen+1 { - t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) } logsCmd = exec.Command(dockerBinary, "logs", "--tail", "random", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines = strings.Split(out, "\n") if len(lines) != testLen+1 { - t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) } deleteContainer(cleanedContainerID) - logDone("logs - logs tail") } -func TestLogsFollowStopped(t *testing.T) { +func (s *DockerSuite) TestLogsFollowStopped(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -264,34 +257,33 @@ func TestLogsFollowStopped(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID) if err := logsCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } - c := make(chan struct{}) + ch := make(chan struct{}) go func() { if err := logsCmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } - close(c) + close(ch) }() select { - case <-c: + case <-ch: case <-time.After(1 * time.Second): - t.Fatal("Following logs is hanged") + c.Fatal("Following logs is hanged") } deleteContainer(cleanedContainerID) - logDone("logs - logs follow stopped container") } // Regression test for #8832 -func TestLogsFollowSlowStdoutConsumer(t *testing.T) { +func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `usleep 200000;yes X | head -c 200000`) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -308,30 +300,29 @@ func TestLogsFollowSlowStdoutConsumer(t *testing.T) { stdout, err := logCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := logCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } // First read slowly bytes1, err := consumeWithSpeed(stdout, 10, 50*time.Millisecond, stopSlowRead) if err != nil { - t.Fatal(err) + c.Fatal(err) } // After the container has finished we can continue reading fast bytes2, err := consumeWithSpeed(stdout, 32*1024, 0, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } actual := bytes1 + bytes2 expected := 200000 if actual != expected { - t.Fatalf("Invalid bytes read: %d, expected %d", actual, expected) + c.Fatalf("Invalid bytes read: %d, expected %d", actual, expected) } - logDone("logs - follow slow consumer") } diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go index 35bd378e4ed09..875b6540ab095 100644 --- a/integration-cli/docker_cli_nat_test.go +++ b/integration-cli/docker_cli_nat_test.go @@ -5,32 +5,32 @@ import ( "net" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestNetworkNat(t *testing.T) { - testRequires(t, SameHostDaemon, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestNetworkNat(c *check.C) { + testRequires(c, SameHostDaemon, NativeExecDriver) iface, err := net.InterfaceByName("eth0") if err != nil { - t.Skipf("Test not running with `make test`. Interface eth0 not found: %s", err) + c.Skip(fmt.Sprintf("Test not running with `make test`. Interface eth0 not found: %v", err)) } ifaceAddrs, err := iface.Addrs() if err != nil || len(ifaceAddrs) == 0 { - t.Fatalf("Error retrieving addresses for eth0: %v (%d addresses)", err, len(ifaceAddrs)) + c.Fatalf("Error retrieving addresses for eth0: %v (%d addresses)", err, len(ifaceAddrs)) } ifaceIP, _, err := net.ParseCIDR(ifaceAddrs[0].String()) if err != nil { - t.Fatalf("Error retrieving the up for eth0: %s", err) + c.Fatalf("Error retrieving the up for eth0: %s", err) } runCmd := exec.Command(dockerBinary, "run", "-dt", "-p", "8080:8080", "busybox", "nc", "-lp", "8080") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -38,25 +38,24 @@ func TestNetworkNat(t *testing.T) { runCmd = exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", ifaceIP)) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to retrieve logs for container: %s, %v", out, err) + c.Fatalf("failed to retrieve logs for container: %s, %v", out, err) } out = strings.Trim(out, "\r\n") if expected := "hello world"; out != expected { - t.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP) + c.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } - logDone("network - make sure nat works through the host") } diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go index 6c620e7cde943..0256fb92bd379 100644 --- a/integration-cli/docker_cli_pause_test.go +++ b/integration-cli/docker_cli_pause_test.go @@ -4,78 +4,76 @@ import ( "fmt" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestPause(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPause(c *check.C) { defer unpauseAllContainers() name := "testeventpause" - out, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(c, "images", "-q") image := strings.Split(out, "\n")[0] - dockerCmd(t, "run", "-d", "--name", name, image, "top") + dockerCmd(c, "run", "-d", "--name", name, image, "top") - dockerCmd(t, "pause", name) + dockerCmd(c, "pause", name) pausedContainers, err := getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if len(pausedContainers) != 1 { - t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) + c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - dockerCmd(t, "unpause", name) + dockerCmd(c, "unpause", name) - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") if len(events) <= 1 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } pauseEvent := strings.Fields(events[len(events)-3]) unpauseEvent := strings.Fields(events[len(events)-2]) if pauseEvent[len(pauseEvent)-1] != "pause" { - t.Fatalf("event should be pause, not %#v", pauseEvent) + c.Fatalf("event should be pause, not %#v", pauseEvent) } if unpauseEvent[len(unpauseEvent)-1] != "unpause" { - t.Fatalf("event should be unpause, not %#v", unpauseEvent) + c.Fatalf("event should be unpause, not %#v", unpauseEvent) } - logDone("pause - pause/unpause is logged") } -func TestPauseMultipleContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) { defer unpauseAllContainers() containers := []string{ "testpausewithmorecontainers1", "testpausewithmorecontainers2", } - out, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(c, "images", "-q") image := strings.Split(out, "\n")[0] for _, name := range containers { - dockerCmd(t, "run", "-d", "--name", name, image, "top") + dockerCmd(c, "run", "-d", "--name", name, image, "top") } - dockerCmd(t, append([]string{"pause"}, containers...)...) + dockerCmd(c, append([]string{"pause"}, containers...)...) pausedContainers, err := getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if len(pausedContainers) != len(containers) { - t.Fatalf("there should be %d paused container and not %d", len(containers), len(pausedContainers)) + c.Fatalf("there should be %d paused container and not %d", len(containers), len(pausedContainers)) } - dockerCmd(t, append([]string{"unpause"}, containers...)...) + dockerCmd(c, append([]string{"unpause"}, containers...)...) - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") if len(events) <= len(containers)*3-2 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } pauseEvents := make([][]string, len(containers)) @@ -87,14 +85,13 @@ func TestPauseMultipleContainers(t *testing.T) { for _, pauseEvent := range pauseEvents { if pauseEvent[len(pauseEvent)-1] != "pause" { - t.Fatalf("event should be pause, not %#v", pauseEvent) + c.Fatalf("event should be pause, not %#v", pauseEvent) } } for _, unpauseEvent := range unpauseEvents { if unpauseEvent[len(unpauseEvent)-1] != "unpause" { - t.Fatalf("event should be unpause, not %#v", unpauseEvent) + c.Fatalf("event should be unpause, not %#v", unpauseEvent) } } - logDone("pause - multi pause/unpause is logged") } diff --git a/integration-cli/docker_cli_port_test.go b/integration-cli/docker_cli_port_test.go index 8fe6c2dc62d95..f0cb6639648c5 100644 --- a/integration-cli/docker_cli_port_test.go +++ b/integration-cli/docker_cli_port_test.go @@ -5,42 +5,42 @@ import ( "os/exec" "sort" "strings" - "testing" + + "github.com/go-check/check" ) -func TestPortList(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPortList(c *check.C) { // one port runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", firstID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "port", firstID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"80/tcp -> 0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"80/tcp -> 0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // three port @@ -51,36 +51,36 @@ func TestPortList(t *testing.T) { "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", ID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "port", ID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{ + if !assertPortList(c, out, []string{ "80/tcp -> 0.0.0.0:9876", "81/tcp -> 0.0.0.0:9877", "82/tcp -> 0.0.0.0:9878"}) { - t.Error("Port list is not correct") + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "rm", "-f", ID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // more and one port mapped to the same container port @@ -92,46 +92,45 @@ func TestPortList(t *testing.T) { "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ID = strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", ID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876", "0.0.0.0:9999"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876", "0.0.0.0:9999"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "port", ID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{ + if !assertPortList(c, out, []string{ "80/tcp -> 0.0.0.0:9876", "80/tcp -> 0.0.0.0:9999", "81/tcp -> 0.0.0.0:9877", "82/tcp -> 0.0.0.0:9878"}) { - t.Error("Port list is not correct\n", out) + c.Error("Port list is not correct\n", out) } runCmd = exec.Command(dockerBinary, "rm", "-f", ID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("port - test port list") } -func assertPortList(t *testing.T, out string, expected []string) bool { +func assertPortList(c *check.C, out string, expected []string) bool { //lines := strings.Split(out, "\n") lines := strings.Split(strings.Trim(out, "\n "), "\n") if len(lines) != len(expected) { - t.Errorf("different size lists %s, %d, %d", out, len(lines), len(expected)) + c.Errorf("different size lists %s, %d, %d", out, len(lines), len(expected)) return false } sort.Strings(lines) @@ -139,7 +138,7 @@ func assertPortList(t *testing.T, out string, expected []string) bool { for i := 0; i < len(expected); i++ { if lines[i] != expected[i] { - t.Error("|" + lines[i] + "!=" + expected[i] + "|") + c.Error("|" + lines[i] + "!=" + expected[i] + "|") return false } } @@ -147,84 +146,78 @@ func assertPortList(t *testing.T, out string, expected []string) bool { return true } -func TestPortHostBinding(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestPortHostBinding(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox", "nc", "-l", "-p", "80") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", firstID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", "9876") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", "9876") if out, _, err = runCommandWithOutput(runCmd); err == nil { - t.Error("Port is still bound after the Container is removed") + c.Error("Port is still bound after the Container is removed") } - logDone("port - test host binding done") } -func TestPortExposeHostBinding(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestPortExposeHostBinding(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--expose", "80", "busybox", "nc", "-l", "-p", "80") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", firstID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } _, exposedPort, err := net.SplitHostPort(out) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", strings.TrimSpace(exposedPort)) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", strings.TrimSpace(exposedPort)) if out, _, err = runCommandWithOutput(runCmd); err == nil { - t.Error("Port is still bound after the Container is removed") + c.Error("Port is still bound after the Container is removed") } - logDone("port - test port expose done") } diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go index 55c54400324fb..c07ed64068517 100644 --- a/integration-cli/docker_cli_proxy_test.go +++ b/integration-cli/docker_cli_proxy_test.go @@ -4,30 +4,30 @@ import ( "net" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestCliProxyDisableProxyUnixSock(t *testing.T) { - testRequires(t, SameHostDaemon) // test is valid when DOCKER_HOST=unix://.. +func (s *DockerSuite) TestCliProxyDisableProxyUnixSock(c *check.C) { + testRequires(c, SameHostDaemon) // test is valid when DOCKER_HOST=unix://.. cmd := exec.Command(dockerBinary, "info") cmd.Env = appendBaseEnv([]string{"HTTP_PROXY=http://127.0.0.1:9999"}) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("cli proxy - HTTP_PROXY is not used when connecting to unix sock") } // Can't use localhost here since go has a special case to not use proxy if connecting to localhost // See https://golang.org/pkg/net/http/#ProxyFromEnvironment -func TestCliProxyProxyTCPSock(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCliProxyProxyTCPSock(c *check.C) { + testRequires(c, SameHostDaemon) // get the IP to use to connect since we can't use localhost addrs, err := net.InterfaceAddrs() if err != nil { - t.Fatal(err) + c.Fatal(err) } var ip string for _, addr := range addrs { @@ -40,25 +40,24 @@ func TestCliProxyProxyTCPSock(t *testing.T) { } if ip == "" { - t.Fatal("could not find ip to connect to") + c.Fatal("could not find ip to connect to") } - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start("-H", "tcp://"+ip+":2375"); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "info") cmd.Env = []string{"DOCKER_HOST=tcp://" + ip + ":2375", "HTTP_PROXY=127.0.0.1:9999"} if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } // Test with no_proxy cmd.Env = append(cmd.Env, "NO_PROXY="+ip) if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "info")); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("cli proxy - HTTP_PROXY is used for TCP sock") } diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index deb426fce624e..d702153504011 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -6,24 +6,24 @@ import ( "reflect" "strconv" "strings" - "testing" "time" + + "github.com/go-check/check" ) -func TestPsListContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainers(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondID := strings.TrimSpace(out) @@ -31,53 +31,53 @@ func TestPsListContainers(t *testing.T) { runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } thirdID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } fourthID := strings.TrimSpace(out) // make sure the second is running if err := waitRun(secondID); err != nil { - t.Fatalf("waiting for container failed: %v", err) + c.Fatalf("waiting for container failed: %v", err) } // make sure third one is not running runCmd = exec.Command(dockerBinary, "wait", thirdID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // make sure the forth is running if err := waitRun(fourthID); err != nil { - t.Fatalf("waiting for container failed: %v", err) + c.Fatalf("waiting for container failed: %v", err) } // all runCmd = exec.Command(dockerBinary, "ps", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, []string{fourthID, thirdID, secondID, firstID}) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // running runCmd = exec.Command(dockerBinary, "ps") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, []string{fourthID, secondID, firstID}) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // from here all flag '-a' is ignored @@ -86,156 +86,155 @@ func TestPsListContainers(t *testing.T) { runCmd = exec.Command(dockerBinary, "ps", "-n=2", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected := []string{fourthID, thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "-n=2") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{fourthID, thirdID, secondID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // before runCmd = exec.Command(dockerBinary, "ps", "--before", thirdID, "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{secondID, firstID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--before", thirdID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since & before runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{thirdID, secondID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since & limit runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-n=2", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{fourthID, thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-n=2") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // before & limit runCmd = exec.Command(dockerBinary, "ps", "--before", fourthID, "-n=1", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--before", fourthID, "-n=1") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since & before & limit runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-n=1", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-n=1") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } - logDone("ps - test ps options") } func assertContainerList(out string, expected []string) bool { @@ -255,8 +254,7 @@ func assertContainerList(out string, expected []string) bool { return true } -func TestPsListContainersSize(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersSize(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") runCommandWithOutput(cmd) @@ -267,18 +265,18 @@ func TestPsListContainersSize(t *testing.T) { baseFoundsize := baseLines[1][baseSizeIndex:] baseBytes, err := strconv.Atoi(strings.Split(baseFoundsize, " ")[0]) if err != nil { - t.Fatal(err) + c.Fatal(err) } name := "test_size" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo 1 > test") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id, err := getIDByName(name) if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "ps", "-s", "-n=1") @@ -290,54 +288,52 @@ func TestPsListContainersSize(t *testing.T) { select { case <-wait: case <-time.After(3 * time.Second): - t.Fatalf("Calling \"docker ps -s\" timed out!") + c.Fatalf("Calling \"docker ps -s\" timed out!") } if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } lines := strings.Split(strings.Trim(out, "\n "), "\n") if len(lines) != 2 { - t.Fatalf("Expected 2 lines for 'ps -s -n=1' output, got %d", len(lines)) + c.Fatalf("Expected 2 lines for 'ps -s -n=1' output, got %d", len(lines)) } sizeIndex := strings.Index(lines[0], "SIZE") idIndex := strings.Index(lines[0], "CONTAINER ID") foundID := lines[1][idIndex : idIndex+12] if foundID != id[:12] { - t.Fatalf("Expected id %s, got %s", id[:12], foundID) + c.Fatalf("Expected id %s, got %s", id[:12], foundID) } expectedSize := fmt.Sprintf("%d B", (2 + baseBytes)) foundSize := lines[1][sizeIndex:] if foundSize != expectedSize { - t.Fatalf("Expected size %q, got %q", expectedSize, foundSize) + c.Fatalf("Expected size %q, got %q", expectedSize, foundSize) } - logDone("ps - test ps size") } -func TestPsListContainersFilterStatus(t *testing.T) { +func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) { // FIXME: this should test paused, but it makes things hang and its wonky // this is because paused containers can't be controlled by signals - defer deleteAllContainers() // start exited container runCmd := exec.Command(dockerBinary, "run", "-d", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // make sure the exited cintainer is not running runCmd = exec.Command(dockerBinary, "wait", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // start running container runCmd = exec.Command(dockerBinary, "run", "-itd", "busybox") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondID := strings.TrimSpace(out) @@ -345,313 +341,302 @@ func TestPsListContainersFilterStatus(t *testing.T) { runCmd = exec.Command(dockerBinary, "ps", "-q", "--filter=status=exited") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID[:12] { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) } runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=status=running") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if containerOut != secondID[:12] { - t.Fatalf("Expected id %s, got %s for running filter, output: %q", secondID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for running filter, output: %q", secondID[:12], containerOut, out) } - logDone("ps - test ps filter status") } -func TestPsListContainersFilterID(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersFilterID(c *check.C) { // start container runCmd := exec.Command(dockerBinary, "run", "-d", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // filter containers by id runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=id="+firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID[:12] { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) } - logDone("ps - test ps filter id") } -func TestPsListContainersFilterName(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersFilterName(c *check.C) { // start container runCmd := exec.Command(dockerBinary, "run", "-d", "--name=a_name_to_match", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "--name=b_name_to_match", "busybox", "top") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // filter containers by name runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=name=a_name_to_match") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID[:12] { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) } - logDone("ps - test ps filter name") } -func TestPsListContainersFilterLabel(t *testing.T) { +func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) { // start container runCmd := exec.Command(dockerBinary, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondID := strings.TrimSpace(out) // start third container runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } thirdID := strings.TrimSpace(out) // filter containers by exact match runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) } // filter containers by two labels runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if containerOut != firstID { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) } // filter containers by two labels, but expect not found because of AND behavior runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag-no") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if containerOut != "" { - t.Fatalf("Expected nothing, got %s for exited filter, output: %q", containerOut, out) + c.Fatalf("Expected nothing, got %s for exited filter, output: %q", containerOut, out) } // filter containers by exact key runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) { - t.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out) + c.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out) } deleteAllContainers() - logDone("ps - test ps filter label") } -func TestPsListContainersFilterExited(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--name", "zero1", "busybox", "true") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstZero, err := getIDByName("zero1") if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "--name", "zero2", "busybox", "true") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondZero, err := getIDByName("zero2") if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "--name", "nonzero1", "busybox", "false") if out, _, err := runCommandWithOutput(runCmd); err == nil { - t.Fatal("Should fail.", out, err) + c.Fatal("Should fail.", out, err) } firstNonZero, err := getIDByName("nonzero1") if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "--name", "nonzero2", "busybox", "false") if out, _, err := runCommandWithOutput(runCmd); err == nil { - t.Fatal("Should fail.", out, err) + c.Fatal("Should fail.", out, err) } secondNonZero, err := getIDByName("nonzero2") if err != nil { - t.Fatal(err) + c.Fatal(err) } // filter containers by exited=0 runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=exited=0") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ids := strings.Split(strings.TrimSpace(out), "\n") if len(ids) != 2 { - t.Fatalf("Should be 2 zero exited containers got %d: %s", len(ids), out) + c.Fatalf("Should be 2 zero exited containers got %d: %s", len(ids), out) } if ids[0] != secondZero { - t.Fatalf("First in list should be %q, got %q", secondZero, ids[0]) + c.Fatalf("First in list should be %q, got %q", secondZero, ids[0]) } if ids[1] != firstZero { - t.Fatalf("Second in list should be %q, got %q", firstZero, ids[1]) + c.Fatalf("Second in list should be %q, got %q", firstZero, ids[1]) } runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=exited=1") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ids = strings.Split(strings.TrimSpace(out), "\n") if len(ids) != 2 { - t.Fatalf("Should be 2 zero exited containerst got %d", len(ids)) + c.Fatalf("Should be 2 zero exited containerst got %d", len(ids)) } if ids[0] != secondNonZero { - t.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0]) + c.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0]) } if ids[1] != firstNonZero { - t.Fatalf("Second in list should be %q, got %q", firstNonZero, ids[1]) + c.Fatalf("Second in list should be %q, got %q", firstNonZero, ids[1]) } - logDone("ps - test ps filter exited") } -func TestPsRightTagName(t *testing.T) { +func (s *DockerSuite) TestPsRightTagName(c *check.C) { tag := "asybox:shmatest" - defer deleteAllContainers() defer deleteImages(tag) if out, err := exec.Command(dockerBinary, "tag", "busybox", tag).CombinedOutput(); err != nil { - t.Fatalf("Failed to tag image: %s, out: %q", err, out) + c.Fatalf("Failed to tag image: %s, out: %q", err, out) } var id1 string if out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "top").CombinedOutput(); err != nil { - t.Fatalf("Failed to run container: %s, out: %q", err, out) + c.Fatalf("Failed to run container: %s, out: %q", err, out) } else { id1 = strings.TrimSpace(string(out)) } var id2 string if out, err := exec.Command(dockerBinary, "run", "-d", tag, "top").CombinedOutput(); err != nil { - t.Fatalf("Failed to run container: %s, out: %q", err, out) + c.Fatalf("Failed to run container: %s, out: %q", err, out) } else { id2 = strings.TrimSpace(string(out)) } var imageID string if out, err := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox").CombinedOutput(); err != nil { - t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) + c.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) } else { imageID = strings.TrimSpace(string(out)) } var id3 string if out, err := exec.Command(dockerBinary, "run", "-d", imageID, "top").CombinedOutput(); err != nil { - t.Fatalf("Failed to run container: %s, out: %q", err, out) + c.Fatalf("Failed to run container: %s, out: %q", err, out) } else { id3 = strings.TrimSpace(string(out)) } out, err := exec.Command(dockerBinary, "ps", "--no-trunc").CombinedOutput() if err != nil { - t.Fatalf("Failed to run 'ps': %s, out: %q", err, out) + c.Fatalf("Failed to run 'ps': %s, out: %q", err, out) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") // skip header lines = lines[1:] if len(lines) != 3 { - t.Fatalf("There should be 3 running container, got %d", len(lines)) + c.Fatalf("There should be 3 running container, got %d", len(lines)) } for _, line := range lines { f := strings.Fields(line) switch f[0] { case id1: if f[1] != "busybox" { - t.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1]) + c.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1]) } case id2: if f[1] != tag { - t.Fatalf("Expected %s tag for id %s, got %s", tag, id2, f[1]) + c.Fatalf("Expected %s tag for id %s, got %s", tag, id2, f[1]) } case id3: if f[1] != imageID { - t.Fatalf("Expected %s imageID for id %s, got %s", tag, id3, f[1]) + c.Fatalf("Expected %s imageID for id %s, got %s", tag, id3, f[1]) } default: - t.Fatalf("Unexpected id %s, expected %s and %s and %s", f[0], id1, id2, id3) + c.Fatalf("Unexpected id %s, expected %s and %s and %s", f[0], id1, id2, id3) } } - logDone("ps - right tags for containers") } -func TestPsLinkedWithNoTrunc(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsLinkedWithNoTrunc(c *check.C) { if out, err := exec.Command(dockerBinary, "run", "--name=first", "-d", "busybox", "top").CombinedOutput(); err != nil { - t.Fatalf("Output: %s, err: %s", out, err) + c.Fatalf("Output: %s, err: %s", out, err) } if out, err := exec.Command(dockerBinary, "run", "--name=second", "--link=first:first", "-d", "busybox", "top").CombinedOutput(); err != nil { - t.Fatalf("Output: %s, err: %s", out, err) + c.Fatalf("Output: %s, err: %s", out, err) } out, err := exec.Command(dockerBinary, "ps", "--no-trunc").CombinedOutput() if err != nil { - t.Fatalf("Output: %s, err: %s", out, err) + c.Fatalf("Output: %s, err: %s", out, err) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") // strip header @@ -663,28 +648,26 @@ func TestPsLinkedWithNoTrunc(t *testing.T) { names = append(names, fields[len(fields)-1]) } if !reflect.DeepEqual(expected, names) { - t.Fatalf("Expected array: %v, got: %v", expected, names) + c.Fatalf("Expected array: %v, got: %v", expected, names) } } -func TestPsGroupPortRange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsGroupPortRange(c *check.C) { portRange := "3800-3900" out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "porttest", "-p", portRange+":"+portRange, "busybox", "top")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "ps")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // check that the port range is in the output if !strings.Contains(string(out), portRange) { - t.Fatalf("docker ps output should have had the port range %q: %s", portRange, string(out)) + c.Fatalf("docker ps output should have had the port range %q: %s", portRange, string(out)) } - logDone("ps - port range") } diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index f9fd1785240ab..8cf09a72610f7 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -4,12 +4,13 @@ import ( "fmt" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // See issue docker/docker#8141 -func TestPullImageWithAliases(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) defer deleteImages(repoName) @@ -22,40 +23,39 @@ func TestPullImageWithAliases(t *testing.T) { // Tag and push the same image multiple times. for _, repo := range repos { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil { - t.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out) + c.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out) } defer deleteImages(repo) if out, err := exec.Command(dockerBinary, "push", repo).CombinedOutput(); err != nil { - t.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) + c.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) } } // Clear local images store. args := append([]string{"rmi"}, repos...) if out, err := exec.Command(dockerBinary, args...).CombinedOutput(); err != nil { - t.Fatalf("Failed to clean images: error %v, output %q", err, string(out)) + c.Fatalf("Failed to clean images: error %v, output %q", err, string(out)) } // Pull a single tag and verify it doesn't bring down all aliases. pullCmd := exec.Command(dockerBinary, "pull", repos[0]) if out, _, err := runCommandWithOutput(pullCmd); err != nil { - t.Fatalf("Failed to pull %v: error %v, output %q", repoName, err, out) + c.Fatalf("Failed to pull %v: error %v, output %q", repoName, err, out) } if err := exec.Command(dockerBinary, "inspect", repos[0]).Run(); err != nil { - t.Fatalf("Image %v was not pulled down", repos[0]) + c.Fatalf("Image %v was not pulled down", repos[0]) } for _, repo := range repos[1:] { if err := exec.Command(dockerBinary, "inspect", repo).Run(); err == nil { - t.Fatalf("Image %v shouldn't have been pulled down", repo) + c.Fatalf("Image %v shouldn't have been pulled down", repo) } } - logDone("pull - image with aliases") } // pulling library/hello-world should show verified message -func TestPullVerified(t *testing.T) { - t.Skip("Skipping hub dependent test") +func (s *DockerSuite) TestPullVerified(c *check.C) { + c.Skip("Skipping hub dependent test") // Image must be pulled from central repository to get verified message // unless keychain is manually updated to contain the daemon's sign key. @@ -68,49 +68,46 @@ func TestPullVerified(t *testing.T) { pullCmd := exec.Command(dockerBinary, "pull", verifiedName) if out, exitCode, err := runCommandWithOutput(pullCmd); err != nil || !strings.Contains(out, expected) { if err != nil || exitCode != 0 { - t.Skipf("pulling the '%s' image from the registry has failed: %s", verifiedName, err) + c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err)) } - t.Fatalf("pulling a verified image failed. expected: %s\ngot: %s, %v", expected, out, err) + c.Fatalf("pulling a verified image failed. expected: %s\ngot: %s, %v", expected, out, err) } // pull it again pullCmd = exec.Command(dockerBinary, "pull", verifiedName) if out, exitCode, err := runCommandWithOutput(pullCmd); err != nil || strings.Contains(out, expected) { if err != nil || exitCode != 0 { - t.Skipf("pulling the '%s' image from the registry has failed: %s", verifiedName, err) + c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err)) } - t.Fatalf("pulling a verified image failed. unexpected verify message\ngot: %s, %v", out, err) + c.Fatalf("pulling a verified image failed. unexpected verify message\ngot: %s, %v", out, err) } - logDone("pull - pull verified") } // pulling an image from the central registry should work -func TestPullImageFromCentralRegistry(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) { + testRequires(c, Network) defer deleteImages("hello-world") pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { - t.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) + c.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) } - logDone("pull - pull hello-world") } // pulling a non-existing image from the central registry should return a non-zero exit code -func TestPullNonExistingImage(t *testing.T) { +func (s *DockerSuite) TestPullNonExistingImage(c *check.C) { pullCmd := exec.Command(dockerBinary, "pull", "fooblahblah1234") if out, _, err := runCommandWithOutput(pullCmd); err == nil { - t.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out) + c.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out) } - logDone("pull - pull fooblahblah1234 (non-existing image)") } // pulling an image from the central registry using official names should work // ensure all pulls result in the same image -func TestPullImageOfficialNames(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestPullImageOfficialNames(c *check.C) { + testRequires(c, Network) names := []string{ "docker.io/hello-world", @@ -123,7 +120,7 @@ func TestPullImageOfficialNames(t *testing.T) { pullCmd := exec.Command(dockerBinary, "pull", name) out, exitCode, err := runCommandWithOutput(pullCmd) if err != nil || exitCode != 0 { - t.Errorf("pulling the '%s' image from the registry has failed: %s", name, err) + c.Errorf("pulling the '%s' image from the registry has failed: %s", name, err) continue } @@ -131,10 +128,9 @@ func TestPullImageOfficialNames(t *testing.T) { imagesCmd := exec.Command(dockerBinary, "images") out, _, err = runCommandWithOutput(imagesCmd) if err != nil { - t.Errorf("listing images failed with errors: %v", err) + c.Errorf("listing images failed with errors: %v", err) } else if strings.Contains(out, name) { - t.Errorf("images should not have listed '%s'", name) + c.Errorf("images should not have listed '%s'", name) } } - logDone("pull - pull official names") } diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index f1274ba706e55..5c74fe8dcf742 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -6,72 +6,68 @@ import ( "os" "os/exec" "strings" - "testing" "time" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" + "github.com/go-check/check" ) // pulling an image from the central registry should work -func TestPushBusyboxImage(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushBusyboxImage(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it to the private registry tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { - t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) + c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) } - logDone("push - busybox to private registry") } // pushing an image without a prefix should throw an error -func TestPushUnprefixedRepo(t *testing.T) { +func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) { pushCmd := exec.Command(dockerBinary, "push", "busybox") if out, _, err := runCommandWithOutput(pushCmd); err == nil { - t.Fatalf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out) + c.Fatalf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out) } - logDone("push - unprefixed busybox repo must not pass") } -func TestPushUntagged(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushUntagged(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) expected := "Repository does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) } else if !strings.Contains(out, expected) { - t.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) + c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } - logDone("push - untagged image") } -func TestPushBadTag(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushBadTag(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL) expected := "does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) } else if !strings.Contains(out, expected) { - t.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) + c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } - logDone("push - image with bad tag") } -func TestPushMultipleTags(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushMultipleTags(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL) @@ -79,80 +75,79 @@ func TestPushMultipleTags(t *testing.T) { // tag the image to upload it tot he private registry tagCmd1 := exec.Command(dockerBinary, "tag", "busybox", repoTag1) if out, _, err := runCommandWithOutput(tagCmd1); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoTag1) tagCmd2 := exec.Command(dockerBinary, "tag", "busybox", repoTag2) if out, _, err := runCommandWithOutput(tagCmd2); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoTag2) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { - t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) + c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) } - logDone("push - multiple tags to private registry") } -func TestPushInterrupt(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushInterrupt(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it tot he private registry tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if err := pushCmd.Start(); err != nil { - t.Fatalf("Failed to start pushing to private registry: %v", err) + c.Fatalf("Failed to start pushing to private registry: %v", err) } // Interrupt push (yes, we have no idea at what point it will get killed). time.Sleep(200 * time.Millisecond) if err := pushCmd.Process.Kill(); err != nil { - t.Fatalf("Failed to kill push process: %v", err) + c.Fatalf("Failed to kill push process: %v", err) } // Try agin pushCmd = exec.Command(dockerBinary, "push", repoName) - if err := pushCmd.Start(); err != nil { - t.Fatalf("Failed to start pushing to private registry: %v", err) + if out, err := pushCmd.CombinedOutput(); err == nil { + str := string(out) + if !strings.Contains(str, "already in progress") { + c.Fatalf("Push should be continued on daemon side, but seems ok: %v, %s", err, out) + } } - - logDone("push - interrupted") } -func TestPushEmptyLayer(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushEmptyLayer(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL) emptyTarball, err := ioutil.TempFile("", "empty_tarball") if err != nil { - t.Fatalf("Unable to create test file: %v", err) + c.Fatalf("Unable to create test file: %v", err) } tw := tar.NewWriter(emptyTarball) err = tw.Close() if err != nil { - t.Fatalf("Error creating empty tarball: %v", err) + c.Fatalf("Error creating empty tarball: %v", err) } freader, err := os.Open(emptyTarball.Name()) if err != nil { - t.Fatalf("Could not open test tarball: %v", err) + c.Fatalf("Could not open test tarball: %v", err) } importCmd := exec.Command(dockerBinary, "import", "-", repoName) importCmd.Stdin = freader out, _, err := runCommandWithOutput(importCmd) if err != nil { - t.Errorf("import failed with errors: %v, output: %q", err, out) + c.Errorf("import failed with errors: %v, output: %q", err, out) } // Now verify we can push it pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { - t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) + c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) } - logDone("push - empty layer config to private registry") } diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index ed24d971d5de6..fcd87b54b0b29 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/integration-cli/docker_cli_rename_test.go @@ -3,16 +3,16 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestRenameStoppedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -20,7 +20,7 @@ func TestRenameStoppedContainer(t *testing.T) { runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err := inspectField(cleanedContainerID, "Name") @@ -28,94 +28,87 @@ func TestRenameStoppedContainer(t *testing.T) { runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err = inspectField(cleanedContainerID, "Name") if err != nil { - t.Fatal(err) + c.Fatal(err) } if name != "/new_name" { - t.Fatal("Failed to rename container ", name) + c.Fatal("Failed to rename container ", name) } - logDone("rename - stopped container") } -func TestRenameRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameRunningContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err := inspectField(cleanedContainerID, "Name") if err != nil { - t.Fatal(err) + c.Fatal(err) } if name != "/new_name" { - t.Fatal("Failed to rename container ") + c.Fatal("Failed to rename container ") } - logDone("rename - running container") } -func TestRenameCheckNames(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameCheckNames(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err := inspectField("new_name", "Name") if err != nil { - t.Fatal(err) + c.Fatal(err) } if name != "/new_name" { - t.Fatal("Failed to rename container ") + c.Fatal("Failed to rename container ") } name, err = inspectField("first_name", "Name") if err == nil && !strings.Contains(err.Error(), "No such image or container: first_name") { - t.Fatal(err) + c.Fatal(err) } - logDone("rename - old name released") } -func TestRenameInvalidName(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameInvalidName(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "myname", "-d", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } runCmd = exec.Command(dockerBinary, "rename", "myname", "new:invalid") if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Invalid container name") { - t.Fatalf("Renaming container to invalid name should have failed: %s\n%v", out, err) + c.Fatalf("Renaming container to invalid name should have failed: %s\n%v", out, err) } runCmd = exec.Command(dockerBinary, "ps", "-a") if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "myname") { - t.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err) + c.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err) } - logDone("rename - invalid container name") } diff --git a/integration-cli/docker_cli_restart_test.go b/integration-cli/docker_cli_restart_test.go index dde450dcd0515..2b9d5e2323511 100644 --- a/integration-cli/docker_cli_restart_test.go +++ b/integration-cli/docker_cli_restart_test.go @@ -3,61 +3,59 @@ package main import ( "os/exec" "strings" - "testing" "time" + + "github.com/go-check/check" ) -func TestRestartStoppedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartStoppedContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "foobar") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "foobar\n" { - t.Errorf("container should've printed 'foobar'") + c.Errorf("container should've printed 'foobar'") } runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "foobar\nfoobar\n" { - t.Errorf("container should've printed 'foobar' twice") + c.Errorf("container should've printed 'foobar' twice") } - logDone("restart - echo foobar for stopped container") } -func TestRestartRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartRunningContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "echo foobar && sleep 30 && echo 'should not print this'") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -67,41 +65,39 @@ func TestRestartRunningContainer(t *testing.T) { runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "foobar\n" { - t.Errorf("container should've printed 'foobar'") + c.Errorf("container should've printed 'foobar'") } runCmd = exec.Command(dockerBinary, "restart", "-t", "1", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } time.Sleep(1 * time.Second) if out != "foobar\nfoobar\n" { - t.Errorf("container should've printed 'foobar' twice") + c.Errorf("container should've printed 'foobar' twice") } - logDone("restart - echo foobar for running container") } // Test that restarting a container with a volume does not create a new volume on restart. Regression test for #819. -func TestRestartWithVolumes(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartWithVolumes(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "-v", "/test", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -109,148 +105,139 @@ func TestRestartWithVolumes(t *testing.T) { runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out = strings.Trim(out, " \n\r"); out != "1" { - t.Errorf("expect 1 volume received %s", out) + c.Errorf("expect 1 volume received %s", out) } runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ .Volumes }}", cleanedContainerID) volumes, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(volumes, err) + c.Fatal(volumes, err) } runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out = strings.Trim(out, " \n\r"); out != "1" { - t.Errorf("expect 1 volume after restart received %s", out) + c.Errorf("expect 1 volume after restart received %s", out) } runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ .Volumes }}", cleanedContainerID) volumesAfterRestart, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(volumesAfterRestart, err) + c.Fatal(volumesAfterRestart, err) } if volumes != volumesAfterRestart { volumes = strings.Trim(volumes, " \n\r") volumesAfterRestart = strings.Trim(volumesAfterRestart, " \n\r") - t.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart) + c.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart) } - logDone("restart - does not create a new volume on restart") } -func TestRestartPolicyNO(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartPolicyNO(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--restart=no", "busybox", "false") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(string(out)) name, err := inspectField(id, "HostConfig.RestartPolicy.Name") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if name != "no" { - t.Fatalf("Container restart policy name is %s, expected %s", name, "no") + c.Fatalf("Container restart policy name is %s, expected %s", name, "no") } - logDone("restart - recording restart policy name for --restart=no") } -func TestRestartPolicyAlways(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartPolicyAlways(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--restart=always", "busybox", "false") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(string(out)) name, err := inspectField(id, "HostConfig.RestartPolicy.Name") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if name != "always" { - t.Fatalf("Container restart policy name is %s, expected %s", name, "always") + c.Fatalf("Container restart policy name is %s, expected %s", name, "always") } MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } // MaximumRetryCount=0 if the restart policy is always if MaximumRetryCount != "0" { - t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "0") + c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "0") } - logDone("restart - recording restart policy name for --restart=always") } -func TestRestartPolicyOnFailure(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartPolicyOnFailure(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:1", "busybox", "false") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(string(out)) name, err := inspectField(id, "HostConfig.RestartPolicy.Name") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if name != "on-failure" { - t.Fatalf("Container restart policy name is %s, expected %s", name, "on-failure") + c.Fatalf("Container restart policy name is %s, expected %s", name, "on-failure") } - logDone("restart - recording restart policy name for --restart=on-failure") } // a good container with --restart=on-failure:3 // MaximumRetryCount!=0; RestartCount=0 -func TestContainerRestartwithGoodContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerRestartwithGoodContainer(c *check.C) { out, err := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:3", "busybox", "true").CombinedOutput() if err != nil { - t.Fatal(string(out), err) + c.Fatal(string(out), err) } id := strings.TrimSpace(string(out)) if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 5); err != nil { - t.Fatal(err) + c.Fatal(err) } count, err := inspectField(id, "RestartCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if count != "0" { - t.Fatalf("Container was restarted %s times, expected %d", count, 0) + c.Fatalf("Container was restarted %s times, expected %d", count, 0) } MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if MaximumRetryCount != "3" { - t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") + c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") } - logDone("restart - for a good container with restart policy, MaximumRetryCount is not 0 and RestartCount is 0") } diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 5f9a5dda5733d..8668bc70b6e80 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -5,93 +5,83 @@ import ( "os" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestRmContainerWithRemovedVolume(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRmContainerWithRemovedVolume(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "--name", "losemyvolumes", "-v", "/tmp/testing:/test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.Remove("/tmp/testing"); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "rm", "-v", "losemyvolumes") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("rm - removed volume") } -func TestRmContainerWithVolume(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmContainerWithVolume(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "foo", "-v", "/srv", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "rm", "-v", "foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("rm - volume") } -func TestRmRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmRunningContainer(c *check.C) { - createRunningContainer(t, "foo") + createRunningContainer(c, "foo") // Test cannot remove running container cmd := exec.Command(dockerBinary, "rm", "foo") if _, err := runCommand(cmd); err == nil { - t.Fatalf("Expected error, can't rm a running container") + c.Fatalf("Expected error, can't rm a running container") } - logDone("rm - running container") } -func TestRmRunningContainerCheckError409(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmRunningContainerCheckError409(c *check.C) { - createRunningContainer(t, "foo") + createRunningContainer(c, "foo") endpoint := "/containers/foo" status, _, err := sockRequest("DELETE", endpoint, nil) if err == nil { - t.Fatalf("Expected error, can't rm a running container") + c.Fatalf("Expected error, can't rm a running container") } else if status != http.StatusConflict { - t.Fatalf("Expected error to contain '409 Conflict' but found %s", err) + c.Fatalf("Expected error to contain '409 Conflict' but found %s", err) } - logDone("rm - running container with Error 409") } -func TestRmForceRemoveRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmForceRemoveRunningContainer(c *check.C) { - createRunningContainer(t, "foo") + createRunningContainer(c, "foo") // Stop then remove with -s cmd := exec.Command(dockerBinary, "rm", "-f", "foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("rm - running container with --force=true") } -func TestRmContainerOrphaning(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmContainerOrphaning(c *check.C) { dockerfile1 := `FROM busybox:latest ENTRYPOINT ["/bin/true"]` @@ -104,45 +94,43 @@ func TestRmContainerOrphaning(t *testing.T) { img1, err := buildImage(img, dockerfile1, true) defer deleteImages(img1) if err != nil { - t.Fatalf("Could not build image %s: %v", img, err) + c.Fatalf("Could not build image %s: %v", img, err) } // run container on first image if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", img)); err != nil { - t.Fatalf("Could not run image %s: %v: %s", img, err, out) + c.Fatalf("Could not run image %s: %v: %s", img, err, out) } // rebuild dockerfile with a small addition at the end if _, err := buildImage(img, dockerfile2, true); err != nil { - t.Fatalf("Could not rebuild image %s: %v", img, err) + c.Fatalf("Could not rebuild image %s: %v", img, err) } // try to remove the image, should error out. if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", img)); err == nil { - t.Fatalf("Expected to error out removing the image, but succeeded: %s", out) + c.Fatalf("Expected to error out removing the image, but succeeded: %s", out) } // check if we deleted the first image out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc")) if err != nil { - t.Fatalf("%v: %s", err, out) + c.Fatalf("%v: %s", err, out) } if !strings.Contains(out, img1) { - t.Fatalf("Orphaned container (could not find %q in docker images): %s", img1, out) + c.Fatalf("Orphaned container (could not find %q in docker images): %s", img1, out) } - logDone("rm - container orphaning") } -func TestRmInvalidContainer(t *testing.T) { +func (s *DockerSuite) TestRmInvalidContainer(c *check.C) { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rm", "unknown")); err == nil { - t.Fatal("Expected error on rm unknown container, got none") + c.Fatal("Expected error on rm unknown container, got none") } else if !strings.Contains(out, "failed to remove one or more containers") { - t.Fatalf("Expected output to contain 'failed to remove one or more containers', got %q", out) + c.Fatalf("Expected output to contain 'failed to remove one or more containers', got %q", out) } - logDone("rm - delete unknown container") } -func createRunningContainer(t *testing.T, name string) { +func createRunningContainer(c *check.C, name string) { cmd := exec.Command(dockerBinary, "run", "-dt", "--name", name, "busybox", "top") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } } diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index f6b12aa6c7df8..786a3e3061d4c 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -3,17 +3,18 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestRmiWithContainerFails(t *testing.T) { +func (s *DockerSuite) TestRmiWithContainerFails(c *check.C) { errSubstr := "is using it" // create a container runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -22,123 +23,117 @@ func TestRmiWithContainerFails(t *testing.T) { runCmd = exec.Command(dockerBinary, "rmi", "busybox") out, _, err = runCommandWithOutput(runCmd) if err == nil { - t.Fatalf("Container %q is using image, should not be able to rmi: %q", cleanedContainerID, out) + c.Fatalf("Container %q is using image, should not be able to rmi: %q", cleanedContainerID, out) } if !strings.Contains(out, errSubstr) { - t.Fatalf("Container %q is using image, error message should contain %q: %v", cleanedContainerID, errSubstr, out) + c.Fatalf("Container %q is using image, error message should contain %q: %v", cleanedContainerID, errSubstr, out) } // make sure it didn't delete the busybox name - images, _ := dockerCmd(t, "images") + images, _ := dockerCmd(c, "images") if !strings.Contains(images, "busybox") { - t.Fatalf("The name 'busybox' should not have been removed from images: %q", images) + c.Fatalf("The name 'busybox' should not have been removed from images: %q", images) } deleteContainer(cleanedContainerID) - logDone("rmi - container using image while rmi, should not remove image name") } -func TestRmiTag(t *testing.T) { - imagesBefore, _ := dockerCmd(t, "images", "-a") - dockerCmd(t, "tag", "busybox", "utest:tag1") - dockerCmd(t, "tag", "busybox", "utest/docker:tag2") - dockerCmd(t, "tag", "busybox", "utest:5000/docker:tag3") +func (s *DockerSuite) TestRmiTag(c *check.C) { + imagesBefore, _ := dockerCmd(c, "images", "-a") + dockerCmd(c, "tag", "busybox", "utest:tag1") + dockerCmd(c, "tag", "busybox", "utest/docker:tag2") + dockerCmd(c, "tag", "busybox", "utest:5000/docker:tag3") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+3 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - dockerCmd(t, "rmi", "utest/docker:tag2") + dockerCmd(c, "rmi", "utest/docker:tag2") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+2 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - dockerCmd(t, "rmi", "utest:5000/docker:tag3") + dockerCmd(c, "rmi", "utest:5000/docker:tag3") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+1 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - dockerCmd(t, "rmi", "utest:tag1") + dockerCmd(c, "rmi", "utest:tag1") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+0 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - logDone("rmi - tag,rmi - tagging the same images multiple times then removing tags") } -func TestRmiImgIDForce(t *testing.T) { +func (s *DockerSuite) TestRmiImgIDForce(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container:%s, %v", out, err) + c.Fatalf("failed to create a container:%s, %v", out, err) } containerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-test") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) + c.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) } - imagesBefore, _ := dockerCmd(t, "images", "-a") - dockerCmd(t, "tag", "busybox-test", "utest:tag1") - dockerCmd(t, "tag", "busybox-test", "utest:tag2") - dockerCmd(t, "tag", "busybox-test", "utest/docker:tag3") - dockerCmd(t, "tag", "busybox-test", "utest:5000/docker:tag4") + imagesBefore, _ := dockerCmd(c, "images", "-a") + dockerCmd(c, "tag", "busybox-test", "utest:tag1") + dockerCmd(c, "tag", "busybox-test", "utest:tag2") + dockerCmd(c, "tag", "busybox-test", "utest/docker:tag3") + dockerCmd(c, "tag", "busybox-test", "utest:5000/docker:tag4") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 { - t.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) + c.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) } } - out, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") + out, _ = dockerCmd(c, "inspect", "-f", "{{.Id}}", "busybox-test") imgID := strings.TrimSpace(out) - dockerCmd(t, "rmi", "-f", imgID) + dockerCmd(c, "rmi", "-f", imgID) { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Contains(imagesAfter, imgID[:12]) { - t.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) + c.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) } } - logDone("rmi - imgID,rmi -f imgID delete all tagged repos of specific imgID") } -func TestRmiTagWithExistingContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmiTagWithExistingContainers(c *check.C) { container := "test-delete-tag" newtag := "busybox:newtag" bb := "busybox:latest" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", bb, newtag)); err != nil { - t.Fatalf("Could not tag busybox: %v: %s", err, out) + c.Fatalf("Could not tag busybox: %v: %s", err, out) } if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", container, bb, "/bin/true")); err != nil { - t.Fatalf("Could not run busybox: %v: %s", err, out) + c.Fatalf("Could not run busybox: %v: %s", err, out) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", newtag)) if err != nil { - t.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out) + c.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out) } if d := strings.Count(out, "Untagged: "); d != 1 { - t.Fatalf("Expected 1 untagged entry got %d: %q", d, out) + c.Fatalf("Expected 1 untagged entry got %d: %q", d, out) } - logDone("rmi - delete tag with existing containers") } -func TestRmiForceWithExistingContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmiForceWithExistingContainers(c *check.C) { image := "busybox-clone" @@ -147,64 +142,60 @@ func TestRmiForceWithExistingContainers(t *testing.T) { MAINTAINER foo`) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("Could not build %s: %s, %v", image, out, err) + c.Fatalf("Could not build %s: %s, %v", image, out, err) } if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "test-force-rmi", image, "/bin/true")); err != nil { - t.Fatalf("Could not run container: %s, %v", out, err) + c.Fatalf("Could not run container: %s, %v", out, err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", "-f", image)) if err != nil { - t.Fatalf("Could not remove image %s: %s, %v", image, out, err) + c.Fatalf("Could not remove image %s: %s, %v", image, out, err) } - logDone("rmi - force delete with existing containers") } -func TestRmiWithMultipleRepositories(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmiWithMultipleRepositories(c *check.C) { newRepo := "127.0.0.1:5000/busybox" oldRepo := "busybox" newTag := "busybox:test" cmd := exec.Command(dockerBinary, "tag", oldRepo, newRepo) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("Could not tag busybox: %v: %s", err, out) + c.Fatalf("Could not tag busybox: %v: %s", err, out) } cmd = exec.Command(dockerBinary, "run", "--name", "test", oldRepo, "touch", "/home/abcd") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %s", err, out) + c.Fatalf("failed to run container: %v, output: %s", err, out) } cmd = exec.Command(dockerBinary, "commit", "test", newTag) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to commit container: %v, output: %s", err, out) + c.Fatalf("failed to commit container: %v, output: %s", err, out) } cmd = exec.Command(dockerBinary, "rmi", newTag) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to remove image: %v, output: %s", err, out) + c.Fatalf("failed to remove image: %v, output: %s", err, out) } if !strings.Contains(out, "Untagged: "+newTag) { - t.Fatalf("Could not remove image %s: %s, %v", newTag, out, err) + c.Fatalf("Could not remove image %s: %s, %v", newTag, out, err) } - logDone("rmi - delete a image which its dependency tagged to multiple repositories success") } -func TestRmiBlank(t *testing.T) { +func (s *DockerSuite) TestRmiBlank(c *check.C) { // try to delete a blank image name runCmd := exec.Command(dockerBinary, "rmi", "") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("Should have failed to delete '' image") + c.Fatal("Should have failed to delete '' image") } if strings.Contains(out, "No such image") { - t.Fatalf("Wrong error message generated: %s", out) + c.Fatalf("Wrong error message generated: %s", out) } - logDone("rmi - blank image name") } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 98f923fa4c844..7e12fc5a9d428 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -16,713 +16,593 @@ import ( "strconv" "strings" "sync" - "testing" "time" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/resolvconf" + "github.com/go-check/check" ) // "test123" should be printed by docker run -func TestRunEchoStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunEchoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "busybox", "echo", "test123") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test123\n" { - t.Errorf("container should've printed 'test123'") + c.Fatalf("container should've printed 'test123'") } - - logDone("run - echo test123") } // "test" should be printed -func TestRunEchoStdoutWithMemoryLimit(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunEchoStdoutWithMemoryLimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-m", "16m", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.Trim(out, "\r\n") if expected := "test"; out != expected { - t.Errorf("container should've printed %q but printed %q", expected, out) - + c.Fatalf("container should've printed %q but printed %q", expected, out) } - - logDone("run - echo with memory limit") } // should run without memory swap -func TestRunWithoutMemoryswapLimit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithoutMemoryswapLimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-m", "16m", "--memory-swap", "-1", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to run container, output: %q", out) + c.Fatalf("failed to run container, output: %q", out) } - - logDone("run - without memory swap limit") } // "test" should be printed -func TestRunEchoStdoutWitCPULimit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoStdoutWitCPULimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test\n" { - t.Errorf("container should've printed 'test'") + c.Errorf("container should've printed 'test'") } - - logDone("run - echo with CPU limit") } // "test" should be printed -func TestRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoStdoutWithCPUAndMemoryLimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "-m", "16m", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test\n" { - t.Errorf("container should've printed 'test', got %q instead", out) + c.Errorf("container should've printed 'test', got %q instead", out) } - - logDone("run - echo with CPU and memory limit") } // "test" should be printed -func TestRunEchoStdoutWitCPUQuota(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoStdoutWitCPUQuota(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) if strings.Contains(out, "Your kernel does not support CPU cfs quota") { - t.Skip("Your kernel does not support CPU cfs quota, skip this test") + c.Skip("Your kernel does not support CPU cfs quota, skip this test") } if out != "test" { - t.Errorf("container should've printed 'test'") + c.Errorf("container should've printed 'test'") } cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.CpuQuota}}", "test") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to inspect container: %s, %v", out, err) + c.Fatalf("failed to inspect container: %s, %v", out, err) } out = strings.TrimSpace(out) if out != "8000" { - t.Errorf("setting the CPU CFS quota failed") + c.Errorf("setting the CPU CFS quota failed") } - - logDone("run - echo with CPU quota") } // "test" should be printed -func TestRunEchoNamedContainer(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoNamedContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "testfoonamedcontainer", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test\n" { - t.Errorf("container should've printed 'test'") + c.Errorf("container should've printed 'test'") } if err := deleteContainer("testfoonamedcontainer"); err != nil { - t.Errorf("failed to remove the named container: %v", err) + c.Errorf("failed to remove the named container: %v", err) } - - logDone("run - echo with named container") } // docker run should not leak file descriptors -func TestRunLeakyFileDescriptors(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLeakyFileDescriptors(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "busybox", "ls", "-C", "/proc/self/fd") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } // normally, we should only get 0, 1, and 2, but 3 gets created by "ls" when it does "opendir" on the "fd" directory if out != "0 1 2 3\n" { - t.Errorf("container should've printed '0 1 2 3', not: %s", out) + c.Errorf("container should've printed '0 1 2 3', not: %s", out) } - - logDone("run - check file descriptor leakage") } // it should be possible to lookup Google DNS // this will fail when Internet access is unavailable -func TestRunLookupGoogleDns(t *testing.T) { - testRequires(t, Network) - defer deleteAllContainers() +func (s *DockerSuite) TestRunLookupGoogleDns(c *check.C) { + testRequires(c, Network) out, _, _, err := runCommandWithStdoutStderr(exec.Command(dockerBinary, "run", "busybox", "nslookup", "google.com")) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } - - logDone("run - nslookup google.com") } // the exit code should be 0 // some versions of lxc might make this test fail -func TestRunExitCodeZero(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExitCodeZero(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "busybox", "true") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Errorf("container should've exited with exit code 0: %s, %v", out, err) + c.Errorf("container should've exited with exit code 0: %s, %v", out, err) } - - logDone("run - exit with 0") } // the exit code should be 1 // some versions of lxc might make this test fail -func TestRunExitCodeOne(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExitCodeOne(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "busybox", "false") exitCode, err := runCommand(runCmd) if err != nil && !strings.Contains("exit status 1", fmt.Sprintf("%s", err)) { - t.Fatal(err) + c.Fatal(err) } if exitCode != 1 { - t.Errorf("container should've exited with exit code 1") + c.Errorf("container should've exited with exit code 1") } - - logDone("run - exit with 1") } // it should be possible to pipe in data via stdin to a process running in a container // some versions of lxc might make this test fail -func TestRunStdinPipe(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunStdinPipe(c *check.C) { runCmd := exec.Command("bash", "-c", `echo "blahblah" | docker run -i -a stdin busybox cat`) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", out) if out, _, err := runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s %v", out, err) + c.Fatalf("out should've been a container id: %s %v", out, err) } waitCmd := exec.Command(dockerBinary, "wait", out) if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) + c.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) } logsCmd := exec.Command(dockerBinary, "logs", out) logsOut, _, err := runCommandWithOutput(logsCmd) if err != nil { - t.Fatalf("error thrown while trying to get container logs: %s, %v", logsOut, err) + c.Fatalf("error thrown while trying to get container logs: %s, %v", logsOut, err) } containerLogs := strings.TrimSpace(logsOut) if containerLogs != "blahblah" { - t.Errorf("logs didn't print the container's logs %s", containerLogs) + c.Errorf("logs didn't print the container's logs %s", containerLogs) } rmCmd := exec.Command(dockerBinary, "rm", out) if out, _, err = runCommandWithOutput(rmCmd); err != nil { - t.Fatalf("rm failed to remove container: %s, %v", out, err) + c.Fatalf("rm failed to remove container: %s, %v", out, err) } - - logDone("run - pipe in with -i -a stdin") } // the container's ID should be printed when starting a container in detached mode -func TestRunDetachedContainerIDPrinting(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDetachedContainerIDPrinting(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", out) if inspectOut, _, err := runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s %v", inspectOut, err) + c.Fatalf("out should've been a container id: %s %v", inspectOut, err) } waitCmd := exec.Command(dockerBinary, "wait", out) if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) + c.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) } rmCmd := exec.Command(dockerBinary, "rm", out) rmOut, _, err := runCommandWithOutput(rmCmd) if err != nil { - t.Fatalf("rm failed to remove container: %s, %v", rmOut, err) + c.Fatalf("rm failed to remove container: %s, %v", rmOut, err) } rmOut = strings.TrimSpace(rmOut) if rmOut != out { - t.Errorf("rm didn't print the container ID %s %s", out, rmOut) + c.Errorf("rm didn't print the container ID %s %s", out, rmOut) } - - logDone("run - print container ID in detached mode") } // the working directory should be set correctly -func TestRunWorkingDirectory(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWorkingDirectory(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-w", "/root", "busybox", "pwd") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) if out != "/root" { - t.Errorf("-w failed to set working directory") + c.Errorf("-w failed to set working directory") } runCmd = exec.Command(dockerBinary, "run", "--workdir", "/root", "busybox", "pwd") out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if out != "/root" { - t.Errorf("--workdir failed to set working directory") + c.Errorf("--workdir failed to set working directory") } - - logDone("run - run with working directory set by -w/--workdir") } // pinging Google's DNS resolver should fail when we disable the networking -func TestRunWithoutNetworking(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithoutNetworking(c *check.C) { 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) + c.Fatal(out, err) } if exitCode != 1 { - t.Errorf("--net=none should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") + c.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") out, _, exitCode, err = runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 1 { - t.Fatal(out, err) + c.Fatal(out, err) } if exitCode != 1 { - t.Errorf("-n=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") + c.Errorf("-n=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") } - - logDone("run - disable networking with --net=none/-n=false") } //test --link use container name to link target -func TestRunLinksContainerWithContainerName(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLinksContainerWithContainerName(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-i", "-t", "-d", "--name", "parent", "busybox") out, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", "parent") ip, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to inspect container: %v, output: %q", err, ip) + c.Fatalf("failed to inspect container: %v, output: %q", err, ip) } ip = strings.TrimSpace(ip) cmd = exec.Command(dockerBinary, "run", "--link", "parent:test", "busybox", "/bin/cat", "/etc/hosts") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, ip+" test") { - t.Fatalf("use a container name to link target failed") + c.Fatalf("use a container name to link target failed") } - - logDone("run - use a container name to link target work") } //test --link use container id to link target -func TestRunLinksContainerWithContainerId(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-i", "-t", "-d", "busybox") cID, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, cID) + c.Fatalf("failed to run container: %v, output: %q", err, cID) } cID = strings.TrimSpace(cID) cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", cID) ip, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("faild to inspect container: %v, output: %q", err, ip) + c.Fatalf("faild to inspect container: %v, output: %q", err, ip) } ip = strings.TrimSpace(ip) cmd = exec.Command(dockerBinary, "run", "--link", cID+":test", "busybox", "/bin/cat", "/etc/hosts") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, ip+" test") { - t.Fatalf("use a container id to link target failed") + c.Fatalf("use a container id to link target failed") } - - logDone("run - use a container id to link target work") } -func TestRunLinkToContainerNetMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLinkToContainerNetMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "test", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "--name", "parent", "-d", "--net=container:test", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "-d", "--link=parent:parent", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "--name", "child", "-d", "--net=container:parent", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "-d", "--link=child:child", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } - - logDone("run - link to a container which net mode is container success") } -func TestRunModeNetContainerHostname(t *testing.T) { - testRequires(t, ExecSupport) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeNetContainerHostname(c *check.C) { + testRequires(c, ExecSupport) cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "exec", "parent", "cat", "/etc/hostname") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to exec command: %v, output: %q", err, out) + c.Fatalf("failed to exec command: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "--net=container:parent", "busybox", "cat", "/etc/hostname") out1, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out1) + c.Fatalf("failed to run container: %v, output: %q", err, out1) } if out1 != out { - t.Fatal("containers with shared net namespace should have same hostname") + c.Fatal("containers with shared net namespace should have same hostname") } - - logDone("run - containers with shared net namespace have same hostname") } // Regression test for #4741 -func TestRunWithVolumesAsFiles(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithVolumesAsFiles(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/etc/hosts:/target-file", "busybox", "true") out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 0 { - t.Fatal("1", out, stderr, err) + c.Fatal("1", out, stderr, err) } runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/target-file") out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 0 { - t.Fatal("2", out, stderr, err) + c.Fatal("2", out, stderr, err) } - - logDone("run - regression test for #4741 - volumes from as files") } // Regression test for #4979 -func TestRunWithVolumesFromExited(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithVolumesFromExited(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/some/dir", "busybox", "touch", "/some/dir/file") out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 0 { - t.Fatal("1", out, stderr, err) + c.Fatal("1", out, stderr, err) } runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/some/dir/file") out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 0 { - t.Fatal("2", out, stderr, err) + c.Fatal("2", out, stderr, err) } - - logDone("run - regression test for #4979 - volumes-from on exited container") } // Volume path is a symlink which also exists on the host, and the host side is a file not a dir // But the volume call is just a normal volume, not a bind mount -func TestRunCreateVolumesInSymlinkDir(t *testing.T) { - testRequires(t, SameHostDaemon) - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunCreateVolumesInSymlinkDir(c *check.C) { + testRequires(c, SameHostDaemon) + testRequires(c, NativeExecDriver) name := "test-volume-symlink" dir, err := ioutil.TempDir("", name) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(dir) f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_CREATE, 0700) if err != nil { - t.Fatal(err) + c.Fatal(err) } f.Close() dockerFile := fmt.Sprintf("FROM busybox\nRUN mkdir -p %s\nRUN ln -s %s /test", dir, dir) if _, err := buildImage(name, dockerFile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } defer deleteImages(name) - dockerCmd(t, "run", "-v", "/test/test", name) - - logDone("run - create volume in symlink directory") + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/test/test", name)) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } } // Regression test for #4830 -func TestRunWithRelativePath(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithRelativePath(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-v", "tmp:/other-tmp", "busybox", "true") if _, _, _, err := runCommandWithStdoutStderr(runCmd); err == nil { - t.Fatalf("relative path should result in an error") + c.Fatalf("relative path should result in an error") } - - logDone("run - volume with relative path") } -func TestRunVolumesMountedAsReadonly(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVolumesMountedAsReadonly(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-v", "/test:/test:ro", "busybox", "touch", "/test/somefile") if code, err := runCommand(cmd); err == nil || code == 0 { - t.Fatalf("run should fail because volume is ro: exit code %d", code) + c.Fatalf("run should fail because volume is ro: exit code %d", code) } - - logDone("run - volumes as readonly mount") } -func TestRunVolumesFromInReadonlyMode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunVolumesFromInReadonlyMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:ro", "busybox", "touch", "/test/file") if code, err := runCommand(cmd); err == nil || code == 0 { - t.Fatalf("run should fail because volume is ro: exit code %d", code) + c.Fatalf("run should fail because volume is ro: exit code %d", code) } - - logDone("run - volumes from as readonly mount") } // Regression test for #1201 -func TestRunVolumesFromInReadWriteMode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunVolumesFromInReadWriteMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("running --volumes-from parent:rw failed with output: %q\nerror: %v", out, err) + c.Fatalf("running --volumes-from parent:rw failed with output: %q\nerror: %v", out, err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file") if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "invalid mode for volumes-from: bar") { - t.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out) + c.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "busybox", "touch", "/test/file") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("running --volumes-from parent failed with output: %q\nerror: %v", out, err) + c.Fatalf("running --volumes-from parent failed with output: %q\nerror: %v", out, err) } - - logDone("run - volumes from as read write mount") } -func TestVolumesFromGetsProperMode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestVolumesFromGetsProperMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test:/test:ro", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // Expect this "rw" mode to be be ignored since the inherited volume is "ro" cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file") if _, err := runCommand(cmd); err == nil { - t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `rw`") + c.Fatal("Expected volumes-from to inherit read-only volume even when passing in `rw`") } cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/test:/test:ro", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // Expect this to be read-only since both are "ro" cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent2:ro", "busybox", "touch", "/test/file") if _, err := runCommand(cmd); err == nil { - t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`") + c.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`") } - - logDone("run - volumes from ignores `rw` if inherrited volume is `ro`") } // Test for GH#10618 -func TestRunNoDupVolumes(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunNoDupVolumes(c *check.C) { mountstr1 := randomUnixTmpDirPath("test1") + ":/someplace" mountstr2 := randomUnixTmpDirPath("test2") + ":/someplace" cmd := exec.Command(dockerBinary, "run", "-v", mountstr1, "-v", mountstr2, "busybox", "true") if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("Expected error about duplicate volume definitions") + c.Fatal("Expected error about duplicate volume definitions") } else { if !strings.Contains(out, "Duplicate volume") { - t.Fatalf("Expected 'duplicate volume' error, got %v", err) + c.Fatalf("Expected 'duplicate volume' error, got %v", err) } } - - logDone("run - don't allow multiple (bind) volumes on the same container target") } // Test for #1351 -func TestRunApplyVolumesFromBeforeVolumes(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunApplyVolumesFromBeforeVolumes(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "touch", "/test/foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "-v", "/test", "busybox", "cat", "/test/foo") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - volumes from mounted first") } -func TestRunMultipleVolumesFrom(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunMultipleVolumesFrom(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent1", "-v", "/test", "busybox", "touch", "/test/foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/other", "busybox", "touch", "/other/bar") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent1", "--volumes-from", "parent2", "busybox", "sh", "-c", "cat /test/foo && cat /other/bar") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - multiple volumes from") } // this tests verifies the ID format for the container -func TestRunVerifyContainerID(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVerifyContainerID(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, exit, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if exit != 0 { - t.Fatalf("expected exit code 0 received %d", exit) + c.Fatalf("expected exit code 0 received %d", exit) } match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !match { - t.Fatalf("Invalid container ID: %s", out) + c.Fatalf("Invalid container ID: %s", out) } - - logDone("run - verify container ID") } // Test that creating a container with a volume doesn't crash. Regression test for #995. -func TestRunCreateVolume(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCreateVolume(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - create docker managed volume") } // Test that creating a volume with a symlink in its path works correctly. Test for #5152. // Note that this bug happens only with symlinks with a target that starts with '/'. -func TestRunCreateVolumeWithSymlink(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCreateVolumeWithSymlink(c *check.C) { image := "docker-test-createvolumewithsymlink" defer deleteImages(image) @@ -732,41 +612,37 @@ func TestRunCreateVolumeWithSymlink(t *testing.T) { buildCmd.Dir = workingDirectory err := buildCmd.Run() if err != nil { - t.Fatalf("could not build '%s': %v", image, err) + c.Fatalf("could not build '%s': %v", image, err) } cmd := exec.Command(dockerBinary, "run", "-v", "/bar/foo", "--name", "test-createvolumewithsymlink", image, "sh", "-c", "mount | grep -q /home/foo") exitCode, err := runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) } var volPath string cmd = exec.Command(dockerBinary, "inspect", "-f", "{{range .Volumes}}{{.}}{{end}}", "test-createvolumewithsymlink") volPath, exitCode, err = runCommandWithOutput(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[inspect] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[inspect] err: %v, exitcode: %d", err, exitCode) } cmd = exec.Command(dockerBinary, "rm", "-v", "test-createvolumewithsymlink") exitCode, err = runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[rm] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[rm] err: %v, exitcode: %d", err, exitCode) } f, err := os.Open(volPath) defer f.Close() if !os.IsNotExist(err) { - t.Fatalf("[open] (expecting 'file does not exist' error) err: %v, volPath: %s", err, volPath) + c.Fatalf("[open] (expecting 'file does not exist' error) err: %v, volPath: %s", err, volPath) } - - logDone("run - create volume with symlink") } // Tests that a volume path that has a symlink exists in a container mounting it with `--volumes-from`. -func TestRunVolumesFromSymlinkPath(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVolumesFromSymlinkPath(c *check.C) { name := "docker-test-volumesfromsymlinkpath" defer deleteImages(name) @@ -777,145 +653,109 @@ func TestRunVolumesFromSymlinkPath(t *testing.T) { buildCmd.Dir = workingDirectory err := buildCmd.Run() if err != nil { - t.Fatalf("could not build 'docker-test-volumesfromsymlinkpath': %v", err) + c.Fatalf("could not build 'docker-test-volumesfromsymlinkpath': %v", err) } cmd := exec.Command(dockerBinary, "run", "--name", "test-volumesfromsymlinkpath", name) exitCode, err := runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[run] (volume) err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[run] (volume) err: %v, exitcode: %d", err, exitCode) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-volumesfromsymlinkpath", "busybox", "sh", "-c", "ls /foo | grep -q bar") exitCode, err = runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) } - - logDone("run - volumes-from symlink path") } -func TestRunExitCode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExitCode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") exit, err := runCommand(cmd) if err == nil { - t.Fatal("should not have a non nil error") + c.Fatal("should not have a non nil error") } if exit != 72 { - t.Fatalf("expected exit code 72 received %d", exit) + c.Fatalf("expected exit code 72 received %d", exit) } - - logDone("run - correct exit code") } -func TestRunUserDefaultsToRoot(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserDefaultsToRoot(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) + c.Fatalf("expected root user got %s", out) } - - logDone("run - default user") } -func TestRunUserByName(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByName(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) + c.Fatalf("expected root user got %s", out) } - - logDone("run - user by name") } -func TestRunUserByID(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByID(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { - t.Fatalf("expected daemon user got %s", out) + c.Fatalf("expected daemon user got %s", out) } - - logDone("run - user by id") } -func TestRunUserByIDBig(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByIDBig(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "2147483648", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal("No error, but must be.", out) + c.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) + c.Fatalf("expected error about uids range, got %s", out) } - - logDone("run - user by id, id too big") } -func TestRunUserByIDNegative(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByIDNegative(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "-1", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal("No error, but must be.", out) + c.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) + c.Fatalf("expected error about uids range, got %s", out) } - - logDone("run - user by id, id negative") } -func TestRunUserByIDZero(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByIDZero(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "0", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root) groups=10(wheel)") { - t.Fatalf("expected daemon user got %s", out) + c.Fatalf("expected daemon user got %s", out) } - - logDone("run - user by id, zero uid") } -func TestRunUserNotFound(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserNotFound(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") _, err := runCommand(cmd) if err == nil { - t.Fatal("unknown user should cause container to fail") + c.Fatal("unknown user should cause container to fail") } - - logDone("run - user not found") } -func TestRunTwoConcurrentContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunTwoConcurrentContainers(c *check.C) { group := sync.WaitGroup{} group.Add(2) @@ -924,19 +764,15 @@ func TestRunTwoConcurrentContainers(t *testing.T) { defer group.Done() cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } }() } group.Wait() - - logDone("run - two concurrent containers") } -func TestRunEnvironment(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEnvironment(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "-e=HOME=", "busybox", "env") cmd.Env = append(os.Environ(), "TRUE=false", @@ -945,7 +781,7 @@ func TestRunEnvironment(t *testing.T) { out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n") @@ -969,29 +805,26 @@ func TestRunEnvironment(t *testing.T) { } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) + c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) } for i := range goodEnv { if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) } } - - logDone("run - verify environment") } -func TestRunEnvironmentErase(t *testing.T) { +func (s *DockerSuite) TestRunEnvironmentErase(c *check.C) { // Test to make sure that when we use -e on env vars that are // not set in our local env that they're removed (if present) in // the container - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-e", "FOO", "-e", "HOSTNAME", "busybox", "env") cmd.Env = appendBaseEnv([]string{}) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n") @@ -1009,28 +842,25 @@ func TestRunEnvironmentErase(t *testing.T) { } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) + c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) } for i := range goodEnv { if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) } } - - logDone("run - verify environment erase") } -func TestRunEnvironmentOverride(t *testing.T) { +func (s *DockerSuite) TestRunEnvironmentOverride(c *check.C) { // Test to make sure that when we use -e on env vars that are // already in the env that we're overriding them - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-e", "HOSTNAME", "-e", "HOME=/root2", "busybox", "env") cmd.Env = appendBaseEnv([]string{"HOSTNAME=bar"}) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n") @@ -1049,60 +879,47 @@ func TestRunEnvironmentOverride(t *testing.T) { } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) + c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) } for i := range goodEnv { if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) } } - - logDone("run - verify environment override") } -func TestRunContainerNetwork(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunContainerNetwork(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - test container network via ping") } // Issue #4681 -func TestRunLoopbackWhenNetworkDisabled(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLoopbackWhenNetworkDisabled(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ping", "-c", "1", "127.0.0.1") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - test container loopback when networking disabled") } -func TestRunNetHostNotAllowedWithLinks(t *testing.T) { - defer deleteAllContainers() - - _, _ = dockerCmd(t, "run", "--name", "linked", "busybox", "true") +func (s *DockerSuite) TestRunNetHostNotAllowedWithLinks(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "linked", "busybox", "true")) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } cmd := exec.Command(dockerBinary, "run", "--net=host", "--link", "linked:linked", "busybox", "true") - _, _, err := runCommandWithOutput(cmd) + _, _, err = runCommandWithOutput(cmd) if err == nil { - t.Fatal("Expected error") + c.Fatal("Expected error") } - - logDone("run - don't allow --net=host to be used with links") } -func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLoopbackOnlyExistsWhenNetworkingDisabled(c *check.C) { 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) + c.Fatal(err, out) } var ( @@ -1117,14 +934,12 @@ func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { } if count != 1 { - t.Fatalf("Wrong interface count in container %d", count) + c.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) + c.Fatalf("Wrong interface in test container: expected [1: lo], got %s", out) } - - logDone("run - test loopback only exists when networking disabled") } // #7851 hostname outside container shows FQDN, inside only shortname @@ -1132,304 +947,220 @@ func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { // and use "--net=host" (as the original issue submitter did), as the same // codepath is executed with "docker run -h ". Both were manually // tested, but this testcase takes the simpler path of using "run -h .." -func TestRunFullHostnameSet(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunFullHostnameSet(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-h", "foo.bar.baz", "busybox", "hostname") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "foo.bar.baz" { - t.Fatalf("expected hostname 'foo.bar.baz', received %s", actual) + c.Fatalf("expected hostname 'foo.bar.baz', received %s", actual) } - - logDone("run - test fully qualified hostname set with -h") } -func TestRunPrivilegedCanMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunPrivilegedCanMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test privileged can mknod") } -func TestRunUnPrivilegedCanMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUnPrivilegedCanMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test un-privileged can mknod") } -func TestRunCapDropInvalid(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunCapDropInvalid(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=CHPASS", "busybox", "ls") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - test --cap-drop=CHPASS invalid") } -func TestRunCapDropCannotMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropCannotMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-drop=MKNOD cannot mknod") } -func TestRunCapDropCannotMknodLowerCase(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropCannotMknodLowerCase(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=mknod", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-drop=mknod cannot mknod lowercase") } -func TestRunCapDropALLCannotMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropALLCannotMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "--cap-add=SETGID", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-drop=ALL cannot mknod") } -func TestRunCapDropALLAddMknodCanMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropALLAddMknodCanMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "--cap-add=MKNOD", "--cap-add=SETGID", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test --cap-drop=ALL --cap-add=MKNOD can mknod") } -func TestRunCapAddInvalid(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddInvalid(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=CHPASS", "busybox", "ls") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - test --cap-add=CHPASS invalid") } -func TestRunCapAddCanDownInterface(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddCanDownInterface(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test --cap-add=NET_ADMIN can set eth0 down") } -func TestRunCapAddALLCanDownInterface(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddALLCanDownInterface(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test --cap-add=ALL can set eth0 down") } -func TestRunCapAddALLDropNetAdminCanDownInterface(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddALLDropNetAdminCanDownInterface(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "--cap-drop=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-add=ALL --cap-drop=NET_ADMIN cannot set eth0 down") } -func TestRunPrivilegedCanMount(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunPrivilegedCanMount(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test privileged can mount") } -func TestRunUnPrivilegedCannotMount(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUnPrivilegedCannotMount(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test un-privileged cannot mount") } -func TestRunSysNotWritableInNonPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunSysNotWritableInNonPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "touch", "/sys/kernel/profiling") if code, err := runCommand(cmd); err == nil || code == 0 { - t.Fatal("sys should not be writable in a non privileged container") + c.Fatal("sys should not be writable in a non privileged container") } - - logDone("run - sys not writable in non privileged container") } -func TestRunSysWritableInPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunSysWritableInPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "touch", "/sys/kernel/profiling") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("sys should be writable in privileged container") + c.Fatalf("sys should be writable in privileged container") } - - logDone("run - sys writable in privileged container") } -func TestRunProcNotWritableInNonPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunProcNotWritableInNonPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "touch", "/proc/sysrq-trigger") if code, err := runCommand(cmd); err == nil || code == 0 { - t.Fatal("proc should not be writable in a non privileged container") + c.Fatal("proc should not be writable in a non privileged container") } - - logDone("run - proc not writable in non privileged container") } -func TestRunProcWritableInPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunProcWritableInPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "touch", "/proc/sysrq-trigger") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("proc should be writable in privileged container") + c.Fatalf("proc should be writable in privileged container") } - logDone("run - proc writable in privileged container") } -func TestRunWithCpuset(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithCpuset(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cpuset", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfully with cpuset of 0: %s", err) + c.Fatalf("container should run successfully with cpuset of 0: %s", err) } - - logDone("run - cpuset 0") } -func TestRunWithCpusetCpus(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithCpusetCpus(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cpuset-cpus", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfully with cpuset-cpus of 0: %s", err) + c.Fatalf("container should run successfully with cpuset-cpus of 0: %s", err) } - - logDone("run - cpuset-cpus 0") } -func TestRunWithCpusetMems(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithCpusetMems(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cpuset-mems", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfully with cpuset-mems of 0: %s", err) + c.Fatalf("container should run successfully with cpuset-mems of 0: %s", err) } - - logDone("run - cpuset-mems 0") } -func TestRunDeviceNumbers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDeviceNumbers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "ls -l /dev/null") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } deviceLineFields := strings.Fields(out) deviceLineFields[6] = "" @@ -1438,131 +1169,107 @@ func TestRunDeviceNumbers(t *testing.T) { 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) + c.Fatalf("expected output\ncrw-rw-rw- 1 root root 1, 3 May 24 13:29 /dev/null\n received\n %s\n", out) } - - logDone("run - test device numbers") } -func TestRunThatCharacterDevicesActLikeCharacterDevices(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunThatCharacterDevicesActLikeCharacterDevices(c *check.C) { 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) + c.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) + c.Fatalf("expected a new file called /zero to be create that is greater than 0 bytes long, but du says: %s", actual) } - - logDone("run - test that character devices work.") } -func TestRunUnprivilegedWithChroot(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUnprivilegedWithChroot(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "chroot", "/", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - unprivileged with chroot") } -func TestRunAddingOptionalDevices(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAddingOptionalDevices(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--device", "/dev/zero:/dev/nulo", "busybox", "sh", "-c", "ls /dev/nulo") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "/dev/nulo" { - t.Fatalf("expected output /dev/nulo, received %s", actual) + c.Fatalf("expected output /dev/nulo, received %s", actual) } - - logDone("run - test --device argument") } -func TestRunModeHostname(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeHostname(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-h=testhostname", "busybox", "cat", "/etc/hostname") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "testhostname" { - t.Fatalf("expected 'testhostname', but says: %q", actual) + c.Fatalf("expected 'testhostname', but says: %q", actual) } cmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "cat", "/etc/hostname") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } hostname, err := os.Hostname() if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != hostname { - t.Fatalf("expected %q, but says: %q", hostname, actual) + c.Fatalf("expected %q, but says: %q", hostname, actual) } - - logDone("run - hostname and several network modes") } -func TestRunRootWorkdir(t *testing.T) { - defer deleteAllContainers() - - s, _ := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd") - if s != "/\n" { - t.Fatalf("pwd returned %q (expected /\\n)", s) +func (s *DockerSuite) TestRunRootWorkdir(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--workdir", "/", "busybox", "pwd")) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } + if out != "/\n" { + c.Fatalf("pwd returned %q (expected /\\n)", s) } - - logDone("run - workdir /") } -func TestRunAllowBindMountingRoot(t *testing.T) { - defer deleteAllContainers() - - _, _ = dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host") - - logDone("run - bind mount / as volume") +func (s *DockerSuite) TestRunAllowBindMountingRoot(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/:/host", "busybox", "ls", "/host")) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } } -func TestRunDisallowBindMountingRootToRoot(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDisallowBindMountingRootToRoot(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-v", "/:/", "busybox", "ls", "/host") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - bind mount /:/ as volume should not work") } // Verify that a container gets default DNS when only localhost resolvers exist -func TestRunDnsDefaultOptions(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunDnsDefaultOptions(c *check.C) { + testRequires(c, SameHostDaemon) // preserve original resolv.conf for restoring after test origResolvConf, err := ioutil.ReadFile("/etc/resolv.conf") if os.IsNotExist(err) { - t.Fatalf("/etc/resolv.conf does not exist") + c.Fatalf("/etc/resolv.conf does not exist") } // defer restored original conf defer func() { if err := ioutil.WriteFile("/etc/resolv.conf", origResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } }() @@ -1571,14 +1278,14 @@ func TestRunDnsDefaultOptions(t *testing.T) { // GetNameservers(), leading to a replacement of nameservers with the default set tmpResolvConf := []byte("nameserver 127.0.0.1\n#nameserver 127.0.2.1\nnameserver ::1") if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "busybox", "cat", "/etc/resolv.conf") actual, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, actual) + c.Fatal(err, actual) } // check that the actual defaults are appended to the commented out @@ -1586,54 +1293,47 @@ func TestRunDnsDefaultOptions(t *testing.T) { // NOTE: if we ever change the defaults from google dns, this will break expected := "#nameserver 127.0.2.1\n\nnameserver 8.8.8.8\nnameserver 8.8.4.4" if actual != expected { - t.Fatalf("expected resolv.conf be: %q, but was: %q", expected, actual) + c.Fatalf("expected resolv.conf be: %q, but was: %q", expected, actual) } - - logDone("run - dns default options") } -func TestRunDnsOptions(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDnsOptions(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf") out, stderr, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } // The client will get a warning on stderr when setting DNS to a localhost address; verify this: if !strings.Contains(stderr, "Localhost DNS setting") { - t.Fatalf("Expected warning on stderr about localhost resolver, but got %q", stderr) + c.Fatalf("Expected warning on stderr about localhost resolver, but got %q", stderr) } actual := strings.Replace(strings.Trim(out, "\r\n"), "\n", " ", -1) if actual != "nameserver 127.0.0.1 search mydomain" { - t.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: %q", actual) + c.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: %q", actual) } cmd = exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=.", "busybox", "cat", "/etc/resolv.conf") out, _, _, err = runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actual = strings.Replace(strings.Trim(strings.Trim(out, "\r\n"), " "), "\n", " ", -1) if actual != "nameserver 127.0.0.1" { - t.Fatalf("expected 'nameserver 127.0.0.1', but says: %q", actual) + c.Fatalf("expected 'nameserver 127.0.0.1', but says: %q", actual) } - - logDone("run - dns options") } -func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunDnsOptionsBasedOnHostResolvConf(c *check.C) { + testRequires(c, SameHostDaemon) origResolvConf, err := ioutil.ReadFile("/etc/resolv.conf") if os.IsNotExist(err) { - t.Fatalf("/etc/resolv.conf does not exist") + c.Fatalf("/etc/resolv.conf does not exist") } hostNamservers := resolvconf.GetNameservers(origResolvConf) @@ -1642,58 +1342,58 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) { var out string cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "busybox", "cat", "/etc/resolv.conf") if out, _, _, err = runCommandWithStdoutStderr(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actualNameservers := resolvconf.GetNameservers([]byte(out)); string(actualNameservers[0]) != "127.0.0.1" { - t.Fatalf("expected '127.0.0.1', but says: %q", string(actualNameservers[0])) + c.Fatalf("expected '127.0.0.1', but says: %q", string(actualNameservers[0])) } actualSearch := resolvconf.GetSearchDomains([]byte(out)) if len(actualSearch) != len(hostSearch) { - t.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) + c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) } for i := range actualSearch { if actualSearch[i] != hostSearch[i] { - t.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) + c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) } } cmd = exec.Command(dockerBinary, "run", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf") if out, _, err = runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualNameservers := resolvconf.GetNameservers([]byte(out)) if len(actualNameservers) != len(hostNamservers) { - t.Fatalf("expected %q nameserver(s), but it has: %q", len(hostNamservers), len(actualNameservers)) + c.Fatalf("expected %q nameserver(s), but it has: %q", len(hostNamservers), len(actualNameservers)) } for i := range actualNameservers { if actualNameservers[i] != hostNamservers[i] { - t.Fatalf("expected %q nameserver, but says: %q", actualNameservers[i], hostNamservers[i]) + c.Fatalf("expected %q nameserver, but says: %q", actualNameservers[i], hostNamservers[i]) } } if actualSearch = resolvconf.GetSearchDomains([]byte(out)); string(actualSearch[0]) != "mydomain" { - t.Fatalf("expected 'mydomain', but says: %q", string(actualSearch[0])) + c.Fatalf("expected 'mydomain', but says: %q", string(actualSearch[0])) } // test with file tmpResolvConf := []byte("search example.com\nnameserver 12.34.56.78\nnameserver 127.0.0.1") if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } // put the old resolvconf back defer func() { if err := ioutil.WriteFile("/etc/resolv.conf", origResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } }() resolvConf, err := ioutil.ReadFile("/etc/resolv.conf") if os.IsNotExist(err) { - t.Fatalf("/etc/resolv.conf does not exist") + c.Fatalf("/etc/resolv.conf does not exist") } hostNamservers = resolvconf.GetNameservers(resolvConf) @@ -1702,35 +1402,32 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "busybox", "cat", "/etc/resolv.conf") if out, _, err = runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actualNameservers = resolvconf.GetNameservers([]byte(out)); string(actualNameservers[0]) != "12.34.56.78" || len(actualNameservers) != 1 { - t.Fatalf("expected '12.34.56.78', but has: %v", actualNameservers) + c.Fatalf("expected '12.34.56.78', but has: %v", actualNameservers) } actualSearch = resolvconf.GetSearchDomains([]byte(out)) if len(actualSearch) != len(hostSearch) { - t.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) + c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) } for i := range actualSearch { if actualSearch[i] != hostSearch[i] { - t.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) + c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) } } - defer deleteAllContainers() - - logDone("run - dns options based on host resolv.conf") } // Test the file watch notifier on docker host's /etc/resolv.conf // A go-routine is responsible for auto-updating containers which are // stopped and have an unmodified copy of resolv.conf, as well as // marking running containers as requiring an update on next restart -func TestRunResolvconfUpdater(t *testing.T) { +func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { // Because overlay doesn't support inotify properly, we need to skip // this test if the docker daemon has Storage Driver == overlay - testRequires(t, SameHostDaemon, NotOverlay) + testRequires(c, SameHostDaemon, NotOverlay) tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78") tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1") @@ -1738,97 +1435,97 @@ func TestRunResolvconfUpdater(t *testing.T) { //take a copy of resolv.conf for restoring after test completes resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } // This test case is meant to test monitoring resolv.conf when it is - // a regular file not a bind mount. So we unmount resolv.conf and replace + // a regular file not a bind mounc. So we unmount resolv.conf and replace // it with a file containing the original settings. cmd := exec.Command("umount", "/etc/resolv.conf") if _, err = runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } //cleanup defer func() { deleteAllContainers() if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } }() //1. test that a non-running container gets an updated resolv.conf cmd = exec.Command(dockerBinary, "run", "--name='first'", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } containerID1, err := getIDByName("first") if err != nil { - t.Fatal(err) + c.Fatal(err) } // replace resolv.conf with our temporary copy bytesResolvConf := []byte(tmpResolvConf) if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) // check for update in container containerResolv, err := readContainerFile(containerID1, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - t.Fatalf("Stopped container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Stopped container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) } //2. test that a non-running container does not receive resolv.conf updates // if it modified the container copy of the starting point resolv.conf cmd = exec.Command(dockerBinary, "run", "--name='second'", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf") if _, err = runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } containerID2, err := getIDByName("second") if err != nil { - t.Fatal(err) + c.Fatal(err) } containerResolvHashBefore, err := readContainerFile(containerID2, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } //make a change to resolv.conf (in this case replacing our tmp copy with orig copy) if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) containerResolvHashAfter, err := readContainerFile(containerID2, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { - t.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) + c.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) } //3. test that a running container's resolv.conf is not modified while running cmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } runningContainerID := strings.TrimSpace(out) containerResolvHashBefore, err = readContainerFile(runningContainerID, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } // replace resolv.conf if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } // make sure the updater has time to run to validate we really aren't @@ -1836,27 +1533,27 @@ func TestRunResolvconfUpdater(t *testing.T) { time.Sleep(time.Second / 2) containerResolvHashAfter, err = readContainerFile(runningContainerID, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { - t.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) + c.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) } //4. test that a running container's resolv.conf is updated upon restart // (the above container is still running..) cmd = exec.Command(dockerBinary, "restart", runningContainerID) if _, err = runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // check for update in container containerResolv, err = readContainerFile(runningContainerID, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - t.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) } //5. test that additions of a localhost resolver are cleaned from @@ -1865,7 +1562,7 @@ func TestRunResolvconfUpdater(t *testing.T) { // replace resolv.conf with a localhost-only nameserver copy bytesResolvConf = []byte(tmpLocalhostResolvConf) if err = ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) @@ -1873,12 +1570,12 @@ func TestRunResolvconfUpdater(t *testing.T) { // after the cleanup of resolv.conf found only a localhost nameserver: containerResolv, err = readContainerFile(containerID1, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "\nnameserver 8.8.8.8\nnameserver 8.8.4.4" if !bytes.Equal(containerResolv, []byte(expected)) { - t.Fatalf("Container does not have cleaned/replaced DNS in resolv.conf; expected %q, got %q", expected, string(containerResolv)) + c.Fatalf("Container does not have cleaned/replaced DNS in resolv.conf; expected %q, got %q", expected, string(containerResolv)) } //6. Test that replacing (as opposed to modifying) resolv.conf triggers an update @@ -1886,194 +1583,171 @@ func TestRunResolvconfUpdater(t *testing.T) { // Restore the original resolv.conf if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } // Run the container so it picks up the old settings cmd = exec.Command(dockerBinary, "run", "--name='third'", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } containerID3, err := getIDByName("third") if err != nil { - t.Fatal(err) + c.Fatal(err) } // Create a modified resolv.conf.aside and override resolv.conf with it bytesResolvConf = []byte(tmpResolvConf) if err := ioutil.WriteFile("/etc/resolv.conf.aside", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } err = os.Rename("/etc/resolv.conf.aside", "/etc/resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) // check for update in container containerResolv, err = readContainerFile(containerID3, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - t.Fatalf("Stopped container does not have updated resolv.conf; expected\n%q\n got\n%q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Stopped container does not have updated resolv.conf; expected\n%q\n got\n%q", tmpResolvConf, string(containerResolv)) } //cleanup, restore original resolv.conf happens in defer func() - logDone("run - resolv.conf updater") } -func TestRunAddHost(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunAddHost(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--add-host=extra:86.75.30.9", "busybox", "grep", "extra", "/etc/hosts") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actual := strings.Trim(out, "\r\n") if actual != "86.75.30.9\textra" { - t.Fatalf("expected '86.75.30.9\textra', but says: %q", actual) + c.Fatalf("expected '86.75.30.9\textra', but says: %q", actual) } - - logDone("run - add-host option") } // Regression test for #6983 -func TestRunAttachStdErrOnlyTTYMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachStdErrOnlyTTYMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stderr", "busybox", "true") exitCode, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } else if exitCode != 0 { - t.Fatalf("Container should have exited with error code 0") + c.Fatalf("Container should have exited with error code 0") } - - logDone("run - Attach stderr only with -t") } // Regression test for #6983 -func TestRunAttachStdOutOnlyTTYMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachStdOutOnlyTTYMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stdout", "busybox", "true") exitCode, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } else if exitCode != 0 { - t.Fatalf("Container should have exited with error code 0") + c.Fatalf("Container should have exited with error code 0") } - - logDone("run - Attach stdout only with -t") } // Regression test for #6983 -func TestRunAttachStdOutAndErrTTYMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachStdOutAndErrTTYMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stdout", "-a", "stderr", "busybox", "true") exitCode, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } else if exitCode != 0 { - t.Fatalf("Container should have exited with error code 0") + c.Fatalf("Container should have exited with error code 0") } - - logDone("run - Attach stderr and stdout with -t") } // Test for #10388 - this will run the same test as TestRunAttachStdOutAndErrTTYMode // but using --attach instead of -a to make sure we read the flag correctly -func TestRunAttachWithDettach(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachWithDettach(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--attach", "stdout", "busybox", "true") _, stderr, _, err := runCommandWithStdoutStderr(cmd) if err == nil { - t.Fatal("Container should have exited with error code different than 0") + c.Fatal("Container should have exited with error code different than 0") } else if !strings.Contains(stderr, "Conflicting options: -a and -d") { - t.Fatal("Should have been returned an error with conflicting options -a and -d") + c.Fatal("Should have been returned an error with conflicting options -a and -d") } - - logDone("run - Attach stdout with -d") } -func TestRunState(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunState(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) state, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "true" { - t.Fatal("Container state is 'not running'") + c.Fatal("Container state is 'not running'") } pid1, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pid1 == "0" { - t.Fatal("Container state Pid 0") + c.Fatal("Container state Pid 0") } cmd = exec.Command(dockerBinary, "stop", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } state, err = inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "false" { - t.Fatal("Container state is 'running'") + c.Fatal("Container state is 'running'") } pid2, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pid2 == pid1 { - t.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) + c.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) } cmd = exec.Command(dockerBinary, "start", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } state, err = inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "true" { - t.Fatal("Container state is 'not running'") + c.Fatal("Container state is 'not running'") } pid3, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pid3 == pid1 { - t.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) + c.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) } - logDone("run - test container state.") } // Test for #1737 -func TestRunCopyVolumeUidGid(t *testing.T) { +func (s *DockerSuite) TestRunCopyVolumeUidGid(c *check.C) { name := "testrunvolumesuidgid" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd @@ -2081,178 +1755,164 @@ func TestRunCopyVolumeUidGid(t *testing.T) { RUN mkdir -p /hello && touch /hello/test && chown dockerio.dockerio /hello`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Test that the uid and gid is copied from the image to the volume cmd := exec.Command(dockerBinary, "run", "--rm", "-v", "/hello", name, "sh", "-c", "ls -l / | grep hello | awk '{print $3\":\"$4}'") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out = strings.TrimSpace(out) if out != "dockerio:dockerio" { - t.Fatalf("Wrong /hello ownership: %s, expected dockerio:dockerio", out) + c.Fatalf("Wrong /hello ownership: %s, expected dockerio:dockerio", out) } - - logDone("run - copy uid/gid for volume") } // Test for #1582 -func TestRunCopyVolumeContent(t *testing.T) { +func (s *DockerSuite) TestRunCopyVolumeContent(c *check.C) { name := "testruncopyvolumecontent" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox RUN mkdir -p /hello/local && echo hello > /hello/local/world`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Test that the content is copied from the image to the volume cmd := exec.Command(dockerBinary, "run", "--rm", "-v", "/hello", name, "find", "/hello") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !(strings.Contains(out, "/hello/local/world") && strings.Contains(out, "/hello/local")) { - t.Fatal("Container failed to transfer content to volume") + c.Fatal("Container failed to transfer content to volume") } - logDone("run - copy volume content") } -func TestRunCleanupCmdOnEntrypoint(t *testing.T) { +func (s *DockerSuite) TestRunCleanupCmdOnEntrypoint(c *check.C) { name := "testrunmdcleanuponentrypoint" defer deleteImages(name) - defer deleteAllContainers() if _, err := buildImage(name, `FROM busybox ENTRYPOINT ["echo"] CMD ["testingpoint"]`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd := exec.Command(dockerBinary, "run", "--entrypoint", "whoami", name) out, exit, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error: %v, out: %q", err, out) + c.Fatalf("Error: %v, out: %q", err, out) } if exit != 0 { - t.Fatalf("expected exit code 0 received %d, out: %q", exit, out) + c.Fatalf("expected exit code 0 received %d, out: %q", exit, out) } out = strings.TrimSpace(out) if out != "root" { - t.Fatalf("Expected output root, got %q", out) + c.Fatalf("Expected output root, got %q", out) } - logDone("run - cleanup cmd on --entrypoint") } // TestRunWorkdirExistsAndIsFile checks that if 'docker run -w' with existing file can be detected -func TestRunWorkdirExistsAndIsFile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunWorkdirExistsAndIsFile(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-w", "/bin/cat", "busybox") out, exit, err := runCommandWithOutput(runCmd) if !(err != nil && exit == 1 && strings.Contains(out, "Cannot mkdir: /bin/cat is not a directory")) { - t.Fatalf("Docker must complains about making dir, but we got out: %s, exit: %d, err: %s", out, exit, err) + c.Fatalf("Docker must complains about making dir, but we got out: %s, exit: %d, err: %s", out, exit, err) } - logDone("run - error on existing file for workdir") } -func TestRunExitOnStdinClose(t *testing.T) { +func (s *DockerSuite) TestRunExitOnStdinClose(c *check.C) { name := "testrunexitonstdinclose" - defer deleteAllContainers() runCmd := exec.Command(dockerBinary, "run", "--name", name, "-i", "busybox", "/bin/cat") stdin, err := runCmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } stdout, err := runCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := runCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := stdin.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } r := bufio.NewReader(stdout) line, err := r.ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } line = strings.TrimSpace(line) if line != "hello" { - t.Fatalf("Output should be 'hello', got '%q'", line) + c.Fatalf("Output should be 'hello', got '%q'", line) } if err := stdin.Close(); err != nil { - t.Fatal(err) + c.Fatal(err) } finish := make(chan struct{}) go func() { if err := runCmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } close(finish) }() select { case <-finish: case <-time.After(1 * time.Second): - t.Fatal("docker run failed to exit on stdin close") + c.Fatal("docker run failed to exit on stdin close") } state, err := inspectField(name, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "false" { - t.Fatal("Container must be stopped after stdin closing") + c.Fatal("Container must be stopped after stdin closing") } - logDone("run - exit on stdin closing") } // Test for #2267 -func TestRunWriteHostsFileAndNotCommit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWriteHostsFileAndNotCommit(c *check.C) { name := "writehosts" cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/hosts && cat /etc/hosts") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "test2267") { - t.Fatal("/etc/hosts should contain 'test2267'") + c.Fatal("/etc/hosts should contain 'test2267'") } cmd = exec.Command(dockerBinary, "diff", name) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) { - t.Fatal("diff should be empty") + if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) { + c.Fatal("diff should be empty") } - - logDone("run - write to /etc/hosts and not commited") } -func eqToBaseDiff(out string, t *testing.T) bool { +func eqToBaseDiff(out string, c *check.C) bool { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") out1, _, err := runCommandWithOutput(cmd) cID := strings.TrimSpace(out1) cmd = exec.Command(dockerBinary, "diff", cID) baseDiff, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, baseDiff) + c.Fatal(err, baseDiff) } baseArr := strings.Split(baseDiff, "\n") sort.Strings(baseArr) @@ -2276,346 +1936,299 @@ func sliceEq(a, b []string) bool { } // Test for #2267 -func TestRunWriteHostnameFileAndNotCommit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWriteHostnameFileAndNotCommit(c *check.C) { name := "writehostname" cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/hostname && cat /etc/hostname") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "test2267") { - t.Fatal("/etc/hostname should contain 'test2267'") + c.Fatal("/etc/hostname should contain 'test2267'") } cmd = exec.Command(dockerBinary, "diff", name) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) { - t.Fatal("diff should be empty") + if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) { + c.Fatal("diff should be empty") } - - logDone("run - write to /etc/hostname and not commited") } // Test for #2267 -func TestRunWriteResolvFileAndNotCommit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWriteResolvFileAndNotCommit(c *check.C) { name := "writeresolv" cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/resolv.conf && cat /etc/resolv.conf") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "test2267") { - t.Fatal("/etc/resolv.conf should contain 'test2267'") + c.Fatal("/etc/resolv.conf should contain 'test2267'") } cmd = exec.Command(dockerBinary, "diff", name) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) { - t.Fatal("diff should be empty") + if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) { + c.Fatal("diff should be empty") } - - logDone("run - write to /etc/resolv.conf and not commited") } -func TestRunWithBadDevice(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithBadDevice(c *check.C) { name := "baddevice" cmd := exec.Command(dockerBinary, "run", "--name", name, "--device", "/etc", "busybox", "true") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal("Run should fail with bad device") + c.Fatal("Run should fail with bad device") } expected := `\"/etc\": not a device node` if !strings.Contains(out, expected) { - t.Fatalf("Output should contain %q, actual out: %q", expected, out) + c.Fatalf("Output should contain %q, actual out: %q", expected, out) } - logDone("run - error with bad device") } -func TestRunEntrypoint(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEntrypoint(c *check.C) { name := "entrypoint" cmd := exec.Command(dockerBinary, "run", "--name", name, "--entrypoint", "/bin/echo", "busybox", "-n", "foobar") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } expected := "foobar" if out != expected { - t.Fatalf("Output should be %q, actual out: %q", expected, out) + c.Fatalf("Output should be %q, actual out: %q", expected, out) } - logDone("run - entrypoint") } -func TestRunBindMounts(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunBindMounts(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir("", "docker-test-container") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) - writeFile(path.Join(tmpDir, "touch-me"), "", t) + writeFile(path.Join(tmpDir, "touch-me"), "", c) // Test reading from a read-only bind mount cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp:ro", tmpDir), "busybox", "ls", "/tmp") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "touch-me") { - t.Fatal("Container failed to read from bind mount") + c.Fatal("Container failed to read from bind mount") } // test writing to bind mount cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp:rw", tmpDir), "busybox", "touch", "/tmp/holla") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist + readFile(path.Join(tmpDir, "holla"), c) // Will fail if the file doesn't exist // test mounting to an illegal destination directory cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:.", tmpDir), "busybox", "ls", ".") _, err = runCommand(cmd) if err == nil { - t.Fatal("Container bind mounted illegal directory") + c.Fatal("Container bind mounted illegal directory") } // test mount a file cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s/holla:/tmp/holla:rw", tmpDir), "busybox", "sh", "-c", "echo -n 'yotta' > /tmp/holla") _, err = runCommand(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - content := readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist + content := readFile(path.Join(tmpDir, "holla"), c) // Will fail if the file doesn't exist expected := "yotta" if content != expected { - t.Fatalf("Output should be %q, actual out: %q", expected, content) + c.Fatalf("Output should be %q, actual out: %q", expected, content) } - - logDone("run - bind mounts") } // Ensure that CIDFile gets deleted if it's empty // Perform this test by making `docker run` fail -func TestRunCidFileCleanupIfEmpty(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCidFileCleanupIfEmpty(c *check.C) { tmpDir, err := ioutil.TempDir("", "TestRunCidFile") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) tmpCidFile := path.Join(tmpDir, "cid") cmd := exec.Command(dockerBinary, "run", "--cidfile", tmpCidFile, "emptyfs") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatalf("Run without command must fail. out=%s", out) + c.Fatalf("Run without command must fail. out=%s", out) } else if !strings.Contains(out, "No command specified") { - t.Fatalf("Run without command failed with wrong output. out=%s\nerr=%v", out, err) + c.Fatalf("Run without command failed with wrong outpuc. out=%s\nerr=%v", out, err) } if _, err := os.Stat(tmpCidFile); err == nil { - t.Fatalf("empty CIDFile %q should've been deleted", tmpCidFile) + c.Fatalf("empty CIDFile %q should've been deleted", tmpCidFile) } - logDone("run - cleanup empty cidfile on error") } // #2098 - Docker cidFiles only contain short version of the containerId -//sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" +//sudo docker run --cidfile /tmp/docker_tesc.cid ubuntu echo "test" // TestRunCidFile tests that run --cidfile returns the longid -func TestRunCidFileCheckIDLength(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCidFileCheckIDLength(c *check.C) { tmpDir, err := ioutil.TempDir("", "TestRunCidFile") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpCidFile := path.Join(tmpDir, "cid") defer os.RemoveAll(tmpDir) cmd := exec.Command(dockerBinary, "run", "-d", "--cidfile", tmpCidFile, "busybox", "true") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) buffer, err := ioutil.ReadFile(tmpCidFile) if err != nil { - t.Fatal(err) + c.Fatal(err) } cid := string(buffer) if len(cid) != 64 { - t.Fatalf("--cidfile should be a long id, not %q", id) + c.Fatalf("--cidfile should be a long id, not %q", id) } if cid != id { - t.Fatalf("cid must be equal to %s, got %s", id, cid) + c.Fatalf("cid must be equal to %s, got %s", id, cid) } - - logDone("run - cidfile contains long id") } -func TestRunNetworkNotInitializedNoneMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunNetworkNotInitializedNoneMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--net=none", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) res, err := inspectField(id, "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "" { - t.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res) + c.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res) } - - logDone("run - network must not be initialized in 'none' mode") } -func TestRunSetMacAddress(t *testing.T) { +func (s *DockerSuite) TestRunSetMacAddress(c *check.C) { mac := "12:34:56:78:9a:bc" - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-i", "--rm", fmt.Sprintf("--mac-address=%s", mac), "busybox", "/bin/sh", "-c", "ip link show eth0 | tail -1 | awk '{print $2}'") out, ec, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("exec failed:\nexit code=%v\noutput=%s", ec, out) + c.Fatalf("exec failed:\nexit code=%v\noutput=%s", ec, out) } actualMac := strings.TrimSpace(out) if actualMac != mac { - t.Fatalf("Set MAC address with --mac-address failed. The container has an incorrect MAC address: %q, expected: %q", actualMac, mac) + c.Fatalf("Set MAC address with --mac-address failed. The container has an incorrect MAC address: %q, expected: %q", actualMac, mac) } - - logDone("run - setting MAC address with --mac-address") } -func TestRunInspectMacAddress(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunInspectMacAddress(c *check.C) { mac := "12:34:56:78:9a:bc" cmd := exec.Command(dockerBinary, "run", "-d", "--mac-address="+mac, "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) inspectedMac, err := inspectField(id, "NetworkSettings.MacAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } if inspectedMac != mac { - t.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac) + c.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac) } - - logDone("run - inspecting MAC address") } // test docker run use a invalid mac address -func TestRunWithInvalidMacAddress(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithInvalidMacAddress(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--mac-address", "92:d0:c6:0a:29", "busybox") out, _, err := runCommandWithOutput(runCmd) //use a invalid mac address should with a error out if err == nil || !strings.Contains(out, "is not a valid mac address") { - t.Fatalf("run with an invalid --mac-address should with error out") + c.Fatalf("run with an invalid --mac-address should with error out") } - - logDone("run - can't use an invalid mac address") } -func TestRunDeallocatePortOnMissingIptablesRule(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunDeallocatePortOnMissingIptablesRule(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "-p", "23:23", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) ip, err := inspectField(id, "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } iptCmd := exec.Command("iptables", "-D", "DOCKER", "-d", fmt.Sprintf("%s/32", ip), "!", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-m", "tcp", "--dport", "23", "-j", "ACCEPT") out, _, err = runCommandWithOutput(iptCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := deleteContainer(id); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "-d", "-p", "23:23", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - port should be deallocated even on iptables error") } -func TestRunPortInUse(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunPortInUse(c *check.C) { + testRequires(c, SameHostDaemon) port := "1234" l, err := net.Listen("tcp", ":"+port) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer l.Close() cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatalf("Binding on used port must fail") + c.Fatalf("Binding on used port must fail") } if !strings.Contains(out, "address already in use") { - t.Fatalf("Out must be about \"address already in use\", got %s", out) + c.Fatalf("Out must be about \"address already in use\", got %s", out) } - - logDone("run - error out if port already in use") } // https://github.com/docker/docker/issues/8428 -func TestRunPortProxy(t *testing.T) { - testRequires(t, SameHostDaemon) - - defer deleteAllContainers() +func (s *DockerSuite) TestRunPortProxy(c *check.C) { + testRequires(c, SameHostDaemon) port := "12345" cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err) + c.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err) } - // connect for 10 times here. This will trigger 10 EPIPES in the child + // connett for 10 times here. This will trigger 10 EPIPES in the child // process and kill it when it writes to a closed stdout/stderr for i := 0; i < 10; i++ { net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port)) @@ -2624,343 +2237,308 @@ func TestRunPortProxy(t *testing.T) { listPs := exec.Command("sh", "-c", "ps ax | grep docker") out, _, err = runCommandWithOutput(listPs) if err != nil { - t.Errorf("list docker process failed with output %s, error %s", out, err) + c.Errorf("list docker process failed with output %s, error %s", out, err) } if strings.Contains(out, "docker ") { - t.Errorf("Unexpected defunct docker process") + c.Errorf("Unexpected defunct docker process") } if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") { - t.Errorf("Failed to find docker-proxy process, got %s", out) + c.Errorf("Failed to find docker-proxy process, got %s", out) } - - logDone("run - proxy should work with unavailable port") } // Regression test for #7792 -func TestRunMountOrdering(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunMountOrdering(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir("", "docker_nested_mount_test") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) tmpDir2, err := ioutil.TempDir("", "docker_nested_mount_test2") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir2) - // Create a temporary tmpfs mount. + // Create a temporary tmpfs mounc. fooDir := filepath.Join(tmpDir, "foo") if err := os.MkdirAll(filepath.Join(tmpDir, "foo"), 0755); err != nil { - t.Fatalf("failed to mkdir at %s - %s", fooDir, err) + c.Fatalf("failed to mkdir at %s - %s", fooDir, err) } if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", fooDir), []byte{}, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", tmpDir), []byte{}, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", tmpDir2), []byte{}, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp", tmpDir), "-v", fmt.Sprintf("%s:/tmp/foo", fooDir), "-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2), "-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir), "busybox:latest", "sh", "-c", "ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - volumes are mounted in the correct order") } // Regression test for https://github.com/docker/docker/issues/8259 -func TestRunReuseBindVolumeThatIsSymlink(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunReuseBindVolumeThatIsSymlink(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir(os.TempDir(), "testlink") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) linkPath := os.TempDir() + "/testlink2" if err := os.Symlink(tmpDir, linkPath); err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(linkPath) // Create first container cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp/test", linkPath), "busybox", "ls", "-lh", "/tmp/test") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // Create second container with same symlinked path // This will fail if the referenced issue is hit with a "Volume exists" error cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp/test", linkPath), "busybox", "ls", "-lh", "/tmp/test") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - can remount old bindmount volume") } //GH#10604: Test an "/etc" volume doesn't overlay special bind mounts in container -func TestRunCreateVolumeEtc(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCreateVolumeEtc(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "-v", "/etc", "busybox", "cat", "/etc/resolv.conf") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, "nameserver 127.0.0.1") { - t.Fatal("/etc volume mount hides /etc/resolv.conf") + c.Fatal("/etc volume mount hides /etc/resolv.conf") } cmd = exec.Command(dockerBinary, "run", "-h=test123", "-v", "/etc", "busybox", "cat", "/etc/hostname") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, "test123") { - t.Fatal("/etc volume mount hides /etc/hostname") + c.Fatal("/etc volume mount hides /etc/hostname") } cmd = exec.Command(dockerBinary, "run", "--add-host=test:192.168.0.1", "-v", "/etc", "busybox", "cat", "/etc/hosts") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.Replace(out, "\n", " ", -1) if !strings.Contains(out, "192.168.0.1\ttest") || !strings.Contains(out, "127.0.0.1\tlocalhost") { - t.Fatal("/etc volume mount hides /etc/hosts") + c.Fatal("/etc volume mount hides /etc/hosts") } - - logDone("run - verify /etc volume doesn't hide special bind mounts") } -func TestVolumesNoCopyData(t *testing.T) { +func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) { defer deleteImages("dataimage") - defer deleteAllContainers() if _, err := buildImage("dataimage", `FROM busybox RUN mkdir -p /foo RUN touch /foo/bar`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--name", "test", "-v", "/foo", "busybox") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "test", "dataimage", "ls", "-lh", "/foo/bar") if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "No such file or directory") { - t.Fatalf("Data was copied on volumes-from but shouldn't be:\n%q", out) + c.Fatalf("Data was copied on volumes-from but shouldn't be:\n%q", out) } tmpDir := randomUnixTmpDirPath("docker_test_bind_mount_copy_data") cmd = exec.Command(dockerBinary, "run", "-v", tmpDir+":/foo", "dataimage", "ls", "-lh", "/foo/bar") if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "No such file or directory") { - t.Fatalf("Data was copied on bind-mount but shouldn't be:\n%q", out) + c.Fatalf("Data was copied on bind-mount but shouldn't be:\n%q", out) } - - logDone("run - volumes do not copy data for volumes-from and bindmounts") } -func TestRunVolumesNotRecreatedOnStart(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunVolumesNotRecreatedOnStart(c *check.C) { + testRequires(c, SameHostDaemon) // Clear out any remnants from other tests deleteAllContainers() info, err := ioutil.ReadDir(volumesConfigPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(info) > 0 { for _, f := range info { if err := os.RemoveAll(volumesConfigPath + "/" + f.Name()); err != nil { - t.Fatal(err) + c.Fatal(err) } } } - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "--name", "lone_starr", "busybox") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "start", "lone_starr") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } info, err = ioutil.ReadDir(volumesConfigPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(info) != 1 { - t.Fatalf("Expected only 1 volume have %v", len(info)) + c.Fatalf("Expected only 1 volume have %v", len(info)) } - - logDone("run - volumes not recreated on start") } -func TestRunNoOutputFromPullInStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunNoOutputFromPullInStdout(c *check.C) { // just run with unknown image cmd := exec.Command(dockerBinary, "run", "asdfsg") stdout := bytes.NewBuffer(nil) cmd.Stdout = stdout if err := cmd.Run(); err == nil { - t.Fatal("Run with unknown image should fail") + c.Fatal("Run with unknown image should fail") } if stdout.Len() != 0 { - t.Fatalf("Stdout contains output from pull: %s", stdout) + c.Fatalf("Stdout contains output from pull: %s", stdout) } - logDone("run - no output from pull in stdout") } -func TestRunVolumesCleanPaths(t *testing.T) { +func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { if _, err := buildImage("run_volumes_clean_paths", `FROM busybox VOLUME /foo/`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } defer deleteImages("run_volumes_clean_paths") - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "-v", "/bar/", "--name", "dark_helmet", "run_volumes_clean_paths") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, err := inspectFieldMap("dark_helmet", "Volumes", "/foo/") if err != nil { - t.Fatal(err) + c.Fatal(err) } if out != "" { - t.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out) + c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, volumesStoragePath) { - t.Fatalf("Volume was not defined for /foo\n%q", out) + c.Fatalf("Volume was not defined for /foo\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar/") if err != nil { - t.Fatal(err) + c.Fatal(err) } if out != "" { - t.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out) + c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, volumesStoragePath) { - t.Fatalf("Volume was not defined for /bar\n%q", out) + c.Fatalf("Volume was not defined for /bar\n%q", out) } - - logDone("run - volume paths are cleaned") } // Regression test for #3631 -func TestRunSlowStdoutConsumer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunSlowStdoutConsumer(c *check.C) { + cont := exec.Command(dockerBinary, "run", "--rm", "busybox", "/bin/sh", "-c", "dd if=/dev/zero of=/dev/stdout bs=1024 count=2000 | catv") - c := exec.Command(dockerBinary, "run", "--rm", "busybox", "/bin/sh", "-c", "dd if=/dev/zero of=/dev/stdout bs=1024 count=2000 | catv") - - stdout, err := c.StdoutPipe() + stdout, err := cont.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } - if err := c.Start(); err != nil { - t.Fatal(err) + if err := cont.Start(); err != nil { + c.Fatal(err) } n, err := consumeWithSpeed(stdout, 10000, 5*time.Millisecond, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := 2 * 1024 * 2000 if n != expected { - t.Fatalf("Expected %d, got %d", expected, n) + c.Fatalf("Expected %d, got %d", expected, n) } - - logDone("run - slow consumer") } -func TestRunAllowPortRangeThroughExpose(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAllowPortRangeThroughExpose(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-P", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) portstr, err := inspectFieldJSON(id, "NetworkSettings.Ports") if err != nil { - t.Fatal(err) + c.Fatal(err) } var ports nat.PortMap if err = unmarshalJSON([]byte(portstr), &ports); err != nil { - t.Fatal(err) + c.Fatal(err) } for port, binding := range ports { portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0]) if portnum < 3000 || portnum > 3003 { - t.Fatalf("Port %d is out of range ", portnum) + c.Fatalf("Port %d is out of range ", portnum) } if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 { - t.Fatalf("Port is not mapped for the port %d", port) + c.Fatalf("Port is not mapped for the port %d", port) } } if err := deleteContainer(id); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("run - allow port range through --expose flag") } // test docker run expose a invalid port -func TestRunExposePort(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExposePort(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--expose", "80000", "busybox") out, _, err := runCommandWithOutput(runCmd) //expose a invalid port should with a error out if err == nil || !strings.Contains(out, "Invalid range format for --expose") { - t.Fatalf("run --expose a invalid port should with error out") + c.Fatalf("run --expose a invalid port should with error out") } - - logDone("run - can't expose a invalid port") } -func TestRunUnknownCommand(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunUnknownCommand(c *check.C) { + testRequires(c, NativeExecDriver) runCmd := exec.Command(dockerBinary, "create", "busybox", "/bin/nada") cID, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("Failed to create container: %v, output: %q", err, cID) + c.Fatalf("Failed to create container: %v, output: %q", err, cID) } cID = strings.TrimSpace(cID) @@ -2972,176 +2550,159 @@ func TestRunUnknownCommand(t *testing.T) { rc = strings.TrimSpace(rc) if err2 != nil { - t.Fatalf("Error getting status of container: %v", err2) + c.Fatalf("Error getting status of container: %v", err2) } if rc == "0" { - t.Fatalf("ExitCode(%v) cannot be 0", rc) + c.Fatalf("ExitCode(%v) cannot be 0", rc) } - - logDone("run - Unknown Command") } -func TestRunModeIpcHost(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeIpcHost(c *check.C) { + testRequires(c, SameHostDaemon) hostIpc, err := os.Readlink("/proc/1/ns/ipc") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--ipc=host", "busybox", "readlink", "/proc/self/ns/ipc") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostIpc != out2 { - t.Fatalf("IPC different with --ipc=host %s != %s\n", hostIpc, out2) + c.Fatalf("IPC different with --ipc=host %s != %s\n", hostIpc, out2) } cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/ipc") out2, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostIpc == out2 { - t.Fatalf("IPC should be different without --ipc=host %s == %s\n", hostIpc, out2) + c.Fatalf("IPC should be different without --ipc=host %s == %s\n", hostIpc, out2) } - - logDone("run - ipc host mode") } -func TestRunModeIpcContainer(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunModeIpcContainer(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) state, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "true" { - t.Fatal("Container state is 'not running'") + c.Fatal("Container state is 'not running'") } pid1, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } parentContainerIpc, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/ipc", pid1)) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--ipc=container:%s", id), "busybox", "readlink", "/proc/self/ns/ipc") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if parentContainerIpc != out2 { - t.Fatalf("IPC different with --ipc=container:%s %s != %s\n", id, parentContainerIpc, out2) + c.Fatalf("IPC different with --ipc=container:%s %s != %s\n", id, parentContainerIpc, out2) } - - logDone("run - ipc container mode") } -func TestRunModeIpcContainerNotExists(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeIpcContainerNotExists(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--ipc", "container:abcd1234", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if !strings.Contains(out, "abcd1234") || err == nil { - t.Fatalf("run IPC from a non exists container should with correct error out") + c.Fatalf("run IPC from a non exists container should with correct error out") } - - logDone("run - ipc from a non exists container failed with correct error out") } -func TestContainerNetworkMode(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestContainerNetworkMode(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } pid1, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } parentContainerNet, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/net", pid1)) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--net=container:%s", id), "busybox", "readlink", "/proc/self/ns/net") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if parentContainerNet != out2 { - t.Fatalf("NET different with --net=container:%s %s != %s\n", id, parentContainerNet, out2) + c.Fatalf("NET different with --net=container:%s %s != %s\n", id, parentContainerNet, out2) } - - logDone("run - container shared network namespace") } -func TestRunModePidHost(t *testing.T) { - testRequires(t, NativeExecDriver, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModePidHost(c *check.C) { + testRequires(c, NativeExecDriver, SameHostDaemon) hostPid, err := os.Readlink("/proc/1/ns/pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--pid=host", "busybox", "readlink", "/proc/self/ns/pid") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostPid != out2 { - t.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out2) + c.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out2) } cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/pid") out2, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostPid == out2 { - t.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out2) + c.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out2) } - - logDone("run - pid host mode") } -func TestRunTLSverify(t *testing.T) { +func (s *DockerSuite) TestRunTLSverify(c *check.C) { cmd := exec.Command(dockerBinary, "ps") out, ec, err := runCommandWithOutput(cmd) if err != nil || ec != 0 { - t.Fatalf("Should have worked: %v:\n%v", err, out) + c.Fatalf("Should have worked: %v:\n%v", err, out) } // Regardless of whether we specify true or false we need to @@ -3150,195 +2711,172 @@ func TestRunTLSverify(t *testing.T) { cmd = exec.Command(dockerBinary, "--tlsverify=false", "ps") out, ec, err = runCommandWithOutput(cmd) if err == nil || ec == 0 || !strings.Contains(out, "trying to connect") { - t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err) + c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", ec, out, err) } cmd = exec.Command(dockerBinary, "--tlsverify=true", "ps") out, ec, err = runCommandWithOutput(cmd) if err == nil || ec == 0 || !strings.Contains(out, "cert") { - t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err) + c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", ec, out, err) } - - logDone("run - verify tls is set for --tlsverify") } -func TestRunPortFromDockerRangeInUse(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunPortFromDockerRangeInUse(c *check.C) { // first find allocator current position cmd := exec.Command(dockerBinary, "run", "-d", "-p", ":80", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) cmd = exec.Command(dockerBinary, "port", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if out == "" { - t.Fatal("docker port command output is empty") + c.Fatal("docker port command output is empty") } out = strings.Split(out, ":")[1] lastPort, err := strconv.Atoi(out) if err != nil { - t.Fatal(err) + c.Fatal(err) } port := lastPort + 1 l, err := net.Listen("tcp", ":"+strconv.Itoa(port)) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer l.Close() cmd = exec.Command(dockerBinary, "run", "-d", "-p", ":80", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } id = strings.TrimSpace(out) cmd = exec.Command(dockerBinary, "port", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - find another port if port from autorange already bound") } -func TestRunTtyWithPipe(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunTtyWithPipe(c *check.C) { done := make(chan struct{}) go func() { defer close(done) cmd := exec.Command(dockerBinary, "run", "-ti", "busybox", "true") if _, err := cmd.StdinPipe(); err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("run should have failed") + c.Fatal("run should have failed") } else if !strings.Contains(out, expected) { - t.Fatalf("run failed with error %q: expected %q", out, expected) + c.Fatalf("run failed with error %q: expected %q", out, expected) } }() select { case <-done: case <-time.After(3 * time.Second): - t.Fatal("container is running but should have failed") + c.Fatal("container is running but should have failed") } - - logDone("run - forbid piped stdin with tty") } -func TestRunNonLocalMacAddress(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunNonLocalMacAddress(c *check.C) { addr := "00:16:3E:08:00:50" cmd := exec.Command(dockerBinary, "run", "--mac-address", addr, "busybox", "ifconfig") if out, _, err := runCommandWithOutput(cmd); err != nil || !strings.Contains(out, addr) { - t.Fatalf("Output should have contained %q: %s, %v", addr, out, err) + c.Fatalf("Output should have contained %q: %s, %v", addr, out, err) } - - logDone("run - use non-local mac-address") } -func TestRunNetHost(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunNetHost(c *check.C) { + testRequires(c, SameHostDaemon) hostNet, err := os.Readlink("/proc/1/ns/net") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "readlink", "/proc/self/ns/net") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostNet != out2 { - t.Fatalf("Net namespace different with --net=host %s != %s\n", hostNet, out2) + c.Fatalf("Net namespace different with --net=host %s != %s\n", hostNet, out2) } cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/net") out2, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostNet == out2 { - t.Fatalf("Net namespace should be different without --net=host %s == %s\n", hostNet, out2) + c.Fatalf("Net namespace should be different without --net=host %s == %s\n", hostNet, out2) } - - logDone("run - net host mode") } -func TestRunNetContainerWhichHost(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunNetContainerWhichHost(c *check.C) { + testRequires(c, SameHostDaemon) hostNet, err := os.Readlink("/proc/1/ns/net") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "-d", "--net=host", "--name=test", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } cmd = exec.Command(dockerBinary, "run", "--net=container:test", "busybox", "readlink", "/proc/self/ns/net") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out = strings.Trim(out, "\n") if hostNet != out { - t.Fatalf("Container should have host network namespace") + c.Fatalf("Container should have host network namespace") } - - logDone("run - net container mode, where container in host mode") } -func TestRunAllowPortRangeThroughPublish(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAllowPortRangeThroughPublish(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-p", "3000-3003", "busybox", "top") out, _, err := runCommandWithOutput(cmd) id := strings.TrimSpace(out) portstr, err := inspectFieldJSON(id, "NetworkSettings.Ports") if err != nil { - t.Fatal(err) + c.Fatal(err) } var ports nat.PortMap err = unmarshalJSON([]byte(portstr), &ports) for port, binding := range ports { portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0]) if portnum < 3000 || portnum > 3003 { - t.Fatalf("Port %d is out of range ", portnum) + c.Fatalf("Port %d is out of range ", portnum) } if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 { - t.Fatal("Port is not mapped for the port "+port, out) + c.Fatal("Port is not mapped for the port "+port, out) } } - logDone("run - allow port range through --expose flag") } -func TestRunOOMExitCode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunOOMExitCode(c *check.C) { done := make(chan struct{}) go func() { defer close(done) @@ -3346,167 +2884,143 @@ func TestRunOOMExitCode(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done") out, exitCode, _ := runCommandWithOutput(runCmd) if expected := 137; exitCode != expected { - t.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) + c.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) } }() select { case <-done: case <-time.After(30 * time.Second): - t.Fatal("Timeout waiting for container to die on OOM") + c.Fatal("Timeout waiting for container to die on OOM") } - - logDone("run - exit code on oom") } -func TestRunSetDefaultRestartPolicy(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunSetDefaultRestartPolicy(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "test", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.RestartPolicy.Name}}", "test") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to inspect container: %v, output: %q", err, out) + c.Fatalf("failed to inspect container: %v, output: %q", err, out) } out = strings.Trim(out, "\r\n") if out != "no" { - t.Fatalf("Set default restart policy failed") + c.Fatalf("Set default restart policy failed") } - - logDone("run - set default restart policy success") } -func TestRunRestartMaxRetries(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunRestartMaxRetries(c *check.C) { out, err := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:3", "busybox", "false").CombinedOutput() if err != nil { - t.Fatal(string(out), err) + c.Fatal(string(out), err) } id := strings.TrimSpace(string(out)) if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 10); err != nil { - t.Fatal(err) + c.Fatal(err) } count, err := inspectField(id, "RestartCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if count != "3" { - t.Fatalf("Container was restarted %s times, expected %d", count, 3) + c.Fatalf("Container was restarted %s times, expected %d", count, 3) } MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if MaximumRetryCount != "3" { - t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") + c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") } - logDone("run - test max-retries for --restart") } -func TestRunContainerWithWritableRootfs(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithWritableRootfs(c *check.C) { out, err := exec.Command(dockerBinary, "run", "--rm", "busybox", "touch", "/file").CombinedOutput() if err != nil { - t.Fatal(string(out), err) + c.Fatal(string(out), err) } - logDone("run - writable rootfs") } -func TestRunContainerWithReadonlyRootfs(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) { + testRequires(c, NativeExecDriver) out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", "/file").CombinedOutput() if err == nil { - t.Fatal("expected container to error on run with read only error") + c.Fatal("expected container to error on run with read only error") } expected := "Read-only file system" if !strings.Contains(string(out), expected) { - t.Fatalf("expected output from failure to contain %s but contains %s", expected, out) + c.Fatalf("expected output from failure to contain %s but contains %s", expected, out) } - logDone("run - read only rootfs") } -func TestRunVolumesFromRestartAfterRemoved(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVolumesFromRestartAfterRemoved(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "voltest", "-v", "/foo", "busybox")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "restarter", "--volumes-from", "voltest", "busybox", "top")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // Remove the main volume container and restart the consuming container out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "rm", "-f", "voltest")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // This should not fail since the volumes-from were already applied out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "restarter")) if err != nil { - t.Fatalf("expected container to restart successfully: %v\n%s", err, out) + c.Fatalf("expected container to restart successfully: %v\n%s", err, out) } - - logDone("run - can restart a volumes-from container after producer is removed") } // run container with --rm should remove container if exit code != 0 -func TestRunContainerWithRmFlagExitCodeNotEqualToZero(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunContainerWithRmFlagExitCodeNotEqualToZero(c *check.C) { name := "flowers" runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "ls", "/notexists") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("Expected docker run to fail", out, err) + c.Fatal("Expected docker run to fail", out, err) } out, err = getAllContainers() if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "" { - t.Fatal("Expected not to have containers", out) + c.Fatal("Expected not to have containers", out) } - - logDone("run - container is removed if run with --rm and exit code != 0") } -func TestRunContainerWithRmFlagCannotStartContainer(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunContainerWithRmFlagCannotStartContainer(c *check.C) { name := "sparkles" runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "commandNotFound") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("Expected docker run to fail", out, err) + c.Fatal("Expected docker run to fail", out, err) } out, err = getAllContainers() if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "" { - t.Fatal("Expected not to have containers", out) + c.Fatal("Expected not to have containers", out) } - - logDone("run - container is removed if run with --rm and cannot start") } -func TestRunPidHostWithChildIsKillable(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunPidHostWithChildIsKillable(c *check.C) { name := "ibuildthecloud" if out, err := exec.Command(dockerBinary, "run", "-d", "--pid=host", "--name", name, "busybox", "sh", "-c", "sleep 30; echo hi").CombinedOutput(); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } time.Sleep(1 * time.Second) errchan := make(chan error) @@ -3519,10 +3033,9 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) { select { case err := <-errchan: if err != nil { - t.Fatal(err) + c.Fatal(err) } case <-time.After(5 * time.Second): - t.Fatal("Kill container timed out") + c.Fatal("Kill container timed out") } - logDone("run - can kill container with pid-host and some childs of pid 1") } diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 211e6c1f553fd..74fae1735259d 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -11,22 +11,19 @@ import ( "path" "path/filepath" "strings" - "testing" "time" "github.com/docker/docker/pkg/mount" + "github.com/go-check/check" "github.com/kr/pty" ) // #6509 -func TestRunRedirectStdout(t *testing.T) { - - defer deleteAllContainers() - +func (s *DockerSuite) TestRunRedirectStdout(c *check.C) { checkRedirect := func(command string) { _, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command("sh", "-c", command) cmd.Stdin = tty @@ -34,35 +31,31 @@ func TestRunRedirectStdout(t *testing.T) { cmd.Stderr = tty ch := make(chan struct{}) if err := cmd.Start(); err != nil { - t.Fatalf("start err: %v", err) + c.Fatalf("start err: %v", err) } go func() { if err := cmd.Wait(); err != nil { - t.Fatalf("wait err=%v", err) + c.Fatalf("wait err=%v", err) } close(ch) }() select { case <-time.After(10 * time.Second): - t.Fatal("command timeout") + c.Fatal("command timeout") case <-ch: } } checkRedirect(dockerBinary + " run -i busybox cat /etc/passwd | grep -q root") checkRedirect(dockerBinary + " run busybox cat /etc/passwd | grep -q root") - - logDone("run - redirect stdout") } // Test recursive bind mount works by default -func TestRunWithVolumesIsRecursive(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithVolumesIsRecursive(c *check.C) { tmpDir, err := ioutil.TempDir("", "docker_recursive_mount_test") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) @@ -70,68 +63,62 @@ func TestRunWithVolumesIsRecursive(t *testing.T) { // Create a temporary tmpfs mount. tmpfsDir := filepath.Join(tmpDir, "tmpfs") if err := os.MkdirAll(tmpfsDir, 0777); err != nil { - t.Fatalf("failed to mkdir at %s - %s", tmpfsDir, err) + c.Fatalf("failed to mkdir at %s - %s", tmpfsDir, err) } if err := mount.Mount("tmpfs", tmpfsDir, "tmpfs", ""); err != nil { - t.Fatalf("failed to create a tmpfs mount at %s - %s", tmpfsDir, err) + c.Fatalf("failed to create a tmpfs mount at %s - %s", tmpfsDir, err) } f, err := ioutil.TempFile(tmpfsDir, "touch-me") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer f.Close() runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", fmt.Sprintf("%s:/tmp:ro", tmpDir), "busybox:latest", "ls", "/tmp/tmpfs") out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 0 { - t.Fatal(out, stderr, err) + c.Fatal(out, stderr, err) } if !strings.Contains(out, filepath.Base(f.Name())) { - t.Fatal("Recursive bind mount test failed. Expected file not found") + c.Fatal("Recursive bind mount test failed. Expected file not found") } - - logDone("run - volumes are bind mounted recursively") } -func TestRunWithUlimits(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunWithUlimits(c *check.C) { + testRequires(c, NativeExecDriver) out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name=testulimits", "--ulimit", "nofile=42", "busybox", "/bin/sh", "-c", "ulimit -n")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } ul := strings.TrimSpace(out) if ul != "42" { - t.Fatalf("expected `ulimit -n` to be 42, got %s", ul) + c.Fatalf("expected `ulimit -n` to be 42, got %s", ul) } - - logDone("run - ulimits are set") } -func TestRunContainerWithCgroupParent(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithCgroupParent(c *check.C) { + testRequires(c, NativeExecDriver) cgroupParent := "test" data, err := ioutil.ReadFile("/proc/self/cgroup") if err != nil { - t.Fatalf("failed to read '/proc/self/cgroup - %v", err) + c.Fatalf("failed to read '/proc/self/cgroup - %v", err) } selfCgroupPaths := parseCgroupPaths(string(data)) selfCpuCgroup, found := selfCgroupPaths["memory"] if !found { - t.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) + c.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--cgroup-parent", cgroupParent, "--rm", "busybox", "cat", "/proc/self/cgroup")) if err != nil { - t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) + c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) } cgroupPaths := parseCgroupPaths(string(out)) if len(cgroupPaths) == 0 { - t.Fatalf("unexpected output - %q", string(out)) + c.Fatalf("unexpected output - %q", string(out)) } found = false expectedCgroupPrefix := path.Join(selfCpuCgroup, cgroupParent) @@ -142,24 +129,22 @@ func TestRunContainerWithCgroupParent(t *testing.T) { } } if !found { - t.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", expectedCgroupPrefix, cgroupPaths) + c.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", expectedCgroupPrefix, cgroupPaths) } - logDone("run - cgroup parent") } -func TestRunContainerWithCgroupParentAbsPath(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithCgroupParentAbsPath(c *check.C) { + testRequires(c, NativeExecDriver) cgroupParent := "/cgroup-parent/test" out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--cgroup-parent", cgroupParent, "--rm", "busybox", "cat", "/proc/self/cgroup")) if err != nil { - t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) + c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) } cgroupPaths := parseCgroupPaths(string(out)) if len(cgroupPaths) == 0 { - t.Fatalf("unexpected output - %q", string(out)) + c.Fatalf("unexpected output - %q", string(out)) } found := false for _, path := range cgroupPaths { @@ -169,81 +154,75 @@ func TestRunContainerWithCgroupParentAbsPath(t *testing.T) { } } if !found { - t.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", cgroupParent, cgroupPaths) + c.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", cgroupParent, cgroupPaths) } - - logDone("run - cgroup parent with absolute cgroup path") } -func TestRunDeviceDirectory(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunDeviceDirectory(c *check.C) { + testRequires(c, NativeExecDriver) cmd := exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/snd", "busybox", "sh", "-c", "ls /dev/snd/") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "timer") { - t.Fatalf("expected output /dev/snd/timer, received %s", actual) + c.Fatalf("expected output /dev/snd/timer, received %s", actual) } cmd = exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/othersnd", "busybox", "sh", "-c", "ls /dev/othersnd/") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "seq") { - t.Fatalf("expected output /dev/othersnd/seq, received %s", actual) + c.Fatalf("expected output /dev/othersnd/seq, received %s", actual) } - - logDone("run - test --device directory mounts all internal devices") } // TestRunDetach checks attaching and detaching with the escape sequence. -func TestRunAttachDetach(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunAttachDetach(c *check.C) { name := "attach-detach" cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat") stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } cpty, tty, err := pty.Open() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer cpty.Close() cmd.Stdin = tty if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := waitRun(name); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := cpty.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err := bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } // escape sequence if _, err := cpty.Write([]byte{16}); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(100 * time.Millisecond) if _, err := cpty.Write([]byte{17}); err != nil { - t.Fatal(err) + c.Fatal(err) } ch := make(chan struct{}) @@ -254,21 +233,19 @@ func TestRunAttachDetach(t *testing.T) { running, err := inspectField(name, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } go func() { - dockerCmd(t, "kill", name) + exec.Command(dockerBinary, "kill", name).Run() }() select { case <-ch: case <-time.After(10 * time.Millisecond): - t.Fatal("timed out waiting for container to exit") + c.Fatal("timed out waiting for container to exit") } - - logDone("run - attach detach") } diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index c7bfb945d38ea..ead436a925db6 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -9,15 +9,16 @@ import ( "reflect" "sort" "strings" - "testing" + + "github.com/go-check/check" ) // save a repo using gz compression and try to load it using stdout -func TestSaveXzAndLoadRepoStdout(t *testing.T) { +func (s *DockerSuite) TestSaveXzAndLoadRepoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %v %v", out, err) + c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -27,19 +28,19 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) + c.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container: %v %v", out, err) + c.Fatalf("failed to commit container: %v %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %v %v", before, err) + c.Fatalf("the repo should exist before saving it: %v %v", before, err) } repoTarball, _, err := runCommandPipelineWithOutput( @@ -47,7 +48,7 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) { exec.Command("xz", "-c"), exec.Command("gzip", "-c")) if err != nil { - t.Fatalf("failed to save repo: %v %v", out, err) + c.Fatalf("failed to save repo: %v %v", out, err) } deleteImages(repoName) @@ -55,26 +56,25 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) { loadCmd.Stdin = strings.NewReader(repoTarball) out, _, err = runCommandWithOutput(loadCmd) if err == nil { - t.Fatalf("expected error, but succeeded with no error and output: %v", out) + c.Fatalf("expected error, but succeeded with no error and output: %v", out) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err == nil { - t.Fatalf("the repo should not exist: %v", after) + c.Fatalf("the repo should not exist: %v", after) } deleteImages(repoName) - logDone("load - save a repo with xz compression & load it using stdout") } // save a repo using xz+gz compression and try to load it using stdout -func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { +func (s *DockerSuite) TestSaveXzGzAndLoadRepoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %v %v", out, err) + c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -84,19 +84,19 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) + c.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container: %v %v", out, err) + c.Fatalf("failed to commit container: %v %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %v %v", before, err) + c.Fatalf("the repo should exist before saving it: %v %v", before, err) } out, _, err = runCommandPipelineWithOutput( @@ -104,7 +104,7 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { exec.Command("xz", "-c"), exec.Command("gzip", "-c")) if err != nil { - t.Fatalf("failed to save repo: %v %v", out, err) + c.Fatalf("failed to save repo: %v %v", out, err) } deleteImages(repoName) @@ -113,34 +113,33 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { loadCmd.Stdin = strings.NewReader(out) out, _, err = runCommandWithOutput(loadCmd) if err == nil { - t.Fatalf("expected error, but succeeded with no error and output: %v", out) + c.Fatalf("expected error, but succeeded with no error and output: %v", out) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err == nil { - t.Fatalf("the repo should not exist: %v", after) + c.Fatalf("the repo should not exist: %v", after) } deleteContainer(cleanedContainerID) deleteImages(repoName) - logDone("load - save a repo with xz+gz compression & load it using stdout") } -func TestSaveSingleTag(t *testing.T) { +func (s *DockerSuite) TestSaveSingleTag(c *check.C) { repoName := "foobar-save-single-tag-test" tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", fmt.Sprintf("%v:latest", repoName)) defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } idCmd := exec.Command(dockerBinary, "images", "-q", "--no-trunc", repoName) out, _, err := runCommandWithOutput(idCmd) if err != nil { - t.Fatalf("failed to get repo ID: %s, %v", out, err) + c.Fatalf("failed to get repo ID: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) @@ -149,25 +148,24 @@ func TestSaveSingleTag(t *testing.T) { exec.Command("tar", "t"), exec.Command("grep", "-E", fmt.Sprintf("(^repositories$|%v)", cleanedImageID))) if err != nil { - t.Fatalf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err) + c.Fatalf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err) } - logDone("save - save a specific image:tag") } -func TestSaveImageId(t *testing.T) { +func (s *DockerSuite) TestSaveImageId(c *check.C) { repoName := "foobar-save-image-id-test" tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName)) defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } idLongCmd := exec.Command(dockerBinary, "images", "-q", "--no-trunc", repoName) out, _, err := runCommandWithOutput(idLongCmd) if err != nil { - t.Fatalf("failed to get repo ID: %s, %v", out, err) + c.Fatalf("failed to get repo ID: %s, %v", out, err) } cleanedLongImageID := strings.TrimSpace(out) @@ -175,7 +173,7 @@ func TestSaveImageId(t *testing.T) { idShortCmd := exec.Command(dockerBinary, "images", "-q", repoName) out, _, err = runCommandWithOutput(idShortCmd) if err != nil { - t.Fatalf("failed to get repo short ID: %s, %v", out, err) + c.Fatalf("failed to get repo short ID: %s, %v", out, err) } cleanedShortImageID := strings.TrimSpace(out) @@ -184,19 +182,19 @@ func TestSaveImageId(t *testing.T) { tarCmd := exec.Command("tar", "t") tarCmd.Stdin, err = saveCmd.StdoutPipe() if err != nil { - t.Fatalf("cannot set stdout pipe for tar: %v", err) + c.Fatalf("cannot set stdout pipe for tar: %v", err) } grepCmd := exec.Command("grep", cleanedLongImageID) grepCmd.Stdin, err = tarCmd.StdoutPipe() if err != nil { - t.Fatalf("cannot set stdout pipe for grep: %v", err) + c.Fatalf("cannot set stdout pipe for grep: %v", err) } if err = tarCmd.Start(); err != nil { - t.Fatalf("tar failed with error: %v", err) + c.Fatalf("tar failed with error: %v", err) } if err = saveCmd.Start(); err != nil { - t.Fatalf("docker save failed with error: %v", err) + c.Fatalf("docker save failed with error: %v", err) } defer saveCmd.Wait() defer tarCmd.Wait() @@ -204,18 +202,17 @@ func TestSaveImageId(t *testing.T) { out, _, err = runCommandWithOutput(grepCmd) if err != nil { - t.Fatalf("failed to save repo with image ID: %s, %v", out, err) + c.Fatalf("failed to save repo with image ID: %s, %v", out, err) } - logDone("save - save a image by ID") } // save a repo and try to load it using flags -func TestSaveAndLoadRepoFlags(t *testing.T) { +func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -225,19 +222,19 @@ func TestSaveAndLoadRepoFlags(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been a container id: %s, %v", out, err) + c.Fatalf("output should've been a container id: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) deleteImages(repoName) if out, _, err = runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container: %s, %v", out, err) + c.Fatalf("failed to commit container: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %s, %v", before, err) + c.Fatalf("the repo should exist before saving it: %s, %v", before, err) } @@ -245,29 +242,28 @@ func TestSaveAndLoadRepoFlags(t *testing.T) { exec.Command(dockerBinary, "save", repoName), exec.Command(dockerBinary, "load")) if err != nil { - t.Fatalf("failed to save and load repo: %s, %v", out, err) + c.Fatalf("failed to save and load repo: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist after loading it: %s, %v", after, err) + c.Fatalf("the repo should exist after loading it: %s, %v", after, err) } if before != after { - t.Fatalf("inspect is not the same after a save / load") + c.Fatalf("inspect is not the same after a save / load") } - logDone("save - save a repo using -o && load a repo using -i") } -func TestSaveMultipleNames(t *testing.T) { +func (s *DockerSuite) TestSaveMultipleNames(c *check.C) { repoName := "foobar-save-multi-name-test" // Make one image tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-one:latest", repoName)) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } defer deleteImages(repoName + "-one") @@ -275,7 +271,7 @@ func TestSaveMultipleNames(t *testing.T) { tagCmd = exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-two:latest", repoName)) out, _, err := runCommandWithOutput(tagCmd) if err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } defer deleteImages(repoName + "-two") @@ -285,13 +281,12 @@ func TestSaveMultipleNames(t *testing.T) { exec.Command("grep", "-q", "-E", "(-one|-two)"), ) if err != nil { - t.Fatalf("failed to save multiple repos: %s, %v", out, err) + c.Fatalf("failed to save multiple repos: %s, %v", out, err) } - logDone("save - save by multiple names") } -func TestSaveRepoWithMultipleImages(t *testing.T) { +func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { makeImage := func(from string, tag string) string { runCmd := exec.Command(dockerBinary, "run", "-d", from, "true") @@ -300,14 +295,14 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { err error ) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatalf("failed to create a container: %v %v", out, err) + c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, tag) if out, _, err = runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container: %v %v", out, err) + c.Fatalf("failed to commit container: %v %v", out, err) } imageID := strings.TrimSpace(out) return imageID @@ -331,14 +326,14 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { exec.Command("grep", "VERSION"), exec.Command("cut", "-d", "/", "-f1")) if err != nil { - t.Fatalf("failed to save multiple images: %s, %v", out, err) + c.Fatalf("failed to save multiple images: %s, %v", out, err) } actual := strings.Split(strings.TrimSpace(out), "\n") // make the list of expected layers out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "history", "-q", "--no-trunc", "busybox:latest")) if err != nil { - t.Fatalf("failed to get history: %s, %v", out, err) + c.Fatalf("failed to get history: %s, %v", out, err) } expected := append(strings.Split(strings.TrimSpace(out), "\n"), idFoo, idBar) @@ -346,21 +341,20 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { sort.Strings(actual) sort.Strings(expected) if !reflect.DeepEqual(expected, actual) { - t.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected) + c.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected) } - logDone("save - save repository with multiple images") } // Issue #6722 #5892 ensure directories are included in changes -func TestSaveDirectoryPermissions(t *testing.T) { +func (s *DockerSuite) TestSaveDirectoryPermissions(c *check.C) { layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"} layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"} name := "save-directory-permissions" tmpDir, err := ioutil.TempDir("", "save-layers-with-directories") if err != nil { - t.Errorf("failed to create temporary directory: %s", err) + c.Errorf("failed to create temporary directory: %s", err) } extractionDirectory := filepath.Join(tmpDir, "image-extraction-dir") os.Mkdir(extractionDirectory, 0777) @@ -373,19 +367,19 @@ func TestSaveDirectoryPermissions(t *testing.T) { RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if out, _, err := runCommandPipelineWithOutput( exec.Command(dockerBinary, "save", name), exec.Command("tar", "-xf", "-", "-C", extractionDirectory), ); err != nil { - t.Errorf("failed to save and extract image: %s", out) + c.Errorf("failed to save and extract image: %s", out) } dirs, err := ioutil.ReadDir(extractionDirectory) if err != nil { - t.Errorf("failed to get a listing of the layer directories: %s", err) + c.Errorf("failed to get a listing of the layer directories: %s", err) } found := false @@ -396,7 +390,7 @@ func TestSaveDirectoryPermissions(t *testing.T) { f, err := os.Open(layerPath) if err != nil { - t.Fatalf("failed to open %s: %s", layerPath, err) + c.Fatalf("failed to open %s: %s", layerPath, err) } entries, err := ListTar(f) @@ -406,7 +400,7 @@ func TestSaveDirectoryPermissions(t *testing.T) { } } if err != nil { - t.Fatalf("encountered error while listing tar entries: %s", err) + c.Fatalf("encountered error while listing tar entries: %s", err) } if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) { @@ -417,8 +411,7 @@ func TestSaveDirectoryPermissions(t *testing.T) { } if !found { - t.Fatalf("failed to find the layer with the right content listing") + c.Fatalf("failed to find the layer with the right content listing") } - logDone("save - ensure directories exist in exported layers") } diff --git a/integration-cli/docker_cli_save_load_unix_test.go b/integration-cli/docker_cli_save_load_unix_test.go index 7eb948d7aec33..658666d6b806e 100644 --- a/integration-cli/docker_cli_save_load_unix_test.go +++ b/integration-cli/docker_cli_save_load_unix_test.go @@ -8,17 +8,17 @@ import ( "os" "os/exec" "strings" - "testing" "github.com/docker/docker/vendor/src/github.com/kr/pty" + "github.com/go-check/check" ) // save a repo and try to load it using stdout -func TestSaveAndLoadRepoStdout(t *testing.T) { +func (s *DockerSuite) TestSaveAndLoadRepoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -27,25 +27,25 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been a container id: %s, %v", out, err) + c.Fatalf("output should've been a container id: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) if out, _, err = runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container: %s, %v", out, err) + c.Fatalf("failed to commit container: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %s, %v", before, err) + c.Fatalf("the repo should exist before saving it: %s, %v", before, err) } saveCmdTemplate := `%v save %v > /tmp/foobar-save-load-test.tar` saveCmdFinal := fmt.Sprintf(saveCmdTemplate, dockerBinary, repoName) saveCmd := exec.Command("bash", "-c", saveCmdFinal) if out, _, err = runCommandWithOutput(saveCmd); err != nil { - t.Fatalf("failed to save repo: %s, %v", out, err) + c.Fatalf("failed to save repo: %s, %v", out, err) } deleteImages(repoName) @@ -53,17 +53,17 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { loadCmdFinal := `cat /tmp/foobar-save-load-test.tar | docker load` loadCmd := exec.Command("bash", "-c", loadCmdFinal) if out, _, err = runCommandWithOutput(loadCmd); err != nil { - t.Fatalf("failed to load repo: %s, %v", out, err) + c.Fatalf("failed to load repo: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist after loading it: %s %v", after, err) + c.Fatalf("the repo should exist after loading it: %s %v", after, err) } if before != after { - t.Fatalf("inspect is not the same after a save / load") + c.Fatalf("inspect is not the same after a save / load") } deleteContainer(cleanedContainerID) @@ -73,29 +73,28 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { pty, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command(dockerBinary, "save", repoName) cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty if err := cmd.Start(); err != nil { - t.Fatalf("start err: %v", err) + c.Fatalf("start err: %v", err) } if err := cmd.Wait(); err == nil { - t.Fatal("did not break writing to a TTY") + c.Fatal("did not break writing to a TTY") } buf := make([]byte, 1024) n, err := pty.Read(buf) if err != nil { - t.Fatal("could not read tty output") + c.Fatal("could not read tty output") } if !bytes.Contains(buf[:n], []byte("Cowardly refusing")) { - t.Fatal("help output is not being yielded", out) + c.Fatal("help output is not being yielded", out) } - logDone("save - save/load a repo using stdout") } diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go index fcfd9eceb9b5c..c5ecdd03b9c11 100644 --- a/integration-cli/docker_cli_search_test.go +++ b/integration-cli/docker_cli_search_test.go @@ -3,95 +3,93 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // search for repos named "registry" on the central registry -func TestSearchOnCentralRegistry(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestSearchOnCentralRegistry(c *check.C) { + testRequires(c, Network) searchCmd := exec.Command(dockerBinary, "search", "busybox") out, exitCode, err := runCommandWithOutput(searchCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to search on the central registry: %s, %v", out, err) + c.Fatalf("failed to search on the central registry: %s, %v", out, err) } if !strings.Contains(out, "Busybox base image.") { - t.Fatal("couldn't find any repository named (or containing) 'Busybox base image.'") + c.Fatal("couldn't find any repository named (or containing) 'Busybox base image.'") } - logDone("search - search for repositories named (or containing) 'Busybox base image.'") } -func TestSearchStarsOptionWithWrongParameter(t *testing.T) { +func (s *DockerSuite) TestSearchStarsOptionWithWrongParameter(c *check.C) { searchCmdStarsChars := exec.Command(dockerBinary, "search", "--stars=a", "busybox") out, exitCode, err := runCommandWithOutput(searchCmdStarsChars) if err == nil || exitCode == 0 { - t.Fatalf("Should not get right information: %s, %v", out, err) + c.Fatalf("Should not get right information: %s, %v", out, err) } if !strings.Contains(out, "invalid value") { - t.Fatal("couldn't find the invalid value warning") + c.Fatal("couldn't find the invalid value warning") } searchCmdStarsNegativeNumber := exec.Command(dockerBinary, "search", "-s=-1", "busybox") out, exitCode, err = runCommandWithOutput(searchCmdStarsNegativeNumber) if err == nil || exitCode == 0 { - t.Fatalf("Should not get right information: %s, %v", out, err) + c.Fatalf("Should not get right information: %s, %v", out, err) } if !strings.Contains(out, "invalid value") { - t.Fatal("couldn't find the invalid value warning") + c.Fatal("couldn't find the invalid value warning") } - logDone("search - Verify search with wrong parameter.") } -func TestSearchCmdOptions(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestSearchCmdOptions(c *check.C) { + testRequires(c, Network) searchCmdhelp := exec.Command(dockerBinary, "search", "--help") out, exitCode, err := runCommandWithOutput(searchCmdhelp) if err != nil || exitCode != 0 { - t.Fatalf("failed to get search help information: %s, %v", out, err) + c.Fatalf("failed to get search help information: %s, %v", out, err) } if !strings.Contains(out, "Usage: docker search [OPTIONS] TERM") { - t.Fatalf("failed to show docker search usage: %s, %v", out, err) + c.Fatalf("failed to show docker search usage: %s, %v", out, err) } searchCmd := exec.Command(dockerBinary, "search", "busybox") outSearchCmd, exitCode, err := runCommandWithOutput(searchCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err) + c.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err) } searchCmdautomated := exec.Command(dockerBinary, "search", "--automated=true", "busybox") outSearchCmdautomated, exitCode, err := runCommandWithOutput(searchCmdautomated) //The busybox is a busybox base image, not an AUTOMATED image. if err != nil || exitCode != 0 { - t.Fatalf("failed to search with automated=true on the central registry: %s, %v", outSearchCmdautomated, err) + c.Fatalf("failed to search with automated=true on the central registry: %s, %v", outSearchCmdautomated, err) } outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n") for i := range outSearchCmdautomatedSlice { if strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox ") { - t.Fatalf("The busybox is not an AUTOMATED image: %s, %v", out, err) + c.Fatalf("The busybox is not an AUTOMATED image: %s, %v", out, err) } } searchCmdStars := exec.Command(dockerBinary, "search", "-s=2", "busybox") outSearchCmdStars, exitCode, err := runCommandWithOutput(searchCmdStars) if err != nil || exitCode != 0 { - t.Fatalf("failed to search with stars=2 on the central registry: %s, %v", outSearchCmdStars, err) + c.Fatalf("failed to search with stars=2 on the central registry: %s, %v", outSearchCmdStars, err) } if strings.Count(outSearchCmdStars, "[OK]") > strings.Count(outSearchCmd, "[OK]") { - t.Fatalf("The quantity of images with stars should be less than that of all images: %s, %v", outSearchCmdStars, err) + c.Fatalf("The quantity of images with stars should be less than that of all images: %s, %v", outSearchCmdStars, err) } searchCmdOptions := exec.Command(dockerBinary, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox") out, exitCode, err = runCommandWithOutput(searchCmdOptions) if err != nil || exitCode != 0 { - t.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err) + c.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err) } - logDone("search - have a try for search options.") } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 342209dc4b539..52afac1af1a07 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -4,20 +4,20 @@ import ( "fmt" "os/exec" "strings" - "testing" "time" + + "github.com/go-check/check" ) // Regression test for https://github.com/docker/docker/issues/7843 -func TestStartAttachReturnsOnError(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartAttachReturnsOnError(c *check.C) { - dockerCmd(t, "run", "-d", "--name", "test", "busybox") - dockerCmd(t, "wait", "test") + dockerCmd(c, "run", "-d", "--name", "test", "busybox") + dockerCmd(c, "wait", "test") // Expect this to fail because the above container is stopped, this is what we want if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "test2", "--link", "test:test", "busybox")); err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } ch := make(chan struct{}) @@ -25,7 +25,7 @@ func TestStartAttachReturnsOnError(t *testing.T) { // Attempt to start attached to the container that won't start // This should return an error immediately since the container can't be started if _, err := runCommand(exec.Command(dockerBinary, "start", "-a", "test2")); err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } close(ch) }() @@ -33,20 +33,18 @@ func TestStartAttachReturnsOnError(t *testing.T) { select { case <-ch: case <-time.After(time.Second): - t.Fatalf("Attach did not exit properly") + c.Fatalf("Attach did not exit properly") } - logDone("start - error on start with attach exits") } // gh#8555: Exit code should be passed through when using start -a -func TestStartAttachCorrectExitCode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartAttachCorrectExitCode(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 2; exit 1") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) @@ -54,167 +52,157 @@ func TestStartAttachCorrectExitCode(t *testing.T) { // make sure the container has exited before trying the "start -a" waitCmd := exec.Command(dockerBinary, "wait", out) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("Failed to wait on container: %v", err) + c.Fatalf("Failed to wait on container: %v", err) } startCmd := exec.Command(dockerBinary, "start", "-a", out) startOut, exitCode, err := runCommandWithOutput(startCmd) if err != nil && !strings.Contains("exit status 1", fmt.Sprintf("%s", err)) { - t.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) + c.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) } if exitCode != 1 { - t.Fatalf("start -a did not respond with proper exit code: expected 1, got %d", exitCode) + c.Fatalf("start -a did not respond with proper exit code: expected 1, got %d", exitCode) } - logDone("start - correct exit code returned with -a") } -func TestStartAttachSilent(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartAttachSilent(c *check.C) { name := "teststartattachcorrectexitcode" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } // make sure the container has exited before trying the "start -a" waitCmd := exec.Command(dockerBinary, "wait", name) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("wait command failed with error: %v", err) + c.Fatalf("wait command failed with error: %v", err) } startCmd := exec.Command(dockerBinary, "start", "-a", name) startOut, _, err := runCommandWithOutput(startCmd) if err != nil { - t.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) + c.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) } if expected := "test\n"; startOut != expected { - t.Fatalf("start -a produced unexpected output: expected %q, got %q", expected, startOut) + c.Fatalf("start -a produced unexpected output: expected %q, got %q", expected, startOut) } - logDone("start - don't echo container ID when attaching") } -func TestStartRecordError(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartRecordError(c *check.C) { // when container runs successfully, we should not have state.Error - dockerCmd(t, "run", "-d", "-p", "9999:9999", "--name", "test", "busybox", "top") + dockerCmd(c, "run", "-d", "-p", "9999:9999", "--name", "test", "busybox", "top") stateErr, err := inspectField("test", "State.Error") if err != nil { - t.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) + c.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) } if stateErr != "" { - t.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) + c.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) } // Expect this to fail and records error because of ports conflict out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "test2", "-p", "9999:9999", "busybox", "top")) if err == nil { - t.Fatalf("Expected error but got none, output %q", out) + c.Fatalf("Expected error but got none, output %q", out) } stateErr, err = inspectField("test2", "State.Error") if err != nil { - t.Fatalf("Failed to inspect %q state's error, got error %q", "test2", err) + c.Fatalf("Failed to inspect %q state's error, got error %q", "test2", err) } expected := "port is already allocated" if stateErr == "" || !strings.Contains(stateErr, expected) { - t.Fatalf("State.Error(%q) does not include %q", stateErr, expected) + c.Fatalf("State.Error(%q) does not include %q", stateErr, expected) } // Expect the conflict to be resolved when we stop the initial container - dockerCmd(t, "stop", "test") - dockerCmd(t, "start", "test2") + dockerCmd(c, "stop", "test") + dockerCmd(c, "start", "test2") stateErr, err = inspectField("test2", "State.Error") if err != nil { - t.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) + c.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) } if stateErr != "" { - t.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) + c.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) } - logDone("start - set state error when start is unsuccessful") } // gh#8726: a failed Start() breaks --volumes-from on subsequent Start()'s -func TestStartVolumesFromFailsCleanly(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartVolumesFromFailsCleanly(c *check.C) { // Create the first data volume - dockerCmd(t, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox") + dockerCmd(c, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox") // Expect this to fail because the data test after contaienr doesn't exist yet if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "consumer", "--volumes-from", "data_before", "--volumes-from", "data_after", "busybox")); err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } // Create the second data volume - dockerCmd(t, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox") + dockerCmd(c, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox") // Now, all the volumes should be there - dockerCmd(t, "start", "consumer") + dockerCmd(c, "start", "consumer") // Check that we have the volumes we want - out, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer") + out, _ := dockerCmd(c, "inspect", "--format='{{ len .Volumes }}'", "consumer") nVolumes := strings.Trim(out, " \r\n'") if nVolumes != "2" { - t.Fatalf("Missing volumes: expected 2, got %s", nVolumes) + c.Fatalf("Missing volumes: expected 2, got %s", nVolumes) } - logDone("start - missing containers in --volumes-from did not affect subsequent runs") } -func TestStartPausedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartPausedContainer(c *check.C) { defer unpauseAllContainers() runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "pause", "testing") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "start", "testing") if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Cannot start a paused container, try unpause instead.") { - t.Fatalf("an error should have been shown that you cannot start paused container: %s\n%v", out, err) + c.Fatalf("an error should have been shown that you cannot start paused container: %s\n%v", out, err) } - logDone("start - error should show if trying to start paused container") } -func TestStartMultipleContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartMultipleContainers(c *check.C) { // run a container named 'parent' and create two container link to `parent` cmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } for _, container := range []string{"child_first", "child_second"} { cmd = exec.Command(dockerBinary, "create", "--name", container, "--link", "parent:parent", "busybox", "top") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } } // stop 'parent' container cmd = exec.Command(dockerBinary, "stop", "parent") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", "parent") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if out != "false" { - t.Fatal("Container should be stopped") + c.Fatal("Container should be stopped") } // start all the three containers, container `child_first` start first which should be faild @@ -222,35 +210,33 @@ func TestStartMultipleContainers(t *testing.T) { cmd = exec.Command(dockerBinary, "start", "child_first", "parent", "child_second") out, _, err = runCommandWithOutput(cmd) if !strings.Contains(out, "Cannot start container child_first") || err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } for container, expected := range map[string]string{"parent": "true", "child_first": "false", "child_second": "true"} { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if out != expected { - t.Fatal("Container running state wrong") + c.Fatal("Container running state wrong") } } - logDone("start - start multiple containers continue on one failed") } -func TestStartAttachMultipleContainers(t *testing.T) { +func (s *DockerSuite) TestStartAttachMultipleContainers(c *check.C) { var cmd *exec.Cmd - defer deleteAllContainers() // run multiple containers to test for _, container := range []string{"test1", "test2", "test3"} { cmd = exec.Command(dockerBinary, "run", "-d", "--name", container, "busybox", "top") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } } @@ -258,7 +244,7 @@ func TestStartAttachMultipleContainers(t *testing.T) { for _, container := range []string{"test1", "test2", "test3"} { cmd = exec.Command(dockerBinary, "stop", container) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } } @@ -267,7 +253,7 @@ func TestStartAttachMultipleContainers(t *testing.T) { cmd = exec.Command(dockerBinary, "start", option, "test1", "test2", "test3") out, _, err := runCommandWithOutput(cmd) if !strings.Contains(out, "You cannot start and attach multiple containers at once.") || err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } } @@ -276,13 +262,12 @@ func TestStartAttachMultipleContainers(t *testing.T) { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if out != expected { - t.Fatal("Container running state wrong") + c.Fatal("Container running state wrong") } } - logDone("start - error on start and attach multiple containers at once") } diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index 8a5d322713534..1b4d36b7bf3d4 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -3,48 +3,46 @@ package main import ( "os/exec" "strings" - "testing" "github.com/docker/docker/pkg/stringutils" + "github.com/go-check/check" ) // tagging a named image in a new unprefixed repo should work -func TestTagUnprefixedRepoByName(t *testing.T) { +func (s *DockerSuite) TestTagUnprefixedRepoByName(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "testfoobarbaz") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } deleteImages("testfoobarbaz") - logDone("tag - busybox -> testfoobarbaz") } // tagging an image by ID in a new unprefixed repo should work -func TestTagUnprefixedRepoByID(t *testing.T) { +func (s *DockerSuite) TestTagUnprefixedRepoByID(c *check.C) { getIDCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox") out, _, err := runCommandWithOutput(getIDCmd) if err != nil { - t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) + c.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) tagCmd := exec.Command(dockerBinary, "tag", cleanedImageID, "testfoobarbaz") if out, _, err = runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } deleteImages("testfoobarbaz") - logDone("tag - busybox's image ID -> testfoobarbaz") } // ensure we don't allow the use of invalid repository names; these tag operations should fail -func TestTagInvalidUnprefixedRepo(t *testing.T) { +func (s *DockerSuite) TestTagInvalidUnprefixedRepo(c *check.C) { invalidRepos := []string{"fo$z$", "Foo@3cc", "Foo$3", "Foo*3", "Fo^3", "Foo!3", "F)xcz(", "fo%asd"} @@ -52,14 +50,13 @@ func TestTagInvalidUnprefixedRepo(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "busybox", repo) _, _, err := runCommandWithOutput(tagCmd) if err == nil { - t.Fatalf("tag busybox %v should have failed", repo) + c.Fatalf("tag busybox %v should have failed", repo) } } - logDone("tag - busybox invalid repo names --> must not work") } // ensure we don't allow the use of invalid tags; these tag operations should fail -func TestTagInvalidPrefixedRepo(t *testing.T) { +func (s *DockerSuite) TestTagInvalidPrefixedRepo(c *check.C) { longTag := stringutils.GenerateRandomAlphaOnlyString(121) invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", longTag} @@ -68,16 +65,15 @@ func TestTagInvalidPrefixedRepo(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "busybox", repotag) _, _, err := runCommandWithOutput(tagCmd) if err == nil { - t.Fatalf("tag busybox %v should have failed", repotag) + c.Fatalf("tag busybox %v should have failed", repotag) } } - logDone("tag - busybox with invalid repo:tagnames --> must not work") } // ensure we allow the use of valid tags -func TestTagValidPrefixedRepo(t *testing.T) { +func (s *DockerSuite) TestTagValidPrefixedRepo(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } validRepos := []string{"fooo/bar", "fooaa/test", "foooo:t"} @@ -86,56 +82,53 @@ func TestTagValidPrefixedRepo(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", repo) _, _, err := runCommandWithOutput(tagCmd) if err != nil { - t.Errorf("tag busybox %v should have worked: %s", repo, err) + c.Errorf("tag busybox %v should have worked: %s", repo, err) continue } deleteImages(repo) } - logDone("tag - tag valid prefixed repo") } // tag an image with an existed tag name without -f option should fail -func TestTagExistedNameWithoutForce(t *testing.T) { +func (s *DockerSuite) TestTagExistedNameWithoutForce(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } tagCmd = exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test") out, _, err := runCommandWithOutput(tagCmd) if err == nil || !strings.Contains(out, "Conflict: Tag test is already set to image") { - t.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed") + c.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed") } deleteImages("busybox:test") - logDone("tag - busybox with an existed tag name without -f option --> must not work") } // tag an image with an existed tag name with -f option should work -func TestTagExistedNameWithForce(t *testing.T) { +func (s *DockerSuite) TestTagExistedNameWithForce(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } tagCmd = exec.Command(dockerBinary, "tag", "-f", "busybox:latest", "busybox:test") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } deleteImages("busybox:test") - logDone("tag - busybox with an existed tag name with -f option work") } // ensure tagging using official names works // ensure all tags result in the same name -func TestTagOfficialNames(t *testing.T) { +func (s *DockerSuite) TestTagOfficialNames(c *check.C) { names := []string{ "docker.io/busybox", "index.docker.io/busybox", @@ -148,7 +141,7 @@ func TestTagOfficialNames(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "-f", "busybox:latest", name+":latest") out, exitCode, err := runCommandWithOutput(tagCmd) if err != nil || exitCode != 0 { - t.Errorf("tag busybox %v should have worked: %s, %s", name, err, out) + c.Errorf("tag busybox %v should have worked: %s, %s", name, err, out) continue } @@ -156,9 +149,9 @@ func TestTagOfficialNames(t *testing.T) { imagesCmd := exec.Command(dockerBinary, "images") out, _, err = runCommandWithOutput(imagesCmd) if err != nil { - t.Errorf("listing images failed with errors: %v, %s", err, out) + c.Errorf("listing images failed with errors: %v, %s", err, out) } else if strings.Contains(out, name) { - t.Errorf("images should not have listed '%s'", name) + c.Errorf("images should not have listed '%s'", name) deleteImages(name + ":latest") } } @@ -167,10 +160,9 @@ func TestTagOfficialNames(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "-f", name+":latest", "fooo/bar:latest") _, exitCode, err := runCommandWithOutput(tagCmd) if err != nil || exitCode != 0 { - t.Errorf("tag %v fooo/bar should have worked: %s", name, err) + c.Errorf("tag %v fooo/bar should have worked: %s", name, err) continue } deleteImages("fooo/bar:latest") } - logDone("tag - tag official names") } diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go index b5dca0be983fd..7e75a38d576a0 100644 --- a/integration-cli/docker_cli_top_test.go +++ b/integration-cli/docker_cli_top_test.go @@ -3,14 +3,15 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestTopMultipleArgs(t *testing.T) { +func (s *DockerSuite) TestTopMultipleArgs(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -19,21 +20,20 @@ func TestTopMultipleArgs(t *testing.T) { topCmd := exec.Command(dockerBinary, "top", cleanedContainerID, "-o", "pid") out, _, err = runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out, err) + c.Fatalf("failed to run top: %s, %v", out, err) } if !strings.Contains(out, "PID") { - t.Fatalf("did not see PID after top -o pid: %s", out) + c.Fatalf("did not see PID after top -o pid: %s", out) } - logDone("top - multiple arguments") } -func TestTopNonPrivileged(t *testing.T) { +func (s *DockerSuite) TestTopNonPrivileged(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -41,38 +41,37 @@ func TestTopNonPrivileged(t *testing.T) { topCmd := exec.Command(dockerBinary, "top", cleanedContainerID) out1, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out1, err) + c.Fatalf("failed to run top: %s, %v", out1, err) } topCmd = exec.Command(dockerBinary, "top", cleanedContainerID) out2, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out2, err) + c.Fatalf("failed to run top: %s, %v", out2, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } deleteContainer(cleanedContainerID) if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed twice") + c.Fatal("top should've listed `top` in the process list, but failed twice") } else if !strings.Contains(out1, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the first time") + c.Fatal("top should've listed `top` in the process list, but failed the first time") } else if !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the second itime") + c.Fatal("top should've listed `top` in the process list, but failed the second itime") } - logDone("top - top process should be listed in non privileged mode") } -func TestTopPrivileged(t *testing.T) { +func (s *DockerSuite) TestTopPrivileged(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--privileged", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -80,29 +79,28 @@ func TestTopPrivileged(t *testing.T) { topCmd := exec.Command(dockerBinary, "top", cleanedContainerID) out1, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out1, err) + c.Fatalf("failed to run top: %s, %v", out1, err) } topCmd = exec.Command(dockerBinary, "top", cleanedContainerID) out2, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out2, err) + c.Fatalf("failed to run top: %s, %v", out2, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } deleteContainer(cleanedContainerID) if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed twice") + c.Fatal("top should've listed `top` in the process list, but failed twice") } else if !strings.Contains(out1, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the first time") + c.Fatal("top should've listed `top` in the process list, but failed the first time") } else if !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the second itime") + c.Fatal("top should've listed `top` in the process list, but failed the second itime") } - logDone("top - top process should be listed in privileged mode") } diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go index ceaeba8e209ff..3616da988f0a7 100644 --- a/integration-cli/docker_cli_version_test.go +++ b/integration-cli/docker_cli_version_test.go @@ -3,15 +3,16 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // ensure docker version works -func TestVersionEnsureSucceeds(t *testing.T) { +func (s *DockerSuite) TestVersionEnsureSucceeds(c *check.C) { versionCmd := exec.Command(dockerBinary, "version") out, _, err := runCommandWithOutput(versionCmd) if err != nil { - t.Fatalf("failed to execute docker version: %s, %v", out, err) + c.Fatalf("failed to execute docker version: %s, %v", out, err) } stringsToCheck := []string{ @@ -29,9 +30,8 @@ func TestVersionEnsureSucceeds(t *testing.T) { for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { - t.Errorf("couldn't find string %v in output", linePrefix) + c.Errorf("couldn't find string %v in output", linePrefix) } } - logDone("version - verify that it works and that the output is properly formatted") } diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go index cc0e778ea3ec6..09d0272bcf03b 100644 --- a/integration-cli/docker_cli_wait_test.go +++ b/integration-cli/docker_cli_wait_test.go @@ -3,18 +3,18 @@ package main import ( "os/exec" "strings" - "testing" "time" + + "github.com/go-check/check" ) // non-blocking wait with 0 exit code -func TestWaitNonBlockedExitZero(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitNonBlockedExitZero(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -23,13 +23,13 @@ func TestWaitNonBlockedExitZero(t *testing.T) { runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.Running}}'", containerID) status, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(status, err) + c.Fatal(status, err) } status = strings.TrimSpace(status) time.Sleep(time.Second) if i >= 60 { - t.Fatal("Container should have stopped by now") + c.Fatal("Container should have stopped by now") } } @@ -37,20 +37,18 @@ func TestWaitNonBlockedExitZero(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - non-blocking wait with 0 exit code") } // blocking wait with 0 exit code -func TestWaitBlockedExitZero(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitBlockedExitZero(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -58,20 +56,18 @@ func TestWaitBlockedExitZero(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - blocking wait with 0 exit code") } // non-blocking wait with random exit code -func TestWaitNonBlockedExitRandom(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitNonBlockedExitRandom(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "exit 99") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -80,13 +76,13 @@ func TestWaitNonBlockedExitRandom(t *testing.T) { runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.Running}}'", containerID) status, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(status, err) + c.Fatal(status, err) } status = strings.TrimSpace(status) time.Sleep(time.Second) if i >= 60 { - t.Fatal("Container should have stopped by now") + c.Fatal("Container should have stopped by now") } } @@ -94,20 +90,18 @@ func TestWaitNonBlockedExitRandom(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "99" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - non-blocking wait with random exit code") } // blocking wait with random exit code -func TestWaitBlockedExitRandom(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10; exit 99") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -115,8 +109,7 @@ func TestWaitBlockedExitRandom(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "99" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - blocking wait with random exit code") } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 4e622b84c79fc..855352973ef01 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -18,17 +18,17 @@ import ( "path/filepath" "strconv" "strings" - "testing" "time" "github.com/docker/docker/api" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" + "github.com/go-check/check" ) // Daemon represents a Docker daemon for the testing framework. type Daemon struct { - t *testing.T + c *check.C logFile *os.File folder string stdin io.WriteCloser @@ -42,24 +42,24 @@ type Daemon struct { // NewDaemon returns a Daemon instance to be used for testing. // This will create a directory such as daemon123456789 in the folder specified by $DEST. // The daemon will not automatically start. -func NewDaemon(t *testing.T) *Daemon { +func NewDaemon(c *check.C) *Daemon { dest := os.Getenv("DEST") if dest == "" { - t.Fatal("Please set the DEST environment variable") + c.Fatal("Please set the DEST environment variable") } dir := filepath.Join(dest, fmt.Sprintf("daemon%d", time.Now().UnixNano()%100000000)) daemonFolder, err := filepath.Abs(dir) if err != nil { - t.Fatalf("Could not make %q an absolute path: %v", dir, err) + c.Fatalf("Could not make %q an absolute path: %v", dir, err) } if err := os.MkdirAll(filepath.Join(daemonFolder, "graph"), 0600); err != nil { - t.Fatalf("Could not create %s/graph directory", daemonFolder) + c.Fatalf("Could not create %s/graph directory", daemonFolder) } return &Daemon{ - t: t, + c: c, folder: daemonFolder, storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"), execDriver: os.Getenv("DOCKER_EXECDRIVER"), @@ -71,7 +71,7 @@ func NewDaemon(t *testing.T) *Daemon { func (d *Daemon) Start(arg ...string) error { dockerBinary, err := exec.LookPath(dockerBinary) if err != nil { - d.t.Fatalf("could not find docker binary in $PATH: %v", err) + d.c.Fatalf("could not find docker binary in $PATH: %v", err) } args := []string{ @@ -105,7 +105,7 @@ func (d *Daemon) Start(arg ...string) error { d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) if err != nil { - d.t.Fatalf("Could not create %s/docker.log: %v", d.folder, err) + d.c.Fatalf("Could not create %s/docker.log: %v", d.folder, err) } d.cmd.Stdout = d.logFile @@ -119,7 +119,7 @@ func (d *Daemon) Start(arg ...string) error { go func() { wait <- d.cmd.Wait() - d.t.Log("exiting daemon") + d.c.Log("exiting daemon") close(wait) }() @@ -129,7 +129,7 @@ func (d *Daemon) Start(arg ...string) error { // make sure daemon is ready to receive requests startTime := time.Now().Unix() for { - d.t.Log("waiting for daemon to start") + d.c.Log("waiting for daemon to start") if time.Now().Unix()-startTime > 5 { // After 5 seconds, give up return errors.New("Daemon exited and never started") @@ -148,7 +148,7 @@ func (d *Daemon) Start(arg ...string) error { req, err := http.NewRequest("GET", "/_ping", nil) if err != nil { - d.t.Fatalf("could not create new request: %v", err) + d.c.Fatalf("could not create new request: %v", err) } resp, err := client.Do(req) @@ -156,10 +156,10 @@ func (d *Daemon) Start(arg ...string) error { continue } if resp.StatusCode != http.StatusOK { - d.t.Logf("received status != 200 OK: %s", resp.Status) + d.c.Logf("received status != 200 OK: %s", resp.Status) } - d.t.Log("daemon started") + d.c.Log("daemon started") return nil } } @@ -186,7 +186,7 @@ func (d *Daemon) StartWithBusybox(arg ...string) error { return fmt.Errorf("could not load busybox image: %v", err) } if err := os.Remove(bb); err != nil { - d.t.Logf("Could not remove %s: %v", bb, err) + d.c.Logf("Could not remove %s: %v", bb, err) } return nil } @@ -218,7 +218,7 @@ out1: return err case <-time.After(15 * time.Second): // time for stopping jobs and run onShutdown hooks - d.t.Log("timeout") + d.c.Log("timeout") break out1 } } @@ -231,10 +231,10 @@ out2: case <-tick: i++ if i > 4 { - d.t.Logf("tried to interrupt daemon for %d times, now try to kill it", i) + d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i) break out2 } - d.t.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) + d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) if err := d.cmd.Process.Signal(os.Interrupt); err != nil { return fmt.Errorf("could not send signal: %v", err) } @@ -242,7 +242,7 @@ out2: } if err := d.cmd.Process.Kill(); err != nil { - d.t.Logf("Could not kill daemon: %v", err) + d.c.Logf("Could not kill daemon: %v", err) return err } @@ -478,10 +478,10 @@ func pullImageIfNotExist(image string) (err error) { return } -func dockerCmd(t *testing.T, args ...string) (string, int) { +func dockerCmd(c *check.C, args ...string) (string, int) { out, status, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) if err != nil { - t.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err) + c.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err) } return out, status } @@ -496,7 +496,7 @@ func dockerCmdWithTimeout(timeout time.Duration, args ...string) (string, int, e } // execute a docker command in a directory -func dockerCmdInDir(t *testing.T, path string, args ...string) (string, int, error) { +func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error) { dockerCommand := exec.Command(dockerBinary, args...) dockerCommand.Dir = path out, status, err := runCommandWithOutput(dockerCommand) @@ -517,11 +517,11 @@ func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...strin return out, status, err } -func findContainerIP(t *testing.T, id string) string { +func findContainerIP(c *check.C, id string) string { cmd := exec.Command(dockerBinary, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } return strings.Trim(out, " \r\n'") @@ -779,12 +779,12 @@ func getIDByName(name string) (string, error) { // getContainerState returns the exit code of the container // and true if it's running // the exit code should be ignored if it's running -func getContainerState(t *testing.T, id string) (int, bool, error) { +func getContainerState(c *check.C, id string) (int, bool, error) { var ( exitStatus int running bool ) - out, exitCode := dockerCmd(t, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) + out, exitCode := dockerCmd(c, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) if exitCode != 0 { return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out) } @@ -985,28 +985,28 @@ func fakeGIT(name string, files map[string]string, enforceLocalServer bool) (*Fa // Write `content` to the file at path `dst`, creating it if necessary, // as well as any missing directories. // The file is truncated if it already exists. -// Call t.Fatal() at the first error. -func writeFile(dst, content string, t *testing.T) { +// Call c.Fatal() at the first error. +func writeFile(dst, content string, c *check.C) { // Create subdirectories if necessary if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) { - t.Fatal(err) + c.Fatal(err) } f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Write content (truncate if it exists) if _, err := io.Copy(f, strings.NewReader(content)); err != nil { - t.Fatal(err) + c.Fatal(err) } } // Return the contents of file at path `src`. -// Call t.Fatal() at the first error (including if the file doesn't exist) -func readFile(src string, t *testing.T) (content string) { +// Call c.Fatal() at the first error (including if the file doesn't exist) +func readFile(src string, c *check.C) (content string) { data, err := ioutil.ReadFile(src) if err != nil { - t.Fatal(err) + c.Fatal(err) } return string(data) @@ -1051,14 +1051,14 @@ func readContainerFileWithExec(containerId, filename string) ([]byte, error) { } // daemonTime provides the current time on the daemon host -func daemonTime(t *testing.T) time.Time { +func daemonTime(c *check.C) time.Time { if isLocalDaemon { return time.Now() } _, body, err := sockRequest("GET", "/info", nil) if err != nil { - t.Fatalf("daemonTime: failed to get /info: %v", err) + c.Fatalf("daemonTime: failed to get /info: %v", err) } type infoJSON struct { @@ -1066,21 +1066,21 @@ func daemonTime(t *testing.T) time.Time { } var info infoJSON if err = json.Unmarshal(body, &info); err != nil { - t.Fatalf("unable to unmarshal /info response: %v", err) + c.Fatalf("unable to unmarshal /info response: %v", err) } dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) if err != nil { - t.Fatal(err) + c.Fatal(err) } return dt } -func setupRegistry(t *testing.T) func() { - testRequires(t, RegistryHosting) - reg, err := newTestRegistryV2(t) +func setupRegistry(c *check.C) func() { + testRequires(c, RegistryHosting) + reg, err := newTestRegistryV2(c) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Wait for registry to be ready to serve requests. @@ -1092,7 +1092,7 @@ func setupRegistry(t *testing.T) func() { } if err != nil { - t.Fatal("Timeout waiting for test registry to become available") + c.Fatal("Timeout waiting for test registry to become available") } return func() { reg.Close() } diff --git a/integration-cli/registry.go b/integration-cli/registry.go index 8290e710fd631..2801eacb5f514 100644 --- a/integration-cli/registry.go +++ b/integration-cli/registry.go @@ -7,7 +7,8 @@ import ( "os" "os/exec" "path/filepath" - "testing" + + "github.com/go-check/check" ) const v2binary = "registry-v2" @@ -17,7 +18,7 @@ type testRegistryV2 struct { dir string } -func newTestRegistryV2(t *testing.T) (*testRegistryV2, error) { +func newTestRegistryV2(c *check.C) (*testRegistryV2, error) { template := `version: 0.1 loglevel: debug storage: @@ -43,7 +44,7 @@ http: if err := cmd.Start(); err != nil { os.RemoveAll(tmp) if os.IsNotExist(err) { - t.Skip() + c.Skip(err.Error()) } return nil, err } diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 9769e2d3a5a1e..7499fc50688e6 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -7,7 +7,8 @@ import ( "net/http" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) type TestCondition func() bool @@ -92,10 +93,10 @@ var ( // testRequires checks if the environment satisfies the requirements // for the test to run or skips the tests. -func testRequires(t *testing.T, requirements ...TestRequirement) { +func testRequires(c *check.C, requirements ...TestRequirement) { for _, r := range requirements { if !r.Condition() { - t.Skip(r.SkipMessage) + c.Skip(r.SkipMessage) } } } diff --git a/integration-cli/utils.go b/integration-cli/utils.go index c3a84bbc5928e..4ca7158aeb7bd 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -168,10 +168,6 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in return runCommandWithOutput(cmds[len(cmds)-1]) } -func logDone(message string) { - fmt.Printf("[PASSED]: %.69s\n", message) -} - func unmarshalJSON(data []byte, result interface{}) error { err := json.Unmarshal(data, result) if err != nil { From ba0017595ed9ac30273832b93365b0cb1cf60c05 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 19:59:45 +0200 Subject: [PATCH 214/332] Remove job image_tarlayer Signed-off-by: Antonio Murdaca --- graph/export.go | 4 +--- graph/service.go | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/graph/export.go b/graph/export.go index f689ba10efbe0..2450d502730eb 100644 --- a/graph/export.go +++ b/graph/export.go @@ -139,9 +139,7 @@ func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { if err != nil { return err } - job = eng.Job("image_tarlayer", n) - job.Stdout.Add(fsTar) - if err := job.Run(); err != nil { + if err := s.ImageTarLayer(n, fsTar); err != nil { return err } diff --git a/graph/service.go b/graph/service.go index e16d4ac04fda6..007a57d134d5f 100644 --- a/graph/service.go +++ b/graph/service.go @@ -11,14 +11,13 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ - "image_set": s.CmdSet, - "image_get": s.CmdGet, - "image_inspect": s.CmdLookup, - "image_tarlayer": s.CmdTarLayer, - "image_export": s.CmdImageExport, - "viz": s.CmdViz, - "load": s.CmdLoad, - "push": s.CmdPush, + "image_set": s.CmdSet, + "image_get": s.CmdGet, + "image_inspect": s.CmdLookup, + "image_export": s.CmdImageExport, + "viz": s.CmdViz, + "load": s.CmdLoad, + "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { return fmt.Errorf("Could not register %q: %v", name, err) @@ -152,12 +151,8 @@ func (s *TagStore) CmdLookup(job *engine.Job) error { return fmt.Errorf("No such image: %s", name) } -// CmdTarLayer return the tarLayer of the image -func (s *TagStore) CmdTarLayer(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) - } - name := job.Args[0] +// ImageTarLayer return the tarLayer of the image +func (s *TagStore) ImageTarLayer(name string, dest io.Writer) error { if image, err := s.LookupImage(name); err == nil && image != nil { fs, err := image.TarLayer() if err != nil { @@ -165,7 +160,7 @@ func (s *TagStore) CmdTarLayer(job *engine.Job) error { } defer fs.Close() - written, err := io.Copy(job.Stdout, fs) + written, err := io.Copy(dest, fs) if err != nil { return err } From d07fe1836579b0d55813cae8736be31a2891501a Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 19:42:06 +0200 Subject: [PATCH 215/332] Remove job image_get Signed-off-by: Antonio Murdaca --- graph/export.go | 7 +++---- graph/load.go | 2 +- graph/service.go | 41 ----------------------------------------- 3 files changed, 4 insertions(+), 46 deletions(-) diff --git a/graph/export.go b/graph/export.go index 2450d502730eb..56b5fba719c39 100644 --- a/graph/export.go +++ b/graph/export.go @@ -144,12 +144,11 @@ func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { } // find parent - job = eng.Job("image_get", n) - info, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { + img, err := s.LookupImage(n) + if err != nil { return err } - n = info.Get("Parent") + n = img.Parent } return nil } diff --git a/graph/load.go b/graph/load.go index bf2cc6d700625..5272eb139488f 100644 --- a/graph/load.go +++ b/graph/load.go @@ -80,7 +80,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { } func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { - if err := eng.Job("image_get", address).Run(); err != nil { + if _, err := s.LookupImage(address); err != nil { logrus.Debugf("Loading %s", address) imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) diff --git a/graph/service.go b/graph/service.go index 007a57d134d5f..b829b130f62c3 100644 --- a/graph/service.go +++ b/graph/service.go @@ -12,7 +12,6 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ "image_set": s.CmdSet, - "image_get": s.CmdGet, "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, @@ -73,46 +72,6 @@ func (s *TagStore) CmdSet(job *engine.Job) error { return nil } -// 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) error { - if len(job.Args) != 1 { - return fmt.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 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: - // - 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.SetAuto("Created", img.Created) - res.SetJson("Author", img.Author) - res.Set("Os", img.OS) - res.Set("Architecture", img.Architecture) - res.Set("DockerVersion", img.DockerVersion) - res.SetJson("Id", img.ID) - res.SetJson("Parent", img.Parent) - } - res.WriteTo(job.Stdout) - return nil -} - // CmdLookup return an image encoded in JSON func (s *TagStore) CmdLookup(job *engine.Job) error { if len(job.Args) != 1 { From a4676503d9531b50b26643df5ea3975bdec6b4df Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Tue, 21 Apr 2015 12:47:09 -0700 Subject: [PATCH 216/332] Add Image Digest doc in userguide/dockerimages Fixes #12551 Signed-off-by: Ankush Agarwal --- docs/sources/userguide/dockerimages.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index f97231506e3f1..fc5dbe74a09ed 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -505,6 +505,25 @@ 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 +## Image Digests + +Images that use the v2 or later format have a content-addressable identifier +called a `digest`. As long as the input used to generate the image is +unchanged, the digest value is predictable. To list image digest values, use +the `--digests` flag: + + $ docker images --digests | head + REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE + ouruser/sinatra latest sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 5db5f8471261 11 hours ago 446.7 MB + +When pushing or pulling to a 2.0 registry, the `push` or `pull` command +output includes the image digest. You can `pull` using a digest value. + + $ docker pull ouruser/sinatra@cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf + +You can also reference by digest in `create`, `run`, and `rmi` commands, as well as the +`FROM` image reference in a Dockerfile. + ## Push an image to Docker Hub Once you've built or created a new image you can push it to [Docker From a3c4801c9291c0db78c5cc0748d5d2dd5e44a98f Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 21:59:59 +0200 Subject: [PATCH 217/332] Remove not needed call to container.readHostConfig() Signed-off-by: Antonio Murdaca --- daemon/container.go | 6 ++++-- daemon/daemon.go | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 9dc0696ea79c9..a611f0da8d0c0 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -178,11 +178,13 @@ func (container *Container) readHostConfig() error { return nil } - data, err := ioutil.ReadFile(pth) + f, err := os.Open(pth) if err != nil { return err } - return json.Unmarshal(data, container.hostConfig) + defer f.Close() + + return json.NewDecoder(f).Decode(&container.hostConfig) } func (container *Container) WriteHostConfig() error { diff --git a/daemon/daemon.go b/daemon/daemon.go index d08d22cfdf228..3a9e7c8c8138e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -194,8 +194,6 @@ func (daemon *Daemon) load(id string) (*Container, error) { return container, fmt.Errorf("Container %s is stored at %s", container.ID, id) } - container.readHostConfig() - return container, nil } From 79a7fedcd8f9aaa42ec4e7f0fdd1f915c27e850b Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 21 Apr 2015 10:22:36 -0700 Subject: [PATCH 218/332] Remove image_set engine job It was unused Signed-off-by: Alexander Morozov --- graph/service.go | 49 ------------------------------------------------ 1 file changed, 49 deletions(-) diff --git a/graph/service.go b/graph/service.go index b829b130f62c3..022d5d499d8eb 100644 --- a/graph/service.go +++ b/graph/service.go @@ -6,12 +6,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" - "github.com/docker/docker/image" ) func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ - "image_set": s.CmdSet, "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, @@ -25,53 +23,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { 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) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) - } - var ( - imgJSON = []byte(job.Getenv("json")) - layer = job.Stdin - ) - if len(imgJSON) == 0 { - return fmt.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 redundant. - img, err := image.NewImgJSON(imgJSON) - if err != nil { - return err - } - if err := s.graph.Register(img, layer); err != nil { - return err - } - return nil -} - // CmdLookup return an image encoded in JSON func (s *TagStore) CmdLookup(job *engine.Job) error { if len(job.Args) != 1 { From bc7a43cb44fc0541939f10186e326e3139403605 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 16 Apr 2015 15:04:07 -0700 Subject: [PATCH 219/332] Putting into our new format for cloud Adding in Seb's comments Updating with Fred's comments Signed-off-by: Mary Anthony --- docs/sources/installation/amazon.md | 56 +++++------------------------ 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/docs/sources/installation/amazon.md b/docs/sources/installation/amazon.md index 60a6653b7bd8e..3fdeb7228acb4 100644 --- a/docs/sources/installation/amazon.md +++ b/docs/sources/installation/amazon.md @@ -2,52 +2,14 @@ page_title: Installation on Amazon EC2 page_description: Installation instructions for Docker on Amazon EC2. page_keywords: amazon ec2, virtualization, cloud, docker, documentation, installation -# Amazon EC2 +## Amazon EC2 -There are several ways to install Docker on AWS EC2. You can use Amazon Linux, which includes the Docker packages in its Software Repository, or opt for any of the other supported Linux images, for example a [*Standard Ubuntu Installation*](#standard-ubuntu-installation). +You can install Docker on any AWS EC2 Amazon Machine Image (AMI) which runs an +operating system that Docker supports. Amazon's website includes specific +instructions for [installing on Amazon +Linux](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html#install_docker). To install on +another AMI, follow the instructions for its specific operating +system in this installation guide. -**You'll need an** [AWS account](http://aws.amazon.com/) **first, of -course.** - -## Amazon QuickStart with Amazon Linux AMI 2014.09.1 - -The latest Amazon Linux AMI, 2014.09.1, is Docker ready. Docker packages can be installed from Amazon's provided Software -Repository. - -1. **Choose an image:** - - Launch the [Create Instance - Wizard](https://console.aws.amazon.com/ec2/v2/home?#LaunchInstanceWizard:) - menu on your AWS Console. - - In the Quick Start menu, select the Amazon provided AMI for Amazon Linux 2014.09.1 - - For testing you can use the default (possibly free) - `t2.micro` instance (more info on - [pricing](http://aws.amazon.com/ec2/pricing/)). - - Click the `Next: Configure Instance Details` - button at the bottom right. -2. After a few more standard choices where defaults are probably ok, - your Amazon Linux instance should be running! -3. SSH to your instance to install Docker : - `ssh -i ec2-user@` -4. Add the ec2-user to the docker group : - `sudo usermod -a -G docker ec2-user` -5. Restart the machine and log back in - `sudo shutdown -r now` -6. Once connected to the instance, type - `sudo yum install -y docker ; sudo service docker start` - to install and start Docker - -**If this is your first AWS instance, you may need to set up your Security Group to allow SSH.** By default all incoming ports to your new instance will be blocked by the AWS Security Group, so you might just get timeouts when you try to connect. - -Once you`ve got Docker installed, you're ready to try it out – head on -over to the [User Guide](/userguide). - -## Standard Ubuntu Installation - -If you want a more hands-on installation, then you can follow the -[*Ubuntu*](/installation/ubuntulinux) instructions installing Docker -on any EC2 instance running Ubuntu. Just follow Step 1 from the Amazon -QuickStart above to pick an image (or use one of your -own) and skip the step with the *User Data*. Then continue with the -[*Ubuntu*](/installation/ubuntulinux) instructions. - -Continue with the [User Guide](/userguide/). +For detailed information on Amazon AWS support for Docker, refer to [Amazon's +documentation](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html). From a2f74aa4b449479dc3953b129c839ca90b089494 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 21 Apr 2015 14:23:48 -0700 Subject: [PATCH 220/332] Remove chain of engine passing from builder to loadManifest Signed-off-by: Alexander Morozov --- api/server/server.go | 8 ++++---- builder/evaluator.go | 2 -- builder/internals.go | 2 +- builder/job.go | 11 ++++------- graph/manifest.go | 3 +-- graph/pull.go | 15 +++++++-------- integration/runtime_test.go | 2 +- 7 files changed, 18 insertions(+), 25 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 94338097ba405..6e1f35a4bfc00 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -708,7 +708,7 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. Config: c, } - imgID, err := builder.Commit(s.daemon, eng, cont, containerCommitConfig) + imgID, err := builder.Commit(s.daemon, cont, containerCommitConfig) if err != nil { return err } @@ -764,7 +764,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imagePullConfig.Json = false } - if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { + if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig); err != nil { return err } } else { //import @@ -785,7 +785,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imageImportConfig.Json = false } - newConfig, err := builder.BuildFromConfig(s.daemon, eng, &runconfig.Config{}, imageImportConfig.Changes) + newConfig, err := builder.BuildFromConfig(s.daemon, &runconfig.Config{}, imageImportConfig.Changes) if err != nil { return err } @@ -1327,7 +1327,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R }() } - if err := builder.Build(s.daemon, eng, buildConfig); err != nil { + if err := builder.Build(s.daemon, buildConfig); err != nil { // Do not write the error in the http output if it's still empty. // This prevents from writing a 200(OK) when there is an interal error. if !output.Flushed() { diff --git a/builder/evaluator.go b/builder/evaluator.go index 0eba4a6ebd826..7cbba03514bfc 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -31,7 +31,6 @@ import ( "github.com/docker/docker/builder/command" "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" @@ -80,7 +79,6 @@ func init() { // processing as it evaluates the parsing result. type Builder struct { Daemon *daemon.Daemon - Engine *engine.Engine // effectively stdio for the run. Because it is not stdio, I said // "Effectively". Do not use stdio anywhere in this package for any reason. diff --git a/builder/internals.go b/builder/internals.go index ae5f2ab3f0f5c..9574351ca6a71 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -454,7 +454,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { Json: b.StreamFormatter.Json(), } - if err := b.Daemon.Repositories().Pull(remote, tag, imagePullConfig, b.Engine); err != nil { + if err := b.Daemon.Repositories().Pull(remote, tag, imagePullConfig); err != nil { return nil, err } diff --git a/builder/job.go b/builder/job.go index 8a1cf3054caaa..4c6e55b0a9b1c 100644 --- a/builder/job.go +++ b/builder/job.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" @@ -83,7 +82,7 @@ func NewBuildConfig() *Config { } } -func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { +func Build(d *daemon.Daemon, buildConfig *Config) error { var ( repoName string tag string @@ -150,7 +149,6 @@ func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { builder := &Builder{ Daemon: d, - Engine: e, OutStream: &streamformatter.StdoutFormater{ Writer: buildConfig.Stdout, StreamFormatter: sf, @@ -188,7 +186,7 @@ func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { return nil } -func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, changes []string) (*runconfig.Config, error) { +func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) { ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) if err != nil { return nil, err @@ -203,7 +201,6 @@ func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, ch builder := &Builder{ Daemon: d, - Engine: e, Config: c, OutStream: ioutil.Discard, ErrStream: ioutil.Discard, @@ -219,13 +216,13 @@ func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, ch return builder.Config, nil } -func Commit(d *daemon.Daemon, eng *engine.Engine, name string, c *daemon.ContainerCommitConfig) (string, error) { +func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (string, error) { container, err := d.Get(name) if err != nil { return "", err } - newConfig, err := BuildFromConfig(d, eng, c.Config, c.Changes) + newConfig, err := BuildFromConfig(d, c.Config, c.Changes) if err != nil { return "", err } diff --git a/graph/manifest.go b/graph/manifest.go index e6d5ebc39ce3f..053a185ba5289 100644 --- a/graph/manifest.go +++ b/graph/manifest.go @@ -6,7 +6,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/engine" "github.com/docker/docker/registry" "github.com/docker/docker/trust" "github.com/docker/docker/utils" @@ -18,7 +17,7 @@ import ( // contains no signatures by a trusted key for the name in the manifest, the // image is not considered verified. The parsed manifest object and a boolean // for whether the manifest is verified is returned. -func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) { +func (s *TagStore) loadManifest(manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) { sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures") if err != nil { return nil, false, fmt.Errorf("error parsing payload: %s", err) diff --git a/graph/pull.go b/graph/pull.go index edc67df9d653f..b62591ffb6c81 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -12,7 +12,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -29,7 +28,7 @@ type ImagePullConfig struct { OutStream io.Writer } -func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig, eng *engine.Engine) error { +func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error { var ( sf = streamformatter.NewStreamFormatter(imagePullConfig.Json) ) @@ -74,7 +73,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf } logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) - if err := s.pullV2Repository(eng, r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err == nil { + if err := s.pullV2Repository(r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err == nil { s.eventsService.Log("pull", logName, "") return nil } else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable { @@ -369,7 +368,7 @@ type downloadInfo struct { err chan error } -func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool) error { +func (s *TagStore) pullV2Repository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool) error { endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) if err != nil { if repoInfo.Index.Official { @@ -393,14 +392,14 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out return registry.ErrDoesNotExist } for _, t := range tags { - if downloaded, err := s.pullV2Tag(eng, r, out, endpoint, repoInfo, t, sf, parallel, auth); err != nil { + if downloaded, err := s.pullV2Tag(r, out, endpoint, repoInfo, t, sf, parallel, auth); err != nil { return err } else if downloaded { layersDownloaded = true } } } else { - if downloaded, err := s.pullV2Tag(eng, r, out, endpoint, repoInfo, tag, sf, parallel, auth); err != nil { + if downloaded, err := s.pullV2Tag(r, out, endpoint, repoInfo, tag, sf, parallel, auth); err != nil { return err } else if downloaded { layersDownloaded = true @@ -415,7 +414,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out return nil } -func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { +func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { logrus.Debugf("Pulling tag from V2 registry: %q", tag) manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth) @@ -425,7 +424,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri // loadManifest ensures that the manifest payload has the expected digest // if the tag is a digest reference. - manifest, verified, err := s.loadManifest(eng, manifestBytes, manifestDigest, tag) + manifest, verified, err := s.loadManifest(manifestBytes, manifestDigest, tag) if err != nil { return false, fmt.Errorf("error verifying manifest: %s", err) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 2e106972e46ac..0c412c86293b0 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -138,7 +138,7 @@ func setupBaseImage() { AuthConfig: ®istry.AuthConfig{}, } d := getDaemon(eng) - if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig, eng); err != nil { + if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { logrus.Fatalf("Unable to pull the test image: %s", err) } } From f3dc35169780e1555b4116986649b729cb80b5d1 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Wed, 22 Apr 2015 08:15:00 +0800 Subject: [PATCH 221/332] remove redundant warning And warning is not supposed to have a prefix WARNING. Signed-off-by: Qiang Huang --- pkg/sysinfo/sysinfo.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index d1dcea3bf8470..76a61fa95f059 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -23,33 +23,32 @@ func New(quiet bool) *SysInfo { sysInfo := &SysInfo{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { if !quiet { - logrus.Warnf("%s", err) + logrus.Warnf("%v", err) } } else { _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) sysInfo.MemoryLimit = err1 == nil && err2 == nil if !sysInfo.MemoryLimit && !quiet { - logrus.Warnf("Your kernel does not support cgroup memory limit.") + logrus.Warn("Your kernel does not support cgroup memory limit.") } _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) sysInfo.SwapLimit = err == nil if !sysInfo.SwapLimit && !quiet { - logrus.Warnf("Your kernel does not support cgroup swap limit.") + logrus.Warn("Your kernel does not support cgroup swap limit.") } } if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil { if !quiet { - logrus.Warnf("WARING: %s\n", err) + logrus.Warnf("%v", err) } } else { _, err1 := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us")) - logrus.Warnf("%s", cgroupCpuMountpoint) sysInfo.CpuCfsQuota = err1 == nil if !sysInfo.CpuCfsQuota && !quiet { - logrus.Warnf("WARING: Your kernel does not support cgroup cfs quotas") + logrus.Warn("Your kernel does not support cgroup cfs quotas") } } From 9424fc14bb7465f9874e6e3560cf62b560dbd13d Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Tue, 21 Apr 2015 17:46:43 -0700 Subject: [PATCH 222/332] Add Configuring Docker article [WIP] Fixes #12088 Signed-off-by: Ankush Agarwal --- docs/mkdocs.yml | 1 + docs/sources/articles/configuring.md | 60 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/sources/articles/configuring.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 49c9b80b77426..59819afeebcbf 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -109,6 +109,7 @@ pages: - ['articles/dockerfile_best-practices.md', 'Articles', 'Best practices for writing Dockerfiles'] - ['articles/certificates.md', 'Articles', 'Using certificates for repository client verification'] - ['articles/using_supervisord.md', 'Articles', 'Using Supervisor'] +- ['articles/configuring.md', 'Articles', 'Configuring Docker'] - ['articles/cfengine_process_management.md', 'Articles', 'Process management with CFEngine'] - ['articles/puppet.md', 'Articles', 'Using Puppet'] - ['articles/chef.md', 'Articles', 'Using Chef'] diff --git a/docs/sources/articles/configuring.md b/docs/sources/articles/configuring.md new file mode 100644 index 0000000000000..5fae38c5bc0bb --- /dev/null +++ b/docs/sources/articles/configuring.md @@ -0,0 +1,60 @@ +page_title: Configuring Docker +page_description: Configuring the Docker daemon on various distributions +page_keywords: docker, daemon, configuration + +# Configuring Docker on various distributions + +After successfully installing the Docker daemon on a distribution, it runs with it's default +config. Usually it is required to change the default config to meet one's personal requirements. + +Docker can be configured by passing the config flags to the daemon directly if the daemon +is started directly. Usually that is not the case. A process manager (like SysVinit, Upstart, +systemd, etc) is responsible for starting and running the daemon. + +Some common config options are + +* `-D` : Enable debug mode + +* `-H` : Daemon socket(s) to connect to + +* `--tls` : Enable or disable TLS authentication + +The complete list of flags can found at [Docker Command Line Reference](/reference/commandline/cli/) + +## Ubuntu + +After successfully [installing Docker for Ubuntu](/installation/ubuntulinux/), you can check the +running status using (if running Upstart) + + $ sudo status docker + docker start/running, process 989 + +You can start/stop/restart `docker` using + + $ sudo start docker + + $ sudo stop docker + + $ sudo restart docker + + +### Configuring Docker + +Docker options can be configured by editing the file `/etc/default/docker`. If this file does not +exist, it needs to be createdThis file contains a variable named `DOCKER_OPTS`. All the +config options need to be placed in this variable. For example + + DOCKER_OPTS=" --dns 8.8.8.8 -D --tls=false -H tcp://0.0.0.0:2375 " + +The above daemon options : + +1. Set dns server for all containers + +2. Enable Debug mode + +3. Set tls to false + +4. Make the daemon listen for connections on `tcp://0.0.0.0:2375` + +After saving the file, restart docker using `sudo restart docker`. Verify that the daemon is +running with the options specified by running `ps aux | grep docker | grep -v grep` From 54ff1dcb829e67d62dd4686bdc19522d63c3e0d3 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 21 Apr 2015 18:36:37 -0700 Subject: [PATCH 223/332] Fixing a few links in registry Signed-off-by: Mary Anthony --- docs/Dockerfile | 6 ++++++ docs/mkdocs.yml | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/docs/Dockerfile b/docs/Dockerfile index 91cf52bc10194..e30d4bbd540d9 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -49,6 +49,12 @@ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md \ /docs/sources/registry/spec/ + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/s3.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/azure.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/filesystem.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/inmemory.md \ + /docs/sources/registry/storage-drivers/ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 49c9b80b77426..5479bb59ac049 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -171,6 +171,12 @@ pages: - ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries'] - ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker Hub Accounts API'] +# Hidden registry files +- ['registry/storage-drivers/azure.md', '**HIDDEN**' ] +- ['registry/storage-drivers/filesystem.md', '**HIDDEN**' ] +- ['registry/storage-drivers/inmemory.md', '**HIDDEN**' ] +- ['registry/storage-drivers/s3.md', '**HIDDEN**' ] + - ['jsearch.md', '**HIDDEN**'] # - ['static_files/README.md', 'static_files', 'README'] @@ -184,6 +190,8 @@ pages: - ['terms/image.md', '**HIDDEN**'] + + # Project: - ['project/index.md', '**HIDDEN**'] - ['project/who-written-for.md', 'Contributor Guide', 'README first'] From 90a8e45604f42d60d58b4cefa37a5e5d3112b64a Mon Sep 17 00:00:00 2001 From: Gosuke Miyashita Date: Sat, 21 Mar 2015 01:52:05 +0900 Subject: [PATCH 224/332] Append icc related iptables rules, not INSERT Signed-off-by: Gosuke Miyashita --- daemon/networkdriver/bridge/driver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 8f240ef598d08..0ea4d5dca4bce 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -340,7 +340,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if !iptables.Exists(iptables.Filter, "FORWARD", dropArgs...) { logrus.Debugf("Disable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, dropArgs...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-A", "FORWARD"}, dropArgs...)...); err != nil { return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) } else if len(output) != 0 { return fmt.Errorf("Error disabling intercontainer communication: %s", output) @@ -351,7 +351,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if !iptables.Exists(iptables.Filter, "FORWARD", acceptArgs...) { logrus.Debugf("Enable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, acceptArgs...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-A", "FORWARD"}, acceptArgs...)...); err != nil { return fmt.Errorf("Unable to allow intercontainer communication: %s", err) } else if len(output) != 0 { return fmt.Errorf("Error enabling intercontainer communication: %s", output) From 58065d0dd9adec4b2f397a453652cc8cc7237a17 Mon Sep 17 00:00:00 2001 From: Peggy Li Date: Tue, 21 Apr 2015 23:45:18 -0700 Subject: [PATCH 225/332] Fix golint errors in docker/api/client Signed-off-by: Peggy Li --- api/client/cli.go | 7 +++++++ api/client/images.go | 12 ++++++------ api/client/rm.go | 3 +++ api/client/search.go | 1 + api/client/utils.go | 9 +++++---- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/api/client/cli.go b/api/client/cli.go index 3146b17b31414..0a1fb2ef8a0d0 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -97,6 +97,11 @@ func (cli *DockerCli) Cmd(args ...string) error { return cli.CmdHelp() } +// Subcmd is a subcommand of the main "docker" command. +// A subcommand represents an action that can be performed +// from the Docker command line client. +// +// To see all available subcommands, run "docker --help". func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet { var errorHandling flag.ErrorHandling if exitOnError { @@ -121,6 +126,8 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo return flags } +// CheckTtyInput checks if we are trying to attach to a container tty +// from a non-tty client input stream, and if so, returns an error. func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { // In order to attach to a container tty, input stream for the client must // be a tty itself: redirecting or piping the client standard input is diff --git a/api/client/images.go b/api/client/images.go index b47c6d65f4f0c..32440d48d1261 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -19,19 +19,19 @@ import ( ) // FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) WalkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { +func (cli *DockerCli) walkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { length := len(images) if length > 1 { for index, image := range images { if index+1 == length { printNode(cli, noTrunc, image, prefix+"└─") if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) + cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } else { printNode(cli, noTrunc, image, prefix+"\u251C─") if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) + cli.walkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) } } } @@ -39,7 +39,7 @@ func (cli *DockerCli) WalkTree(noTrunc bool, images []*types.Image, byParent map for _, image := range images { printNode(cli, noTrunc, image, prefix+"└─") if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) + cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } } @@ -181,9 +181,9 @@ func (cli *DockerCli) CmdImages(args ...string) error { if startImage != nil { root := []*types.Image{startImage} - cli.WalkTree(*noTrunc, root, byParent, "", printNode) + cli.walkTree(*noTrunc, root, byParent, "", printNode) } else if matchName == "" { - cli.WalkTree(*noTrunc, roots, byParent, "", printNode) + cli.walkTree(*noTrunc, roots, byParent, "", printNode) } if *flViz { fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") diff --git a/api/client/rm.go b/api/client/rm.go index 1d49e98a844ee..1ecc0d65727c1 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -7,6 +7,9 @@ import ( flag "github.com/docker/docker/pkg/mflag" ) +// CmdRm removes one or more containers. +// +// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdRm(args ...string) error { cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true) v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container") diff --git a/api/client/search.go b/api/client/search.go index 2cff7708d3872..4e493b234ac42 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/registry" ) +// ByStars sorts search results in ascending order by number of stars. type ByStars []registry.SearchResult func (r ByStars) Len() int { return len(r) } diff --git a/api/client/utils.go b/api/client/utils.go index 026593d00dac9..8efbd26c913b5 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -29,9 +29,10 @@ import ( ) var ( - ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") + errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") ) +// HTTPClient creates a new HTP client with the cli's client transport instance. func (cli *DockerCli) HTTPClient() *http.Client { return &http.Client{Transport: cli.transport} } @@ -93,7 +94,7 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m } if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, "", statusCode, ErrConnectionRefused + return nil, "", statusCode, errConnectionRefused } if cli.tlsConfig == nil { @@ -250,7 +251,7 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil) if err != nil { // If we can't connect, then the daemon probably died. - if err != ErrConnectionRefused { + if err != errConnectionRefused { return false, -1, err } return false, -1, nil @@ -271,7 +272,7 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil) if err != nil { // If we can't connect, then the daemon probably died. - if err != ErrConnectionRefused { + if err != errConnectionRefused { return false, -1, err } return false, -1, nil From b121d94369164391369cd02a559a7b64b482f59d Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Wed, 22 Apr 2015 18:45:29 +0800 Subject: [PATCH 226/332] Use svg instead of png to get better image quality Signed-off-by: Peter Dave Hello --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03aa0139c5fd4..5603a55a7f178 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ Under the hood, Docker is built on the following components: Contributing to Docker ====================== -[![GoDoc](https://godoc.org/github.com/docker/docker?status.png)](https://godoc.org/github.com/docker/docker) +[![GoDoc](https://godoc.org/github.com/docker/docker?status.svg)](https://godoc.org/github.com/docker/docker) [![Jenkins Build Status](https://jenkins.dockerproject.com/job/Docker%20Master/badge/icon)](https://jenkins.dockerproject.com/job/Docker%20Master/) Want to hack on Docker? Awesome! We have [instructions to help you get From 2dd88af79b0e1af6df0551b993cd9fffbd5881ee Mon Sep 17 00:00:00 2001 From: Andrew Martin Date: Wed, 22 Apr 2015 11:37:47 +0100 Subject: [PATCH 227/332] Add kali to install script https://www.kali.org/ is a Debian derivative. This script completes succesfully using the Debian install path Signed-off-by: Andrew Martin --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index 32e506cdd2b4b..e15565fc79a3f 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -126,7 +126,7 @@ do_install() { exit 0 ;; - ubuntu|debian|linuxmint|'elementary os') + ubuntu|debian|linuxmint|'elementary os'|kali) export DEBIAN_FRONTEND=noninteractive did_apt_get_update= From eeb8ceb9edb61f4b8889360e4c4a2c4250c2d9f6 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 18 Apr 2015 14:58:57 +0200 Subject: [PATCH 228/332] Fix TestEventsImageImport racy, fixes #12499 Signed-off-by: Antonio Murdaca --- integration-cli/docker_cli_events_test.go | 43 +++++++++++++++++------ 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index b8e24260a72f7..35a9c0a213546 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -204,6 +204,31 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) { func (s *DockerSuite) TestEventsImageImport(c *check.C) { since := daemonTime(c).Unix() + id := make(chan string) + eventImport := make(chan struct{}) + eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(since, 10)) + stdout, err := eventsCmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + err = eventsCmd.Start() + if err != nil { + c.Fatal(err) + } + defer eventsCmd.Process.Kill() + + go func() { + containerID := <-id + + matchImport := regexp.MustCompile(containerID + `: import$`) + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + if matchImport.MatchString(scanner.Text()) { + close(eventImport) + } + } + }() + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { @@ -218,19 +243,15 @@ func (s *DockerSuite) TestEventsImageImport(c *check.C) { if err != nil { c.Errorf("import failed with errors: %v, output: %q", err, out) } + newContainerID := strings.TrimSpace(out) + id <- newContainerID - eventsCmd := exec.Command(dockerBinary, "events", - fmt.Sprintf("--since=%d", since), - fmt.Sprintf("--until=%d", daemonTime(c).Unix())) - out, _, _ = runCommandWithOutput(eventsCmd) - - events := strings.Split(strings.TrimSpace(out), "\n") - event := strings.TrimSpace(events[len(events)-1]) - - if !strings.HasSuffix(event, ": import") { - c.Fatalf("Missing import event - got:%q", event) + select { + case <-time.After(5 * time.Second): + c.Fatal("failed to observe image import in timely fashion") + case <-eventImport: + // ignore, done } - } func (s *DockerSuite) TestEventsFilters(c *check.C) { From 9689aab5ec0141a70c2134d18cb581ec5a923c5f Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Wed, 22 Apr 2015 10:35:58 -0700 Subject: [PATCH 229/332] Update with @moxiegirl's patch and add direct config Signed-off-by: Ankush Agarwal --- docs/sources/articles/configuring.md | 84 ++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/docs/sources/articles/configuring.md b/docs/sources/articles/configuring.md index 5fae38c5bc0bb..35d0eb8e5823a 100644 --- a/docs/sources/articles/configuring.md +++ b/docs/sources/articles/configuring.md @@ -4,27 +4,46 @@ page_keywords: docker, daemon, configuration # Configuring Docker on various distributions -After successfully installing the Docker daemon on a distribution, it runs with it's default -config. Usually it is required to change the default config to meet one's personal requirements. - -Docker can be configured by passing the config flags to the daemon directly if the daemon -is started directly. Usually that is not the case. A process manager (like SysVinit, Upstart, -systemd, etc) is responsible for starting and running the daemon. +After successfully installing Docker, the `docker` daemon runs with it's default +configuration. You can configure the `docker` daemon by passing configuration +flags to it directly when you start it. -Some common config options are +In a production environment, system administrators typically configure the +`docker` daemon to start and stop according to an organization's requirements. In most +cases, the system administrator configures a process manager such as `SysVinit`, `Upstart`, +or `systemd` to manage the `docker` daemon's start and stop. -* `-D` : Enable debug mode +Some of the daemon's options are: -* `-H` : Daemon socket(s) to connect to +| Flag | Description | +|-----------------------|-----------------------------------------------------------| +| `-D`, `--debug=false` | Enable or disable debug mode. By default, this is false. | +| `-H`,`--host=[]` | Daemon socket(s) to connect to. | +| `--tls=false` | Enable or disable TLS. By default, this is false. | -* `--tls` : Enable or disable TLS authentication +The command line reference has the [complete list of daemon flags](/reference/commandline/cli/#daemon). + +## Direct Configuration + +If you're running the `docker` daemon directly by running `docker -d` instead of using a process manager, +you can append the config options to the run command directly. + + +Here is a an example of running the `docker` daemon with config options: + + docker -d -D --tls=false -H tcp://0.0.0.0:2375 + +These options : + +- Enable `-D` (debug) mode +- Set `tls` to false +- Listen for connections on `tcp://0.0.0.0:2375` -The complete list of flags can found at [Docker Command Line Reference](/reference/commandline/cli/) ## Ubuntu After successfully [installing Docker for Ubuntu](/installation/ubuntulinux/), you can check the -running status using (if running Upstart) +running status using Upstart in this way: $ sudo status docker docker start/running, process 989 @@ -40,21 +59,40 @@ You can start/stop/restart `docker` using ### Configuring Docker -Docker options can be configured by editing the file `/etc/default/docker`. If this file does not -exist, it needs to be createdThis file contains a variable named `DOCKER_OPTS`. All the -config options need to be placed in this variable. For example +You configure the `docker` daemon in the `/etc/default/docker` file on your +system. You do this by specifying values in a `DOCKER_OPTS` variable. +To configure Docker options: + +1. Log into your system as a user with `sudo` or `root` privileges. + +2. If you don't have one, create the `/etc/default/docker` file in your system. + + Depending on how you installed Docker, you may already have this file. + +3. Open the file with your favorite editor. - DOCKER_OPTS=" --dns 8.8.8.8 -D --tls=false -H tcp://0.0.0.0:2375 " + $ sudo vi /etc/default/docker + +4. Add a `DOCKER_OPTS` variable with the following options. These options are appended to the +`docker` daemon's run command. -The above daemon options : + ``` + DOCKER_OPTS=" --dns 8.8.8.8 --dns 8.8.4.4 -D --tls=false -H tcp://0.0.0.0:2375 " + ``` + +These options : -1. Set dns server for all containers +- Set `dns` server for all containers +- Enable `-D` (debug) mode +- Set `tls` to false +- Listen for connections on `tcp://0.0.0.0:2375` + +5. Save and close the file. -2. Enable Debug mode +6. Restart the `docker` daemon. -3. Set tls to false + $ sudo restart docker -4. Make the daemon listen for connections on `tcp://0.0.0.0:2375` +7. Verify that the `docker` daemon is running as specified wit the `ps` command. -After saving the file, restart docker using `sudo restart docker`. Verify that the daemon is -running with the options specified by running `ps aux | grep docker | grep -v grep` + $ ps aux | grep docker | grep -v grep From b4988d8d75b4ee915ade6013de76bb1336917528 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 22 Apr 2015 11:44:54 -0700 Subject: [PATCH 230/332] Remove old testing stuff that slipped into master Signed-off-by: Doug Davis --- integration-cli/docker_api_info_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go index 5f7c9515c9b9d..67967ab2a67c1 100644 --- a/integration-cli/docker_api_info_test.go +++ b/integration-cli/docker_api_info_test.go @@ -3,15 +3,16 @@ package main import ( "net/http" "strings" - "testing" + + "github.com/go-check/check" ) -func TestInfoApi(t *testing.T) { +func (s *DockerSuite) TestInfoApi(c *check.C) { endpoint := "/info" statusCode, body, err := sockRequest("GET", endpoint, nil) if err != nil || statusCode != http.StatusOK { - t.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) + c.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) } // always shown fields @@ -30,7 +31,7 @@ func TestInfoApi(t *testing.T) { out := string(body) for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { - t.Errorf("couldn't find string %v in output", linePrefix) + c.Errorf("couldn't find string %v in output", linePrefix) } } } From 08150150bbc4477cbcf8286bc15a1cf8b4428e35 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Mon, 20 Apr 2015 21:47:56 -0700 Subject: [PATCH 231/332] Add integration test for history option Parse the history output to locate the size fields and check whether they are the correct format or not. Use Column name SIZE to mark start and end indices of the size fields Fixes #12578 Signed-off-by: Ankush Agarwal --- integration-cli/docker_cli_history_test.go | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index a3ba18abd861a..134f7bc7f8c14 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -3,6 +3,8 @@ package main import ( "fmt" "os/exec" + "regexp" + "strconv" "strings" "github.com/go-check/check" @@ -122,3 +124,40 @@ func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { } } + +func (s *DockerSuite) TestHistoryHumanOptionFalse(c *check.C) { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "history", "--human=false", "busybox")) + lines := strings.Split(out, "\n") + sizeColumnRegex, _ := regexp.Compile("SIZE +") + indices := sizeColumnRegex.FindStringIndex(lines[0]) + startIndex := indices[0] + endIndex := indices[1] + for i := 1; i < len(lines)-1; i++ { + if endIndex > len(lines[i]) { + endIndex = len(lines[i]) + } + sizeString := lines[i][startIndex:endIndex] + if _, err := strconv.Atoi(strings.TrimSpace(sizeString)); err != nil { + c.Fatalf("The size '%s' was not an Integer", sizeString) + } + } +} + +func (s *DockerSuite) TestHistoryHumanOptionTrue(c *check.C) { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "history", "--human=true", "busybox")) + lines := strings.Split(out, "\n") + sizeColumnRegex, _ := regexp.Compile("SIZE +") + humanSizeRegex, _ := regexp.Compile("^\\d+.*B$") // Matches human sizes like 10 MB, 3.2 KB, etc + indices := sizeColumnRegex.FindStringIndex(lines[0]) + startIndex := indices[0] + endIndex := indices[1] + for i := 1; i < len(lines)-1; i++ { + if endIndex > len(lines[i]) { + endIndex = len(lines[i]) + } + sizeString := lines[i][startIndex:endIndex] + if matchSuccess := humanSizeRegex.MatchString(strings.TrimSpace(sizeString)); !matchSuccess { + c.Fatalf("The size '%s' was not in human format", sizeString) + } + } +} From a09ab40f0d980b2a6d6656db638f1fc5d73f5b8b Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 22 Apr 2015 13:19:14 -0700 Subject: [PATCH 232/332] update contrib docs for gocheck Signed-off-by: Jessica Frazelle --- docs/sources/project/test-and-docs.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index 93f73282929dd..31615abe95eb1 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -159,15 +159,16 @@ Most test targets require that you build these precursor targets first: ## Running individual or multiple named tests +We use [gocheck](https://labix.org/gocheck) for our integration-cli tests. You can use the `TESTFLAGS` environment variable to run a single test. The flag's value is passed as arguments to the `go test` command. For example, from your local host you can run the `TestBuild` test with this command: - $ TESTFLAGS='-test.run ^TestBuild$' make test + $ TESTFLAGS='-check.f DockerSuite.TestBuild*' make test To run the same test inside your Docker development container, you do this: - root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-run ^TestBuild$' hack/make.sh + root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-check.f TestBuild*' hack/make.sh ## If tests under Boot2Docker fail due to disk space errors From c09765ac431a85f9ad231c52cbe562bb6f0b0b06 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 21 Apr 2015 21:58:16 -0700 Subject: [PATCH 233/332] Wait until all pushes are done in TestPushInterrupt Background pushes affects other tests Signed-off-by: Alexander Morozov --- integration-cli/docker_cli_push_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 5c74fe8dcf742..ebf08bae8be14 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -92,11 +92,9 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { func (s *DockerSuite) TestPushInterrupt(c *check.C) { defer setupRegistry(c)() - repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it tot he private registry - tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) - if out, _, err := runCommandWithOutput(tagCmd); err != nil { + if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoName) @@ -111,14 +109,17 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { if err := pushCmd.Process.Kill(); err != nil { c.Fatalf("Failed to kill push process: %v", err) } - // Try agin - pushCmd = exec.Command(dockerBinary, "push", repoName) - if out, err := pushCmd.CombinedOutput(); err == nil { + if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "push", repoName)); err == nil { str := string(out) if !strings.Contains(str, "already in progress") { c.Fatalf("Push should be continued on daemon side, but seems ok: %v, %s", err, out) } } + // now wait until all this pushes will complete + // if it will fail with timeout - this is some error, so no logic about it + // here + for exec.Command(dockerBinary, "push", repoName).Run() != nil { + } } func (s *DockerSuite) TestPushEmptyLayer(c *check.C) { From 24425021d26f29a475702064181e6c99fb6bd1c5 Mon Sep 17 00:00:00 2001 From: jianbosun Date: Fri, 17 Apr 2015 13:36:23 +0800 Subject: [PATCH 234/332] remove execCreate & execStart from job Also removed the function ExecConfigFromJob Signed-off-by: Sun Jianbo Signed-off-by: Alexander Morozov --- api/client/exec.go | 11 +++++--- api/server/server.go | 62 +++++++++++++++++++------------------------- api/types/types.go | 12 ++++++--- daemon/daemon.go | 2 -- daemon/exec.go | 34 +++++++----------------- runconfig/exec.go | 22 ---------------- 6 files changed, 52 insertions(+), 91 deletions(-) diff --git a/api/client/exec.go b/api/client/exec.go index 23545ae9bf477..4bf53eaec2cb4 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -32,9 +32,6 @@ func (cli *DockerCli) CmdExec(args ...string) error { if err := json.NewDecoder(stream).Decode(&response); err != nil { return err } - for _, warning := range response.Warnings { - fmt.Fprintf(cli.err, "WARNING: %s\n", warning) - } execID := response.ID @@ -43,12 +40,18 @@ func (cli *DockerCli) CmdExec(args ...string) error { return nil } + //Temp struct for execStart so that we don't need to transfer all the execConfig + execStartCheck := &types.ExecStartCheck{ + Detach: execConfig.Detach, + Tty: execConfig.Tty, + } + if !execConfig.Detach { if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { return err } } else { - if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, nil)); err != nil { + if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil { return err } // For now don't print this - wait for when we support exec wait() diff --git a/api/server/server.go b/api/server/server.go index 6e1f35a4bfc00..6deb88f8278bd 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1,8 +1,6 @@ package server import ( - "bufio" - "bytes" "runtime" "time" @@ -1393,35 +1391,27 @@ func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Ver if err := parseForm(r); err != nil { return nil } - var ( - name = vars["name"] - job = eng.Job("execCreate", name) - stdoutBuffer = bytes.NewBuffer(nil) - outWarnings []string - warnings = bytes.NewBuffer(nil) - ) + name := vars["name"] - if err := job.DecodeEnv(r.Body); err != nil { + execConfig := &runconfig.ExecConfig{} + if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil { return err } + execConfig.Container = name + + if len(execConfig.Cmd) == 0 { + return fmt.Errorf("No exec command specified") + } - job.Stdout.Add(stdoutBuffer) - // Read warnings from stderr - job.Stderr.Add(warnings) // Register an instance of Exec in container. - if err := job.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err) + id, err := s.daemon.ContainerExecCreate(execConfig) + if err != nil { + logrus.Errorf("Error setting up exec command in container %s: %s", name, err) return err } - // Parse warnings from stderr - scanner := bufio.NewScanner(warnings) - for scanner.Scan() { - outWarnings = append(outWarnings, scanner.Text()) - } return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ - ID: engine.Tail(stdoutBuffer, 1), - Warnings: outWarnings, + ID: id, }) } @@ -1431,15 +1421,18 @@ func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Vers return nil } var ( - name = vars["name"] - job = eng.Job("execStart", name) - errOut io.Writer = os.Stderr + execName = vars["name"] + stdin io.ReadCloser + stdout io.Writer + stderr io.Writer ) - if err := job.DecodeEnv(r.Body); err != nil { + execStartCheck := &types.ExecStartCheck{} + if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil { return err } - if !job.GetenvBool("Detach") { + + if !execStartCheck.Detach { // Setting up the streaming http interface. inStream, outStream, err := hijackServer(w) if err != nil { @@ -1455,21 +1448,20 @@ func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Vers fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") } - if !job.GetenvBool("Tty") && version.GreaterThanOrEqualTo("1.6") { + if !execStartCheck.Tty && version.GreaterThanOrEqualTo("1.6") { errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } else { errStream = outStream } - job.Stdin.Add(inStream) - job.Stdout.Add(outStream) - job.Stderr.Set(errStream) - errOut = outStream + stdin = inStream + stdout = outStream + stderr = errStream } // Now run the user process in container. - job.SetCloseIO(false) - if err := job.Run(); err != nil { - fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err) + + if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil { + logrus.Errorf("Error starting exec command in container %s: %s", execName, err) return err } w.WriteHeader(http.StatusNoContent) diff --git a/api/types/types.go b/api/types/types.go index 01b5d38f1f78e..1e8b56bceff96 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -16,9 +16,6 @@ type ContainerCreateResponse struct { type ContainerExecCreateResponse struct { // ID is the exec ID. ID string `json:"Id"` - - // Warnings are any warnings encountered during the execution of the command. - Warnings []string `json:"Warnings"` } // POST /auth @@ -156,3 +153,12 @@ type Info struct { Name string Labels []string } + +// This struct is a temp struct used by execStart +// Config fields is part of ExecConfig in runconfig package +type ExecStartCheck struct { + // ExecStart will first check if it's detached + Detach bool + // Check if there's a tty + Tty bool +} diff --git a/daemon/daemon.go b/daemon/daemon.go index c643ed65c6c99..ca3aff3e28d8b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -118,8 +118,6 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "execCreate": daemon.ContainerExecCreate, - "execStart": daemon.ContainerExecStart, } { if err := eng.Register(name, method); err != nil { return err diff --git a/daemon/exec.go b/daemon/exec.go index 4787189a77b28..22872adc44e89 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -10,7 +10,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver/lxc" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/promise" @@ -111,25 +110,15 @@ func (d *Daemon) getActiveContainer(name string) (*Container, error) { return container, nil } -func (d *Daemon) ContainerExecCreate(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s [options] container command [args]", job.Name) - } +func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, error) { if strings.HasPrefix(d.execDriver.Name(), lxc.DriverName) { - return lxc.ErrExec + return "", lxc.ErrExec } - var name = job.Args[0] - - container, err := d.getActiveContainer(name) + container, err := d.getActiveContainer(config.Container) if err != nil { - return err - } - - config, err := runconfig.ExecConfigFromJob(job) - if err != nil { - return err + return "", err } cmd := runconfig.NewCommand(config.Cmd...) @@ -158,20 +147,15 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { d.registerExecCommand(execConfig) - job.Printf("%s\n", execConfig.ID) + return execConfig.ID, nil - return nil } -func (d *Daemon) ContainerExecStart(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s [options] exec", job.Name) - } +func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer - execName = job.Args[0] ) execConfig, err := d.getExecConfig(execName) @@ -201,15 +185,15 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) error { go func() { defer w.Close() defer logrus.Debugf("Closing buffered stdin pipe") - io.Copy(w, job.Stdin) + io.Copy(w, stdin) }() cStdin = r } if execConfig.OpenStdout { - cStdout = job.Stdout + cStdout = stdout } if execConfig.OpenStderr { - cStderr = job.Stderr + cStderr = stderr } execConfig.StreamConfig.stderr = broadcastwriter.New() diff --git a/runconfig/exec.go b/runconfig/exec.go index e634d3081317b..8fe05be1bb3d3 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -1,9 +1,6 @@ package runconfig import ( - "fmt" - - "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" ) @@ -19,25 +16,6 @@ type ExecConfig struct { Cmd []string } -func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { - execConfig := &ExecConfig{ - User: job.Getenv("User"), - Privileged: job.GetenvBool("Privileged"), - Tty: job.GetenvBool("Tty"), - AttachStdin: job.GetenvBool("AttachStdin"), - AttachStderr: job.GetenvBool("AttachStderr"), - AttachStdout: job.GetenvBool("AttachStdout"), - } - cmd := job.GetenvList("Cmd") - if len(cmd) == 0 { - return nil, fmt.Errorf("No exec command specified") - } - - execConfig.Cmd = cmd - - return execConfig, nil -} - func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { var ( flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") From fd4f7c4e5c4460bad7baf0854f1edc1bbf93aeef Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Wed, 22 Apr 2015 12:33:46 -0700 Subject: [PATCH 235/332] Correctly format API error on image pull Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- api/server/server.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 6e1f35a4bfc00..2523e4463666b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -740,6 +740,15 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w } } + var ( + opErr error + useJSON = version.GreaterThan("1.0") + ) + + if useJSON { + w.Header().Set("Content-Type", "application/json") + } + if image != "" { //pull if tag == "" { image, tag = parsers.ParseRepositoryTag(image) @@ -756,17 +765,10 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w MetaHeaders: metaHeaders, AuthConfig: authConfig, OutStream: utils.NewWriteFlusher(w), - } - if version.GreaterThan("1.0") { - imagePullConfig.Json = true - w.Header().Set("Content-Type", "application/json") - } else { - imagePullConfig.Json = false + Json: useJSON, } - if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig); err != nil { - return err - } + opErr = s.daemon.Repositories().Pull(image, tag, imagePullConfig) } else { //import if tag == "" { repo, tag = parsers.ParseRepositoryTag(repo) @@ -777,12 +779,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w Changes: r.Form["changes"], InConfig: r.Body, OutStream: utils.NewWriteFlusher(w), - } - if version.GreaterThan("1.0") { - imageImportConfig.Json = true - w.Header().Set("Content-Type", "application/json") - } else { - imageImportConfig.Json = false + Json: useJSON, } newConfig, err := builder.BuildFromConfig(s.daemon, &runconfig.Config{}, imageImportConfig.Changes) @@ -791,9 +788,12 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w } imageImportConfig.ContainerConfig = newConfig - if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig); err != nil { - return err - } + opErr = s.daemon.Repositories().Import(src, repo, tag, imageImportConfig) + } + + if opErr != nil { + sf := streamformatter.NewStreamFormatter(useJSON) + return fmt.Errorf(string(sf.FormatError(opErr))) } return nil From 7a525c6cb74af854d621f5560868b32694d2553b Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 22 Apr 2015 11:57:23 -0700 Subject: [PATCH 236/332] add integration test for error pull nonexistent Signed-off-by: Jessica Frazelle --- integration-cli/docker_cli_pull_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 8cf09a72610f7..25a93729b4337 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -98,8 +98,13 @@ func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) { // pulling a non-existing image from the central registry should return a non-zero exit code func (s *DockerSuite) TestPullNonExistingImage(c *check.C) { - pullCmd := exec.Command(dockerBinary, "pull", "fooblahblah1234") - if out, _, err := runCommandWithOutput(pullCmd); err == nil { + testRequires(c, Network) + + name := "sadfsadfasdf" + pullCmd := exec.Command(dockerBinary, "pull", name) + out, _, err := runCommandWithOutput(pullCmd) + + if err == nil || !strings.Contains(out, fmt.Sprintf("Error: image library/%s:latest not found", name)) { c.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out) } } From 28f5541b72a898762575a2ed0995cae7452fa275 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 22 Apr 2015 14:12:46 -0700 Subject: [PATCH 237/332] add regression test for rmi multiple tags without f Signed-off-by: Jessica Frazelle --- integration-cli/docker_cli_rmi_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 786a3e3061d4c..234fa22f0a413 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os/exec" "strings" @@ -102,6 +103,14 @@ func (s *DockerSuite) TestRmiImgIDForce(c *check.C) { } out, _ = dockerCmd(c, "inspect", "-f", "{{.Id}}", "busybox-test") imgID := strings.TrimSpace(out) + + // first checkout without force it fails + runCmd = exec.Command(dockerBinary, "rmi", imgID) + out, _, err = runCommandWithOutput(runCmd) + if err == nil || !strings.Contains(out, fmt.Sprintf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", imgID)) { + c.Fatalf("rmi tagged in mutiple repos should have failed without force:%s, %v", out, err) + } + dockerCmd(c, "rmi", "-f", imgID) { imagesAfter, _ := dockerCmd(c, "images", "-a") From 71b5a754cec09b2f1bcef986bdd6fd109451b8f2 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Wed, 22 Apr 2015 21:53:45 +0000 Subject: [PATCH 238/332] remove unused utils Signed-off-by: Daniel, Dao Quang Minh --- daemon/execdriver/native/utils.go | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 daemon/execdriver/native/utils.go diff --git a/daemon/execdriver/native/utils.go b/daemon/execdriver/native/utils.go deleted file mode 100644 index a703926453e9d..0000000000000 --- a/daemon/execdriver/native/utils.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build linux - -package native - -//func findUserArgs() []string { -//for i, a := range os.Args { -//if a == "--" { -//return os.Args[i+1:] -//} -//} -//return []string{} -//} - -//// loadConfigFromFd loads a container's config from the sync pipe that is provided by -//// fd 3 when running a process -//func loadConfigFromFd() (*configs.Config, error) { -//var config *libcontainer.Config -//if err := json.NewDecoder(os.NewFile(3, "child")).Decode(&config); err != nil { -//return nil, err -//} -//return config, nil -//} From 4b9fe9c298c8778855c1d14e978c791496dd7c42 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 13 Apr 2015 16:17:14 +0200 Subject: [PATCH 239/332] Remove job from container_inspect Signed-off-by: Antonio Murdaca --- api/client/attach.go | 24 +++---- api/client/logs.go | 11 ++-- api/client/utils.go | 27 +++++--- api/common.go | 15 +---- api/server/server.go | 17 +++-- api/server/server_unit_test.go | 35 ---------- api/types/types.go | 48 +++++++++++++- daemon/daemon.go | 7 -- daemon/inspect.go | 113 +++++++++++++++++--------------- docker/docker.go | 6 +- integration-cli/docker_utils.go | 4 +- opts/opts.go | 30 +++++++-- utils/utils.go | 10 --- 13 files changed, 185 insertions(+), 162 deletions(-) diff --git a/api/client/attach.go b/api/client/attach.go index ef2b4ad12226e..8ab3248aceb08 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -1,12 +1,13 @@ package client import ( + "encoding/json" "fmt" "io" "net/url" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" ) @@ -30,25 +31,20 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } - env := engine.Env{} - if err := env.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } - if !env.GetSubEnv("State").GetBool("Running") { + if !c.State.Running { return fmt.Errorf("You cannot attach to a stopped container, start it first") } - var ( - config = env.GetSubEnv("Config") - tty = config.GetBool("Tty") - ) - - if err := cli.CheckTtyInput(!*noStdin, tty); err != nil { + if err := cli.CheckTtyInput(!*noStdin, c.Config.Tty); err != nil { return err } - if tty && cli.isTerminalOut { + if c.Config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { logrus.Debugf("Error monitoring TTY size: %s", err) } @@ -58,7 +54,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { v := url.Values{} v.Set("stream", "1") - if !*noStdin && config.GetBool("OpenStdin") { + if !*noStdin && c.Config.OpenStdin { v.Set("stdin", "1") in = cli.in } @@ -66,12 +62,12 @@ func (cli *DockerCli) CmdAttach(args ...string) error { v.Set("stdout", "1") v.Set("stderr", "1") - if *proxy && !tty { + if *proxy && !c.Config.Tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) defer signal.StopCatch(sigc) } - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), c.Config.Tty, in, cli.out, cli.err, nil, nil); err != nil { return err } diff --git a/api/client/logs.go b/api/client/logs.go index 9039ecf090a20..5e5dd9dd8bcb1 100644 --- a/api/client/logs.go +++ b/api/client/logs.go @@ -1,10 +1,11 @@ package client import ( + "encoding/json" "fmt" "net/url" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" ) @@ -29,12 +30,12 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return err } - env := engine.Env{} - if err := env.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } - if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" { + if c.HostConfig.LogConfig.Type != "json-file" { return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver") } @@ -51,5 +52,5 @@ func (cli *DockerCli) CmdLogs(args ...string) error { } v.Set("tail", *tail) - return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil) + return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), c.Config.Tty, nil, cli.out, cli.err, nil) } diff --git a/api/client/utils.go b/api/client/utils.go index 8efbd26c913b5..804dc0c58daef 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -19,6 +19,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonmessage" @@ -238,11 +239,12 @@ func waitForExit(cli *DockerCli, containerID string) (int, error) { return -1, err } - var out engine.Env - if err := out.Decode(stream); err != nil { + var res types.ContainerWaitResponse + if err := json.NewDecoder(stream).Decode(&res); err != nil { return -1, err } - return out.GetInt("StatusCode"), nil + + return res.StatusCode, nil } // getExitCode perform an inspect on the container. It returns @@ -257,13 +259,12 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { return false, -1, nil } - var result engine.Env - if err := result.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return false, -1, err } - state := result.GetSubEnv("State") - return state.GetBool("Running"), state.GetInt("ExitCode"), nil + return c.State.Running, c.State.ExitCode, nil } // getExecExitCode perform an inspect on the exec command. It returns @@ -278,12 +279,18 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { return false, -1, nil } - var result engine.Env - if err := result.Decode(stream); err != nil { + //TODO: Should we reconsider having a type in api/types? + //this is a response to exex/id/json not container + var c struct { + Running bool + ExitCode int + } + + if err := json.NewDecoder(stream).Decode(&c); err != nil { return false, -1, err } - return result.GetBool("Running"), result.GetInt("ExitCode"), nil + return c.Running, c.ExitCode, nil } func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { diff --git a/api/common.go b/api/common.go index 693df38876b46..cb627824e96ab 100644 --- a/api/common.go +++ b/api/common.go @@ -10,27 +10,16 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/version" "github.com/docker/libtrust" ) // Common constants for daemon and client. const ( - APIVERSION version.Version = "1.19" // Current REST API version - DEFAULTHTTPHOST = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 - DEFAULTUNIXSOCKET = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket - DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build + APIVERSION version.Version = "1.19" // Current REST API version + DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build ) -func ValidateHost(val string) (string, error) { - host, err := parsers.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val) - if err != nil { - return val, err - } - return host, nil -} - type ByPrivatePort []types.Port func (r ByPrivatePort) Len() int { return len(r) } diff --git a/api/server/server.go b/api/server/server.go index 6deb88f8278bd..a9a06fa6ef041 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1210,12 +1210,21 @@ func (s *Server) getContainersByName(eng *engine.Engine, version version.Version if vars == nil { return fmt.Errorf("Missing parameter") } - var job = eng.Job("container_inspect", vars["name"]) + + name := vars["name"] + if version.LessThan("1.12") { - job.SetenvBool("raw", true) + containerJSONRaw, err := s.daemon.ContainerInspectRaw(name) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, containerJSONRaw) } - streamJSON(job.Stdout, w, false) - return job.Run() + containerJSON, err := s.daemon.ContainerInspect(name) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, containerJSON) } func (s *Server) getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index e7a6afcb94ecf..b2a911607d3dd 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -32,41 +32,6 @@ func TesthttpError(t *testing.T) { } } -func TestGetContainersByName(t *testing.T) { - eng := engine.New() - name := "container_name" - var called bool - eng.Register("container_inspect", func(job *engine.Job) error { - called = true - if job.Args[0] != name { - t.Errorf("name != '%s': %#v", name, job.Args[0]) - } - if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") { - t.Errorf("dirty env variable not set") - } else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") { - t.Errorf("dirty env variable set when it shouldn't") - } - v := &engine.Env{} - v.SetBool("dirty", true) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/containers/"+name+"/json", nil, eng, t) - if !called { - t.Fatal("handler was not called") - } - assertContentType(r, "application/json", t) - var stdoutJson interface{} - if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil { - t.Fatalf("%#v", err) - } - if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 { - t.Fatalf("%#v", stdoutJson) - } -} - func TestGetImagesByName(t *testing.T) { eng := engine.New() name := "image_name" diff --git a/api/types/types.go b/api/types/types.go index 1e8b56bceff96..656aa1a6eba19 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -1,6 +1,12 @@ package types -import "github.com/docker/docker/pkg/version" +import ( + "time" + + "github.com/docker/docker/daemon/network" + "github.com/docker/docker/pkg/version" + "github.com/docker/docker/runconfig" +) // ContainerCreateResponse contains the information returned to a client on the // creation of a new container. @@ -162,3 +168,43 @@ type ExecStartCheck struct { // Check if there's a tty Tty bool } + +type ContainerState struct { + Running bool + Paused bool + Restarting bool + OOMKilled bool + Dead bool + Pid int + ExitCode int + Error string + StartedAt time.Time + FinishedAt time.Time +} + +// GET "/containers/{name:.*}/json" +type ContainerJSON struct { + Id string + Created time.Time + Path string + Args []string + Config *runconfig.Config + State *ContainerState + Image string + NetworkSettings *network.Settings + ResolvConfPath string + HostnamePath string + HostsPath string + LogPath string + Name string + RestartCount int + Driver string + ExecDriver string + MountLabel string + ProcessLabel string + Volumes map[string]string + VolumesRW map[string]bool + AppArmorProfile string + ExecIDs []string + HostConfig *runconfig.HostConfig +} diff --git a/daemon/daemon.go b/daemon/daemon.go index ca3aff3e28d8b..8873b4cacd238 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -116,13 +116,6 @@ type Daemon struct { // Install installs daemon capabilities to eng. func (daemon *Daemon) Install(eng *engine.Engine) error { - for name, method := range map[string]engine.Handler{ - "container_inspect": daemon.ContainerInspect, - } { - if err := eng.Register(name, method); err != nil { - return err - } - } if err := daemon.Repositories().Install(eng); err != nil { return err } diff --git a/daemon/inspect.go b/daemon/inspect.go index 7e25626ef7a60..56db3d059b12c 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -1,83 +1,92 @@ package daemon import ( - "encoding/json" "fmt" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerInspect(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) - } - name := job.Args[0] +type ContainerJSONRaw struct { + *Container + HostConfig *runconfig.HostConfig +} + +func (daemon *Daemon) ContainerInspectRaw(name string) (*ContainerJSONRaw, error) { container, err := daemon.Get(name) if err != nil { - return err + return nil, err } container.Lock() defer container.Unlock() - if job.GetenvBool("raw") { - b, err := json.Marshal(&struct { - *Container - HostConfig *runconfig.HostConfig - }{container, container.hostConfig}) - if err != nil { - return err - } - job.Stdout.Write(b) - return nil + + return &ContainerJSONRaw{container, container.hostConfig}, nil +} + +func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error) { + container, err := daemon.Get(name) + if err != nil { + return nil, err } - out := &engine.Env{} - out.SetJson("Id", container.ID) - out.SetAuto("Created", container.Created) - out.SetJson("Path", container.Path) - out.SetList("Args", container.Args) - out.SetJson("Config", container.Config) - out.SetJson("State", container.State) - out.Set("Image", container.ImageID) - out.SetJson("NetworkSettings", container.NetworkSettings) - out.Set("ResolvConfPath", container.ResolvConfPath) - out.Set("HostnamePath", container.HostnamePath) - out.Set("HostsPath", container.HostsPath) - out.Set("LogPath", container.LogPath) - out.SetJson("Name", container.Name) - out.SetInt("RestartCount", container.RestartCount) - out.Set("Driver", container.Driver) - 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("AppArmorProfile", container.AppArmorProfile) + container.Lock() + defer container.Unlock() - out.SetList("ExecIDs", container.GetExecIDs()) + // make a copy to play with + hostConfig := *container.hostConfig if children, err := daemon.Children(container.Name); err == nil { for linkAlias, child := range children { - container.hostConfig.Links = append(container.hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias)) + hostConfig.Links = append(hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias)) } } // we need this trick to preserve empty log driver, so // container will use daemon defaults even if daemon change them - if container.hostConfig.LogConfig.Type == "" { - container.hostConfig.LogConfig = daemon.defaultLogConfig - defer func() { - container.hostConfig.LogConfig = runconfig.LogConfig{} - }() + if hostConfig.LogConfig.Type == "" { + hostConfig.LogConfig = daemon.defaultLogConfig } - out.SetJson("HostConfig", container.hostConfig) + containerState := &types.ContainerState{ + Running: container.State.Running, + Paused: container.State.Paused, + Restarting: container.State.Restarting, + OOMKilled: container.State.OOMKilled, + Dead: container.State.Dead, + Pid: container.State.Pid, + ExitCode: container.State.ExitCode, + Error: container.State.Error, + StartedAt: container.State.StartedAt, + FinishedAt: container.State.FinishedAt, + } - container.hostConfig.Links = nil - if _, err := out.WriteTo(job.Stdout); err != nil { - return err + contJSON := &types.ContainerJSON{ + Id: container.ID, + Created: container.Created, + Path: container.Path, + Args: container.Args, + Config: container.Config, + State: containerState, + Image: container.ImageID, + NetworkSettings: container.NetworkSettings, + ResolvConfPath: container.ResolvConfPath, + HostnamePath: container.HostnamePath, + HostsPath: container.HostsPath, + LogPath: container.LogPath, + Name: container.Name, + RestartCount: container.RestartCount, + Driver: container.Driver, + ExecDriver: container.ExecDriver, + MountLabel: container.MountLabel, + ProcessLabel: container.ProcessLabel, + Volumes: container.Volumes, + VolumesRW: container.VolumesRW, + AppArmorProfile: container.AppArmorProfile, + ExecIDs: container.GetExecIDs(), + HostConfig: &hostConfig, } - return nil + + return contJSON, nil } func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) { diff --git a/docker/docker.go b/docker/docker.go index d2d4986acad17..1096b840f8468 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -9,9 +9,9 @@ import ( "strings" "github.com/Sirupsen/logrus" - "github.com/docker/docker/api" "github.com/docker/docker/api/client" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" @@ -63,9 +63,9 @@ func main() { defaultHost := os.Getenv("DOCKER_HOST") if defaultHost == "" || *flDaemon { // If we do not have a host, default to unix socket - defaultHost = fmt.Sprintf("unix://%s", api.DEFAULTUNIXSOCKET) + defaultHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket) } - defaultHost, err := api.ValidateHost(defaultHost) + defaultHost, err := opts.ValidateHost(defaultHost) if err != nil { logrus.Fatal(err) } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 855352973ef01..e427fdf0db4e7 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -20,7 +20,7 @@ import ( "strings" "time" - "github.com/docker/docker/api" + "github.com/docker/docker/opts" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" "github.com/go-check/check" @@ -274,7 +274,7 @@ func (d *Daemon) LogfileName() string { } func daemonHost() string { - daemonUrlStr := "unix://" + api.DEFAULTUNIXSOCKET + daemonUrlStr := "unix://" + opts.DefaultUnixSocket if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" { daemonUrlStr = daemonHostVar } diff --git a/opts/opts.go b/opts/opts.go index df9decf61fa0c..d2c32f13c7229 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,16 +8,16 @@ import ( "regexp" "strings" - "github.com/docker/docker/api" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/ulimit" - "github.com/docker/docker/utils" ) var ( - alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) - domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) + alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) + domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) + DefaultHTTPHost = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 + DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket ) func ListVar(values *[]string, names []string, usage string) { @@ -25,7 +25,7 @@ func ListVar(values *[]string, names []string, usage string) { } func HostListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, api.ValidateHost), names, usage) + flag.Var(newListOptsRef(values, ValidateHost), names, usage) } func IPListVar(values *[]string, names []string, usage string) { @@ -174,7 +174,7 @@ func ValidateEnv(val string) (string, error) { if len(arr) > 1 { return val, nil } - if !utils.DoesEnvExist(val) { + if !doesEnvExist(val) { return val, nil } return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil @@ -234,3 +234,21 @@ func ValidateLabel(val string) (string, error) { } return val, nil } + +func ValidateHost(val string) (string, error) { + host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val) + if err != nil { + return val, err + } + return host, nil +} + +func doesEnvExist(name string) bool { + for _, entry := range os.Environ() { + parts := strings.SplitN(entry, "=", 2) + if parts[0] == name { + return true + } + } + return false +} diff --git a/utils/utils.go b/utils/utils.go index ab59826783141..05dfb757a3bf9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -239,16 +239,6 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { return defaults } -func DoesEnvExist(name string) bool { - for _, entry := range os.Environ() { - parts := strings.SplitN(entry, "=", 2) - if parts[0] == name { - return true - } - } - return false -} - // 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 From f3680e74946d3a773edb118ea3f508b8237e44a8 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 16 Dec 2014 10:06:45 -0500 Subject: [PATCH 240/332] Cleanup daemon/volumes - Mount struct now called volumeMount - Merged volume creation for each volume type (volumes-from, binds, normal volumes) so this only happens in once place - Simplified container copy of volumes (for when `docker cp` is a volume) Signed-off-by: Brian Goff --- daemon/volumes.go | 309 +++++++++-------------- integration-cli/docker_cli_build_test.go | 2 +- 2 files changed, 123 insertions(+), 188 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index 7c6696d65fde0..4d15023ba717c 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -14,17 +14,14 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" - "github.com/docker/docker/volumes" ) -type Mount struct { - MountToPath string - container *Container - volume *volumes.Volume - Writable bool - copyData bool - from *Container - isBind bool +type volumeMount struct { + containerPath string + hostPath string + writable bool + copyData bool + from string } func (container *Container) prepareVolumes() error { @@ -33,70 +30,119 @@ func (container *Container) prepareVolumes() error { container.VolumesRW = make(map[string]bool) } + if len(container.hostConfig.VolumesFrom) > 0 && container.AppliedVolumesFrom == nil { + container.AppliedVolumesFrom = make(map[string]struct{}) + } return container.createVolumes() } -// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order -func (container *Container) sortedVolumeMounts() []string { - var mountPaths []string - for path := range container.Volumes { - mountPaths = append(mountPaths, path) +func (container *Container) createVolumes() error { + mounts := make(map[string]*volumeMount) + + // get the normal volumes + for path := range container.Config.Volumes { + path = filepath.Clean(path) + // skip if there is already a volume for this container path + if _, exists := container.Volumes[path]; exists { + continue + } + + realPath, err := container.getResourcePath(path) + if err != nil { + return err + } + if stat, err := os.Stat(realPath); err == nil { + if !stat.IsDir() { + return fmt.Errorf("can't mount to container path, file exists - %s", path) + } + } + + mnt := &volumeMount{ + containerPath: path, + writable: true, + copyData: true, + } + mounts[mnt.containerPath] = mnt } - sort.Strings(mountPaths) - return mountPaths -} + // Get all the bind mounts + // track bind paths separately due to #10618 + bindPaths := make(map[string]struct{}) + for _, spec := range container.hostConfig.Binds { + mnt, err := parseBindMountSpec(spec) + if err != nil { + return err + } -func (container *Container) createVolumes() error { - mounts, err := container.parseVolumeMountConfig() - if err != nil { - return err + // #10618 + if _, exists := bindPaths[mnt.containerPath]; exists { + return fmt.Errorf("Duplicate volume mount %s", mnt.containerPath) + } + + bindPaths[mnt.containerPath] = struct{}{} + mounts[mnt.containerPath] = mnt } - for _, mnt := range mounts { - if err := mnt.initialize(); err != nil { + // Get volumes from + for _, from := range container.hostConfig.VolumesFrom { + cID, mode, err := parseVolumesFromSpec(from) + if err != nil { return err } + if _, exists := container.AppliedVolumesFrom[cID]; exists { + // skip since it's already been applied + continue + } + + c, err := container.daemon.Get(cID) + if err != nil { + return fmt.Errorf("container %s not found, impossible to mount its volumes", cID) + } + + for _, mnt := range c.volumeMounts() { + mnt.writable = mnt.writable && (mode == "rw") + mnt.from = cID + mounts[mnt.containerPath] = mnt + } } - // On every start, this will apply any new `VolumesFrom` entries passed in via HostConfig, which may override volumes set in `create` - return container.applyVolumesFrom() -} + for _, mnt := range mounts { + containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, mnt.containerPath), container.basefs) + if err != nil { + return err + } -func (m *Mount) initialize() error { - // No need to initialize anything since it's already been initialized - if hostPath, exists := m.container.Volumes[m.MountToPath]; exists { - // If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create - // We need to make sure bind-mounts/volumes-from passed on start can override existing ones. - if (!m.volume.IsBindMount && !m.isBind) && m.from == nil { - return nil + // Create the actual volume + v, err := container.daemon.volumes.FindOrCreateVolume(mnt.hostPath, mnt.writable) + if err != nil { + return err } - if m.volume.Path == hostPath { - return nil + + container.VolumesRW[mnt.containerPath] = mnt.writable + container.Volumes[mnt.containerPath] = v.Path + v.AddContainer(container.ID) + if mnt.from != "" { + container.AppliedVolumesFrom[mnt.from] = struct{}{} } - // Make sure we remove these old volumes we don't actually want now. - // Ignore any errors here since this is just cleanup, maybe someone volumes-from'd this volume - if v := m.container.daemon.volumes.Get(hostPath); v != nil { - v.RemoveContainer(m.container.ID) - m.container.daemon.volumes.Delete(v.Path) + if mnt.writable && mnt.copyData { + // Copy whatever is in the container at the containerPath to the volume + copyExistingContents(containerMntPath, v.Path) } } - // This is the full path to container fs + mntToPath - containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs) - if err != nil { - return err - } - m.container.VolumesRW[m.MountToPath] = m.Writable - m.container.Volumes[m.MountToPath] = m.volume.Path - m.volume.AddContainer(m.container.ID) - if m.Writable && m.copyData { - // Copy whatever is in the container at the mntToPath to the volume - copyExistingContents(containerMntPath, m.volume.Path) + return nil +} + +// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order +func (container *Container) sortedVolumeMounts() []string { + var mountPaths []string + for path := range container.Volumes { + mountPaths = append(mountPaths, path) } - return nil + sort.Strings(mountPaths) + return mountPaths } func (container *Container) VolumePaths() map[string]struct{} { @@ -139,97 +185,30 @@ func (container *Container) derefVolumes() { } } -func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) { - var mounts = make(map[string]*Mount) - // Get all the bind mounts - for _, spec := range container.hostConfig.Binds { - path, mountToPath, writable, err := parseBindMountSpec(spec) - if err != nil { - return nil, err - } - // Check if a bind mount has already been specified for the same container path - if m, exists := mounts[mountToPath]; exists { - return nil, fmt.Errorf("Duplicate volume %q: %q already in use, mounted from %q", path, mountToPath, m.volume.Path) - } - // Check if a volume already exists for this and use it - vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable) - if err != nil { - return nil, err - } - mounts[mountToPath] = &Mount{ - container: container, - volume: vol, - MountToPath: mountToPath, - Writable: writable, - isBind: true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here - } - } - - // Get the rest of the volumes - for path := range container.Config.Volumes { - // Check if this is already added as a bind-mount - path = filepath.Clean(path) - if _, exists := mounts[path]; exists { - continue - } - - // Check if this has already been created - if _, exists := container.Volumes[path]; exists { - continue - } - realPath, err := container.getResourcePath(path) - if err != nil { - return nil, fmt.Errorf("failed to evaluate the absolute path of symlink") - } - if stat, err := os.Stat(realPath); err == nil { - if !stat.IsDir() { - return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath) - } - } - - vol, err := container.daemon.volumes.FindOrCreateVolume("", true) - if err != nil { - return nil, err - } - mounts[path] = &Mount{ - container: container, - MountToPath: path, - volume: vol, - Writable: true, - copyData: true, - } - } - - return mounts, nil -} - -func parseBindMountSpec(spec string) (string, string, bool, error) { - var ( - path, mountToPath string - writable bool - arr = strings.Split(spec, ":") - ) +func parseBindMountSpec(spec string) (*volumeMount, error) { + arr := strings.Split(spec, ":") + mnt := &volumeMount{} switch len(arr) { case 2: - path = arr[0] - mountToPath = arr[1] - writable = true + mnt.hostPath = arr[0] + mnt.containerPath = arr[1] + mnt.writable = true case 3: - path = arr[0] - mountToPath = arr[1] - writable = validMountMode(arr[2]) && arr[2] == "rw" + mnt.hostPath = arr[0] + mnt.containerPath = arr[1] + mnt.writable = validMountMode(arr[2]) && arr[2] == "rw" default: - return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec) + return nil, fmt.Errorf("Invalid volume specification: %s", spec) } - if !filepath.IsAbs(path) { - return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path) + if !filepath.IsAbs(mnt.hostPath) { + return nil, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", mnt.hostPath) } - path = filepath.Clean(path) - mountToPath = filepath.Clean(mountToPath) - return path, mountToPath, writable, nil + mnt.hostPath = filepath.Clean(mnt.hostPath) + mnt.containerPath = filepath.Clean(mnt.containerPath) + return mnt, nil } func parseVolumesFromSpec(spec string) (string, string, error) { @@ -251,54 +230,6 @@ func parseVolumesFromSpec(spec string) (string, string, error) { return id, mode, nil } -func (container *Container) applyVolumesFrom() error { - volumesFrom := container.hostConfig.VolumesFrom - if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil { - container.AppliedVolumesFrom = make(map[string]struct{}) - } - - mountGroups := make(map[string][]*Mount) - - for _, spec := range volumesFrom { - id, mode, err := parseVolumesFromSpec(spec) - if err != nil { - return err - } - if _, exists := container.AppliedVolumesFrom[id]; exists { - // Don't try to apply these since they've already been applied - continue - } - - c, err := container.daemon.Get(id) - if err != nil { - return fmt.Errorf("Could not apply volumes of non-existent container %q.", id) - } - - var ( - fromMounts = c.VolumeMounts() - mounts []*Mount - ) - - for _, mnt := range fromMounts { - mnt.Writable = mnt.Writable && (mode == "rw") - mounts = append(mounts, mnt) - } - mountGroups[id] = mounts - } - - for id, mounts := range mountGroups { - for _, mnt := range mounts { - mnt.from = mnt.container - mnt.container = container - if err := mnt.initialize(); err != nil { - return err - } - } - container.AppliedVolumesFrom[id] = struct{}{} - } - return nil -} - func validMountMode(mode string) bool { validModes := map[string]bool{ "rw": true, @@ -344,13 +275,17 @@ func (container *Container) setupMounts() error { return nil } -func (container *Container) VolumeMounts() map[string]*Mount { - mounts := make(map[string]*Mount) +func (container *Container) volumeMounts() map[string]*volumeMount { + mounts := make(map[string]*volumeMount) - for mountToPath, path := range container.Volumes { - if v := container.daemon.volumes.Get(path); v != nil { - mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]} + for containerPath, path := range container.Volumes { + v := container.daemon.volumes.Get(path) + if v == nil { + // This should never happen + logrus.Debugf("reference by container %s to non-existent volume path %s", container.ID, path) + continue } + mounts[containerPath] = &volumeMount{hostPath: path, containerPath: containerPath, writable: container.VolumesRW[containerPath]} } return mounts diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index a4ccd18ea4ed9..df5621af1880a 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -4504,7 +4504,7 @@ func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) { _, err := buildImage(name, ` FROM busybox - + ENV SOME_VAR a.b.c RUN [ "$SOME_VAR" = 'a.b.c' ] From b0ef3194aaa8c22b674ed5301c59e8e557a7e85e Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 23 Apr 2015 09:28:07 +0800 Subject: [PATCH 241/332] fix inspect format result Currently `docker inspect -f` use json.Unmarshal() unmarshal to interface, it will store all JSON numbers in float64, so we use `docker inspect 4f0d73b75a0d | grep Memory` and `docker inspect -f {{.HostConfig.Memory}} 4f0d73b75a0d` will get different values. Signed-off-by: Qiang Huang --- api/client/inspect.go | 9 +++++++-- integration-cli/docker_cli_build_test.go | 16 +++++++--------- integration-cli/docker_cli_inspect_test.go | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index f993030f9a1eb..75861cdf205ea 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -57,9 +57,14 @@ func (cli *DockerCli) CmdInspect(args ...string) error { continue } } else { - // Has template, will render var value interface{} - if err := json.Unmarshal(obj, &value); err != nil { + + // Do not use `json.Unmarshal()` because unmarshal JSON into + // an interface value, Unmarshal stores JSON numbers in + // float64, which is different from `json.Indent()` does. + dec := json.NewDecoder(bytes.NewReader(obj)) + dec.UseNumber() + if err := dec.Decode(&value); err != nil { fmt.Fprintf(cli.err, "%s\n", err) status = 1 continue diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index df5621af1880a..7936b2bc348e4 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5383,11 +5383,11 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { cID := strings.TrimSpace(out) type hostConfig struct { - Memory float64 // Use float64 here since the json decoder sees it that way - MemorySwap int + Memory int64 + MemorySwap int64 CpusetCpus string CpusetMems string - CpuShares int + CpuShares int64 } cfg, err := inspectFieldJSON(cID, "HostConfig") @@ -5399,10 +5399,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c1); err != nil { c.Fatal(err, cfg) } - mem := int64(c1.Memory) - if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { + if c1.Memory != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - mem, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) + c1.Memory, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) } // Make sure constraints aren't saved to image @@ -5416,10 +5415,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c2); err != nil { c.Fatal(err, cfg) } - mem = int64(c2.Memory) - if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { + if c2.Memory == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - mem, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) + c2.Memory, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) } } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index 73eb7c8af692c..9a5a7b660482e 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -21,3 +21,23 @@ func (s *DockerSuite) TestInspectImage(c *check.C) { } } + +func (s *DockerSuite) TestInspectInt64(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "-m=300M", "busybox", "true") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + c.Fatalf("failed to run container: %v, output: %q", err, out) + } + + out = strings.TrimSpace(out) + + inspectCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.Memory}}", out) + inspectOut, _, err := runCommandWithOutput(inspectCmd) + if err != nil { + c.Fatalf("failed to inspect container: %v, output: %q", err, inspectOut) + } + + if strings.TrimSpace(inspectOut) != "314572800" { + c.Fatalf("inspect got wrong value, got: %q, expected: 314572800", inspectOut) + } +} From dde0cc78bdec31be1ecbd7def6a83111224ccc55 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 23 Apr 2015 10:23:02 +0800 Subject: [PATCH 242/332] Move setHostConfig to daemon file Signed-off-by: Ma Shimiao --- daemon/daemon.go | 18 ++++++++++++++++++ daemon/start.go | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 8873b4cacd238..bfebd920f82a2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1248,3 +1248,21 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri return warnings, nil } + +func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error { + container.Lock() + defer container.Unlock() + if err := parseSecurityOpt(container, hostConfig); err != nil { + return err + } + + // Register any links from the host config before starting the container + if err := daemon.RegisterLinks(container, hostConfig); err != nil { + return err + } + + container.hostConfig = hostConfig + container.toDisk() + + return nil +} diff --git a/daemon/start.go b/daemon/start.go index d3af073a884da..09b8b2881a41a 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -39,21 +39,3 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConf return nil } - -func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error { - container.Lock() - defer container.Unlock() - if err := parseSecurityOpt(container, hostConfig); err != nil { - return err - } - - // Register any links from the host config before starting the container - if err := daemon.RegisterLinks(container, hostConfig); err != nil { - return err - } - - container.hostConfig = hostConfig - container.toDisk() - - return nil -} From 63593267619378520a03e8984c5fcf0ec8957537 Mon Sep 17 00:00:00 2001 From: Rick Wieman Date: Tue, 21 Apr 2015 17:50:09 +0200 Subject: [PATCH 243/332] Makes headings in documentation consistent Fixes #10673. Signed-off-by: Rick Wieman --- docs/mkdocs.yml | 22 +++++----- .../articles/ambassador_pattern_linking.md | 6 +-- docs/sources/articles/b2d_volume_resize.md | 4 +- docs/sources/articles/baseimages.md | 4 +- .../articles/cfengine_process_management.md | 4 +- docs/sources/articles/chef.md | 2 +- .../articles/dockerfile_best-practices.md | 6 +-- docs/sources/articles/host_integration.md | 6 +-- docs/sources/articles/https.md | 6 +-- docs/sources/articles/networking.md | 14 +++---- docs/sources/articles/puppet.md | 2 +- docs/sources/articles/runmetrics.md | 16 ++++---- docs/sources/articles/security.md | 14 +++---- docs/sources/articles/systemd.md | 8 ++-- .../docker-hub-enterprise/install-config.md | 6 +-- docs/sources/docker-hub/accounts.md | 6 +-- docs/sources/docker-hub/builds.md | 6 +-- docs/sources/docker-hub/home.md | 4 +- docs/sources/docker-hub/index.md | 4 +- docs/sources/docker-hub/official_repos.md | 10 ++--- docs/sources/docker-hub/repos.md | 10 ++--- docs/sources/examples.md | 12 +++--- docs/sources/examples/apt-cacher-ng.md | 2 +- docs/sources/examples/couchdb_data_volumes.md | 4 +- docs/sources/examples/nodejs_web_app.md | 4 +- .../sources/examples/running_redis_service.md | 4 +- docs/sources/examples/running_riak_service.md | 2 +- docs/sources/examples/running_ssh_service.md | 2 +- docs/sources/http-routingtable.md | 2 +- docs/sources/index.md | 6 +-- docs/sources/installation/azure.md | 2 +- docs/sources/installation/binaries.md | 8 ++-- docs/sources/installation/cruxlinux.md | 2 +- docs/sources/installation/mac.md | 4 +- docs/sources/installation/oracle.md | 2 +- docs/sources/installation/rhel.md | 4 +- docs/sources/installation/ubuntulinux.md | 4 +- docs/sources/installation/windows.md | 4 +- .../introduction/understanding-docker.md | 6 +-- docs/sources/project/advanced-contributing.md | 2 +- docs/sources/project/coding-style.md | 4 +- docs/sources/project/create-pr.md | 2 +- docs/sources/project/doc-style.md | 2 +- docs/sources/project/review-pr.md | 6 +-- docs/sources/reference/api/docker-io_api.md | 22 +++++----- .../reference/api/docker_io_accounts_api.md | 4 +- .../reference/api/docker_remote_api.md | 40 +++++++++---------- .../reference/api/docker_remote_api_v1.9.md | 6 +-- .../reference/api/hub_registry_spec.md | 6 +-- .../api/registry_api_client_libraries.md | 4 +- .../api/remote_api_client_libraries.md | 4 +- docs/sources/reference/builder.md | 10 ++--- docs/sources/reference/commandline/cli.md | 6 +-- docs/sources/reference/run.md | 4 +- docs/sources/release-notes.md | 12 +++--- docs/sources/terms/container.md | 2 +- docs/sources/terms/filesystem.md | 4 +- docs/sources/terms/image.md | 6 +-- docs/sources/terms/registry.md | 2 +- docs/sources/userguide/dockerhub.md | 4 +- docs/sources/userguide/dockerimages.md | 4 +- docs/sources/userguide/dockerizing.md | 8 ++-- docs/sources/userguide/dockerlinks.md | 8 ++-- docs/sources/userguide/dockerrepos.md | 4 +- docs/sources/userguide/dockervolumes.md | 10 ++--- docs/sources/userguide/index.md | 18 ++++----- docs/sources/userguide/level1.md | 4 +- docs/sources/userguide/level2.md | 4 +- docs/sources/userguide/usingdocker.md | 22 +++++----- 69 files changed, 234 insertions(+), 234 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 1ff27071d910d..df9d95997ce33 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -25,7 +25,7 @@ pages: # Introduction: - ['index.md', 'About', 'Docker'] -- ['release-notes.md', 'About', 'Release Notes'] +- ['release-notes.md', 'About', 'Release notes'] - ['introduction/index.md', '**HIDDEN**'] - ['introduction/understanding-docker.md', 'About', 'Understanding Docker'] @@ -54,11 +54,11 @@ pages: - ['compose/install.md', 'Installation', 'Docker Compose'] # User Guide: -- ['userguide/index.md', 'User Guide', 'The Docker User Guide' ] -- ['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/index.md', 'User Guide', 'The Docker user guide' ] +- ['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/labels-custom-metadata.md', 'User Guide', 'Apply custom metadata' ] @@ -76,7 +76,7 @@ pages: - ['docker-hub/accounts.md', 'Docker Hub', 'Accounts'] - ['docker-hub/repos.md', 'Docker Hub', 'Repositories'] - ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] -- ['docker-hub/official_repos.md', 'Docker Hub', 'Official Repo Guidelines'] +- ['docker-hub/official_repos.md', 'Docker Hub', 'Official repo guidelines'] # Docker Hub Enterprise #- ['docker-hub-enterprise/index.md', '**HIDDEN**' ] @@ -125,7 +125,7 @@ pages: - ['reference/commandline/cli.md', 'Reference', 'Docker command line'] - ['reference/builder.md', 'Reference', 'Dockerfile'] - ['faq.md', 'Reference', 'FAQ'] -- ['reference/run.md', 'Reference', 'Run Reference'] +- ['reference/run.md', 'Reference', 'Run reference'] - ['compose/cli.md', 'Reference', 'Compose command line'] - ['compose/yml.md', 'Reference', 'Compose yml'] - ['compose/env.md', 'Reference', 'Compose ENV variables'] @@ -145,7 +145,7 @@ pages: - ['registry/spec/auth/token.md', 'Reference', '    ▪  Authenticate via central service' ] - ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry 1.0'] - ['reference/api/registry_api.md', 'Reference', '    ▪ Docker Registry API v1'] -- ['reference/api/registry_api_client_libraries.md', 'Reference', '    ▪ Docker Registry 1.0 API Client Libraries'] +- ['reference/api/registry_api_client_libraries.md', 'Reference', '    ▪ Docker Registry 1.0 API client libraries'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] @@ -169,8 +169,8 @@ pages: - ['reference/api/docker_remote_api_v1.2.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.1.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.0.md', '**HIDDEN**'] -- ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries'] -- ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker Hub Accounts API'] +- ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API client libraries'] +- ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker Hub accounts API'] # Hidden registry files - ['registry/storage-drivers/azure.md', '**HIDDEN**' ] diff --git a/docs/sources/articles/ambassador_pattern_linking.md b/docs/sources/articles/ambassador_pattern_linking.md index 755fa4dc9c468..2f168262a31aa 100644 --- a/docs/sources/articles/ambassador_pattern_linking.md +++ b/docs/sources/articles/ambassador_pattern_linking.md @@ -1,8 +1,8 @@ -page_title: Link via an Ambassador Container +page_title: Link via an ambassador container page_description: Using the Ambassador pattern to abstract (network) services page_keywords: Examples, Usage, links, docker, documentation, examples, names, name, container naming -# Link via an Ambassador Container +# Link via an ambassador container ## Introduction @@ -30,7 +30,7 @@ different docker host from the consumer. Using the `svendowideit/ambassador` container, the link wiring is controlled entirely from the `docker run` parameters. -## Two host Example +## Two host example Start actual Redis server on one Docker host diff --git a/docs/sources/articles/b2d_volume_resize.md b/docs/sources/articles/b2d_volume_resize.md index 1b39b49eda461..65238c669b579 100644 --- a/docs/sources/articles/b2d_volume_resize.md +++ b/docs/sources/articles/b2d_volume_resize.md @@ -1,5 +1,5 @@ -page_title: Resizing a Boot2Docker Volume -page_description: Resizing a Boot2Docker Volume in VirtualBox with GParted +page_title: Resizing a Boot2Docker volume +page_description: Resizing a Boot2Docker volume in VirtualBox with GParted page_keywords: boot2docker, volume, virtualbox # Getting “no space left on device” errors with Boot2Docker? diff --git a/docs/sources/articles/baseimages.md b/docs/sources/articles/baseimages.md index 701f432ffb111..a54f5307ad570 100644 --- a/docs/sources/articles/baseimages.md +++ b/docs/sources/articles/baseimages.md @@ -1,8 +1,8 @@ -page_title: Create a Base Image +page_title: Create a base image page_description: How to create base images page_keywords: Examples, Usage, base image, docker, documentation, examples -# Create a Base Image +# Create a base image So you want to create your own [*Base Image*]( /terms/image/#base-image)? Great! diff --git a/docs/sources/articles/cfengine_process_management.md b/docs/sources/articles/cfengine_process_management.md index a9441a6d351e2..b0437268b0a5f 100644 --- a/docs/sources/articles/cfengine_process_management.md +++ b/docs/sources/articles/cfengine_process_management.md @@ -1,8 +1,8 @@ -page_title: Process Management with CFEngine +page_title: Process management with CFEngine page_description: Managing containerized processes with CFEngine page_keywords: cfengine, process, management, usage, docker, documentation -# Process Management with CFEngine +# Process management with CFEngine Create Docker containers with managed processes. diff --git a/docs/sources/articles/chef.md b/docs/sources/articles/chef.md index 8fe0504ffa556..84ccdffb2be99 100644 --- a/docs/sources/articles/chef.md +++ b/docs/sources/articles/chef.md @@ -1,4 +1,4 @@ -page_title: Chef Usage +page_title: Using Chef page_description: Installation and using Docker via Chef page_keywords: chef, installation, usage, docker, documentation diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 83a77fc74d8a1..425eb86583ac3 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -1,4 +1,4 @@ -page_title: Best Practices for Writing Dockerfiles +page_title: Best practices for writing Dockerfiles page_description: Hints, tips and guidelines for writing clean, reliable Dockerfiles page_keywords: Examples, Usage, base image, docker, documentation, dockerfile, best practices, hub, official repo @@ -419,7 +419,7 @@ fail catastrophically if the new build's context is missing the resource being added. Adding a separate tag, as recommended above, will help mitigate this by allowing the `Dockerfile` author to make a choice. -## Examples For Official Repositories +## Examples for official repositories These Official Repos have exemplary `Dockerfile`s: @@ -428,7 +428,7 @@ These Official Repos have exemplary `Dockerfile`s: * [Hy](https://registry.hub.docker.com/_/hylang/) * [Rails](https://registry.hub.docker.com/_/rails) -## Additional Resources: +## Additional resources: * [Dockerfile Reference](https://docs.docker.com/reference/builder/#onbuild) * [More about Base Images](https://docs.docker.com/articles/baseimages/) diff --git a/docs/sources/articles/host_integration.md b/docs/sources/articles/host_integration.md index cbcb21a357bd0..e3451764bb056 100644 --- a/docs/sources/articles/host_integration.md +++ b/docs/sources/articles/host_integration.md @@ -1,8 +1,8 @@ -page_title: Automatically Start Containers +page_title: Automatically start containers page_description: How to generate scripts for upstart, systemd, etc. page_keywords: systemd, upstart, supervisor, docker, documentation, host integration -# Automatically Start Containers +# Automatically start containers As of Docker 1.2, [restart policies](/reference/commandline/cli/#restart-policies) are the @@ -18,7 +18,7 @@ that depend on Docker containers), you can use a process manager like [supervisor](http://supervisord.org/) instead. -## Using a Process Manager +## Using a process manager Docker does not set any restart policies by default, but be aware that they will conflict with most process managers. So don't set restart policies if you are diff --git a/docs/sources/articles/https.md b/docs/sources/articles/https.md index 94d9ca3f22374..d6689bbf1364e 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/https.md @@ -1,8 +1,8 @@ -page_title: Protecting the Docker daemon Socket with HTTPS +page_title: Protecting the Docker daemon socket with HTTPS page_description: How to setup and run Docker with HTTPS page_keywords: docker, docs, article, example, https, daemon, tls, ca, certificate -# Protecting the Docker daemon Socket with HTTPS +# Protecting the Docker daemon socket with HTTPS By default, Docker runs via a non-networked Unix socket. It can also optionally communicate using a HTTP socket. @@ -193,7 +193,7 @@ location using the environment variable `DOCKER_CERT_PATH`. $ export DOCKER_CERT_PATH=~/.docker/zone1/ $ docker --tlsverify ps -### Connecting to the Secure Docker port using `curl` +### Connecting to the secure Docker port using `curl` To use `curl` to make test API requests, you need to use three extra command line flags: diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 2ce52ce0e0512..18529a0864208 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -1,8 +1,8 @@ -page_title: Network Configuration +page_title: Network configuration page_description: Docker networking page_keywords: network, networking, bridge, docker, documentation -# Network Configuration +# Network configuration ## TL;DR @@ -41,7 +41,7 @@ can use Docker options and — in advanced cases — raw Linux networking commands to tweak, supplement, or entirely replace Docker's default networking configuration. -## Quick Guide to the Options +## Quick guide to the options 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 @@ -601,9 +601,9 @@ You have to execute the `ip -6 neigh add proxy ...` command for every IPv6 address in your Docker subnet. Unfortunately there is no functionality for adding a whole subnet by executing one command. -### Docker IPv6 Cluster +### Docker IPv6 cluster -#### Switched Network Environment +#### Switched network environment Using routable IPv6 addresses allows you to realize communication between containers on different hosts. Let's have a look at a simple Docker IPv6 cluster example: @@ -649,7 +649,7 @@ the Docker subnet on the host, the container IP addresses and the routes on the containers. The configuration above the line is up to the user and can be adapted to the individual environment. -#### Routed Network Environment +#### Routed network environment In a routed network environment you replace the layer 2 switch with a layer 3 router. Now the hosts just have to know their default gateway (the router) and @@ -993,7 +993,7 @@ 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 +## 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 diff --git a/docs/sources/articles/puppet.md b/docs/sources/articles/puppet.md index 705285fbaf852..50504cd475b21 100644 --- a/docs/sources/articles/puppet.md +++ b/docs/sources/articles/puppet.md @@ -1,4 +1,4 @@ -page_title: Puppet Usage +page_title: Using Puppet page_description: Installating and using Puppet page_keywords: puppet, installation, usage, docker, documentation diff --git a/docs/sources/articles/runmetrics.md b/docs/sources/articles/runmetrics.md index 3276409697426..a887d4369a6a8 100644 --- a/docs/sources/articles/runmetrics.md +++ b/docs/sources/articles/runmetrics.md @@ -1,8 +1,8 @@ -page_title: Runtime Metrics +page_title: Runtime metrics page_description: Measure the behavior of running containers page_keywords: docker, metrics, CPU, memory, disk, IO, run, runtime -# Runtime Metrics +# Runtime metrics Linux Containers rely on [control groups]( https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt) @@ -11,7 +11,7 @@ CPU, memory, and block I/O usage. You can access those metrics and obtain network usage metrics as well. This is relevant for "pure" LXC containers, as well as for Docker containers. -## Control Groups +## Control groups Control groups are exposed through a pseudo-filesystem. In recent distros, you should find this filesystem under `/sys/fs/cgroup`. Under @@ -28,7 +28,7 @@ To figure out where your control groups are mounted, you can run: $ grep cgroup /proc/mounts -## Enumerating Cgroups +## Enumerating cgroups You can look into `/proc/cgroups` to see the different control group subsystems known to the system, the hierarchy they belong to, and how many groups they contain. @@ -39,7 +39,7 @@ the hierarchy mountpoint; e.g., `/` means “this process has not been assigned a particular group”, while `/lxc/pumpkin` means that the process is likely to be a member of a container named `pumpkin`. -## Finding the Cgroup for a Given Container +## Finding the cgroup for a given container For each container, one cgroup will be created in each hierarchy. On older systems with older versions of the LXC userland tools, the name of @@ -55,12 +55,12 @@ 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//`. -## Metrics from Cgroups: Memory, CPU, Block IO +## Metrics from cgroups: memory, CPU, block I/O For each subsystem (memory, CPU, and block I/O), you will find one or more pseudo-files containing statistics. -### Memory Metrics: `memory.stat` +### Memory metrics: `memory.stat` Memory metrics are found in the "memory" cgroup. Note that the memory control group adds a little overhead, because it does very fine-grained @@ -262,7 +262,7 @@ relevant ones: not perform more I/O, its queue size can increase just because the device load increases because of other devices. -## Network Metrics +## Network metrics Network metrics are not exposed directly by control groups. There is a good explanation for that: network interfaces exist within the context diff --git a/docs/sources/articles/security.md b/docs/sources/articles/security.md index a26f79cf9bf9e..39a247c38e9e0 100644 --- a/docs/sources/articles/security.md +++ b/docs/sources/articles/security.md @@ -1,8 +1,8 @@ -page_title: Docker Security +page_title: Docker security page_description: Review of the Docker Daemon attack surface page_keywords: Docker, Docker documentation, security -# Docker Security +# Docker security There are three major areas to consider when reviewing Docker security: @@ -14,7 +14,7 @@ There are three major areas to consider when reviewing Docker security: - the "hardening" security features of the kernel and how they interact with containers. -## Kernel Namespaces +## Kernel namespaces Docker containers are very similar to LXC containers, and they have similar security features. When you start a container with `docker @@ -53,7 +53,7 @@ http://en.wikipedia.org/wiki/OpenVZ) in such a way that they could be merged within the mainstream kernel. And OpenVZ was initially released in 2005, so both the design and the implementation are pretty mature. -## Control Groups +## Control groups Control Groups are another key component of Linux Containers. They implement resource accounting and limiting. They provide many @@ -72,7 +72,7 @@ when some applications start to misbehave. Control Groups have been around for a while as well: the code was started in 2006, and initially merged in kernel 2.6.24. -## Docker Daemon Attack Surface +## Docker daemon attack surface Running containers (and applications) with Docker implies running the Docker daemon. This daemon currently requires `root` privileges, and you @@ -132,7 +132,7 @@ containers controlled by Docker. Of course, it is fine to keep your favorite admin tools (probably at least an SSH server), as well as existing monitoring/supervision processes (e.g., NRPE, collectd, etc). -## Linux Kernel Capabilities +## Linux kernel capabilities By default, Docker starts containers with a restricted set of capabilities. What does that mean? @@ -206,7 +206,7 @@ capability removal, or less secure through the addition of capabilities. The best practice for users would be to remove all capabilities except those explicitly required for their processes. -## Other Kernel Security Features +## Other kernel security features Capabilities are just one of the many security features provided by modern Linux kernels. It is also possible to leverage existing, diff --git a/docs/sources/articles/systemd.md b/docs/sources/articles/systemd.md index fddd146b0702b..c4c0d2c81d0c9 100644 --- a/docs/sources/articles/systemd.md +++ b/docs/sources/articles/systemd.md @@ -1,8 +1,8 @@ -page_title: Controlling and configuring Docker using Systemd -page_description: Controlling and configuring Docker using Systemd +page_title: Controlling and configuring Docker using systemd +page_description: Controlling and configuring Docker using systemd page_keywords: docker, daemon, systemd, configuration -# Controlling and configuring Docker using Systemd +# Controlling and configuring Docker using systemd Many Linux distributions use systemd to start the Docker daemon. This document shows a few examples of how to customise Docker's settings. @@ -64,7 +64,7 @@ setting `OPTIONS`: You can also set other environment variables in this file, for example, the `HTTP_PROXY` environment variables described below. -### HTTP Proxy +### HTTP proxy This example overrides the default `docker.service` file. diff --git a/docs/sources/docker-hub-enterprise/install-config.md b/docs/sources/docker-hub-enterprise/install-config.md index 0b7bcfd6fe633..81fa3041efbab 100644 --- a/docs/sources/docker-hub-enterprise/install-config.md +++ b/docs/sources/docker-hub-enterprise/install-config.md @@ -1,8 +1,8 @@ -page_title: Using Docker Hub Enterprise Installation -page_description: Docker Hub Enterprise Installation +page_title: Using Docker Hub Enterprise installation +page_description: Docker Hub Enterprise installation page_keywords: docker hub enterprise -# Docker Hub Enterprise Installation +# Docker Hub Enterprise installation Documenation coming soon. diff --git a/docs/sources/docker-hub/accounts.md b/docs/sources/docker-hub/accounts.md index e4623f99809c3..360eb371f395d 100644 --- a/docs/sources/docker-hub/accounts.md +++ b/docs/sources/docker-hub/accounts.md @@ -4,7 +4,7 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub # Accounts on Docker Hub -## Docker Hub Accounts +## 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 @@ -12,7 +12,7 @@ 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 +### 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 @@ -32,7 +32,7 @@ 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. -## Organizations & Groups +## Organizations and groups Also available on the Docker Hub are organizations and groups that allow you to collaborate across your organization or team. You can see what diff --git a/docs/sources/docker-hub/builds.md b/docs/sources/docker-hub/builds.md index bd3e3d2cb94b5..541bc159462cf 100644 --- a/docs/sources/docker-hub/builds.md +++ b/docs/sources/docker-hub/builds.md @@ -83,7 +83,7 @@ You will be able to review and revoke Docker Hub's access by visiting the > using the "Start Build" button on the Hub, or if the webhook on the GitHub repository > still exists, will be triggered by any subsequent commits. -### Auto builds and Limited linked GitHub accounts. +### Auto builds and limited linked GitHub accounts. If you selected to link your GitHub account with only a "Limited" link, then after creating your automated build, you will need to either manually trigger a @@ -101,7 +101,7 @@ section, "Revoke access". You can now re-link your account at any time. -### GitHub Organizations +### GitHub organizations GitHub organizations and private repositories forked from organizations will be made available to auto build using the "Docker Hub Registry" application, which @@ -205,7 +205,7 @@ can be limited to read-only access to just the repositories required to build. -### GitHub Service hooks +### GitHub service hooks The GitHub Service hook allows GitHub to notify the Docker Hub when something has been committed to that git repository. You will need to add the Service Hook manually diff --git a/docs/sources/docker-hub/home.md b/docs/sources/docker-hub/home.md index 15baf7b83a39f..3f81208c194fc 100644 --- a/docs/sources/docker-hub/home.md +++ b/docs/sources/docker-hub/home.md @@ -1,8 +1,8 @@ -page_title: The Docker Hub 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 Hub, docs, documentation -# The Docker Hub Registry Help +# The Docker Hub Registry help ## Introduction diff --git a/docs/sources/docker-hub/index.md b/docs/sources/docker-hub/index.md index c29a5f787314f..3651497e2c212 100644 --- a/docs/sources/docker-hub/index.md +++ b/docs/sources/docker-hub/index.md @@ -1,4 +1,4 @@ -page_title: The Docker Hub Help +page_title: The Docker Hub help page_description: The Docker Help documentation home page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation, accounts, organizations, repositories, groups @@ -16,7 +16,7 @@ account and manage your organizations and groups. Find out how to share your Docker images in [Docker Hub repositories](repos/) and how to store and manage private images. -## [Automated Builds](builds/) +## [Automated builds](builds/) Learn how to automate your build and deploy pipeline with [Automated Builds](builds/) diff --git a/docs/sources/docker-hub/official_repos.md b/docs/sources/docker-hub/official_repos.md index 4ec431238bbbc..a101d88c1dc62 100644 --- a/docs/sources/docker-hub/official_repos.md +++ b/docs/sources/docker-hub/official_repos.md @@ -1,8 +1,8 @@ -page_title: Guidelines for Official Repositories on Docker Hub +page_title: Guidelines for official repositories on Docker Hub page_description: Guidelines for Official Repositories on Docker Hub page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, official, image, documentation -# Guidelines for Creating and Documenting Official Repositories +# Guidelines for creating and documenting official repositories ## Introduction @@ -18,7 +18,7 @@ This document consists of two major sections: along with best practices for creating those items * Examples embodying those practices -## Expected Files & Resources +## Expected files and resources ### A Git repository @@ -92,7 +92,7 @@ In terms of content, the long description must include the following sections: * How-to/usage * Issues & contributions -#### Overview & links +#### Overview and links This section should provide: @@ -109,7 +109,7 @@ A section that describes how to run and use the image, including common use cases and example `Dockerfile`s (if applicable). Try to provide clear, step-by- step instructions wherever possible. -##### Issues & contributions +##### Issues and contributions In this section, point users to any resources that can help them contribute to the project. Include contribution guidelines and any specific instructions diff --git a/docs/sources/docker-hub/repos.md b/docs/sources/docker-hub/repos.md index 35cd4f8ccbeb5..0a2fa6550094a 100644 --- a/docs/sources/docker-hub/repos.md +++ b/docs/sources/docker-hub/repos.md @@ -1,8 +1,8 @@ -page_title: Repositories and Images on Docker Hub -page_description: Repositories and Images on Docker Hub +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, webhooks, docs, documentation -# Repositories and Images on Docker Hub +# Repositories and images on Docker Hub ![repositories](/docker-hub/hub-images/repos.png) @@ -51,7 +51,7 @@ private to public. You can also collaborate on Docker Hub with organizations and groups. You can read more about that [here](accounts/). -## Official Repositories +## Official repositories The Docker Hub contains a number of [official repositories](http://registry.hub.docker.com/official). These are @@ -67,7 +67,7 @@ optimized and up-to-date image to power your applications. > organization, product or team you can see more information > [here](https://github.com/docker/stackbrew). -## Private Repositories +## Private repositories Private repositories allow you to have repositories that contain images that you want to keep private, either to your own account or within an diff --git a/docs/sources/examples.md b/docs/sources/examples.md index 9dcd67a643343..f4d5b868ef699 100644 --- a/docs/sources/examples.md +++ b/docs/sources/examples.md @@ -1,9 +1,9 @@ # Examples - - [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 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/) + - [Dockerizing a Riak service](running_riak_service/) diff --git a/docs/sources/examples/apt-cacher-ng.md b/docs/sources/examples/apt-cacher-ng.md index 9a3631220ec4c..57aa669666eff 100644 --- a/docs/sources/examples/apt-cacher-ng.md +++ b/docs/sources/examples/apt-cacher-ng.md @@ -2,7 +2,7 @@ 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 -# Dockerizing an Apt-Cacher-ng Service +# Dockerizing an apt-cacher-ng service > **Note**: > - **If you don't like sudo** then see [*Giving non-root diff --git a/docs/sources/examples/couchdb_data_volumes.md b/docs/sources/examples/couchdb_data_volumes.md index 483168ae21178..27bce34a95b7e 100644 --- a/docs/sources/examples/couchdb_data_volumes.md +++ b/docs/sources/examples/couchdb_data_volumes.md @@ -1,8 +1,8 @@ -page_title: Dockerizing a CouchDB Service +page_title: Dockerizing a CouchDB service page_description: Sharing data between 2 couchdb databases page_keywords: docker, example, package installation, networking, couchdb, data volumes -# Dockerizing a CouchDB Service +# Dockerizing a CouchDB service > **Note**: > - **If you don't like sudo** then see [*Giving non-root diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/sources/examples/nodejs_web_app.md index 1db61ae624ca4..ff7179a811496 100644 --- a/docs/sources/examples/nodejs_web_app.md +++ b/docs/sources/examples/nodejs_web_app.md @@ -1,8 +1,8 @@ -page_title: Dockerizing a Node.js Web App +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 -# Dockerizing a Node.js Web App +# Dockerizing a Node.js web app > **Note**: > - **If you don't like sudo** then see [*Giving non-root diff --git a/docs/sources/examples/running_redis_service.md b/docs/sources/examples/running_redis_service.md index a00db9896420f..c46bb09c770f7 100644 --- a/docs/sources/examples/running_redis_service.md +++ b/docs/sources/examples/running_redis_service.md @@ -2,12 +2,12 @@ page_title: Dockerizing a Redis service page_description: Installing and running an redis service page_keywords: docker, example, package installation, networking, redis -# Dockerizing a Redis Service +# Dockerizing a Redis service Very simple, no frills, Redis service attached to a web application using a link. -## Create a docker container for Redis +## Create a Docker container for Redis Firstly, we create a `Dockerfile` for our new Redis image. diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 6d49cc87eb04b..1b14c3a417b31 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -2,7 +2,7 @@ page_title: Dockerizing a Riak service page_description: Build a Docker image with Riak pre-installed page_keywords: docker, example, package installation, networking, riak -# Dockerizing a Riak Service +# Dockerizing a Riak service The goal of this example is to show you how to build a Docker image with Riak pre-installed. diff --git a/docs/sources/examples/running_ssh_service.md b/docs/sources/examples/running_ssh_service.md index e2fc3782d5ca2..b1000a04acee0 100644 --- a/docs/sources/examples/running_ssh_service.md +++ b/docs/sources/examples/running_ssh_service.md @@ -2,7 +2,7 @@ page_title: Dockerizing an SSH service page_description: Installing and running an SSHd service on Docker page_keywords: docker, example, package installation, networking -# Dockerizing an SSH Daemon Service +# Dockerizing an SSH daemon service ## Build an `eg_sshd` image diff --git a/docs/sources/http-routingtable.md b/docs/sources/http-routingtable.md index 07029d2ca8d83..14e1dfcd2e30d 100644 --- a/docs/sources/http-routingtable.md +++ b/docs/sources/http-routingtable.md @@ -1,4 +1,4 @@ -# HTTP Routing Table +# HTTP routing table [**/api**](#cap-/api) | [**/auth**](#cap-/auth) | [**/build**](#cap-/build) | [**/commit**](#cap-/commit) | diff --git a/docs/sources/index.md b/docs/sources/index.md index 993603eb33cc8..ef827acba1a3d 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -75,18 +75,18 @@ The [Understanding Docker section](introduction/understanding-docker.md) will he - See how Docker compares to virtual machines - See some common use cases. -### Installation Guides +### Installation guides The [installation section](/installation/#installation) will show you how to install Docker on a variety of platforms. -### Docker User Guide +### Docker user guide To learn about Docker in more detail and to answer questions about usage and implementation, check out the [Docker User Guide](/userguide/). -## Release Notes +## Release notes A summary of the changes in each release in the current series can now be found on the separate [Release Notes page](/release-notes/) diff --git a/docs/sources/installation/azure.md b/docs/sources/installation/azure.md index a8e700fead838..54910228ec77b 100644 --- a/docs/sources/installation/azure.md +++ b/docs/sources/installation/azure.md @@ -1,4 +1,4 @@ -page_title: Installation on Microsoft Azure Platform +page_title: Installation on Microsoft Azure platform page_description: Instructions for creating a Docker-ready virtual machine on Microsoft Azure cloud platform. page_keywords: Docker, Docker documentation, installation, azure, microsoft diff --git a/docs/sources/installation/binaries.md b/docs/sources/installation/binaries.md index 855d4602874b6..a9a96bec0d9f0 100644 --- a/docs/sources/installation/binaries.md +++ b/docs/sources/installation/binaries.md @@ -1,4 +1,4 @@ -page_title: Installation from Binaries +page_title: Installation from binaries 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 @@ -78,7 +78,7 @@ exhibit unexpected behaviour. > vendor for the system, and might break regulations and security > policies in heavily regulated environments. -## Get the docker binary +## Get the Docker binary You can download either the latest release binary or a specific version. After downloading a binary file, you must set the file's execute bit to run it. @@ -141,7 +141,7 @@ For example: https://get.docker.com/builds/Darwin/x86_64/docker-1.6.0 -### Get the Windows binary +### Get the Windows binary You can only download the Windows client binary for version `1.6.0` onwards. Moreover, the binary is only a client, you cannot use it to run the `docker` daemon. @@ -164,7 +164,7 @@ For example: https://get.docker.com/builds/Windows/x86_64/docker-1.6.0.exe -## Run the docker daemon +## Run the Docker daemon # start the docker in daemon mode from the directory you unpacked $ sudo ./docker -d & diff --git a/docs/sources/installation/cruxlinux.md b/docs/sources/installation/cruxlinux.md index ead4c273caf12..d474aa52f873a 100644 --- a/docs/sources/installation/cruxlinux.md +++ b/docs/sources/installation/cruxlinux.md @@ -20,7 +20,7 @@ Assuming you have contrib enabled, update your ports tree and install docker (*a # prt-get depinst docker -## Kernel Requirements +## Kernel requirements To have a working **CRUX+Docker** Host you must ensure your Kernel has the necessary modules enabled for the Docker Daemon to function correctly. diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 9326f5fc47a25..4b157c1682c60 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -162,7 +162,7 @@ Initialize and run `boot2docker` from the command line, do the following: $ docker run hello-world -## Basic Boot2Docker Exercises +## Basic Boot2Docker exercises At this point, you should have `boot2docker` running and the `docker` client environment initialized. To verify this, run the following commands: @@ -314,7 +314,7 @@ section. The installer places Boot2Docker in your "Applications" folder. -## Learning more and Acknowledgement +## Learning more and acknowledgement Use `boot2docker help` to list the full command line reference. For more diff --git a/docs/sources/installation/oracle.md b/docs/sources/installation/oracle.md index 6d2f782b49264..e05e664c120dc 100644 --- a/docs/sources/installation/oracle.md +++ b/docs/sources/installation/oracle.md @@ -110,7 +110,7 @@ service. On Oracle Linux 7, you can use a `systemd.mount` definition and modify the Docker `systemd.service` to depend on the btrfs mount defined in systemd. -### SElinux Support on Oracle Linux 7 +### SElinux support on Oracle Linux 7 SElinux must be set to `Permissive` or `Disabled` in `/etc/sysconfig/selinux` to use the btrfs storage engine on Oracle Linux 7. diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 58b2316c6f3cb..7be8debce5b1c 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -16,7 +16,7 @@ running on kernels shipped by the distribution. There are kernel changes which will cause issues if one decides to step outside that box and run non-distribution kernel packages. -## Red Hat Enterprise Linux 7 Installation +## Red Hat Enterprise Linux 7 installation **Red Hat Enterprise Linux 7 (64 bit)** has [shipped with Docker](https://access.redhat.com/site/products/red-hat-enterprise-linux/docker-and-containers). @@ -41,7 +41,7 @@ Portal](https://access.redhat.com/). Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -## Red Hat Enterprise Linux 6.5 Installation +## Red Hat Enterprise Linux 6.5 installation You will need **64 bit** [RHEL 6.5](https://access.redhat.com/site/articles/3078#RHEL6) or later, with diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index dbf86f310b196..75b3c9fb68337 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -127,7 +127,7 @@ install Docker using the following: This command downloads a test image and runs it in a container. -## Optional Configurations for Docker on Ubuntu +## Optional configurations for Docker on Ubuntu This section contains optional procedures for configuring your Ubuntu to work better with Docker. @@ -137,7 +137,7 @@ better with Docker. * [Enable UFW forwarding](#enable-ufw-forwarding) * [Configure a DNS server for use by Docker](#configure-a-dns-server-for-docker) -### Create a docker group +### Create a Docker group 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 other users can access it with diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index f93f13e60da3d..fd3cc7eb4a40a 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -59,7 +59,7 @@ Let's try the `hello-world` example image. Run This should download the very small `hello-world` image and print a `Hello from Docker.` message. -## Using docker from Windows Command Line Prompt (cmd.exe) +## Using Docker from Windows Command Line Prompt (cmd.exe) Launch a Windows Command Line Prompt (cmd.exe). @@ -77,7 +77,7 @@ to your console window and you are ready to run docker commands such as ![](/installation/images/windows-boot2docker-cmd.png) -## Using docker from PowerShell +## Using Docker from PowerShell Launch a PowerShell window, then you need to add `ssh.exe` to your PATH: diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index 263690217368a..060428ecc92a7 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -109,7 +109,7 @@ Docker containers. Docker provides a simple way to build new images or update ex images, or you can download Docker images that other people have already created. Docker images are the **build** component of Docker. -#### Docker Registries +#### Docker registries Docker registries hold images. These are public or private stores from which you upload or download images. The public Docker registry is called [Docker Hub](http://hub.docker.com). It provides a huge collection of existing @@ -135,7 +135,7 @@ So far, we've learned that: Let's look at how these elements combine together to make Docker work. -### How does a Docker Image work? +### How does a Docker image work? We've already seen that Docker images are read-only templates from which Docker containers are launched. Each image consists of a series of layers. Docker makes use of [union file systems](http://en.wikipedia.org/wiki/UnionFS) to @@ -280,7 +280,7 @@ BSD Jails or Solaris Zones. ### Installing Docker Visit the [installation section](/installation/#installation). -### The Docker User Guide +### The Docker user guide [Learn Docker in depth](/userguide/). diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md index ee958f4b47d28..f20cbfff9fa0e 100644 --- a/docs/sources/project/advanced-contributing.md +++ b/docs/sources/project/advanced-contributing.md @@ -137,7 +137,7 @@ The following provides greater detail on the process: 14. Acceptance and merge! -## About the Advanced process +## About the advanced process Docker is a large project. Our core team gets a great many design proposals. Design proposal discussions can span days, weeks, and longer. The number of comments can reach the 100s. diff --git a/docs/sources/project/coding-style.md b/docs/sources/project/coding-style.md index bf8267e716398..57f6389365fdb 100644 --- a/docs/sources/project/coding-style.md +++ b/docs/sources/project/coding-style.md @@ -1,8 +1,8 @@ -page_title: Coding Style Checklist +page_title: Coding style checklist page_description: List of guidelines for coding Docker contributions page_keywords: change, commit, squash, request, pull request, test, unit test, integration tests, Go, gofmt, LGTM -# Coding Style Checklist +# Coding style checklist This checklist summarizes the material you experienced working through [make a code contribution](/project/make-a-contribution) and [advanced diff --git a/docs/sources/project/create-pr.md b/docs/sources/project/create-pr.md index f39f0aa98a72d..e9123c463f378 100644 --- a/docs/sources/project/create-pr.md +++ b/docs/sources/project/create-pr.md @@ -11,7 +11,7 @@ repository into the `docker/docker` repository. You can see the list of active pull requests to Docker on GitHub. -## Check Your Work +## Check your work Before you create a pull request, check your work. diff --git a/docs/sources/project/doc-style.md b/docs/sources/project/doc-style.md index 20e4a9f10ced6..0aa0f419a3c71 100644 --- a/docs/sources/project/doc-style.md +++ b/docs/sources/project/doc-style.md @@ -1,4 +1,4 @@ -page_title: Style Guide for Docker Documentation +page_title: Style guide for Docker documentation page_description: Style guide for Docker documentation describing standards and conventions for contributors page_keywords: style, guide, docker, documentation diff --git a/docs/sources/project/review-pr.md b/docs/sources/project/review-pr.md index 3d77ea406c8a2..01cce6fd2200f 100644 --- a/docs/sources/project/review-pr.md +++ b/docs/sources/project/review-pr.md @@ -1,9 +1,9 @@ -page_title: Participate in the PR Review +page_title: Participate in the PR review page_description: Basic workflow for Docker contributions page_keywords: contribute, pull request, review, workflow, beginner, squash, commit -# Participate in the PR Review +# Participate in the PR review Creating a pull request is nearly the end of the contribution process. At this point, your code is reviewed both by our continuous integration (CI) systems and @@ -45,7 +45,7 @@ So, they value your time and will try to work efficiently with you by keeping their comments specific and brief. If they ask you to make a change, you'll need to update your pull request with additional changes. -## Update an Existing Pull Request +## Update an existing pull request To update your existing pull request: diff --git a/docs/sources/reference/api/docker-io_api.md b/docs/sources/reference/api/docker-io_api.md index a7557bacb5d9c..b8da2704494f4 100644 --- a/docs/sources/reference/api/docker-io_api.md +++ b/docs/sources/reference/api/docker-io_api.md @@ -10,7 +10,7 @@ page_keywords: API, Docker, index, REST, documentation, Docker Hub, registry # Repositories -## User Repository +## User repository ### Create a user repository @@ -93,7 +93,7 @@ Status Codes: - **401** – Unauthorized - **403** – Account is not Active -## Library Repository +## Library repository ### Create a library repository @@ -182,9 +182,9 @@ Status Codes: - **401** – Unauthorized - **403** – Account is not Active -# Repository Images +# Repository images -## User Repository Images +## User repository images ### Update user repository images @@ -256,7 +256,7 @@ Status Codes: - **200** – OK - **404** – Not found -## Library Repository Images +## Library repository images ### Update library repository images @@ -326,9 +326,9 @@ Status Codes: - **200** – OK - **404** – Not found -# Repository Authorization +# Repository authorization -## Library Repository +## Library repository ### Authorize a token for a library @@ -361,7 +361,7 @@ Status Codes: - **403** – Permission denied - **404** – Not found -## User Repository +## User repository ### Authorize a token for a user repository @@ -397,7 +397,7 @@ Status Codes: ## Users -### User Login +### User login `GET /v1/users/` @@ -424,7 +424,7 @@ Status Codes: - **401** – Unauthorized - **403** – Account is not Active -### User Register +### User register `POST /v1/users/` @@ -461,7 +461,7 @@ Status Codes: - **201** – User Created - **400** – Errors (invalid json, missing or invalid fields, etc) -### Update User +### Update user `PUT /v1/users/(username)/` diff --git a/docs/sources/reference/api/docker_io_accounts_api.md b/docs/sources/reference/api/docker_io_accounts_api.md index efb86eb33a4a2..34f21eb7d6dfa 100644 --- a/docs/sources/reference/api/docker_io_accounts_api.md +++ b/docs/sources/reference/api/docker_io_accounts_api.md @@ -1,8 +1,8 @@ -page_title: docker.io Accounts API +page_title: docker.io accounts API page_description: API Documentation for docker.io accounts. page_keywords: API, Docker, accounts, REST, documentation -# docker.io Accounts API +# docker.io accounts API ## Get a single user diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 7772a651ac0cb..d92084f29b476 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -40,7 +40,7 @@ You can still call an old version of the API using ## v1.19 -### Full Documentation +### Full documentation [*Docker Remote API v1.19*](/reference/api/docker_remote_api_v1.19/) @@ -49,7 +49,7 @@ You can still call an old version of the API using ## v1.18 -### Full Documentation +### Full documentation [*Docker Remote API v1.18*](/reference/api/docker_remote_api_v1.18/) @@ -96,7 +96,7 @@ Add `Warnings` field to response. ## v1.17 -### Full Documentation +### Full documentation [*Docker Remote API v1.17*](/reference/api/docker_remote_api_v1.17/) @@ -154,7 +154,7 @@ This endpoint now returns the labels associated with each image (`Labels`). ## v1.16 -### Full Documentation +### Full documentation [*Docker Remote API v1.16*](/reference/api/docker_remote_api_v1.16/) @@ -182,7 +182,7 @@ You can now copy data which is contained in a volume. ## v1.15 -### Full Documentation +### Full documentation [*Docker Remote API v1.15*](/reference/api/docker_remote_api_v1.15/) @@ -196,7 +196,7 @@ Previously this was only available when starting a container. ## v1.14 -### Full Documentation +### Full documentation [*Docker Remote API v1.14*](/reference/api/docker_remote_api_v1.14/) @@ -222,7 +222,7 @@ the `tag` parameter at the same time will return an error. ## v1.13 -### Full Documentation +### Full documentation [*Docker Remote API v1.13*](/reference/api/docker_remote_api_v1.13/) @@ -250,7 +250,7 @@ Added a `pause` parameter (default `true`) to pause the container during commit ## v1.12 -### Full Documentation +### Full documentation [*Docker Remote API v1.12*](/reference/api/docker_remote_api_v1.12/) @@ -275,7 +275,7 @@ The `insert` endpoint has been removed. ## v1.11 -### Full Documentation +### Full documentation [*Docker Remote API v1.11*](/reference/api/docker_remote_api_v1.11/) @@ -298,7 +298,7 @@ This url is preferred method for getting container logs now. ## v1.10 -### Full Documentation +### Full documentation [*Docker Remote API v1.10*](/reference/api/docker_remote_api_v1.10/) @@ -321,7 +321,7 @@ You can now use the force parameter to force delete a ## v1.9 -### Full Documentation +### Full documentation [*Docker Remote API v1.9*](/reference/api/docker_remote_api_v1.9/) @@ -337,7 +337,7 @@ accepting an AuthConfig object must be updated. ## v1.8 -### Full Documentation +### Full documentation [*Docker Remote API v1.8*](/reference/api/docker_remote_api_v1.8/) @@ -369,7 +369,7 @@ without having to parse the string. ## v1.7 -### Full Documentation +### Full documentation [*Docker Remote API v1.7*](/reference/api/docker_remote_api_v1.7/) @@ -468,7 +468,7 @@ output is now generated in the client, using the ## v1.6 -### Full Documentation +### Full documentation [*Docker Remote API v1.6*](/reference/api/docker_remote_api_v1.6/) @@ -486,7 +486,7 @@ previous API version didn't change. Stdout and stderr are merged. ## v1.5 -### Full Documentation +### Full documentation [*Docker Remote API v1.5*](/reference/api/docker_remote_api_v1.5/) @@ -513,7 +513,7 @@ port mapping. ## v1.4 -### Full Documentation +### Full documentation [*Docker Remote API v1.4*](/reference/api/docker_remote_api_v1.4/) @@ -540,7 +540,7 @@ Image's name added in the events docker v0.5.0 [51f6c4a](https://github.com/docker/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909) -### Full Documentation +### Full documentation [*Docker Remote API v1.3*](/reference/api/docker_remote_api_v1.3/) @@ -580,7 +580,7 @@ Start containers (/containers//start): docker v0.4.2 [2e7649b](https://github.com/docker/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168) -### Full Documentation +### Full documentation [*Docker Remote API v1.2*](/reference/api/docker_remote_api_v1.2/) @@ -612,7 +612,7 @@ deleted/untagged. docker v0.4.0 [a8ae398](https://github.com/docker/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f) -### Full Documentation +### Full documentation [*Docker Remote API v1.1*](/reference/api/docker_remote_api_v1.1/) @@ -639,7 +639,7 @@ Uses json stream instead of HTML hijack, it looks like this: docker v0.3.4 [8d73740](https://github.com/docker/docker/commit/8d73740343778651c09160cde9661f5f387b36f4) -### Full Documentation +### Full documentation [*Docker Remote API v1.0*](/reference/api/docker_remote_api_v1.0/) diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index b6675dc4e4c8d..bef67a071476e 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -675,7 +675,7 @@ Status Codes: ## 2.2 Images -### List Images +### List images `GET /images/json` @@ -1119,7 +1119,7 @@ Status Codes: - **200** – no error - **500** – server error -### Show the docker version information +### Show the Docker version information `GET /version` @@ -1343,7 +1343,7 @@ Here are the steps of `docker run` : 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 +## 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. diff --git a/docs/sources/reference/api/hub_registry_spec.md b/docs/sources/reference/api/hub_registry_spec.md index 2999f453ff122..b1481e3a0344e 100644 --- a/docs/sources/reference/api/hub_registry_spec.md +++ b/docs/sources/reference/api/hub_registry_spec.md @@ -1,4 +1,4 @@ -page_title: Registry Documentation +page_title: Registry documentation page_description: Documentation for docker Registry and Registry API page_keywords: docker, registry, api, hub @@ -679,7 +679,7 @@ On every request, a special header can be returned: On the next request, the client will always pick a server from this list. -## Authentication & Authorization +## Authentication and authorization ### On the Docker Hub @@ -747,7 +747,7 @@ Next request: GET /(...) Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4=" -## Document Version +## Document version - 1.0 : May 6th 2013 : initial release - 1.1 : June 1st 2013 : Added Delete Repository and way to handle new diff --git a/docs/sources/reference/api/registry_api_client_libraries.md b/docs/sources/reference/api/registry_api_client_libraries.md index 811ac859e420d..965ba460330bd 100644 --- a/docs/sources/reference/api/registry_api_client_libraries.md +++ b/docs/sources/reference/api/registry_api_client_libraries.md @@ -1,8 +1,8 @@ -page_title: Registry API Client Libraries +page_title: Registry API client libraries page_description: Various client libraries available to use with the Docker registry API page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala -# Docker Registry 1.0 API Client Libraries +# Docker Registry 1.0 API client libraries These libraries have not been tested by the Docker maintainers for compatibility. Please file issues with the library owners. If you find diff --git a/docs/sources/reference/api/remote_api_client_libraries.md b/docs/sources/reference/api/remote_api_client_libraries.md index 3226d0eee3a84..cbe8f3a32840f 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.md +++ b/docs/sources/reference/api/remote_api_client_libraries.md @@ -1,8 +1,8 @@ -page_title: Remote API Client Libraries +page_title: Remote API client libraries page_description: Various client libraries available to use with the Docker remote API page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala -# Docker Remote API Client Libraries +# Docker Remote API client libraries These libraries have not been tested by the Docker maintainers for compatibility. Please file issues with the library owners. If you find diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index a4fcbebc1b4bc..583698d882b2d 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -1,8 +1,8 @@ -page_title: Dockerfile Reference +page_title: Dockerfile reference page_description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. page_keywords: builder, docker, Dockerfile, automation, image creation -# Dockerfile Reference +# Dockerfile reference **Docker can build images automatically** by reading the instructions from a `Dockerfile`. A `Dockerfile` is a text document that contains all @@ -105,7 +105,7 @@ be treated as an argument. This allows statements like: Here is the set of instructions you can use in a `Dockerfile` for building images. -### Environment Replacement +### Environment replacement > **Note**: prior to 1.3, `Dockerfile` environment variables were handled > similarly, in that they would be replaced as described below. However, there @@ -288,7 +288,7 @@ guide](/articles/dockerfile_best-practices/#build-cache) for more information. The cache for `RUN` instructions can be invalidated by `ADD` instructions. See [below](#add) for details. -### Known Issues (RUN) +### Known issues (RUN) - [Issue 783](https://github.com/docker/docker/issues/783) is about file permissions problems that can occur when using the AUFS file system. You @@ -973,7 +973,7 @@ For example you might add something like this: > **Warning**: The `ONBUILD` instruction may not trigger `FROM` or `MAINTAINER` instructions. -## Dockerfile Examples +## Dockerfile examples # Nginx # diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 96aef620c9c1e..a871162049b0a 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -24,7 +24,7 @@ the `docker` command, your system administrator can create a Unix group called For more information about installing Docker or `sudo` configuration, refer to the [installation](/installation) instructions for your operating system. -## Environment Variables +## Environment variables For easy reference, the following list of environment variables are supported by the `docker` command line: @@ -48,7 +48,7 @@ These Go environment variables are case-insensitive. See the [Go specification](http://golang.org/pkg/net/http/) for details on these variables. -## Configuration Files +## Configuration files The Docker command line stores its configuration files in a directory called `.docker` within your `HOME` directory. Docker manages most of the files in @@ -2210,7 +2210,7 @@ application change: `--rm` option means that when the container exits, the container's layer is removed. -#### Restart Policies +#### Restart policies Use Docker's `--restart` to specify a container's *restart policy*. A restart policy controls whether the Docker daemon restarts a container after exit. diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index a0d66937f1566..7218fab649299 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -156,7 +156,7 @@ Images using the v2 or later image format have a content-addressable identifier called a digest. As long as the input used to generate the image is unchanged, the digest value is predictable and referenceable. -## PID Settings (--pid) +## PID settings (--pid) --pid="" : Set the PID (Process) Namespace mode for the container, 'host': use the host's PID namespace inside the container @@ -177,7 +177,7 @@ within the container. This command would allow you to use `strace` inside the container on pid 1234 on the host. -## IPC Settings (--ipc) +## IPC settings (--ipc) --ipc="" : Set the IPC mode for the container, 'container:': reuses another container's IPC namespace diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md index 87e5c41972cea..1a32cbb98073e 100644 --- a/docs/sources/release-notes.md +++ b/docs/sources/release-notes.md @@ -1,8 +1,8 @@ -page_title: Docker 1.x Series Release Notes -page_description: Release Notes for Docker 1.x. +page_title: Docker 1.x series release notes +page_description: Release notes for Docker 1.x. page_keywords: docker, documentation, about, technology, understanding, release -# Release Notes Version 1.6.0 +# Release notes version 1.6.0 (2015-04-16) You can view release notes for earlier version of Docker by selecting the @@ -12,7 +12,7 @@ blog](https://blog.docker.com/2015/04/docker-release-1-6/). -## Docker Engine 1.6.0 Features +## Docker Engine 1.6.0 features For a complete list of engine patches, fixes, and other improvements, see the [merge PR on GitHub](https://github.com/docker/docker/pull/11635). You'll also @@ -30,7 +30,7 @@ repository](https://github.com/docker/docker/blob/master/CHANGELOG.md). | Ulimits | You can now specify the default `ulimit` settings for all containers when configuring the daemon. For example:`docker -d --default-ulimit nproc=1024:2048` See [Default Ulimits](http://docs.docker.com/reference/commandline/cli/#default-ulimits) in this documentation. | | Commit and import Dockerfile | You can now make changes to images on the fly without having to re-build the entire image. The feature `commit --change` and `import --change` allows you to apply standard changes to a new image. These are expressed in the Dockerfile syntax and used to modify the image. For details on how to use these, see the [commit](http://docs.docker.com/reference/commandline/cli/#commit) and [import](http://docs.docker.com/reference/commandline/cli/#import). | -### Known Issues in Engine +### Known issues in Engine This section lists significant known issues present in Docker as of release date. For an exhaustive list of issues, see [the issues list on the project @@ -53,7 +53,7 @@ issues. You might have to flush your cookies if it doesn't work right away. For more information, see the [Docker forum post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300). -## Docker Registry 2.0 Features +## Docker Registry 2.0 features This release includes Registry 2.0. The Docker Registry is a central server for pushing and pulling images. In this release, it was completely rewritten in Go diff --git a/docs/sources/terms/container.md b/docs/sources/terms/container.md index 8b42868788eef..d0c31c2455058 100644 --- a/docs/sources/terms/container.md +++ b/docs/sources/terms/container.md @@ -17,7 +17,7 @@ Image*](/terms/image) and some additional information like its unique id, networking configuration, and resource limits is called a **container**. -## Container State +## Container state Containers can change, and so they have state. A container may be **running** or **exited**. diff --git a/docs/sources/terms/filesystem.md b/docs/sources/terms/filesystem.md index 5587e3c83179d..814246d8b998f 100644 --- a/docs/sources/terms/filesystem.md +++ b/docs/sources/terms/filesystem.md @@ -1,8 +1,8 @@ -page_title: File Systems +page_title: File system page_description: How Linux organizes its persistent storage page_keywords: containers, files, linux -# File System +# File system ## Introduction diff --git a/docs/sources/terms/image.md b/docs/sources/terms/image.md index e42a6cfa12162..0a11d91c9ec90 100644 --- a/docs/sources/terms/image.md +++ b/docs/sources/terms/image.md @@ -1,4 +1,4 @@ -page_title: Images +page_title: Image page_description: Definition of an image page_keywords: containers, lxc, concepts, explanation, image, container @@ -19,7 +19,7 @@ images do not have state. ![](/terms/images/docker-filesystems-debianrw.png) -## Parent Image +## Parent image ![](/terms/images/docker-filesystems-multilayer.png) @@ -27,7 +27,7 @@ Each image may depend on one more image which forms the layer beneath it. We sometimes say that the lower image is the **parent** of the upper image. -## Base Image +## Base image An image that has no parent is a **base image**. diff --git a/docs/sources/terms/registry.md b/docs/sources/terms/registry.md index 68120812c37ea..ad5a81d640600 100644 --- a/docs/sources/terms/registry.md +++ b/docs/sources/terms/registry.md @@ -14,7 +14,7 @@ The default registry can be accessed using a browser at [Docker Hub](https://hub.docker.com) or using the `docker search` command. -## Further Reading +## Further reading For more information see [*Working with Repositories*](/userguide/dockerrepos/#working-with-the-repository) diff --git a/docs/sources/userguide/dockerhub.md b/docs/sources/userguide/dockerhub.md index 3d4007d3025c9..2f7170d64f9f0 100644 --- a/docs/sources/userguide/dockerhub.md +++ b/docs/sources/userguide/dockerhub.md @@ -2,7 +2,7 @@ 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 Hub +# Getting started with Docker Hub This section provides a quick introduction to the [Docker Hub](https://hub.docker.com), @@ -21,7 +21,7 @@ most out of Docker. To do this, it provides services such as: In order to use Docker Hub, you will first need to register and create an account. Don't worry, creating an account is simple and free. -## Creating a Docker Hub Account +## Creating a Docker Hub account There are two ways for you to register and create an account: diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index fc5dbe74a09ed..6219466546cf6 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -1,8 +1,8 @@ -page_title: Working with Docker Images +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 -# Working with Docker Images +# Working with Docker images In the [introduction](/introduction/understanding-docker/) we've discovered that Docker images are the basis of containers. In the diff --git a/docs/sources/userguide/dockerizing.md b/docs/sources/userguide/dockerizing.md index 5896dd78e0a1f..7124ba6c9ca1c 100644 --- a/docs/sources/userguide/dockerizing.md +++ b/docs/sources/userguide/dockerizing.md @@ -1,8 +1,8 @@ -page_title: Dockerizing Applications: A "Hello world" +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" +# Dockerizing applications: A "Hello world" *So what's this Docker thing all about?* @@ -48,7 +48,7 @@ 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 +## An interactive container Let's try the `docker run` command again, this time specifying a new command to run in our container. @@ -90,7 +90,7 @@ use the `exit` command or enter Ctrl-D to finish. As with our previous container, once the Bash shell process has finished, the container is stopped. -## A Daemonized Hello world +## 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, diff --git a/docs/sources/userguide/dockerlinks.md b/docs/sources/userguide/dockerlinks.md index 66dd3d7a4ee67..8a20388463cfd 100644 --- a/docs/sources/userguide/dockerlinks.md +++ b/docs/sources/userguide/dockerlinks.md @@ -1,8 +1,8 @@ -page_title: Linking Containers Together +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 +# Linking containers together In [the Using Docker section](/userguide/usingdocker), you saw how you can connect to a service running inside a Docker container via a network @@ -11,7 +11,7 @@ applications running inside Docker containers. In this section, we'll briefly re connecting via a network port and then we'll introduce you to another method of access: container linking. -## Connect using Network port mapping +## Connect using network port mapping In [the Using Docker section](/userguide/usingdocker), you created a container that ran a Python Flask application: @@ -175,7 +175,7 @@ recipient container in two ways: * Environment variables, * Updating the `/etc/hosts` file. -### Environment Variables +### Environment variables Docker creates several environment variables when you link containers. Docker automatically creates environment variables in the target container based on diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md index a8a1800f5129b..efa6ca3d0df22 100644 --- a/docs/sources/userguide/dockerrepos.md +++ b/docs/sources/userguide/dockerrepos.md @@ -101,7 +101,7 @@ information [here](http://docs.docker.com/docker-hub/). * Automated Builds * Webhooks -### Private Repositories +### 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 @@ -150,7 +150,7 @@ repository. You can create multiple Automated Builds per repository and configure them to point to specific `Dockerfile`'s or Git branches. -#### Build Triggers +#### Build triggers Automated Builds can also be triggered via a URL on Docker Hub. This allows you to rebuild an Automated build image on demand. diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index e80483fc2fb4b..c7126d7c33796 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -1,8 +1,8 @@ -page_title: Managing Data in Containers +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 +# Managing data in containers So far we've been introduced to some [basic Docker concepts](/userguide/usingdocker/), seen how to work with [Docker @@ -73,7 +73,7 @@ volumes. The output should look something similar to the following: You will notice in the above 'Volumes' is specifying the location on the host and 'VolumesRW' is specifying that the volume is read/write. -### Mount a Host Directory as a Data Volume +### 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 Docker daemon's host into a container. @@ -116,7 +116,7 @@ read-only. 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. -### Mount a Host File as a Data Volume +### Mount a host file as a data volume The `-v` flag can also be used to mount a single file - instead of *just* directories - from the host machine. @@ -134,7 +134,7 @@ history of the commands typed while in the container. > you want to edit the mounted file, it is often easiest to instead mount the > parent directory. -## Creating and mounting a Data Volume Container +## 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 diff --git a/docs/sources/userguide/index.md b/docs/sources/userguide/index.md index d0dbdb84ee199..9cc1c6db30312 100644 --- a/docs/sources/userguide/index.md +++ b/docs/sources/userguide/index.md @@ -1,8 +1,8 @@ -page_title: The Docker User Guide -page_description: The Docker User Guide home page +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 +# 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 @@ -19,7 +19,7 @@ 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 Hub *How do I use Docker Hub?* @@ -29,7 +29,7 @@ environment. To learn more: Go to [Using Docker Hub](/userguide/dockerhub). -## Dockerizing Applications: A "Hello world" +## Dockerizing applications: A "Hello world" *How do I run applications inside containers?* @@ -38,7 +38,7 @@ applications. To learn how to Dockerize applications and run them: Go to [Dockerizing Applications](/userguide/dockerizing). -## Working with Containers +## Working with containers *How do I manage my containers?* @@ -48,7 +48,7 @@ about how to inspect, monitor and manage containers: Go to [Working With Containers](/userguide/usingdocker). -## Working with Docker Images +## Working with Docker images *How can I access, share and build my own images?* @@ -57,7 +57,7 @@ learn how to build your own application images with Docker. Go to [Working with Docker Images](/userguide/dockerimages). -## Linking Containers Together +## 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 @@ -65,7 +65,7 @@ by linking together multiple Docker containers. Go to [Linking Containers Together](/userguide/dockerlinks). -## Managing Data in Containers +## 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. diff --git a/docs/sources/userguide/level1.md b/docs/sources/userguide/level1.md index cca77dc36291d..320fbfee01be3 100644 --- a/docs/sources/userguide/level1.md +++ b/docs/sources/userguide/level1.md @@ -1,10 +1,10 @@ -page_title: Docker Images Test +page_title: Docker images test 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 Back -# Dockerfile Tutorial +# Dockerfile tutorial ## Test your Dockerfile knowledge - Level 1 diff --git a/docs/sources/userguide/level2.md b/docs/sources/userguide/level2.md index fe6654e71f034..96e91a1c6c743 100644 --- a/docs/sources/userguide/level2.md +++ b/docs/sources/userguide/level2.md @@ -1,10 +1,10 @@ -page_title: Docker Images Test +page_title: Docker images test 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 Back -#Dockerfile Tutorial +#Dockerfile tutorial ## Test your Dockerfile knowledge - Level 2 diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index 70996a21004b7..e33ca717d67a6 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -1,8 +1,8 @@ -page_title: Working with Containers +page_title: Working with containers page_description: Learn how to manage and operate Docker containers. page_keywords: docker, the docker guide, documentation, docker.io, monitoring containers, docker top, docker inspect, docker port, ports, docker logs, log, Logs -# Working with Containers +# Working with containers In the [last section of the Docker User Guide](/userguide/dockerizing) we launched our first containers. We launched two containers using the @@ -91,7 +91,7 @@ This will display the help text and all available flags: > You can see a full list of Docker's commands > [here](/reference/commandline/cli/). -## Running a Web Application in Docker +## 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 @@ -121,7 +121,7 @@ Lastly, we've specified a command for our container to run: `python app.py`. Thi > reference](/reference/commandline/cli/#run) and the [Docker Run > Reference](/reference/run/). -## Viewing our Web Application Container +## Viewing our web application container Now let's see our running container using the `docker ps` command. @@ -189,7 +189,7 @@ Our Python application is live! > > In this case you'd browse to http://192.168.59.103:49155 for the above example. -## A Network Port Shortcut +## 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 @@ -202,7 +202,7 @@ corresponding public-facing port. In this case we've looked up what port is mapped externally to port 5000 inside the container. -## Viewing the Web Application's Logs +## 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`. @@ -217,7 +217,7 @@ 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 +## 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. @@ -229,7 +229,7 @@ running inside it using the `docker top` command. Here we can see our `python app.py` command is the only process running inside the container. -## Inspecting our Web Application 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 @@ -258,7 +258,7 @@ specific element, for example to return the container's IP address we would: $ docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse 172.17.0.5 -## Stopping our Web Application Container +## 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`. @@ -271,7 +271,7 @@ been stopped. $ docker ps -l -## Restarting our Web Application Container +## 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 @@ -289,7 +289,7 @@ responds. > Also available is the `docker restart` command that runs a stop and > then start on the container. -## Removing our Web Application 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. From d6c839cf0f04f30ba95e2b032a4e686dea2eb0d0 Mon Sep 17 00:00:00 2001 From: Tomas Tomecek Date: Thu, 23 Apr 2015 07:40:59 +0200 Subject: [PATCH 244/332] v1 spec: fix typos and formatting Signed-off-by: Tomas Tomecek --- image/spec/v1.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/image/spec/v1.md b/image/spec/v1.md index abed75833bce1..b428cbb202e07 100644 --- a/image/spec/v1.md +++ b/image/spec/v1.md @@ -31,7 +31,7 @@ This specification uses the following terms: Image JSON

- Each layer has an associated A JSON structure which describes some + Each layer has an associated JSON structure which describes some basic information about the image such as date created, author, and the ID of its parent image as well as execution/runtime configuration like its entry point, default arguments, CPU/memory shares, networking, and @@ -81,7 +81,7 @@ This specification uses the following terms: times of any entries differ. For this reason, image checksums are generated using the TarSum algorithm which produces a cryptographic hash of file contents and selected headers only. Details of this - algorithm are described in the separate [TarSum specification](https://github.com/docker/docker/blob/master/pkg/tarsum/tarsum_spec.md). + algorithm are described in the separate TarSum specification.
Tag @@ -492,9 +492,9 @@ Changeset tar archives. There is also a format for a single archive which contains complete information about an image, including: - - repository names/tags - - all image layer JSON files - - all tar archives of each layer filesystem changesets + - repository names/tags + - all image layer JSON files + - all tar archives of each layer filesystem changesets For example, here's what the full archive of `library/busybox` is (displayed in `tree` format): @@ -523,10 +523,10 @@ For example, here's what the full archive of `library/busybox` is (displayed in There are one or more directories named with the ID for each layer in a full image. Each of these directories contains 3 files: - * `VERSION` - The schema version of the `json` file - * `json` - The JSON metadata for an image layer - * `layer.tar` - The Tar archive of the filesystem changeset for an image - layer. + * `VERSION` - The schema version of the `json` file + * `json` - The JSON metadata for an image layer + * `layer.tar` - The Tar archive of the filesystem changeset for an image + layer. The content of the `VERSION` files is simply the semantic version of the JSON metadata schema: From 1f7ba6f80914cc663b3b38ffc0b2c8e1add2547a Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 23 Apr 2015 09:43:04 +0200 Subject: [PATCH 245/332] Fix the design proposal link in advanced contribution page Signed-off-by: Vincent Demeester --- docs/sources/project/advanced-contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md index f20cbfff9fa0e..7ee7a86cbe5d8 100644 --- a/docs/sources/project/advanced-contributing.md +++ b/docs/sources/project/advanced-contributing.md @@ -67,7 +67,7 @@ The following provides greater detail on the process: The design proposals are all online in our GitHub pull requests. + 3Akind%2Fproposal" target="_blank">all online in our GitHub pull requests. 3. Talk to the community about your idea. From 62f91b1d34155a58bfeb6ce8d3595149cf5147d7 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 23 Apr 2015 16:50:41 +0800 Subject: [PATCH 246/332] push test: fix typo Signed-off-by: Ma Shimiao --- integration-cli/docker_cli_push_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index ebf08bae8be14..3fc1ae3a0c8e6 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -72,7 +72,7 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL) repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL) - // tag the image to upload it tot he private registry + // tag the image and upload it to the private registry tagCmd1 := exec.Command(dockerBinary, "tag", "busybox", repoTag1) if out, _, err := runCommandWithOutput(tagCmd1); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) @@ -93,7 +93,7 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { func (s *DockerSuite) TestPushInterrupt(c *check.C) { defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) - // tag the image to upload it tot he private registry + // tag the image and upload it to the private registry if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } @@ -116,8 +116,8 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { } } // now wait until all this pushes will complete - // if it will fail with timeout - this is some error, so no logic about it - // here + // if it failed with timeout - there would be some error, + // so no logic about it here for exec.Command(dockerBinary, "push", repoName).Run() != nil { } } From 47263c6514457d58c1062671077118063189d433 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 23 Apr 2015 12:10:47 +0200 Subject: [PATCH 247/332] Fix typo in builder reference Signed-off-by: Tristan Carel --- docs/sources/reference/builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 583698d882b2d..83121e6bd9379 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -180,7 +180,7 @@ that will be excluded from the context. Globbing is done using Go's > **Note**: > The `.dockerignore` file can even be used to ignore the `Dockerfile` and > `.dockerignore` files. This might be useful if you are copying files from -> the root of the build context into your new containter but do not want to +> the root of the build context into your new container but do not want to > include the `Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`). The following example shows the use of the `.dockerignore` file to exclude the From a8d2fbe7b4593f1e10800a19df14e1bbfb212d41 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 23 Apr 2015 18:41:30 +0800 Subject: [PATCH 248/332] fix test case name Name like this will never run by go test. And this test case won't get PAAS. Signed-off-by: Qiang Huang --- api/server/server_unit_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index e7a6afcb94ecf..0daa99bcd973c 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -13,19 +13,20 @@ import ( "github.com/docker/docker/pkg/version" ) -func TesthttpError(t *testing.T) { +func TestHttpError(t *testing.T) { r := httptest.NewRecorder() - httpError(r, fmt.Errorf("No such method")) if r.Code != http.StatusNotFound { t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code) } + r = httptest.NewRecorder() httpError(r, fmt.Errorf("This accound hasn't been activated")) if r.Code != http.StatusForbidden { t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code) } + r = httptest.NewRecorder() httpError(r, fmt.Errorf("Some error")) if r.Code != http.StatusInternalServerError { t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) From 70bb0d8ed7847d7e7850a1a864ff258767f0ad7a Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 21 Apr 2015 09:26:15 +0800 Subject: [PATCH 249/332] remove job from load Signed-off-by: Simei He Signed-off-by: He Simei --- api/server/server.go | 12 ++++++++---- graph/load.go | 15 +++++++++++---- graph/service.go | 1 - 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 867593e6ad0e8..24b0abd4378f9 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -900,10 +900,14 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - job := eng.Job("load") - job.Stdin.Add(r.Body) - job.Stdout.Add(w) - return job.Run() + + imageLoadConfig := &graph.ImageLoadConfig{ + InTar: r.Body, + OutStream: w, + Engine: eng, + } + + return s.daemon.Repositories().Load(imageLoadConfig) } func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/graph/load.go b/graph/load.go index 5272eb139488f..f62b82ce462b4 100644 --- a/graph/load.go +++ b/graph/load.go @@ -4,6 +4,7 @@ package graph import ( "encoding/json" + "io" "io/ioutil" "os" "path" @@ -15,9 +16,15 @@ import ( "github.com/docker/docker/pkg/chrootarchive" ) +type ImageLoadConfig struct { + InTar io.ReadCloser + OutStream io.Writer + Engine *engine.Engine +} + // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. -func (s *TagStore) CmdLoad(job *engine.Job) error { +func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { return err @@ -41,7 +48,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { excludes[i] = k i++ } - if err := chrootarchive.Untar(job.Stdin, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { + if err := chrootarchive.Untar(imageLoadConfig.InTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { return err } @@ -52,7 +59,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { for _, d := range dirs { if d.IsDir() { - if err := s.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil { + if err := s.recursiveLoad(imageLoadConfig.Engine, d.Name(), tmpImageDir); err != nil { return err } } @@ -67,7 +74,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { for imageName, tagMap := range repositories { for tag, address := range tagMap { - if err := s.SetLoad(imageName, tag, address, true, job.Stdout); err != nil { + if err := s.SetLoad(imageName, tag, address, true, imageLoadConfig.OutStream); err != nil { return err } } diff --git a/graph/service.go b/graph/service.go index 022d5d499d8eb..44c1bdef16f03 100644 --- a/graph/service.go +++ b/graph/service.go @@ -13,7 +13,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, - "load": s.CmdLoad, "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { From ab42a3a23a1276ccad874a13cf8604cb59124e10 Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Thu, 23 Apr 2015 19:37:47 +0800 Subject: [PATCH 250/332] refactor httpError() and add 404 "not found" mapping When docker pull a non-existent repo, daemon will report "image xxx not found" with an error code 500, which should be 404. This commit add 404 "not found" mapping and refactor httpError function. Signed-off-by: Zhang Wei --- api/server/server.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 867593e6ad0e8..178e880e5d571 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -203,18 +203,19 @@ func httpError(w http.ResponseWriter, err error) { // If we need to differentiate between different possible error types, we should // create appropriate error types with clearly defined meaning. errStr := strings.ToLower(err.Error()) - if strings.Contains(errStr, "no such") { - statusCode = http.StatusNotFound - } else if strings.Contains(errStr, "bad parameter") { - statusCode = http.StatusBadRequest - } else if strings.Contains(errStr, "conflict") { - statusCode = http.StatusConflict - } else if strings.Contains(errStr, "impossible") { - statusCode = http.StatusNotAcceptable - } else if strings.Contains(errStr, "wrong login/password") { - statusCode = http.StatusUnauthorized - } else if strings.Contains(errStr, "hasn't been activated") { - statusCode = http.StatusForbidden + for keyword, status := range map[string]int{ + "not found": http.StatusNotFound, + "no such": http.StatusNotFound, + "bad parameter": http.StatusBadRequest, + "conflict": http.StatusConflict, + "impossible": http.StatusNotAcceptable, + "wrong login/password": http.StatusUnauthorized, + "hasn't been activated": http.StatusForbidden, + } { + if strings.Contains(errStr, keyword) { + statusCode = status + break + } } logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error") From d456401fe1d038ddaf9866bb6ab4ac5744186e2d Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 21 Apr 2015 11:16:25 +0800 Subject: [PATCH 251/332] remove job from push Signed-off-by: Simei He Signed-off-by: He Simei --- api/server/server.go | 31 ++++++++++++++++--------------- graph/push.go | 35 +++++++++++++++++------------------ graph/service.go | 1 - 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 24b0abd4378f9..646a8c6776a49 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -858,25 +858,26 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h } } - job := eng.Job("push", vars["name"]) - job.SetenvJson("metaHeaders", metaHeaders) - job.SetenvJson("authConfig", authConfig) - job.Setenv("tag", r.Form.Get("tag")) - if version.GreaterThan("1.0") { - job.SetenvBool("json", true) - streamJSON(job.Stdout, w, true) - } else { - job.Stdout.Add(utils.NewWriteFlusher(w)) + useJSON := version.GreaterThan("1.0") + name := vars["name"] + + imagePushConfig := &graph.ImagePushConfig{ + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + Tag: r.Form.Get("tag"), + OutStream: utils.NewWriteFlusher(w), + Json: useJSON, + } + if useJSON { + w.Header().Set("Content-Type", "application/json") } - if err := job.Run(); err != nil { - if !job.Stdout.Used() { - return err - } - sf := streamformatter.NewStreamFormatter(version.GreaterThan("1.0")) - w.Write(sf.FormatError(err)) + if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { + sf := streamformatter.NewStreamFormatter(useJSON) + return fmt.Errorf(string(sf.FormatError(err))) } return nil + } func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/graph/push.go b/graph/push.go index fedd29498eec0..34db27b910a25 100644 --- a/graph/push.go +++ b/graph/push.go @@ -12,7 +12,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -25,6 +24,14 @@ import ( var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable") +type ImagePushConfig struct { + MetaHeaders map[string][]string + AuthConfig *registry.AuthConfig + Tag string + Json bool + OutStream io.Writer +} + // Retrieve the all the images to be uploaded in the correct order func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) { var ( @@ -486,15 +493,9 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * } // FIXME: Allow to interrupt current push when new push of same image is done. -func (s *TagStore) CmdPush(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s IMAGE", job.Name) - } +func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error { var ( - localName = job.Args[0] - sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) - authConfig = ®istry.AuthConfig{} - metaHeaders map[string][]string + sf = streamformatter.NewStreamFormatter(imagePushConfig.Json) ) // Resolve the Repository name from fqn to RepositoryInfo @@ -503,10 +504,6 @@ func (s *TagStore) CmdPush(job *engine.Job) error { return err } - tag := job.Getenv("tag") - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", &metaHeaders) - if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { return err } @@ -517,16 +514,18 @@ func (s *TagStore) CmdPush(job *engine.Job) error { return err } - r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) + r, err := registry.NewSession(imagePushConfig.AuthConfig, registry.HTTPRequestFactory(imagePushConfig.MetaHeaders), endpoint, false) if err != nil { return err } reposLen := 1 - if tag == "" { + if imagePushConfig.Tag == "" { reposLen = len(s.Repositories[repoInfo.LocalName]) } - job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) + + imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) + // If it fails, try to get the repository localRepo, exists := s.Repositories[repoInfo.LocalName] if !exists { @@ -534,7 +533,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { } if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { - err := s.pushV2Repository(r, localRepo, job.Stdout, repoInfo, tag, sf) + err := s.pushV2Repository(r, localRepo, imagePushConfig.OutStream, repoInfo, imagePushConfig.Tag, sf) if err == nil { s.eventsService.Log("push", repoInfo.LocalName, "") return nil @@ -545,7 +544,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { } } - if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil { + if err := s.pushRepository(r, imagePushConfig.OutStream, repoInfo, localRepo, imagePushConfig.Tag, sf); err != nil { return err } s.eventsService.Log("push", repoInfo.LocalName, "") diff --git a/graph/service.go b/graph/service.go index 44c1bdef16f03..337eaa3cf3a37 100644 --- a/graph/service.go +++ b/graph/service.go @@ -13,7 +13,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, - "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { return fmt.Errorf("Could not register %q: %v", name, err) From 563708d78d42afe89374d5819fdb671ed72c2dad Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Thu, 23 Apr 2015 09:20:17 -0400 Subject: [PATCH 252/332] Fix race with TestContainerApiCommit Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 4 ++-- integration-cli/docker_utils.go | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733e6cd..7a6468b160345 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -625,7 +625,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { } id := strings.TrimSpace(string(out)) - name := "testcommit" + name := "testcommit" + stringid.GenerateRandomID() _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { c.Fatal(err) @@ -650,7 +650,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { // sanity check, make sure the image is what we think it is out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput() if err != nil { - c.Fatal(out, err) + c.Fatalf("error checking commited image: %v - %q", err, string(out)) } } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 855352973ef01..cc5429a570999 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -444,8 +444,7 @@ func unpauseAllContainers() error { } func deleteImages(images ...string) error { - args := make([]string, 1, 2) - args[0] = "rmi" + args := []string{"rmi", "-f"} args = append(args, images...) rmiCmd := exec.Command(dockerBinary, args...) exitCode, err := runCommand(rmiCmd) @@ -453,7 +452,6 @@ func deleteImages(images ...string) error { if exitCode != 0 && err == nil { err = fmt.Errorf("failed to remove image: `docker rmi` exit is non-zero") } - return err } From fca4aea077f1960a0cdd0056477730b3f8e0ca38 Mon Sep 17 00:00:00 2001 From: Rajdeep Dua Date: Wed, 22 Apr 2015 04:03:57 -0700 Subject: [PATCH 253/332] TestCase added for Container Create with HostName Signed-off-by: Rajdeep Dua --- integration-cli/docker_api_containers_test.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733e6cd..8dfdc16d781a2 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -681,6 +681,47 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) { } } +func (s *DockerSuite) TestContainerApiCreateWithHostName(c *check.C) { + var hostName = "test-host" + config := map[string]interface{}{ + "Image": "busybox", + "Hostname": hostName, + } + + _, b, err := sockRequest("POST", "/containers/create", config) + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + c.Fatal(err) + } + type createResp struct { + Id string + } + var container createResp + if err := json.Unmarshal(b, &container); err != nil { + c.Fatal(err) + } + + var id = container.Id + + _, bodyGet, err := sockRequest("GET", "/containers/"+id+"/json", nil) + + type configLocal struct { + Hostname string + } + type getResponse struct { + Id string + Config configLocal + } + + var containerInfo getResponse + if err := json.Unmarshal(bodyGet, &containerInfo); err != nil { + c.Fatal(err) + } + var hostNameActual = containerInfo.Config.Hostname + if hostNameActual != "test-host" { + c.Fatalf("Mismatched Hostname, Expected %v, Actual: %v ", hostName, hostNameActual) + } +} + func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { config := map[string]interface{}{ "Image": "busybox", From cd6cc45d52a135e9e123c2e8c647710a061d38a0 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 23 Apr 2015 16:11:20 +0200 Subject: [PATCH 254/332] Fix TestRenameStoppedContainer race Signed-off-by: Antonio Murdaca --- integration-cli/docker_cli_rename_test.go | 24 +++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index fcd87b54b0b29..156ea6eeb3065 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/integration-cli/docker_cli_rename_test.go @@ -4,11 +4,11 @@ import ( "os/exec" "strings" + "github.com/docker/docker/pkg/stringid" "github.com/go-check/check" ) func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { @@ -25,7 +25,8 @@ func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { name, err := inspectField(cleanedContainerID, "Name") - runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + newName := "new_name" + stringid.GenerateRandomID() + runCmd = exec.Command(dockerBinary, "rename", "first_name", newName) out, _, err = runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) @@ -35,22 +36,22 @@ func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { if err != nil { c.Fatal(err) } - if name != "/new_name" { + if name != "/"+newName { c.Fatal("Failed to rename container ", name) } } func (s *DockerSuite) TestRenameRunningContainer(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) } + newName := "new_name" + stringid.GenerateRandomID() cleanedContainerID := strings.TrimSpace(out) - runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + runCmd = exec.Command(dockerBinary, "rename", "first_name", newName) out, _, err = runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) @@ -60,31 +61,30 @@ func (s *DockerSuite) TestRenameRunningContainer(c *check.C) { if err != nil { c.Fatal(err) } - if name != "/new_name" { + if name != "/"+newName { c.Fatal("Failed to rename container ") } - } func (s *DockerSuite) TestRenameCheckNames(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) } - runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + newName := "new_name" + stringid.GenerateRandomID() + runCmd = exec.Command(dockerBinary, "rename", "first_name", newName) out, _, err = runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) } - name, err := inspectField("new_name", "Name") + name, err := inspectField(newName, "Name") if err != nil { c.Fatal(err) } - if name != "/new_name" { + if name != "/"+newName { c.Fatal("Failed to rename container ") } @@ -92,7 +92,6 @@ func (s *DockerSuite) TestRenameCheckNames(c *check.C) { if err == nil && !strings.Contains(err.Error(), "No such image or container: first_name") { c.Fatal(err) } - } func (s *DockerSuite) TestRenameInvalidName(c *check.C) { @@ -110,5 +109,4 @@ func (s *DockerSuite) TestRenameInvalidName(c *check.C) { if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "myname") { c.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err) } - } From ee7a7b07e752c56bbe1b941feb1e4313275d68c2 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 23 Apr 2015 16:32:48 +0200 Subject: [PATCH 255/332] Remove deleteAllContainers call in test Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_containers_test.go | 3 --- integration-cli/docker_cli_build_test.go | 1 - integration-cli/docker_cli_create_test.go | 7 ------- integration-cli/docker_cli_ps_test.go | 3 --- integration-cli/docker_cli_run_test.go | 2 -- 5 files changed, 16 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733e6cd..82317340fefe5 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -773,7 +773,6 @@ func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { } func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { - defer deleteAllContainers() config := `{ "Image": "busybox", "Cmd": "ls", @@ -795,8 +794,6 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { } func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { - defer deleteAllContainers() - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox")) if err != nil { c.Fatal(err, out) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 7936b2bc348e4..56a11cdd4e81a 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2090,7 +2090,6 @@ func (s *DockerSuite) TestBuildRm(c *check.C) { if containerCountBefore == containerCountAfter { c.Fatalf("--rm=false should have left containers behind") } - deleteAllContainers() deleteImages(name) } diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 5fbd50b6da8d0..10c499982e07c 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -14,7 +14,6 @@ import ( // Make sure we can create a simple container with some args func (s *DockerSuite) TestCreateArgs(c *check.C) { - runCmd := exec.Command(dockerBinary, "create", "busybox", "command", "arg1", "arg2", "arg with space") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { @@ -256,9 +255,6 @@ func (s *DockerSuite) TestCreateLabels(c *check.C) { if !reflect.DeepEqual(expected, actual) { c.Fatalf("Expected %s got %s", expected, actual) } - - deleteAllContainers() - } func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { @@ -287,9 +283,6 @@ func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { if !reflect.DeepEqual(expected, actual) { c.Fatalf("Expected %s got %s", expected, actual) } - - deleteAllContainers() - } func (s *DockerSuite) TestCreateHostnameWithNumber(c *check.C) { diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index d702153504011..ca2d67bc96c2b 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -478,9 +478,6 @@ func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) { if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) { c.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out) } - - deleteAllContainers() - } func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7e12fc5a9d428..f23417037fd28 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -1448,7 +1448,6 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { //cleanup defer func() { - deleteAllContainers() if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { c.Fatal(err) } @@ -2380,7 +2379,6 @@ func (s *DockerSuite) TestRunVolumesNotRecreatedOnStart(c *check.C) { testRequires(c, SameHostDaemon) // Clear out any remnants from other tests - deleteAllContainers() info, err := ioutil.ReadDir(volumesConfigPath) if err != nil { c.Fatal(err) From 05013f1250dc141ed43f987dd8b6a650d0e47ac9 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Mon, 20 Apr 2015 23:21:46 +0000 Subject: [PATCH 256/332] Move https integration tests as unit tests under client Addresses #12255 Signed-off-by: Srini Brahmaroutu --- integration-cli/docker_cli_daemon_test.go | 68 +++++++++++++++ integration-cli/docker_utils.go | 8 ++ .../fixtures/https/ca.pem | 0 .../fixtures/https/client-cert.pem | 0 .../fixtures/https/client-key.pem | 0 .../fixtures/https/client-rogue-cert.pem | 0 .../fixtures/https/client-rogue-key.pem | 0 .../fixtures/https/server-cert.pem | 0 .../fixtures/https/server-key.pem | 0 .../fixtures/https/server-rogue-cert.pem | 0 .../fixtures/https/server-rogue-key.pem | 0 integration/https_test.go | 84 ------------------- integration/runtime_test.go | 58 ------------- 13 files changed, 76 insertions(+), 142 deletions(-) rename {integration => integration-cli}/fixtures/https/ca.pem (100%) rename {integration => integration-cli}/fixtures/https/client-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/client-key.pem (100%) rename {integration => integration-cli}/fixtures/https/client-rogue-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/client-rogue-key.pem (100%) rename {integration => integration-cli}/fixtures/https/server-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/server-key.pem (100%) rename {integration => integration-cli}/fixtures/https/server-rogue-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/server-rogue-key.pem (100%) delete mode 100644 integration/https_test.go diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 81cf0ab0c3f1b..2a945827e1caa 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -910,3 +910,71 @@ func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { } } + +// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint +func (s *DockerSuite) TestHttpsInfo(c *check.C) { + const ( + testDaemonHttpsAddr = "localhost:4271" + ) + + d := NewDaemon(c) + if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { + c.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + //force tcp protocol + host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) + daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-cert.pem", "--tlskey", "fixtures/https/client-key.pem"} + out, err := d.CmdWithArgs(daemonArgs, "info") + if err != nil { + c.Fatalf("Error Occurred: %s and output: %s", err, out) + } +} + +// TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint +// by using a rogue client certificate and checks that it fails with the expected error. +func (s *DockerSuite) TestHttpsInfoRogueCert(c *check.C) { + const ( + errBadCertificate = "remote error: bad certificate" + testDaemonHttpsAddr = "localhost:4271" + ) + d := NewDaemon(c) + if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { + c.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + //force tcp protocol + host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) + daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} + out, err := d.CmdWithArgs(daemonArgs, "info") + if err == nil || !strings.Contains(out, errBadCertificate) { + c.Fatalf("Expected err: %s, got instead: %s and output: %s", errBadCertificate, err, out) + } +} + +// TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint +// which provides a rogue server certificate and checks that it fails with the expected error +func (s *DockerSuite) TestHttpsInfoRogueServerCert(c *check.C) { + const ( + errCaUnknown = "x509: certificate signed by unknown authority" + testDaemonRogueHttpsAddr = "localhost:4272" + ) + d := NewDaemon(c) + if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-rogue-cert.pem", + "--tlskey", "fixtures/https/server-rogue-key.pem", "-H", testDaemonRogueHttpsAddr); err != nil { + c.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + //force tcp protocol + host := fmt.Sprintf("tcp://%s", testDaemonRogueHttpsAddr) + daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} + out, err := d.CmdWithArgs(daemonArgs, "info") + if err == nil || !strings.Contains(out, errCaUnknown) { + c.Fatalf("Expected err: %s, got instead: %s and output: %s", errCaUnknown, err, out) + } +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 855352973ef01..7c26b11bd4a01 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -269,6 +269,14 @@ func (d *Daemon) Cmd(name string, arg ...string) (string, error) { return string(b), err } +func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) { + args := append(daemonArgs, name) + args = append(args, arg...) + c := exec.Command(dockerBinary, args...) + b, err := c.CombinedOutput() + return string(b), err +} + func (d *Daemon) LogfileName() string { return d.logFile.Name() } diff --git a/integration/fixtures/https/ca.pem b/integration-cli/fixtures/https/ca.pem similarity index 100% rename from integration/fixtures/https/ca.pem rename to integration-cli/fixtures/https/ca.pem diff --git a/integration/fixtures/https/client-cert.pem b/integration-cli/fixtures/https/client-cert.pem similarity index 100% rename from integration/fixtures/https/client-cert.pem rename to integration-cli/fixtures/https/client-cert.pem diff --git a/integration/fixtures/https/client-key.pem b/integration-cli/fixtures/https/client-key.pem similarity index 100% rename from integration/fixtures/https/client-key.pem rename to integration-cli/fixtures/https/client-key.pem diff --git a/integration/fixtures/https/client-rogue-cert.pem b/integration-cli/fixtures/https/client-rogue-cert.pem similarity index 100% rename from integration/fixtures/https/client-rogue-cert.pem rename to integration-cli/fixtures/https/client-rogue-cert.pem diff --git a/integration/fixtures/https/client-rogue-key.pem b/integration-cli/fixtures/https/client-rogue-key.pem similarity index 100% rename from integration/fixtures/https/client-rogue-key.pem rename to integration-cli/fixtures/https/client-rogue-key.pem diff --git a/integration/fixtures/https/server-cert.pem b/integration-cli/fixtures/https/server-cert.pem similarity index 100% rename from integration/fixtures/https/server-cert.pem rename to integration-cli/fixtures/https/server-cert.pem diff --git a/integration/fixtures/https/server-key.pem b/integration-cli/fixtures/https/server-key.pem similarity index 100% rename from integration/fixtures/https/server-key.pem rename to integration-cli/fixtures/https/server-key.pem diff --git a/integration/fixtures/https/server-rogue-cert.pem b/integration-cli/fixtures/https/server-rogue-cert.pem similarity index 100% rename from integration/fixtures/https/server-rogue-cert.pem rename to integration-cli/fixtures/https/server-rogue-cert.pem diff --git a/integration/fixtures/https/server-rogue-key.pem b/integration-cli/fixtures/https/server-rogue-key.pem similarity index 100% rename from integration/fixtures/https/server-rogue-key.pem rename to integration-cli/fixtures/https/server-rogue-key.pem diff --git a/integration/https_test.go b/integration/https_test.go deleted file mode 100644 index 17d69345a9529..0000000000000 --- a/integration/https_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package docker - -import ( - "crypto/tls" - "crypto/x509" - "io/ioutil" - "strings" - "testing" - "time" - - "github.com/docker/docker/api/client" -) - -const ( - errBadCertificate = "remote error: bad certificate" - errCaUnknown = "x509: certificate signed by unknown authority" -) - -func getTlsConfig(certFile, keyFile string, t *testing.T) *tls.Config { - certPool := x509.NewCertPool() - file, err := ioutil.ReadFile("fixtures/https/ca.pem") - if err != nil { - t.Fatal(err) - } - certPool.AppendCertsFromPEM(file) - - cert, err := tls.LoadX509KeyPair("fixtures/https/"+certFile, "fixtures/https/"+keyFile) - if err != nil { - t.Fatalf("Couldn't load X509 key pair: %s", err) - } - tlsConfig := &tls.Config{ - RootCAs: certPool, - Certificates: []tls.Certificate{cert}, - } - return tlsConfig -} - -// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint -func TestHttpsInfo(t *testing.T) { - cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, "", testDaemonProto, - testDaemonHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) - - setTimeout(t, "Reading command output time out", 10*time.Second, func() { - if err := cli.CmdInfo(); err != nil { - t.Fatal(err) - } - }) -} - -// TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint -// by using a rogue client certificate and checks that it fails with the expected error. -func TestHttpsInfoRogueCert(t *testing.T) { - cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, "", testDaemonProto, - testDaemonHttpsAddr, getTlsConfig("client-rogue-cert.pem", "client-rogue-key.pem", t)) - - setTimeout(t, "Reading command output time out", 10*time.Second, func() { - err := cli.CmdInfo() - if err == nil { - t.Fatal("Expected error but got nil") - } - if !strings.Contains(err.Error(), errBadCertificate) { - t.Fatalf("Expected error: %s, got instead: %s", errBadCertificate, err) - } - }) -} - -// TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint -// which provides a rogue server certificate and checks that it fails with the expected error -func TestHttpsInfoRogueServerCert(t *testing.T) { - cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, "", testDaemonProto, - testDaemonRogueHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) - - setTimeout(t, "Reading command output time out", 10*time.Second, func() { - err := cli.CmdInfo() - if err == nil { - t.Fatal("Expected error but got nil") - } - - if !strings.Contains(err.Error(), errCaUnknown) { - t.Fatalf("Expected error: %s, got instead: %s", errCaUnknown, err) - } - - }) -} diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 0c412c86293b0..11df1f5d65575 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -120,8 +120,6 @@ func init() { // Create the "global daemon" with a long-running daemons for integration tests spawnGlobalDaemon() - spawnLegitHttpsDaemon() - spawnRogueHttpsDaemon() startFds, startGoroutines = fileutils.GetTotalUsedFds(), runtime.NumGoroutine() } @@ -175,62 +173,6 @@ func spawnGlobalDaemon() { api.AcceptConnections(getDaemon(eng)) } -func spawnLegitHttpsDaemon() { - if globalHttpsEngine != nil { - return - } - globalHttpsEngine = spawnHttpsDaemon(testDaemonHttpsAddr, "fixtures/https/ca.pem", - "fixtures/https/server-cert.pem", "fixtures/https/server-key.pem") -} - -func spawnRogueHttpsDaemon() { - if globalRogueHttpsEngine != nil { - return - } - globalRogueHttpsEngine = spawnHttpsDaemon(testDaemonRogueHttpsAddr, "fixtures/https/ca.pem", - "fixtures/https/server-rogue-cert.pem", "fixtures/https/server-rogue-key.pem") -} - -func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { - t := std_log.New(os.Stderr, "", 0) - root, err := newTestDirectory(unitTestStoreBase) - if err != nil { - t.Fatal(err) - } - // FIXME: here we don't use NewTestEngine because it configures the daemon with Autorestart=false, - // and we want to set it to true. - - eng := newTestEngine(t, true, root) - - serverConfig := &apiserver.ServerConfig{ - Logging: true, - Tls: true, - TlsVerify: true, - TlsCa: cacert, - TlsCert: cert, - TlsKey: key, - } - api := apiserver.New(serverConfig, eng) - // Spawn a Daemon - go func() { - logrus.Debugf("Spawning https daemon for integration tests") - listenURL := &url.URL{ - Scheme: testDaemonHttpsProto, - Host: addr, - } - if err := api.ServeApi([]string{listenURL.String()}); err != nil { - logrus.Fatalf("Unable to spawn the test daemon: %s", err) - } - }() - - // Give some time to ListenAndServer to actually start - time.Sleep(time.Second) - - api.AcceptConnections(getDaemon(eng)) - - return eng -} - // FIXME: test that ImagePull(json=true) send correct json output func GetTestImage(daemon *daemon.Daemon) *image.Image { From bb9da6ba9294a8eab8f4dfaf7cf07c57959fe608 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 22 Apr 2015 05:06:58 -0700 Subject: [PATCH 257/332] Move CLI config processing out from under registry dir No logic changes should be in here, just moving things around. Signed-off-by: Doug Davis --- api/client/cli.go | 9 +- api/client/create.go | 2 +- api/client/login.go | 3 +- api/client/push.go | 2 +- api/client/utils.go | 7 +- api/server/server.go | 24 +-- builder/evaluator.go | 6 +- builder/internals.go | 3 +- builder/job.go | 9 +- cliconfig/config.go | 208 +++++++++++++++++++ {registry => cliconfig}/config_file_test.go | 14 +- graph/pull.go | 3 +- graph/push.go | 3 +- integration/runtime_test.go | 4 +- registry/auth.go | 210 ++------------------ registry/auth_test.go | 43 ++-- registry/registry_test.go | 5 +- registry/service.go | 6 +- registry/session.go | 9 +- 19 files changed, 301 insertions(+), 269 deletions(-) create mode 100644 cliconfig/config.go rename {registry => cliconfig}/config_file_test.go (94%) diff --git a/api/client/cli.go b/api/client/cli.go index 0a1fb2ef8a0d0..600d4cc5a3abb 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -15,10 +15,10 @@ import ( "text/template" "time" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/registry" ) // DockerCli represents the docker command line client. @@ -28,8 +28,9 @@ type DockerCli struct { proto string // addr holds the client address. addr string - // configFile holds the configuration file (instance of registry.ConfigFile). - configFile *registry.ConfigFile + + // configFile has the client configuration file + configFile *cliconfig.ConfigFile // in holds the input stream and closer (io.ReadCloser) for the client. in io.ReadCloser // out holds the output stream (io.Writer) for the client. @@ -184,7 +185,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } - configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker")) + configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker")) if e != nil { fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) } diff --git a/api/client/create.go b/api/client/create.go index d2987a67e46e7..b0819a05d7043 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -38,7 +38,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) buf, err := json.Marshal(authConfig) if err != nil { return err diff --git a/api/client/login.go b/api/client/login.go index e8e87fc5e3834..d7da1de2b04f4 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/docker/docker/api/types" + "github.com/docker/docker/cliconfig" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" @@ -56,7 +57,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig, ok := cli.configFile.AuthConfigs[serverAddress] if !ok { - authconfig = registry.AuthConfig{} + authconfig = cliconfig.AuthConfig{} } if username == "" { diff --git a/api/client/push.go b/api/client/push.go index d4fc4c5c9e2ad..dc4266cb757db 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -28,7 +28,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { return err } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) // If we're not using a custom registry, we know the restrictions // applied to repository names and can warn the user in advance. // Custom repositories can have different rules, and we must also diff --git a/api/client/utils.go b/api/client/utils.go index 804dc0c58daef..7a52ad25f47aa 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -21,6 +21,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/signal" @@ -119,7 +120,7 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m } func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) { - cmdAttempt := func(authConfig registry.AuthConfig) (io.ReadCloser, int, error) { + cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) { buf, err := json.Marshal(authConfig) if err != nil { return nil, -1, err @@ -150,14 +151,14 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(index) + authConfig := registry.ResolveAuthConfig(cli.configFile, index) body, statusCode, err := cmdAttempt(authConfig) if statusCode == http.StatusUnauthorized { fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil { return nil, -1, err } - authConfig = cli.configFile.ResolveAuthConfig(index) + authConfig = registry.ResolveAuthConfig(cli.configFile, index) return cmdAttempt(authConfig) } return body, statusCode, err diff --git a/api/server/server.go b/api/server/server.go index 646a8c6776a49..e43ad29ba77f2 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -22,6 +22,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/builder" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -34,7 +35,6 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -239,7 +239,7 @@ func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) { } func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var config *registry.AuthConfig + var config *cliconfig.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() if err != nil { @@ -728,13 +728,13 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w tag = r.Form.Get("tag") ) authEncoded := r.Header.Get("X-Registry-Auth") - authConfig := ®istry.AuthConfig{} + authConfig := &cliconfig.AuthConfig{} if authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } @@ -802,7 +802,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w return err } var ( - config *registry.AuthConfig + config *cliconfig.AuthConfig authEncoded = r.Header.Get("X-Registry-Auth") headers = map[string][]string{} ) @@ -812,7 +812,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w if err := json.NewDecoder(authJson).Decode(&config); err != nil { // for a search it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - config = ®istry.AuthConfig{} + config = &cliconfig.AuthConfig{} } } for k, v := range r.Header { @@ -841,7 +841,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h if err := parseForm(r); err != nil { return err } - authConfig := ®istry.AuthConfig{} + authConfig := &cliconfig.AuthConfig{} authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { @@ -849,7 +849,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // to increase compatibility to existing api it is defaulting to be empty - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header @@ -1263,9 +1263,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } var ( authEncoded = r.Header.Get("X-Registry-Auth") - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") - configFile = ®istry.ConfigFile{} + configFile = &cliconfig.ConfigFile{} buildConfig = builder.NewBuildConfig() ) @@ -1278,7 +1278,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } @@ -1287,7 +1287,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - configFile = ®istry.ConfigFile{} + configFile = &cliconfig.ConfigFile{} } } diff --git a/builder/evaluator.go b/builder/evaluator.go index 7cbba03514bfc..2f9d4ff85b759 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -30,13 +30,13 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/command" "github.com/docker/docker/builder/parser" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/tarsum" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -99,8 +99,8 @@ type Builder struct { // the final configs of the Dockerfile but dont want the layers disableCommit bool - AuthConfig *registry.AuthConfig - ConfigFile *registry.ConfigFile + AuthConfig *cliconfig.AuthConfig + ConfigFile *cliconfig.ConfigFile // Deprecated, original writer used for ImagePull. To be removed. OutOld io.Writer diff --git a/builder/internals.go b/builder/internals.go index 9574351ca6a71..731ca84fede08 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -36,6 +36,7 @@ import ( "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" + "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" ) @@ -443,7 +444,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { if err != nil { return nil, err } - resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index) + resolvedAuth := registry.ResolveAuthConfig(b.ConfigFile, repoInfo.Index) pullRegistryAuth = &resolvedAuth } diff --git a/builder/job.go b/builder/job.go index 4c6e55b0a9b1c..115d89a4b9ef7 100644 --- a/builder/job.go +++ b/builder/job.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/parser" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" @@ -50,8 +51,8 @@ type Config struct { CpuShares int64 CpuSetCpus string CpuSetMems string - AuthConfig *registry.AuthConfig - ConfigFile *registry.ConfigFile + AuthConfig *cliconfig.AuthConfig + ConfigFile *cliconfig.ConfigFile Stdout io.Writer Context io.ReadCloser @@ -76,8 +77,8 @@ func (b *Config) WaitCancelled() <-chan struct{} { func NewBuildConfig() *Config { return &Config{ - AuthConfig: ®istry.AuthConfig{}, - ConfigFile: ®istry.ConfigFile{}, + AuthConfig: &cliconfig.AuthConfig{}, + ConfigFile: &cliconfig.ConfigFile{}, cancelled: make(chan struct{}), } } diff --git a/cliconfig/config.go b/cliconfig/config.go new file mode 100644 index 0000000000000..19a92fbd85424 --- /dev/null +++ b/cliconfig/config.go @@ -0,0 +1,208 @@ +package cliconfig + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/homedir" +) + +const ( + // Where we store the config file + CONFIGFILE = "config.json" + OLD_CONFIGFILE = ".dockercfg" + + // This constant is only used for really old config files when the + // URL wasn't saved as part of the config file and it was just + // assumed to be this value. + DEFAULT_INDEXSERVER = "https://index.docker.io/v1/" +) + +var ( + ErrConfigFileMissing = errors.New("The Auth config file is missing") +) + +// Registry Auth Info +type AuthConfig struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` + Email string `json:"email"` + ServerAddress string `json:"serveraddress,omitempty"` +} + +// ~/.docker/config.json file info +type ConfigFile struct { + AuthConfigs map[string]AuthConfig `json:"auths"` + HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` + filename string // Note: not serialized - for internal use only +} + +func NewConfigFile(fn string) *ConfigFile { + return &ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + HttpHeaders: make(map[string]string), + filename: fn, + } +} + +// load up the auth config information and return values +// FIXME: use the internal golang config parser +func Load(configDir string) (*ConfigFile, error) { + if configDir == "" { + configDir = filepath.Join(homedir.Get(), ".docker") + } + + configFile := ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + filename: filepath.Join(configDir, CONFIGFILE), + } + + // Try happy path first - latest config file + if _, err := os.Stat(configFile.filename); err == nil { + file, err := os.Open(configFile.filename) + if err != nil { + return &configFile, err + } + defer file.Close() + + if err := json.NewDecoder(file).Decode(&configFile); err != nil { + return &configFile, err + } + + for addr, ac := range configFile.AuthConfigs { + ac.Username, ac.Password, err = DecodeAuth(ac.Auth) + if err != nil { + return &configFile, err + } + ac.Auth = "" + ac.ServerAddress = addr + configFile.AuthConfigs[addr] = ac + } + + return &configFile, nil + } else if !os.IsNotExist(err) { + // if file is there but we can't stat it for any reason other + // than it doesn't exist then stop + return &configFile, err + } + + // Can't find latest config file so check for the old one + confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) + + if _, err := os.Stat(confFile); err != nil { + return &configFile, nil //missing file is not an error + } + + b, err := ioutil.ReadFile(confFile) + if err != nil { + return &configFile, err + } + + if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return &configFile, fmt.Errorf("The Auth config file is empty") + } + authConfig := AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + if len(origAuth) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1]) + if err != nil { + return &configFile, err + } + origEmail := strings.Split(arr[1], " = ") + if len(origEmail) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Email = origEmail[1] + authConfig.ServerAddress = DEFAULT_INDEXSERVER + configFile.AuthConfigs[DEFAULT_INDEXSERVER] = authConfig + } else { + for k, authConfig := range configFile.AuthConfigs { + authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth) + if err != nil { + return &configFile, err + } + authConfig.Auth = "" + authConfig.ServerAddress = k + configFile.AuthConfigs[k] = authConfig + } + } + return &configFile, nil +} + +func (configFile *ConfigFile) Save() error { + // Encode sensitive data into a new/temp struct + tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) + for k, authConfig := range configFile.AuthConfigs { + authCopy := authConfig + + authCopy.Auth = EncodeAuth(&authCopy) + authCopy.Username = "" + authCopy.Password = "" + authCopy.ServerAddress = "" + tmpAuthConfigs[k] = authCopy + } + + saveAuthConfigs := configFile.AuthConfigs + configFile.AuthConfigs = tmpAuthConfigs + defer func() { configFile.AuthConfigs = saveAuthConfigs }() + + data, err := json.MarshalIndent(configFile, "", "\t") + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { + return err + } + + err = ioutil.WriteFile(configFile.filename, data, 0600) + if err != nil { + return err + } + + return nil +} + +func (config *ConfigFile) Filename() string { + return config.filename +} + +// create a base64 encoded auth string to store in config +func EncodeAuth(authConfig *AuthConfig) string { + authStr := authConfig.Username + ":" + authConfig.Password + msg := []byte(authStr) + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) + base64.StdEncoding.Encode(encoded, msg) + return string(encoded) +} + +// decode the auth string +func DecodeAuth(authStr string) (string, string, error) { + decLen := base64.StdEncoding.DecodedLen(len(authStr)) + decoded := make([]byte, decLen) + authByte := []byte(authStr) + n, err := base64.StdEncoding.Decode(decoded, authByte) + if err != nil { + return "", "", err + } + if n > decLen { + return "", "", fmt.Errorf("Something went wrong decoding auth config") + } + arr := strings.SplitN(string(decoded), ":", 2) + if len(arr) != 2 { + return "", "", fmt.Errorf("Invalid auth configuration file") + } + password := strings.Trim(arr[1], "\x00") + return arr[0], password, nil +} diff --git a/registry/config_file_test.go b/cliconfig/config_file_test.go similarity index 94% rename from registry/config_file_test.go rename to cliconfig/config_file_test.go index 6f8bd74f534eb..6d1125f7bf816 100644 --- a/registry/config_file_test.go +++ b/cliconfig/config_file_test.go @@ -1,4 +1,4 @@ -package registry +package cliconfig import ( "io/ioutil" @@ -14,7 +14,7 @@ import ( func TestMissingFile(t *testing.T) { tmpHome, _ := ioutil.TempDir("", "config-test") - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on missing file: %q", err) } @@ -36,7 +36,7 @@ func TestSaveFileToDirs(t *testing.T) { tmpHome += "/.docker" - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on missing file: %q", err) } @@ -58,7 +58,7 @@ func TestEmptyFile(t *testing.T) { fn := filepath.Join(tmpHome, CONFIGFILE) ioutil.WriteFile(fn, []byte(""), 0600) - _, err := LoadConfig(tmpHome) + _, err := Load(tmpHome) if err == nil { t.Fatalf("Was supposed to fail") } @@ -69,7 +69,7 @@ func TestEmptyJson(t *testing.T) { fn := filepath.Join(tmpHome, CONFIGFILE) ioutil.WriteFile(fn, []byte("{}"), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } @@ -104,7 +104,7 @@ func TestOldJson(t *testing.T) { js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` ioutil.WriteFile(fn, []byte(js), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } @@ -133,7 +133,7 @@ func TestNewJson(t *testing.T) { js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` ioutil.WriteFile(fn, []byte(js), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } diff --git a/graph/pull.go b/graph/pull.go index b62591ffb6c81..5bfa37316c234 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -23,7 +24,7 @@ import ( type ImagePullConfig struct { Parallel bool MetaHeaders map[string][]string - AuthConfig *registry.AuthConfig + AuthConfig *cliconfig.AuthConfig Json bool OutStream io.Writer } diff --git a/graph/push.go b/graph/push.go index 34db27b910a25..62ff94e0c41b9 100644 --- a/graph/push.go +++ b/graph/push.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -26,7 +27,7 @@ var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable") type ImagePushConfig struct { MetaHeaders map[string][]string - AuthConfig *registry.AuthConfig + AuthConfig *cliconfig.AuthConfig Tag string Json bool OutStream io.Writer diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 0c412c86293b0..587712ffca063 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -18,6 +18,7 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" @@ -28,7 +29,6 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -135,7 +135,7 @@ func setupBaseImage() { imagePullConfig := &graph.ImagePullConfig{ Parallel: true, OutStream: ioutils.NopWriteCloser(os.Stdout), - AuthConfig: ®istry.AuthConfig{}, + AuthConfig: &cliconfig.AuthConfig{}, } d := getDaemon(eng) if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { diff --git a/registry/auth.go b/registry/auth.go index ef4985abcd746..1ac1ca984e3da 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -1,51 +1,21 @@ package registry import ( - "encoding/base64" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" - "os" - "path/filepath" "strings" "sync" "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/homedir" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/requestdecorator" ) -const ( - // Where we store the config file - CONFIGFILE = "config.json" - OLD_CONFIGFILE = ".dockercfg" -) - -var ( - ErrConfigFileMissing = errors.New("The Auth config file is missing") -) - -// Registry Auth Info -type AuthConfig struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Auth string `json:"auth"` - Email string `json:"email"` - ServerAddress string `json:"serveraddress,omitempty"` -} - -// ~/.docker/config.json file info -type ConfigFile struct { - AuthConfigs map[string]AuthConfig `json:"auths"` - HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` - filename string // Note: not serialized - for internal use only -} - type RequestAuthorization struct { - authConfig *AuthConfig + authConfig *cliconfig.AuthConfig registryEndpoint *Endpoint resource string scope string @@ -56,7 +26,7 @@ type RequestAuthorization struct { tokenExpiration time.Time } -func NewRequestAuthorization(authConfig *AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization { +func NewRequestAuthorization(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization { return &RequestAuthorization{ authConfig: authConfig, registryEndpoint: registryEndpoint, @@ -121,160 +91,8 @@ func (auth *RequestAuthorization) Authorize(req *http.Request) error { return nil } -// create a base64 encoded auth string to store in config -func encodeAuth(authConfig *AuthConfig) string { - authStr := authConfig.Username + ":" + authConfig.Password - msg := []byte(authStr) - encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) - base64.StdEncoding.Encode(encoded, msg) - return string(encoded) -} - -// decode the auth string -func decodeAuth(authStr string) (string, string, error) { - decLen := base64.StdEncoding.DecodedLen(len(authStr)) - decoded := make([]byte, decLen) - authByte := []byte(authStr) - n, err := base64.StdEncoding.Decode(decoded, authByte) - if err != nil { - return "", "", err - } - if n > decLen { - return "", "", fmt.Errorf("Something went wrong decoding auth config") - } - arr := strings.SplitN(string(decoded), ":", 2) - if len(arr) != 2 { - return "", "", fmt.Errorf("Invalid auth configuration file") - } - password := strings.Trim(arr[1], "\x00") - return arr[0], password, nil -} - -// load up the auth config information and return values -// FIXME: use the internal golang config parser -func LoadConfig(configDir string) (*ConfigFile, error) { - if configDir == "" { - configDir = filepath.Join(homedir.Get(), ".docker") - } - - configFile := ConfigFile{ - AuthConfigs: make(map[string]AuthConfig), - filename: filepath.Join(configDir, CONFIGFILE), - } - - // Try happy path first - latest config file - if _, err := os.Stat(configFile.filename); err == nil { - file, err := os.Open(configFile.filename) - if err != nil { - return &configFile, err - } - defer file.Close() - - if err := json.NewDecoder(file).Decode(&configFile); err != nil { - return &configFile, err - } - - for addr, ac := range configFile.AuthConfigs { - ac.Username, ac.Password, err = decodeAuth(ac.Auth) - if err != nil { - return &configFile, err - } - ac.Auth = "" - ac.ServerAddress = addr - configFile.AuthConfigs[addr] = ac - } - - return &configFile, nil - } else if !os.IsNotExist(err) { - // if file is there but we can't stat it for any reason other - // than it doesn't exist then stop - return &configFile, err - } - - // Can't find latest config file so check for the old one - confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) - - if _, err := os.Stat(confFile); err != nil { - return &configFile, nil //missing file is not an error - } - - b, err := ioutil.ReadFile(confFile) - if err != nil { - return &configFile, err - } - - if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { - arr := strings.Split(string(b), "\n") - if len(arr) < 2 { - return &configFile, fmt.Errorf("The Auth config file is empty") - } - authConfig := AuthConfig{} - origAuth := strings.Split(arr[0], " = ") - if len(origAuth) != 2 { - return &configFile, fmt.Errorf("Invalid Auth config file") - } - authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) - if err != nil { - return &configFile, err - } - origEmail := strings.Split(arr[1], " = ") - if len(origEmail) != 2 { - return &configFile, fmt.Errorf("Invalid Auth config file") - } - authConfig.Email = origEmail[1] - authConfig.ServerAddress = IndexServerAddress() - // *TODO: Switch to using IndexServerName() instead? - configFile.AuthConfigs[IndexServerAddress()] = authConfig - } else { - for k, authConfig := range configFile.AuthConfigs { - authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) - if err != nil { - return &configFile, err - } - authConfig.Auth = "" - authConfig.ServerAddress = k - configFile.AuthConfigs[k] = authConfig - } - } - return &configFile, nil -} - -func (configFile *ConfigFile) Save() error { - // Encode sensitive data into a new/temp struct - tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) - for k, authConfig := range configFile.AuthConfigs { - authCopy := authConfig - - authCopy.Auth = encodeAuth(&authCopy) - authCopy.Username = "" - authCopy.Password = "" - authCopy.ServerAddress = "" - tmpAuthConfigs[k] = authCopy - } - - saveAuthConfigs := configFile.AuthConfigs - configFile.AuthConfigs = tmpAuthConfigs - defer func() { configFile.AuthConfigs = saveAuthConfigs }() - - data, err := json.MarshalIndent(configFile, "", "\t") - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { - return err - } - - err = ioutil.WriteFile(configFile.filename, data, 0600) - if err != nil { - return err - } - - return nil -} - // Login tries to register/login to the registry server. -func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { // Separates the v2 registry login logic from the v1 logic. if registryEndpoint.Version == APIVersion2 { return loginV2(authConfig, registryEndpoint, factory) @@ -283,7 +101,7 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestd } // loginV1 tries to register/login to the v1 registry server. -func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { var ( status string reqBody []byte @@ -396,7 +214,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques // now, users should create their account through other means like directly from a web page // served by the v2 registry service provider. Whether this will be supported in the future // is to be determined. -func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) var ( err error @@ -429,7 +247,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors) } -func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { +func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil) if err != nil { return err @@ -450,7 +268,7 @@ func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, regis return nil } -func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { +func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory) if err != nil { return err @@ -477,7 +295,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis } // this method matches a auth configuration to a server address or a url -func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { +func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig { configKey := index.GetAuthConfigKey() // First try the happy case if c, found := config.AuthConfigs[configKey]; found || index.Official { @@ -499,16 +317,12 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // Maybe they have a legacy config file, we will iterate the keys converting // them to the new format and testing - for registry, config := range config.AuthConfigs { + for registry, ac := range config.AuthConfigs { if configKey == convertToHostname(registry) { - return config + return ac } } // When all else fails, return an empty auth config - return AuthConfig{} -} - -func (config *ConfigFile) Filename() string { - return config.filename + return cliconfig.AuthConfig{} } diff --git a/registry/auth_test.go b/registry/auth_test.go index b07aa7dbc8e67..71b963a1f1ee7 100644 --- a/registry/auth_test.go +++ b/registry/auth_test.go @@ -5,14 +5,16 @@ import ( "os" "path/filepath" "testing" + + "github.com/docker/docker/cliconfig" ) func TestEncodeAuth(t *testing.T) { - newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} - authStr := encodeAuth(newAuthConfig) - decAuthConfig := &AuthConfig{} + newAuthConfig := &cliconfig.AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} + authStr := cliconfig.EncodeAuth(newAuthConfig) + decAuthConfig := &cliconfig.AuthConfig{} var err error - decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) + decAuthConfig.Username, decAuthConfig.Password, err = cliconfig.DecodeAuth(authStr) if err != nil { t.Fatal(err) } @@ -27,19 +29,16 @@ func TestEncodeAuth(t *testing.T) { } } -func setupTempConfigFile() (*ConfigFile, error) { +func setupTempConfigFile() (*cliconfig.ConfigFile, error) { root, err := ioutil.TempDir("", "docker-test-auth") if err != nil { return nil, err } - root = filepath.Join(root, CONFIGFILE) - configFile := &ConfigFile{ - AuthConfigs: make(map[string]AuthConfig), - filename: root, - } + root = filepath.Join(root, cliconfig.CONFIGFILE) + configFile := cliconfig.NewConfigFile(root) for _, registry := range []string{"testIndex", IndexServerAddress()} { - configFile.AuthConfigs[registry] = AuthConfig{ + configFile.AuthConfigs[registry] = cliconfig.AuthConfig{ Username: "docker-user", Password: "docker-pass", Email: "docker@docker.io", @@ -54,7 +53,7 @@ func TestSameAuthDataPostSave(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) err = configFile.Save() if err != nil { @@ -81,7 +80,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) indexConfig := configFile.AuthConfigs[IndexServerAddress()] @@ -92,10 +91,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { Official: false, } - resolved := configFile.ResolveAuthConfig(officialIndex) + resolved := ResolveAuthConfig(configFile, officialIndex) assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()") - resolved = configFile.ResolveAuthConfig(privateIndex) + resolved = ResolveAuthConfig(configFile, privateIndex) assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()") } @@ -104,26 +103,26 @@ func TestResolveAuthConfigFullURL(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) - registryAuth := AuthConfig{ + registryAuth := cliconfig.AuthConfig{ Username: "foo-user", Password: "foo-pass", Email: "foo@example.com", } - localAuth := AuthConfig{ + localAuth := cliconfig.AuthConfig{ Username: "bar-user", Password: "bar-pass", Email: "bar@example.com", } - officialAuth := AuthConfig{ + officialAuth := cliconfig.AuthConfig{ Username: "baz-user", Password: "baz-pass", Email: "baz@example.com", } configFile.AuthConfigs[IndexServerAddress()] = officialAuth - expectedAuths := map[string]AuthConfig{ + expectedAuths := map[string]cliconfig.AuthConfig{ "registry.example.com": registryAuth, "localhost:8000": localAuth, "registry.com": localAuth, @@ -160,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { } for _, registry := range registries { configFile.AuthConfigs[registry] = configured - resolved := configFile.ResolveAuthConfig(index) + resolved := ResolveAuthConfig(configFile, index) if resolved.Email != configured.Email { t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) } delete(configFile.AuthConfigs, registry) - resolved = configFile.ResolveAuthConfig(index) + resolved = ResolveAuthConfig(configFile, index) if resolved.Email == configured.Email { t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email) } diff --git a/registry/registry_test.go b/registry/registry_test.go index a066de9f8e6ea..b4bd4ee724c79 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/requestdecorator" ) @@ -20,7 +21,7 @@ const ( ) func spawnTestRegistrySession(t *testing.T) *Session { - authConfig := &AuthConfig{} + authConfig := &cliconfig.AuthConfig{} endpoint, err := NewEndpoint(makeIndex("/v1/")) if err != nil { t.Fatal(err) @@ -33,7 +34,7 @@ func spawnTestRegistrySession(t *testing.T) *Session { } func TestPublicSession(t *testing.T) { - authConfig := &AuthConfig{} + authConfig := &cliconfig.AuthConfig{} getSessionDecorators := func(index *IndexInfo) int { endpoint, err := NewEndpoint(index) diff --git a/registry/service.go b/registry/service.go index cf29732f4903c..87fc1d076f621 100644 --- a/registry/service.go +++ b/registry/service.go @@ -1,5 +1,7 @@ package registry +import "github.com/docker/docker/cliconfig" + type Service struct { Config *ServiceConfig } @@ -15,7 +17,7 @@ func NewService(options *Options) *Service { // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was sucessful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(authConfig *AuthConfig) (string, error) { +func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) { addr := authConfig.ServerAddress if addr == "" { // Use the official registry address if not specified. @@ -35,7 +37,7 @@ func (s *Service) Auth(authConfig *AuthConfig) (string, error) { // Search queries the public registry for images matching the specified // search terms, and returns the results. -func (s *Service) Search(term string, authConfig *AuthConfig, headers map[string][]string) (*SearchResults, error) { +func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) { repoInfo, err := s.ResolveRepository(term) if err != nil { return nil, err diff --git a/registry/session.go b/registry/session.go index 940e407e90562..dd868a2b3547e 100644 --- a/registry/session.go +++ b/registry/session.go @@ -18,20 +18,21 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/requestdecorator" "github.com/docker/docker/pkg/tarsum" ) type Session struct { - authConfig *AuthConfig + authConfig *cliconfig.AuthConfig reqFactory *requestdecorator.RequestFactory indexEndpoint *Endpoint jar *cookiejar.Jar timeout TimeoutType } -func NewSession(authConfig *AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { +func NewSession(authConfig *cliconfig.AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { r = &Session{ authConfig: authConfig, indexEndpoint: endpoint, @@ -600,12 +601,12 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { return result, err } -func (r *Session) GetAuthConfig(withPasswd bool) *AuthConfig { +func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig { password := "" if withPasswd { password = r.authConfig.Password } - return &AuthConfig{ + return &cliconfig.AuthConfig{ Username: r.authConfig.Username, Password: password, Email: r.authConfig.Email, From ec51ba01dbbeadb06cc7f3c14e94f8a2ee938a34 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Thu, 23 Apr 2015 10:27:34 -0700 Subject: [PATCH 258/332] Return weird behaviour of returning json errors We need this, so client can get error from stream and not from status code, which is already 200, because write to ResponseWriter was occured. Signed-off-by: Alexander Morozov --- api/server/server.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 646a8c6776a49..3a524aae4dc42 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -861,11 +861,12 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h useJSON := version.GreaterThan("1.0") name := vars["name"] + output := utils.NewWriteFlusher(w) imagePushConfig := &graph.ImagePushConfig{ MetaHeaders: metaHeaders, AuthConfig: authConfig, Tag: r.Form.Get("tag"), - OutStream: utils.NewWriteFlusher(w), + OutStream: output, Json: useJSON, } if useJSON { @@ -873,8 +874,11 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h } if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { + if !output.Flushed() { + return err + } sf := streamformatter.NewStreamFormatter(useJSON) - return fmt.Errorf(string(sf.FormatError(err))) + output.Write(sf.FormatError(err)) } return nil From ecccfa82aa22829c52778c4457cacd8d766e3dda Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 22 Apr 2015 11:20:32 -0700 Subject: [PATCH 259/332] Validate we're not using the old testing stuff Signed-off-by: Doug Davis --- Makefile | 2 +- hack/make.sh | 1 + hack/make/validate-test | 35 +++++++++++++++++++++++++++++++++++ integration-cli/check_test.go | 4 +++- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 hack/make/validate-test diff --git a/Makefile b/Makefile index 7978b632cacd8..b60b2a4d0004d 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ test-docker-py: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py validate: build - $(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-toml validate-vet + $(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-test validate-toml validate-vet shell: build $(DOCKER_RUN_DOCKER) bash diff --git a/hack/make.sh b/hack/make.sh index eeb26cbce0bb7..31e08cd37618a 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -46,6 +46,7 @@ echo DEFAULT_BUNDLES=( validate-dco validate-gofmt + validate-test validate-toml validate-vet diff --git a/hack/make/validate-test b/hack/make/validate-test new file mode 100644 index 0000000000000..d9d05f3bea8f9 --- /dev/null +++ b/hack/make/validate-test @@ -0,0 +1,35 @@ +#!/bin/bash + +# Make sure we're not using gos' Testing package any more in integration-cli + +source "${MAKEDIR}/.validate" + +IFS=$'\n' +files=( $(validate_diff --diff-filter=ACMR --name-only -- 'integration-cli/*.go' || true) ) +unset IFS + +badFiles=() +for f in "${files[@]}"; do + # skip check_test.go since it *does* use the testing package + if [ "$f" = "integration-cli/check_test.go" ]; then + continue + fi + + # we use "git show" here to validate that what's committed is formatted + if git show "$VALIDATE_HEAD:$f" | grep -q testing.T; then + badFiles+=( "$f" ) + fi +done + +if [ ${#badFiles[@]} -eq 0 ]; then + echo 'Congratulations! No testing.T found.' +else + { + echo "These files use the wrong testing infrastructure:" + for f in "${badFiles[@]}"; do + echo " - $f" + done + echo + } >&2 + false +fi diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 330bc373b5d32..07bb9315966f0 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -8,7 +8,9 @@ import ( "github.com/go-check/check" ) -func Test(t *testing.T) { check.TestingT(t) } +func Test(t *testing.T) { + check.TestingT(t) +} type TimerSuite struct { start time.Time From 929af4c38d8ca4754d2a3ccf087d359bb67c33f3 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 17 Apr 2015 16:19:34 -0600 Subject: [PATCH 260/332] Fix daemon start/stop logic in hack/make/* scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the Bash manual's `set -e` description: (https://www.gnu.org/software/bash/manual/bashref.html#index-set) > Exit immediately if a pipeline (see Pipelines), which may consist of a > single simple command (see Simple Commands), a list (see Lists), or a > compound command (see Compound Commands) returns a non-zero status. > The shell does not exit if the command that fails is part of the > command list immediately following a while or until keyword, part of > the test in an if statement, part of any command executed in a && or > || list except the command following the final && or ||, any command > in a pipeline but the last, or if the command’s return status is being > inverted with !. If a compound command other than a subshell returns a > non-zero status because a command failed while -e was being ignored, > the shell does not exit. Additionally, further down: > If a compound command or shell function executes in a context where -e > is being ignored, none of the commands executed within the compound > command or function body will be affected by the -e setting, even if > -e is set and a command returns a failure status. If a compound > command or shell function sets -e while executing in a context where > -e is ignored, that setting will not have any effect until the > compound command or the command containing the function call > completes. Thus, the only way to have our `.integration-daemon-stop` script actually run appropriately to clean up our daemon on test/script failure is to use `trap ... EXIT`, which we traditionally avoid because it does not have any stacking capabilities, but in this case is a reasonable compromise because it's going to be the only script using it (for now, at least; we can evaluate more complex solutions in the future if they actually become necessary). The alternatives were much less reasonable. One is to have the entire complex chains in any script wanting to use `.integration-daemon-start` / `.integration-daemon-stop` be chained together with `&&` in an `if` block, which is untenable. The other I could think of was taking the body of these scripts out into separate scripts, essentially meaning we'd need two files for each of these, which further complicates the maintenance. Add to that the fact that our `trap ... EXIT` is scoped to the enclosing subshell (`( ... )`) and we're in even more reasonable territory with this pattern. Signed-off-by: Andrew "Tianon" Page --- hack/make/.integration-daemon-start | 1 + hack/make/.integration-daemon-stop | 2 + hack/make/build-deb | 108 +++++++++++++--------------- hack/make/test-docker-py | 26 +++---- hack/make/test-integration-cli | 18 ++--- 5 files changed, 63 insertions(+), 92 deletions(-) diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 570c6c7a9af9f..57fd525028e74 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -25,6 +25,7 @@ if [ -z "$DOCKER_TEST_HOST" ]; then --pidfile "$DEST/docker.pid" \ &> "$DEST/docker.log" ) & + trap "source '${MAKEDIR}/.integration-daemon-stop'" EXIT # make sure that if the script exits unexpectedly, we stop this daemon we just started else export DOCKER_HOST="$DOCKER_TEST_HOST" fi diff --git a/hack/make/.integration-daemon-stop b/hack/make/.integration-daemon-stop index 7e4dc2353afb5..6e1dc844def89 100644 --- a/hack/make/.integration-daemon-stop +++ b/hack/make/.integration-daemon-stop @@ -1,5 +1,7 @@ #!/bin/bash +trap - EXIT # reset EXIT trap applied in .integration-daemon-start + for pidFile in $(find "$DEST" -name docker.pid); do pid=$(set -x; cat "$pidFile") ( set -x; kill "$pid" ) diff --git a/hack/make/build-deb b/hack/make/build-deb index 90c4c16939f72..a5a6d43870b38 100644 --- a/hack/make/build-deb +++ b/hack/make/build-deb @@ -7,76 +7,64 @@ DEST=$1 ( source "${MAKEDIR}/.integration-daemon-start" - # we need to wrap up everything in between integration-daemon-start and - # integration-daemon-stop to make sure we kill the daemon and don't hang, - # even and especially on test failures - didFail= - if ! { - set -e + # TODO consider using frozen images for the dockercore/builder-deb tags - # TODO consider using frozen images for the dockercore/builder-deb tags + debVersion="${VERSION//-/'~'}" + # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better + if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then + gitUnix="$(git log -1 --pretty='%at')" + gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')" + gitCommit="$(git log -1 --pretty='%h')" + gitVersion="git${gitDate}.0.${gitCommit}" + # gitVersion is now something like 'git20150128.112847.0.17e840a' + debVersion="$debVersion~$gitVersion" - debVersion="${VERSION//-/'~'}" - # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better - if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then - gitUnix="$(git log -1 --pretty='%at')" - gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')" - gitCommit="$(git log -1 --pretty='%h')" - gitVersion="git${gitDate}.0.${gitCommit}" - # gitVersion is now something like 'git20150128.112847.0.17e840a' - debVersion="$debVersion~$gitVersion" + # $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false + # true - # $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false - # true - # $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false - # true - # $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false - # true - - # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a - fi + # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a + fi - debSource="$(awk -F ': ' '$1 == "Source" { print $2; exit }' hack/make/.build-deb/control)" - debMaintainer="$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' hack/make/.build-deb/control)" - debDate="$(date --rfc-2822)" + debSource="$(awk -F ': ' '$1 == "Source" { print $2; exit }' hack/make/.build-deb/control)" + debMaintainer="$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' hack/make/.build-deb/control)" + debDate="$(date --rfc-2822)" - # if go-md2man is available, pre-generate the man pages - ./docs/man/md2man-all.sh -q || true - # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this + # if go-md2man is available, pre-generate the man pages + ./docs/man/md2man-all.sh -q || true + # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this - # TODO add a configurable knob for _which_ debs to build so we don't have to modify the file or build all of them every time we need to test - for dir in contrib/builder/deb/*/; do - version="$(basename "$dir")" - suite="${version##*-}" + # TODO add a configurable knob for _which_ debs to build so we don't have to modify the file or build all of them every time we need to test + for dir in contrib/builder/deb/*/; do + version="$(basename "$dir")" + suite="${version##*-}" - image="dockercore/builder-deb:$version" - if ! docker inspect "$image" &> /dev/null; then - ( set -x && docker build -t "$image" "$dir" ) - fi + image="dockercore/builder-deb:$version" + if ! docker inspect "$image" &> /dev/null; then + ( set -x && docker build -t "$image" "$dir" ) + fi - mkdir -p "$DEST/$version" - cat > "$DEST/$version/Dockerfile.build" <<-EOF - FROM $image - WORKDIR /usr/src/docker - COPY . /usr/src/docker - RUN ln -sfv hack/make/.build-deb debian - RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog - RUN dpkg-buildpackage -uc -us - EOF - cp -a "$DEST/$version/Dockerfile.build" . # can't use $DEST because it's in .dockerignore... - tempImage="docker-temp/build-deb:$version" - ( set -x && docker build -t "$tempImage" -f Dockerfile.build . ) - docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version" - docker rmi "$tempImage" - done - }; then - didFail=1 - fi + mkdir -p "$DEST/$version" + cat > "$DEST/$version/Dockerfile.build" <<-EOF + FROM $image + WORKDIR /usr/src/docker + COPY . /usr/src/docker + RUN ln -sfv hack/make/.build-deb debian + RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog + RUN dpkg-buildpackage -uc -us + EOF + cp -a "$DEST/$version/Dockerfile.build" . # can't use $DEST because it's in .dockerignore... + tempImage="docker-temp/build-deb:$version" + ( set -x && docker build -t "$tempImage" -f Dockerfile.build . ) + docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version" + docker rmi "$tempImage" + done # clean up after ourselves rm -f Dockerfile.build source "${MAKEDIR}/.integration-daemon-stop" - - [ -z "$didFail" ] # "set -e" ftw -) 2>&1 | tee -a $DEST/test.log +) 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py index 409cee0e4e0a6..ac5ef3583344c 100644 --- a/hack/make/test-docker-py +++ b/hack/make/test-docker-py @@ -7,24 +7,14 @@ DEST=$1 ( source "${MAKEDIR}/.integration-daemon-start" - # we need to wrap up everything in between integration-daemon-start and - # integration-daemon-stop to make sure we kill the daemon and don't hang, - # even and especially on test failures - didFail= - if ! { - dockerPy='/docker-py' - [ -d "$dockerPy" ] || { - dockerPy="$DEST/docker-py" - git clone https://github.com/docker/docker-py.git "$dockerPy" - } + dockerPy='/docker-py' + [ -d "$dockerPy" ] || { + dockerPy="$DEST/docker-py" + git clone https://github.com/docker/docker-py.git "$dockerPy" + } - # exporting PYTHONPATH to import "docker" from our local docker-py - test_env PYTHONPATH="$dockerPy" python "$dockerPy/tests/integration_test.py" - }; then - didFail=1 - fi + # exporting PYTHONPATH to import "docker" from our local docker-py + test_env PYTHONPATH="$dockerPy" python "$dockerPy/tests/integration_test.py" source "${MAKEDIR}/.integration-daemon-stop" - - [ -z "$didFail" ] # "set -e" ftw -) 2>&1 | tee -a $DEST/test.log +) 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 8e9b975704b21..db1cb298fd592 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -11,21 +11,11 @@ bundle_test_integration_cli() { ( source "${MAKEDIR}/.integration-daemon-start" - # we need to wrap up everything in between integration-daemon-start and - # integration-daemon-stop to make sure we kill the daemon and don't hang, - # even and especially on test failures - didFail= - if ! { - source "${MAKEDIR}/.ensure-frozen-images" - source "${MAKEDIR}/.ensure-httpserver" - source "${MAKEDIR}/.ensure-emptyfs" + source "${MAKEDIR}/.ensure-frozen-images" + source "${MAKEDIR}/.ensure-httpserver" + source "${MAKEDIR}/.ensure-emptyfs" - bundle_test_integration_cli - }; then - didFail=1 - fi + bundle_test_integration_cli source "${MAKEDIR}/.integration-daemon-stop" - - [ -z "$didFail" ] # "set -e" ftw ) 2>&1 | tee -a "$DEST/test.log" From 2a14b7dd35901167d83735a25ff626596391c4ed Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 21 Apr 2015 21:10:30 +0800 Subject: [PATCH 261/332] remove job from image_export Signed-off-by: He Simei Signed-off-by: Alexander Morozov --- api/server/server.go | 27 +++++++++++++++++++++------ graph/export.go | 25 ++++++++++++++----------- graph/service.go | 1 - 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 646a8c6776a49..a17c9ee36572f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -887,17 +887,32 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt if err := parseForm(r); err != nil { return err } - if version.GreaterThan("1.0") { + + useJSON := version.GreaterThan("1.0") + if useJSON { w.Header().Set("Content-Type", "application/x-tar") } - var job *engine.Job + + output := utils.NewWriteFlusher(w) + imageExportConfig := &graph.ImageExportConfig{ + Engine: eng, + Outstream: output, + } if name, ok := vars["name"]; ok { - job = eng.Job("image_export", name) + imageExportConfig.Names = []string{name} } else { - job = eng.Job("image_export", r.Form["names"]...) + imageExportConfig.Names = r.Form["names"] } - job.Stdout.Add(w) - return job.Run() + + if err := s.daemon.Repositories().ImageExport(imageExportConfig); err != nil { + if !output.Flushed() { + return err + } + sf := streamformatter.NewStreamFormatter(useJSON) + output.Write(sf.FormatError(err)) + } + return nil + } func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/graph/export.go b/graph/export.go index 56b5fba719c39..00cfa8975a5a5 100644 --- a/graph/export.go +++ b/graph/export.go @@ -2,7 +2,6 @@ package graph import ( "encoding/json" - "fmt" "io" "io/ioutil" "os" @@ -20,10 +19,14 @@ import ( // uncompressed tar ball. // name is the set of tags to export. // out is the writer where the images are written to. -func (s *TagStore) CmdImageExport(job *engine.Job) error { - if len(job.Args) < 1 { - return fmt.Errorf("Usage: %s IMAGE [IMAGE...]\n", job.Name) - } +type ImageExportConfig struct { + Names []string + Outstream io.Writer + Engine *engine.Engine +} + +func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { + // get image json tempdir, err := ioutil.TempDir("", "docker-export-") if err != nil { @@ -40,7 +43,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { repo[tag] = id } } - for _, name := range job.Args { + for _, name := range imageExportConfig.Names { name = registry.NormalizeLocalName(name) logrus.Debugf("Serializing %s", name) rootRepo := s.Repositories[name] @@ -48,7 +51,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { // this is a base repo name, like 'busybox' for tag, id := range rootRepo { addKey(name, tag, id) - if err := s.exportImage(job.Eng, id, tempdir); err != nil { + if err := s.exportImage(imageExportConfig.Engine, id, tempdir); err != nil { return err } } @@ -67,13 +70,13 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { if len(repoTag) > 0 { addKey(repoName, repoTag, img.ID) } - if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil { + if err := s.exportImage(imageExportConfig.Engine, img.ID, tempdir); err != nil { return err } } else { // this must be an ID that didn't get looked up just right? - if err := s.exportImage(job.Eng, name, tempdir); err != nil { + if err := s.exportImage(imageExportConfig.Engine, name, tempdir); err != nil { return err } } @@ -96,10 +99,10 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { } defer fs.Close() - if _, err := io.Copy(job.Stdout, fs); err != nil { + if _, err := io.Copy(imageExportConfig.Outstream, fs); err != nil { return err } - logrus.Debugf("End export job: %s", job.Name) + logrus.Debugf("End export image") return nil } diff --git a/graph/service.go b/graph/service.go index 337eaa3cf3a37..ab78c1d056d08 100644 --- a/graph/service.go +++ b/graph/service.go @@ -11,7 +11,6 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ "image_inspect": s.CmdLookup, - "image_export": s.CmdImageExport, "viz": s.CmdViz, } { if err := eng.Register(name, handler); err != nil { From b6569b6b82df4c5e29ee8f5ebd9db7e36919cefd Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 23 Apr 2015 08:21:39 -0400 Subject: [PATCH 262/332] contrib/init: unshare mount namespace for inits * openrc * sysvinit-debian * upstart Signed-off-by: Vincent Batts --- contrib/init/openrc/docker.initd | 6 ++++-- contrib/init/sysvinit-debian/docker | 7 ++++--- contrib/init/upstart/docker.conf | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/contrib/init/openrc/docker.initd b/contrib/init/openrc/docker.initd index a9d21b17089a3..f251e9af5a51a 100755 --- a/contrib/init/openrc/docker.initd +++ b/contrib/init/openrc/docker.initd @@ -7,6 +7,7 @@ DOCKER_LOGFILE=${DOCKER_LOGFILE:-/var/log/${SVCNAME}.log} DOCKER_PIDFILE=${DOCKER_PIDFILE:-/run/${SVCNAME}.pid} DOCKER_BINARY=${DOCKER_BINARY:-/usr/bin/docker} DOCKER_OPTS=${DOCKER_OPTS:-} +UNSHARE_BINARY=${UNSHARE_BINARY:-/usr/bin/unshare} start() { checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE" @@ -16,11 +17,12 @@ start() { ebegin "Starting docker daemon" start-stop-daemon --start --background \ - --exec "$DOCKER_BINARY" \ + --exec "$UNSHARE_BINARY" \ --pidfile "$DOCKER_PIDFILE" \ --stdout "$DOCKER_LOGFILE" \ --stderr "$DOCKER_LOGFILE" \ - -- -d -p "$DOCKER_PIDFILE" \ + -- --mount \ + -- "$DOCKER_BINARY" -d -p "$DOCKER_PIDFILE" \ $DOCKER_OPTS eend $? } diff --git a/contrib/init/sysvinit-debian/docker b/contrib/init/sysvinit-debian/docker index cf33c837791a0..35fd71f13e3a9 100755 --- a/contrib/init/sysvinit-debian/docker +++ b/contrib/init/sysvinit-debian/docker @@ -30,6 +30,7 @@ DOCKER_SSD_PIDFILE=/var/run/$BASE-ssd.pid DOCKER_LOGFILE=/var/log/$BASE.log DOCKER_OPTS= DOCKER_DESC="Docker" +UNSHARE=${UNSHARE:-/usr/bin/unshare} # Get lsb functions . /lib/lsb/init-functions @@ -99,11 +100,11 @@ case "$1" in log_begin_msg "Starting $DOCKER_DESC: $BASE" start-stop-daemon --start --background \ --no-close \ - --exec "$DOCKER" \ + --exec "$UNSHARE" \ --pidfile "$DOCKER_SSD_PIDFILE" \ --make-pidfile \ - -- \ - -d -p "$DOCKER_PIDFILE" \ + -- --mount \ + -- "$DOCKER" -d -p "$DOCKER_PIDFILE" \ $DOCKER_OPTS \ >> "$DOCKER_LOGFILE" 2>&1 log_end_msg $? diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index 4ad6058ed03ec..5e8df6e3c207f 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -37,7 +37,7 @@ script if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi - exec "$DOCKER" -d $DOCKER_OPTS + exec unshare -m -- "$DOCKER" -d $DOCKER_OPTS end script # Don't emit "started" event until docker.sock is ready. From 231d362db73310a5243a72dcdb6df2df57c74488 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Thu, 26 Mar 2015 19:43:00 +0000 Subject: [PATCH 263/332] Allow go template to work properly with inspect Closes #11641 Signed-off-by: Srini Brahmaroutu --- api/client/inspect.go | 39 ++++++++----- api/types/types.go | 17 ++++++ integration-cli/docker_api_containers_test.go | 2 +- integration-cli/docker_cli_build_test.go | 29 +++++----- integration-cli/docker_cli_commit_test.go | 2 +- integration-cli/docker_cli_exec_test.go | 2 +- integration-cli/docker_cli_inspect_test.go | 57 +++++++++++++++++++ integration-cli/docker_cli_run_test.go | 4 +- 8 files changed, 120 insertions(+), 32 deletions(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index 75861cdf205ea..db281795cdb42 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -8,12 +8,14 @@ import ( "strings" "text/template" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" ) // CmdInspect displays low-level information on one or more containers or images. // // Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...] + func (cli *DockerCli) CmdInspect(args ...string) error { cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") @@ -34,11 +36,13 @@ func (cli *DockerCli) CmdInspect(args ...string) error { indented := new(bytes.Buffer) indented.WriteByte('[') status := 0 + isImage := false for _, name := range cmd.Args() { obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil)) if err != nil { obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil)) + isImage = true if err != nil { if strings.Contains(err.Error(), "No such") { fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) @@ -57,20 +61,29 @@ func (cli *DockerCli) CmdInspect(args ...string) error { continue } } else { - var value interface{} - - // Do not use `json.Unmarshal()` because unmarshal JSON into - // an interface value, Unmarshal stores JSON numbers in - // float64, which is different from `json.Indent()` does. dec := json.NewDecoder(bytes.NewReader(obj)) - dec.UseNumber() - if err := dec.Decode(&value); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - status = 1 - continue - } - if err := tmpl.Execute(cli.out, value); err != nil { - return err + + if isImage { + inspPtr := types.ImageInspect{} + if err := dec.Decode(&inspPtr); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + status = 1 + continue + } + if err := tmpl.Execute(cli.out, inspPtr); err != nil { + return err + } + } else { + inspPtr := types.ContainerJSON{} + if err := dec.Decode(&inspPtr); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + status = 1 + continue + } + if err := tmpl.Execute(cli.out, inspPtr); err != nil { + return err + + } } cli.out.Write([]byte{'\n'}) } diff --git a/api/types/types.go b/api/types/types.go index 656aa1a6eba19..7c31065460893 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -75,6 +75,23 @@ type Image struct { Labels map[string]string } +// GET "/images/{name:.*}/json" +type ImageInspect struct { + Id string + Parent string + Comment string + Created time.Time + Container string + ContainerConfig *runconfig.Config + DockerVersion string + Author string + Config *runconfig.Config + Architecture string + Os string + Size int64 + VirtualSize int64 +} + type LegacyImage struct { ID string `json:"Id"` Repository string diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733e6cd..b4378eb6c36cc 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -644,7 +644,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { if err != nil { c.Fatal(err) } - if cmd != "[/bin/sh -c touch /test]" { + if cmd != "{[/bin/sh -c touch /test]}" { c.Fatalf("got wrong Cmd from commit: %q", cmd) } // sanity check, make sure the image is what we think it is diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 7936b2bc348e4..ac90b21876ff8 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2350,7 +2350,7 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { func (s *DockerSuite) TestBuildCmd(c *check.C) { name := "testbuildcmd" - expected := "[/bin/echo Hello World]" + expected := "{[/bin/echo Hello World]}" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -2370,7 +2370,7 @@ func (s *DockerSuite) TestBuildCmd(c *check.C) { func (s *DockerSuite) TestBuildExpose(c *check.C) { name := "testbuildexpose" - expected := "map[2375/tcp:map[]]" + expected := "map[2375/tcp:{}]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -2467,7 +2467,7 @@ func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { name := "testbuildexposeuppercaseproto" - expected := "map[5678/udp:map[]]" + expected := "map[5678/udp:{}]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -2488,7 +2488,7 @@ func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { // start building docker file with ip:hostPort:containerPort name := "testbuildexpose" - expected := "map[5678/tcp:map[]]" + expected := "map[5678/tcp:{}]" defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch @@ -2528,7 +2528,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { c.Fatal(err) } - expected := "[/bin/echo]" + expected := "{[/bin/echo]}" if res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) } @@ -2545,7 +2545,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { c.Fatal(err) } - expected = "[]" + expected = "{[]}" if res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) @@ -2556,7 +2556,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { name := "testbuildentrypoint" defer deleteImages(name) - expected := "[]" + expected := "{[]}" _, err := buildImage(name, `FROM busybox @@ -2577,7 +2577,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildEntrypoint(c *check.C) { name := "testbuildentrypoint" - expected := "[/bin/echo]" + expected := "{[/bin/echo]}" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -3297,8 +3297,8 @@ func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { c.Fatal(err) } // Cmd must be cleaned up - if expected := ""; res != expected { - c.Fatalf("Cmd %s, expected %s", res, expected) + if res != "" { + c.Fatalf("Cmd %s, expected nil", res) } } @@ -3371,7 +3371,7 @@ func (s *DockerSuite) TestBuildInheritance(c *check.C) { if err != nil { c.Fatal(err) } - if expected := "[/bin/echo]"; res != expected { + if expected := "{[/bin/echo]}"; res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) } ports2, err := inspectField(name, "Config.ExposedPorts") @@ -4242,14 +4242,15 @@ func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { if err != nil { c.Fatal(err) } - if expected := ""; res != expected { - c.Fatalf("Cmd %s, expected %s", res, expected) + if res != "" { + c.Fatalf("Cmd %s, expected nil", res) } + res, err = inspectField(name, "Config.Entrypoint") if err != nil { c.Fatal(err) } - if expected := "[cat]"; res != expected { + if expected := "{[cat]}"; res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) } } diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 9bbff09ccd0eb..a75621f3a3de6 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -251,7 +251,7 @@ func (s *DockerSuite) TestCommitChange(c *check.C) { defer deleteImages(imageId) expected := map[string]string{ - "Config.ExposedPorts": "map[8080/tcp:map[]]", + "Config.ExposedPorts": "map[8080/tcp:{}]", "Config.Env": "[DEBUG=true test=1 PATH=/foo]", } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 7fc3d4f25c916..0716c486b5d69 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -448,7 +448,7 @@ func (s *DockerSuite) TestInspectExecID(c *check.C) { if err != nil { c.Fatalf("failed to inspect container: %s, %v", out, err) } - if out != "" { + if out != "[]" { c.Fatalf("ExecIDs should be empty, got: %s", out) } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index 9a5a7b660482e..58c61a9d0f8d5 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "os/exec" + "strconv" "strings" "github.com/go-check/check" @@ -41,3 +43,58 @@ func (s *DockerSuite) TestInspectInt64(c *check.C) { c.Fatalf("inspect got wrong value, got: %q, expected: 314572800", inspectOut) } } + +func (s *DockerSuite) TestInspectImageFilterInt(c *check.C) { + imageTest := "emptyfs" + imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Size}}'", imageTest) + out, exitCode, err := runCommandWithOutput(imagesCmd) + if exitCode != 0 || err != nil { + c.Fatalf("failed to inspect image: %s, %v", out, err) + } + size, err := strconv.Atoi(strings.TrimSuffix(out, "\n")) + if err != nil { + c.Fatalf("failed to inspect size of the image: %s, %v", out, err) + } + + //now see if the size turns out to be the same + formatStr := fmt.Sprintf("--format='{{eq .Size %d}}'", size) + imagesCmd = exec.Command(dockerBinary, "inspect", formatStr, imageTest) + out, exitCode, err = runCommandWithOutput(imagesCmd) + if exitCode != 0 || err != nil { + c.Fatalf("failed to inspect image: %s, %v", out, err) + } + if result, err := strconv.ParseBool(strings.TrimSuffix(out, "\n")); err != nil || !result { + c.Fatalf("Expected size: %d for image: %s but received size: %s", size, imageTest, strings.TrimSuffix(out, "\n")) + } +} + +func (s *DockerSuite) TestInspectContainerFilterInt(c *check.C) { + runCmd := exec.Command("bash", "-c", `echo "blahblah" | docker run -i -a stdin busybox cat`) + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + c.Fatalf("failed to run container: %v, output: %q", err, out) + } + + id := strings.TrimSpace(out) + + runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.ExitCode}}'", id) + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("failed to inspect container: %s, %v", out, err) + } + exitCode, err := strconv.Atoi(strings.TrimSuffix(out, "\n")) + if err != nil { + c.Fatalf("failed to inspect exitcode of the container: %s, %v", out, err) + } + + //now get the exit code to verify + formatStr := fmt.Sprintf("--format='{{eq .State.ExitCode %d}}'", exitCode) + runCmd = exec.Command(dockerBinary, "inspect", formatStr, id) + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("failed to inspect container: %s, %v", out, err) + } + if result, err := strconv.ParseBool(strings.TrimSuffix(out, "\n")); err != nil || !result { + c.Fatalf("Expected exitcode: %d for container: %s", exitCode, id) + } +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7e12fc5a9d428..65f1770e3e3a9 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2443,7 +2443,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { if err != nil { c.Fatal(err) } - if out != "" { + if out != "" { c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out) } @@ -2459,7 +2459,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { if err != nil { c.Fatal(err) } - if out != "" { + if out != "" { c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar") From 18f46883851e47387ec2bd116940cdae97ba3c8d Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Wed, 22 Apr 2015 14:41:24 -0700 Subject: [PATCH 264/332] Validate repo name before image pull Checks for reserved 'scratch' image name. fixes #12281 Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- graph/pull.go | 4 ++++ integration-cli/docker_cli_pull_test.go | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/graph/pull.go b/graph/pull.go index b62591ffb6c81..ac814a23403bd 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -39,6 +39,10 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf return err } + if err := validateRepoName(repoInfo.LocalName); err != nil { + return err + } + c, err := s.poolAdd("pull", utils.ImageReference(repoInfo.LocalName, tag)) if err != nil { if c != nil { diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 8cf09a72610f7..a4d4c977bbe7a 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -134,3 +134,22 @@ func (s *DockerSuite) TestPullImageOfficialNames(c *check.C) { } } } + +func (s *DockerSuite) TestPullScratchNotAllowed(c *check.C) { + testRequires(c, Network) + + pullCmd := exec.Command(dockerBinary, "pull", "scratch") + out, exitCode, err := runCommandWithOutput(pullCmd) + if err == nil { + c.Fatal("expected pull of scratch to fail, but it didn't") + } + if exitCode != 1 { + c.Fatalf("pulling scratch expected exit code 1, got %d", exitCode) + } + if strings.Contains(out, "Pulling repository scratch") { + c.Fatalf("pulling scratch should not have begun: %s", out) + } + if !strings.Contains(out, "'scratch' is a reserved name") { + c.Fatalf("unexpected output pulling scratch: %s", out) + } +} From c5ef2901d8190b687e847ab17d306dae5af37b42 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 23 Apr 2015 22:27:46 +0200 Subject: [PATCH 265/332] delete "defer deleteContainer" on tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since docker test suite is now using gocheck, ``defer deleteContainer(…)`` is not needed anymore. Fixes #12705 Signed-off-by: Vincent Demeester --- integration-cli/docker_cli_by_digest_test.go | 2 -- integration-cli/docker_cli_cp_test.go | 10 ---------- integration-cli/docker_cli_export_import_test.go | 2 -- integration-cli/docker_cli_history_test.go | 1 - integration-cli/docker_cli_import_test.go | 1 - integration-cli/docker_cli_logs_test.go | 1 - integration-cli/docker_cli_save_load_test.go | 2 -- integration-cli/docker_cli_top_test.go | 1 - 8 files changed, 20 deletions(-) diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index fc8c4600b9cee..bd4518434183d 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -140,7 +140,6 @@ func (s *DockerSuite) TestCreateByDigest(c *check.C) { if err != nil { c.Fatalf("error creating by digest: %s, %v", out, err) } - defer deleteContainer(containerName) res, err := inspectField(containerName, "Config.Image") if err != nil { @@ -168,7 +167,6 @@ func (s *DockerSuite) TestRunByDigest(c *check.C) { if err != nil { c.Fatalf("error run by digest: %s, %v", out, err) } - defer deleteContainer(containerName) foundRegex := regexp.MustCompile("found=([^\n]+)") matches := foundRegex.FindStringSubmatch(out) diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 022b3cc9ea05e..26e778e4f2b80 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -32,7 +32,6 @@ func (s *DockerSuite) TestCpGarbagePath(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -90,7 +89,6 @@ func (s *DockerSuite) TestCpRelativePath(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -156,7 +154,6 @@ func (s *DockerSuite) TestCpAbsolutePath(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -216,7 +213,6 @@ func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -276,7 +272,6 @@ func (s *DockerSuite) TestCpSymlinkComponent(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -337,7 +332,6 @@ func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -379,7 +373,6 @@ func (s *DockerSuite) TestCpSpecialFiles(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -525,7 +518,6 @@ func (s *DockerSuite) TestCpToDot(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -559,7 +551,6 @@ func (s *DockerSuite) TestCpToStdout(c *check.C) { } cID := strings.TrimSpace(out) - defer deleteContainer(cID) out, _ = dockerCmd(c, "wait", cID) if strings.TrimSpace(out) != "0" { @@ -588,7 +579,6 @@ func (s *DockerSuite) TestCpNameHasColon(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 59a5106303aa4..bc7b16356d94d 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -13,7 +13,6 @@ func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { containerID := "testexportcontainerandimportimage" defer deleteImages("repo/testexp:v1") - defer deleteContainer(containerID) runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) @@ -53,7 +52,6 @@ func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) { containerID := "testexportcontainerwithoutputandimportimage" defer deleteImages("repo/testexp:v1") - defer deleteContainer(containerID) runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 134f7bc7f8c14..8de4008314877 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -85,7 +85,6 @@ func (s *DockerSuite) TestHistoryNonExistentImage(c *check.C) { func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { name := "testhistoryimagewithcomment" - defer deleteContainer(name) defer deleteImages(name) // make a image through docker commit [ -m messages ] diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go index dd06ef822265e..02b857bfd958e 100644 --- a/integration-cli/docker_cli_import_test.go +++ b/integration-cli/docker_cli_import_test.go @@ -14,7 +14,6 @@ func (s *DockerSuite) TestImportDisplay(c *check.C) { c.Fatal("failed to create a container", out, err) } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _, err = runCommandPipelineWithOutput( exec.Command(dockerBinary, "export", cleanedContainerID), diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index 7f04a328b7535..a7c8916226ca6 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -287,7 +287,6 @@ func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) stopSlowRead := make(chan bool) diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index ead436a925db6..f74f69fcc14d3 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -216,7 +216,6 @@ func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) repoName := "foobar-save-load-test" @@ -298,7 +297,6 @@ func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, tag) if out, _, err = runCommandWithOutput(commitCmd); err != nil { diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go index 7e75a38d576a0..f941a42cd0515 100644 --- a/integration-cli/docker_cli_top_test.go +++ b/integration-cli/docker_cli_top_test.go @@ -15,7 +15,6 @@ func (s *DockerSuite) TestTopMultipleArgs(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) topCmd := exec.Command(dockerBinary, "top", cleanedContainerID, "-o", "pid") out, _, err = runCommandWithOutput(topCmd) From b6d8b65e5595304ef9b89f1f2ff2cae46c70dae9 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 23 Apr 2015 14:04:36 -0700 Subject: [PATCH 266/332] Removing firewalld info Signed-off-by: Mary Anthony --- docs/sources/installation/centos.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/sources/installation/centos.md b/docs/sources/installation/centos.md index 862d508988d4a..7868f11b05727 100644 --- a/docs/sources/installation/centos.md +++ b/docs/sources/installation/centos.md @@ -33,17 +33,6 @@ run the following command: Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -### FirewallD - -CentOS-7 introduced firewalld, which is a wrapper around iptables and can -conflict with Docker. - -When `firewalld` is started or restarted it will remove the `DOCKER` chain -from iptables, preventing Docker from working properly. - -When using Systemd, `firewalld` is started before Docker, but if you -start or restart `firewalld` after Docker, you will have to restart the Docker daemon. - ## Installing Docker - CentOS-6.5 For CentOS-6.5, the Docker package is part of [Extra Packages From 55d1ac645cc36bb40046b2bcc400f8740ed2e86c Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Thu, 23 Apr 2015 21:11:08 +0000 Subject: [PATCH 267/332] [docs] fix formatting issue Signed-off-by: Florian Weingarten --- docs/sources/project/set-up-dev-env.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 80d4f335b3a46..60a59b6155f91 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -209,7 +209,7 @@ build and run a `docker` binary in your container. root@5f8630b873fe:/go/src/github.com/docker/docker# The command creates a container from your `dry-run-test` image. It opens an - interactive terminal (`-ti`) running a `/bin/bash shell`. The + interactive terminal (`-ti`) running a `/bin/bash` shell. The `--privileged` flag gives the container access to kernel features and device access. This flag allows you to run a container in a container. Finally, the `-rm` flag instructs Docker to remove the container when you From fa2c68a89e153cfc82c5af7cbb6d7f15b06e0a8c Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 23 Apr 2015 21:05:21 +0200 Subject: [PATCH 268/332] Remove engine/job from graph Signed-off-by: Antonio Murdaca --- api/server/server.go | 33 ++++++----- api/server/server_unit_test.go | 104 --------------------------------- daemon/daemon.go | 3 - graph/export.go | 23 ++++---- graph/load.go | 19 ++---- graph/load_unsupported.go | 7 +-- graph/service.go | 76 +++++++++++------------- graph/viz.go | 39 ------------- integration/runtime_test.go | 13 +++-- 9 files changed, 81 insertions(+), 236 deletions(-) delete mode 100644 api/server/server_unit_test.go delete mode 100644 graph/viz.go diff --git a/api/server/server.go b/api/server/server.go index 92770e3b46854..830e731a7ecb0 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -899,10 +899,7 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } output := utils.NewWriteFlusher(w) - imageExportConfig := &graph.ImageExportConfig{ - Engine: eng, - Outstream: output, - } + imageExportConfig := &graph.ImageExportConfig{Outstream: output} if name, ok := vars["name"]; ok { imageExportConfig.Names = []string{name} } else { @@ -921,14 +918,7 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - - imageLoadConfig := &graph.ImageLoadConfig{ - InTar: r.Body, - OutStream: w, - Engine: eng, - } - - return s.daemon.Repositories().Load(imageLoadConfig) + return s.daemon.Repositories().Load(r.Body, w) } func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1269,12 +1259,23 @@ func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w if vars == nil { return fmt.Errorf("Missing parameter") } - var job = eng.Job("image_inspect", vars["name"]) + + name := vars["name"] if version.LessThan("1.12") { - job.SetenvBool("raw", true) + imageInspectRaw, err := s.daemon.Repositories().LookupRaw(name) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, imageInspectRaw) } - streamJSON(job.Stdout, w, false) - return job.Run() + + imageInspect, err := s.daemon.Repositories().Lookup(name) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, imageInspect) } func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go deleted file mode 100644 index 60cee8224c77d..0000000000000 --- a/api/server/server_unit_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/docker/docker/api" - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/version" -) - -func TestHttpError(t *testing.T) { - r := httptest.NewRecorder() - httpError(r, fmt.Errorf("No such method")) - if r.Code != http.StatusNotFound { - t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code) - } - - r = httptest.NewRecorder() - httpError(r, fmt.Errorf("This accound hasn't been activated")) - if r.Code != http.StatusForbidden { - t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code) - } - - r = httptest.NewRecorder() - httpError(r, fmt.Errorf("Some error")) - if r.Code != http.StatusInternalServerError { - t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) - } -} - -func TestGetImagesByName(t *testing.T) { - eng := engine.New() - name := "image_name" - var called bool - eng.Register("image_inspect", func(job *engine.Job) error { - called = true - if job.Args[0] != name { - t.Fatalf("name != '%s': %#v", name, job.Args[0]) - } - if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") { - t.Fatal("dirty env variable not set") - } else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") { - t.Fatal("dirty env variable set when it shouldn't") - } - v := &engine.Env{} - v.SetBool("dirty", true) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/images/"+name+"/json", nil, eng, t) - if !called { - t.Fatal("handler was not called") - } - if r.HeaderMap.Get("Content-Type") != "application/json" { - t.Fatalf("%#v\n", r) - } - var stdoutJson interface{} - if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil { - t.Fatalf("%#v", err) - } - if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 { - t.Fatalf("%#v", stdoutJson) - } -} - -func serveRequest(method, target string, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder { - return serveRequestUsingVersion(method, target, api.APIVERSION, body, eng, t) -} - -func serveRequestUsingVersion(method, target string, version version.Version, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder { - r := httptest.NewRecorder() - req, err := http.NewRequest(method, target, body) - if err != nil { - t.Fatal(err) - } - ServeRequest(eng, version, r, req) - return r -} - -func readEnv(src io.Reader, t *testing.T) *engine.Env { - out := engine.NewOutput() - v, err := out.AddEnv() - if err != nil { - t.Fatal(err) - } - if _, err := io.Copy(out, src); err != nil { - t.Fatal(err) - } - out.Close() - return v -} - -func assertContentType(recorder *httptest.ResponseRecorder, contentType string, t *testing.T) { - if recorder.HeaderMap.Get("Content-Type") != contentType { - t.Fatalf("%#v\n", recorder) - } -} diff --git a/daemon/daemon.go b/daemon/daemon.go index bfebd920f82a2..d186854ad8c5f 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -116,9 +116,6 @@ type Daemon struct { // Install installs daemon capabilities to eng. func (daemon *Daemon) Install(eng *engine.Engine) error { - if err := daemon.Repositories().Install(eng); err != nil { - return err - } // FIXME: this hack is necessary for legacy integration tests to access // the daemon object. eng.HackSetGlobalVar("httpapi.daemon", daemon) diff --git a/graph/export.go b/graph/export.go index 00cfa8975a5a5..ee626aae63429 100644 --- a/graph/export.go +++ b/graph/export.go @@ -8,7 +8,6 @@ import ( "path" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" @@ -22,7 +21,6 @@ import ( type ImageExportConfig struct { Names []string Outstream io.Writer - Engine *engine.Engine } func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { @@ -51,7 +49,7 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { // this is a base repo name, like 'busybox' for tag, id := range rootRepo { addKey(name, tag, id) - if err := s.exportImage(imageExportConfig.Engine, id, tempdir); err != nil { + if err := s.exportImage(id, tempdir); err != nil { return err } } @@ -70,13 +68,13 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { if len(repoTag) > 0 { addKey(repoName, repoTag, img.ID) } - if err := s.exportImage(imageExportConfig.Engine, img.ID, tempdir); err != nil { + if err := s.exportImage(img.ID, tempdir); err != nil { return err } } else { // this must be an ID that didn't get looked up just right? - if err := s.exportImage(imageExportConfig.Engine, name, tempdir); err != nil { + if err := s.exportImage(name, tempdir); err != nil { return err } } @@ -107,7 +105,7 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { } // FIXME: this should be a top-level function, not a class method -func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { +func (s *TagStore) exportImage(name, tempdir string) error { for n := name; n != ""; { // temporary directory tmpImageDir := path.Join(tempdir, n) @@ -130,12 +128,17 @@ func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { if err != nil { return err } - job := eng.Job("image_inspect", n) - job.SetenvBool("raw", true) - job.Stdout.Add(json) - if err := job.Run(); err != nil { + imageInspectRaw, err := s.LookupRaw(n) + if err != nil { return err } + written, err := json.Write(imageInspectRaw) + if err != nil { + return err + } + if written != len(imageInspectRaw) { + logrus.Warnf("%d byes should have been written instead %d have been written", written, len(imageInspectRaw)) + } // serialize filesystem fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) diff --git a/graph/load.go b/graph/load.go index f62b82ce462b4..be968bab5b3e2 100644 --- a/graph/load.go +++ b/graph/load.go @@ -10,21 +10,14 @@ import ( "path" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" ) -type ImageLoadConfig struct { - InTar io.ReadCloser - OutStream io.Writer - Engine *engine.Engine -} - // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. -func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { +func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { return err @@ -48,7 +41,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { excludes[i] = k i++ } - if err := chrootarchive.Untar(imageLoadConfig.InTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { + if err := chrootarchive.Untar(inTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { return err } @@ -59,7 +52,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { for _, d := range dirs { if d.IsDir() { - if err := s.recursiveLoad(imageLoadConfig.Engine, d.Name(), tmpImageDir); err != nil { + if err := s.recursiveLoad(d.Name(), tmpImageDir); err != nil { return err } } @@ -74,7 +67,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { for imageName, tagMap := range repositories { for tag, address := range tagMap { - if err := s.SetLoad(imageName, tag, address, true, imageLoadConfig.OutStream); err != nil { + if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil { return err } } @@ -86,7 +79,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { return nil } -func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { +func (s *TagStore) recursiveLoad(address, tmpImageDir string) error { if _, err := s.LookupImage(address); err != nil { logrus.Debugf("Loading %s", address) @@ -126,7 +119,7 @@ func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string if img.Parent != "" { if !s.graph.Exists(img.Parent) { - if err := s.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil { + if err := s.recursiveLoad(img.Parent, tmpImageDir); err != nil { return err } } diff --git a/graph/load_unsupported.go b/graph/load_unsupported.go index 707534480f8d1..7c515596962e9 100644 --- a/graph/load_unsupported.go +++ b/graph/load_unsupported.go @@ -4,10 +4,9 @@ package graph import ( "fmt" - - "github.com/docker/docker/engine" + "io" ) -func (s *TagStore) CmdLoad(job *engine.Job) error { - return fmt.Errorf("CmdLoad is not supported on this platform") +func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { + return fmt.Errorf("Load is not supported on this platform") } diff --git a/graph/service.go b/graph/service.go index ab78c1d056d08..52dde1d980504 100644 --- a/graph/service.go +++ b/graph/service.go @@ -5,57 +5,47 @@ import ( "io" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" ) -func (s *TagStore) Install(eng *engine.Engine) error { - for name, handler := range map[string]engine.Handler{ - "image_inspect": s.CmdLookup, - "viz": s.CmdViz, - } { - if err := eng.Register(name, handler); err != nil { - return fmt.Errorf("Could not register %q: %v", name, err) - } +func (s *TagStore) LookupRaw(name string) ([]byte, error) { + image, err := s.LookupImage(name) + if err != nil || image == nil { + return nil, fmt.Errorf("No such image %s", name) + } + + imageInspectRaw, err := image.RawJson() + if err != nil { + return nil, err } - return nil + + return imageInspectRaw, nil } -// CmdLookup return an image encoded in JSON -func (s *TagStore) CmdLookup(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) +// Lookup return an image encoded in JSON +func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) { + image, err := s.LookupImage(name) + if err != nil || image == nil { + return nil, fmt.Errorf("No such image: %s", name) } - name := job.Args[0] - if image, err := s.LookupImage(name); err == nil && image != nil { - if job.GetenvBool("raw") { - b, err := image.RawJson() - if err != nil { - return err - } - job.Stdout.Write(b) - return nil - } - out := &engine.Env{} - out.SetJson("Id", image.ID) - out.SetJson("Parent", image.Parent) - out.SetJson("Comment", image.Comment) - out.SetAuto("Created", image.Created) - out.SetJson("Container", image.Container) - out.SetJson("ContainerConfig", image.ContainerConfig) - out.Set("DockerVersion", image.DockerVersion) - out.SetJson("Author", image.Author) - out.SetJson("Config", image.Config) - out.Set("Architecture", image.Architecture) - out.Set("Os", image.OS) - out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) - if _, err = out.WriteTo(job.Stdout); err != nil { - return err - } - return nil + imageInspect := &types.ImageInspect{ + Id: image.ID, + Parent: image.Parent, + Comment: image.Comment, + Created: image.Created, + Container: image.Container, + ContainerConfig: &image.ContainerConfig, + DockerVersion: image.DockerVersion, + Author: image.Author, + Config: image.Config, + Architecture: image.Architecture, + Os: image.OS, + Size: image.Size, + VirtualSize: image.GetParentsSize(0) + image.Size, } - return fmt.Errorf("No such image: %s", name) + + return imageInspect, nil } // ImageTarLayer return the tarLayer of the image diff --git a/graph/viz.go b/graph/viz.go deleted file mode 100644 index 0c45caa9efbba..0000000000000 --- a/graph/viz.go +++ /dev/null @@ -1,39 +0,0 @@ -package graph - -import ( - "fmt" - "strings" - - "github.com/docker/docker/engine" - "github.com/docker/docker/image" -) - -func (s *TagStore) CmdViz(job *engine.Job) error { - images, _ := s.graph.Map() - if images == nil { - return nil - } - job.Stdout.Write([]byte("digraph docker {\n")) - - var ( - parentImage *image.Image - err error - ) - for _, image := range images { - parentImage, err = image.GetParent() - if err != nil { - return fmt.Errorf("Error while getting parent image: %v", err) - } - if parentImage != nil { - job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) - } else { - job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n")) - } - } - - for id, repos := range s.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")) - return nil -} diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 99995016edb3e..82f21b70083c0 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -125,17 +125,22 @@ func init() { func setupBaseImage() { eng := newTestEngine(std_log.New(os.Stderr, "", 0), false, unitTestStoreBase) - job := eng.Job("image_inspect", unitTestImageName) - img, _ := job.Stdout.AddEnv() + d := getDaemon(eng) + + _, err := d.Repositories().Lookup(unitTestImageName) // If the unit test is not found, try to download it. - if err := job.Run(); err != nil || img.Get("Id") != unitTestImageID { + if err != nil { + // seems like we can just ignore the error here... + // there was a check of imgId from job stdout against unittestid but + // if there was an error how could the imgid from the job + // be compared?! it's obvious it's different, am I totally wrong? + // Retrieve the Image imagePullConfig := &graph.ImagePullConfig{ Parallel: true, OutStream: ioutils.NopWriteCloser(os.Stdout), AuthConfig: &cliconfig.AuthConfig{}, } - d := getDaemon(eng) if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { logrus.Fatalf("Unable to pull the test image: %s", err) } From ade8146aa82baa88bacdcf2d9c2559e8f47d71e4 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Wed, 22 Apr 2015 23:37:15 +0000 Subject: [PATCH 269/332] reuse same code for setting pipes in run/exec This also moves `exec -i` test to _unix_test.go because it seems to need a pty to reliably reproduce the behavior. Signed-off-by: Daniel, Dao Quang Minh --- daemon/execdriver/native/driver.go | 68 +++++++++++--------- daemon/execdriver/native/exec.go | 25 +------ integration-cli/docker_cli_exec_test.go | 38 ----------- integration-cli/docker_cli_exec_unix_test.go | 47 ++++++++++++++ 4 files changed, 87 insertions(+), 91 deletions(-) create mode 100644 integration-cli/docker_cli_exec_unix_test.go diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 7bc28f10f5fb7..ad13e1c1eb700 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -87,8 +87,6 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba return execdriver.ExitStatus{ExitCode: -1}, err } - var term execdriver.Terminal - p := &libcontainer.Process{ Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...), Env: c.ProcessConfig.Env, @@ -96,36 +94,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba User: c.ProcessConfig.User, } - if c.ProcessConfig.Tty { - rootuid, err := container.HostUID() - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - cons, err := p.NewConsole(rootuid) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - term, err = NewTtyConsole(cons, pipes, rootuid) - } else { - p.Stdout = pipes.Stdout - p.Stderr = pipes.Stderr - r, w, err := os.Pipe() - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - if pipes.Stdin != nil { - go func() { - io.Copy(w, pipes.Stdin) - w.Close() - }() - p.Stdin = r - } - term = &execdriver.StdConsole{} - } - if err != nil { + if err := setupPipes(container, &c.ProcessConfig, p, pipes); err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } - c.ProcessConfig.Terminal = term cont, err := d.factory.Create(c.ID, container) if err != nil { @@ -398,3 +369,40 @@ func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error { func (t *TtyConsole) Close() error { return t.console.Close() } + +func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error { + var term execdriver.Terminal + var err error + + if processConfig.Tty { + rootuid, err := container.HostUID() + if err != nil { + return err + } + cons, err := p.NewConsole(rootuid) + if err != nil { + return err + } + term, err = NewTtyConsole(cons, pipes, rootuid) + } else { + p.Stdout = pipes.Stdout + p.Stderr = pipes.Stderr + r, w, err := os.Pipe() + if err != nil { + return err + } + if pipes.Stdin != nil { + go func() { + io.Copy(w, pipes.Stdin) + w.Close() + }() + p.Stdin = r + } + term = &execdriver.StdConsole{} + } + if err != nil { + return err + } + processConfig.Terminal = term + return nil +} diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index 04239fdac724b..dd41c0ad1da2b 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -20,9 +20,6 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo return -1, fmt.Errorf("No active container exists with ID %s", c.ID) } - var term execdriver.Terminal - var err error - p := &libcontainer.Process{ Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...), Env: c.ProcessConfig.Env, @@ -34,29 +31,11 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo p.Capabilities = execdriver.GetAllCapabilities() } - if processConfig.Tty { - config := active.Config() - rootuid, err := config.HostUID() - if err != nil { - return -1, err - } - cons, err := p.NewConsole(rootuid) - if err != nil { - return -1, err - } - term, err = NewTtyConsole(cons, pipes, rootuid) - } else { - p.Stdout = pipes.Stdout - p.Stderr = pipes.Stderr - p.Stdin = pipes.Stdin - term = &execdriver.StdConsole{} - } - if err != nil { + config := active.Config() + if err := setupPipes(&config, processConfig, p, pipes); err != nil { return -1, err } - processConfig.Terminal = term - if err := active.Start(p); err != nil { return -1, err } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 7fc3d4f25c916..f5909005e6ad5 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -38,44 +38,6 @@ func (s *DockerSuite) TestExec(c *check.C) { } -func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) { - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat")) - if err != nil { - c.Fatal(err) - } - - contId := strings.TrimSpace(out) - - returnchan := make(chan struct{}) - - go func() { - var err error - cmd := exec.Command(dockerBinary, "exec", "-i", contId, "/bin/ls", "/") - cmd.Stdin = os.Stdin - if err != nil { - c.Fatal(err) - } - - out, err := cmd.CombinedOutput() - if err != nil { - c.Fatal(err, string(out)) - } - - if string(out) == "" { - c.Fatalf("Output was empty, likely blocked by standard input") - } - - returnchan <- struct{}{} - }() - - select { - case <-returnchan: - case <-time.After(10 * time.Second): - c.Fatal("timed out running docker exec") - } - -} - func (s *DockerSuite) TestExecInteractive(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") diff --git a/integration-cli/docker_cli_exec_unix_test.go b/integration-cli/docker_cli_exec_unix_test.go new file mode 100644 index 0000000000000..bee44b9902593 --- /dev/null +++ b/integration-cli/docker_cli_exec_unix_test.go @@ -0,0 +1,47 @@ +// +build !windows,!test_no_exec + +package main + +import ( + "bytes" + "io" + "os/exec" + "strings" + "time" + + "github.com/go-check/check" + "github.com/kr/pty" +) + +// regression test for #12546 +func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat")) + if err != nil { + c.Fatal(err) + } + contId := strings.TrimSpace(out) + + cmd := exec.Command(dockerBinary, "exec", "-i", contId, "echo", "-n", "hello") + p, err := pty.Start(cmd) + if err != nil { + c.Fatal(err) + } + + b := bytes.NewBuffer(nil) + go io.Copy(b, p) + + ch := make(chan error) + go func() { ch <- cmd.Wait() }() + + select { + case err := <-ch: + if err != nil { + c.Errorf("cmd finished with error %v", err) + } + if output := b.String(); strings.TrimSpace(output) != "hello" { + c.Fatalf("Unexpected output %s", output) + } + case <-time.After(1 * time.Second): + c.Fatal("timed out running docker exec") + } +} From c7845e27ee2b0462b01d043caeb771f21d5e4ac7 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Mon, 20 Apr 2015 14:03:56 -0700 Subject: [PATCH 270/332] Fixing statusCode checks for sockRequest Signed-off-by: Megan Kostick --- integration-cli/docker_api_containers_test.go | 228 ++++++++---------- .../docker_api_exec_resize_test.go | 8 +- integration-cli/docker_api_exec_test.go | 10 +- integration-cli/docker_api_images_test.go | 32 +-- integration-cli/docker_api_info_test.go | 7 +- integration-cli/docker_api_inspect_test.go | 8 +- integration-cli/docker_api_logs_test.go | 16 +- integration-cli/docker_api_resize_test.go | 23 +- integration-cli/docker_api_version_test.go | 9 +- integration-cli/docker_cli_rm_test.go | 9 +- integration-cli/docker_utils.go | 10 +- integration-cli/requirements.go | 4 +- 12 files changed, 165 insertions(+), 199 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 8efc6239d954f..24efbb7a27cb1 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -28,10 +28,9 @@ func (s *DockerSuite) TestContainerApiGetAll(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } - _, body, err := sockRequest("GET", "/containers/json?all=1", nil) - if err != nil { - c.Fatalf("GET all containers sockRequest failed: %v", err) - } + status, body, err := sockRequest("GET", "/containers/json?all=1", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) var inspectJSON []struct { Names []string @@ -57,10 +56,9 @@ func (s *DockerSuite) TestContainerApiGetExport(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } - _, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) - if err != nil { - c.Fatalf("GET containers/export sockRequest failed: %v", err) - } + status, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) found := false for tarReader := tar.NewReader(bytes.NewReader(body)); ; { @@ -90,10 +88,9 @@ func (s *DockerSuite) TestContainerApiGetChanges(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } - _, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) - if err != nil { - c.Fatalf("GET containers/changes sockRequest failed: %v", err) - } + status, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) changes := []struct { Kind int @@ -122,17 +119,17 @@ func (s *DockerSuite) TestContainerApiStartVolumeBinds(c *check.C) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) bindPath := randomUnixTmpDirPath("test") config = map[string]interface{}{ "Binds": []string{bindPath + ":/tmp"}, } - if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err = sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pth, err := inspectFieldMap(name, "Volumes", "/tmp") if err != nil { @@ -152,9 +149,9 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) bindPath1 := randomUnixTmpDirPath("test1") bindPath2 := randomUnixTmpDirPath("test2") @@ -162,14 +159,15 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) { config = map[string]interface{}{ "Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"}, } - if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { - c.Fatal("expected container start to fail when duplicate volume binds to same container path") - } else { - if !strings.Contains(string(body), "Duplicate volume") { - c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) - } + status, body, err := sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + + if !strings.Contains(string(body), "Duplicate volume") { + c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) } } + func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { volName := "voltst" volPath := "/tmp" @@ -184,16 +182,16 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { "Volumes": map[string]struct{}{volPath: {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) config = map[string]interface{}{ "VolumesFrom": []string{volName}, } - if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err = sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { @@ -225,18 +223,18 @@ func (s *DockerSuite) TestVolumesFromHasPriority(c *check.C) { "Volumes": map[string]struct{}{volPath: {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) bindPath := randomUnixTmpDirPath("test") config = map[string]interface{}{ "VolumesFrom": []string{volName}, "Binds": []string{bindPath + ":/tmp"}, } - if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err = sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { @@ -267,7 +265,9 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { } bc := make(chan b, 1) go func() { - _, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) bc <- b{body, err} }() @@ -309,10 +309,9 @@ func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) { go func() { // We'll never get return for GET stats from sockRequest as of now, // just send request and see if panic or error would happen on daemon side. - _, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) - if err != nil { - c.Fatal(err) - } + status, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) }() // allow some time to send request and let daemon deal with it @@ -340,11 +339,10 @@ func (s *DockerSuite) TestBuildApiDockerfilePath(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - _, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") - if err == nil { - out, _ := readBody(body) - c.Fatalf("Build was supposed to fail: %s", out) - } + status, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + out, err := readBody(body) if err != nil { c.Fatal(err) @@ -367,10 +365,10 @@ RUN find /tmp/`, } defer server.Close() - _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") - if err != nil { - c.Fatalf("Build failed: %s", err) - } + status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -395,11 +393,10 @@ RUN echo from dockerfile`, } defer git.Close() - _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - if err != nil { - buf, _ := readBody(body) - c.Fatalf("Build failed: %s\n%q", err, buf) - } + status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -424,11 +421,10 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") - if err != nil { - buf, _ := readBody(body) - c.Fatalf("Build failed: %s\n%q", err, buf) - } + status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -454,10 +450,10 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - if err != nil { - c.Fatalf("Build failed: %s", err) - } + status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -487,11 +483,10 @@ func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - _, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") - if err == nil { - out, _ := readBody(body) - c.Fatalf("Build was supposed to fail: %s", out) - } + status, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + out, err := readBody(body) if err != nil { c.Fatal(err) @@ -524,9 +519,9 @@ func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) { } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} - if status, _, err := sockRequest("POST", "/containers/two/start", bindSpec); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/two/start", bindSpec) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) fooDir2, err := inspectFieldMap("two", "Volumes", "/foo") if err != nil { @@ -548,9 +543,9 @@ func (s *DockerSuite) TestContainerApiPause(c *check.C) { } ContainerID := strings.TrimSpace(out) - if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && status != http.StatusNoContent { - c.Fatalf("POST a container pause: sockRequest failed: %v", err) - } + status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pausedContainers, err := getSliceOfPausedContainers() @@ -562,9 +557,9 @@ func (s *DockerSuite) TestContainerApiPause(c *check.C) { c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && status != http.StatusNoContent { - c.Fatalf("POST a container pause: sockRequest failed: %v", err) - } + status, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pausedContainers, err = getSliceOfPausedContainers() @@ -592,10 +587,10 @@ func (s *DockerSuite) TestContainerApiTop(c *check.C) { Processes [][]string } var top topResp - _, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) - if err != nil { - c.Fatal(err) - } + status, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + if err := json.Unmarshal(b, &top); err != nil { c.Fatal(err) } @@ -626,10 +621,9 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { id := strings.TrimSpace(string(out)) name := "testcommit" + stringid.GenerateRandomID() - _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - c.Fatal(err) - } + status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) type resp struct { Id string @@ -660,10 +654,10 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) { "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"}, } - _, b, err := sockRequest("POST", "/containers/create", config) - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - c.Fatal(err) - } + status, b, err := sockRequest("POST", "/containers/create", config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) + type createResp struct { Id string } @@ -736,26 +730,21 @@ func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { } // Try with no content-type - _, body, err := create("") - if err == nil { - b, _ := readBody(body) - c.Fatalf("expected error when content-type is not set: %q", string(b)) - } + status, body, err := create("") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) body.Close() + // Try with wrong content-type - _, body, err = create("application/xml") - if err == nil { - b, _ := readBody(body) - c.Fatalf("expected error when content-type is not set: %q", string(b)) - } + status, body, err = create("application/xml") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) body.Close() // now application/json - _, body, err = create("application/json") - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - b, _ := readBody(body) - c.Fatalf("%v - %q", err, string(b)) - } + status, body, err = create("application/json") + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) body.Close() } @@ -786,11 +775,9 @@ func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { "NetworkDisabled":false, "OnBuild":null}` - _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - b, _ := readBody(body) - c.Fatal(err, string(b)) - } + status, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) b, err := readBody(body) if err != nil { @@ -822,16 +809,14 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + status, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") - } - + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { @@ -847,13 +832,12 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - _, body, err := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") + status, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") - } + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go index 09a13bad21bed..ab753d8ecf163 100644 --- a/integration-cli/docker_api_exec_resize_test.go +++ b/integration-cli/docker_api_exec_resize_test.go @@ -18,10 +18,6 @@ func (s *DockerSuite) TestExecResizeApiHeightWidthNoInt(c *check.C) { endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) - if err == nil { - c.Fatal("Expected exec resize Request to fail") - } - if status != http.StatusInternalServerError { - c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) - } + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) } diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go index 4299f00ec9c73..b7957480f50a0 100644 --- a/integration-cli/docker_api_exec_test.go +++ b/integration-cli/docker_api_exec_test.go @@ -5,6 +5,7 @@ package main import ( "bytes" "fmt" + "net/http" "os/exec" "github.com/go-check/check" @@ -18,8 +19,11 @@ func (s *DockerSuite) TestExecApiCreateNoCmd(c *check.C) { c.Fatal(out, err) } - _, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) - if err == nil || !bytes.Contains(body, []byte("No exec command specified")) { - c.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) + status, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + + if !bytes.Contains(body, []byte("No exec command specified")) { + c.Fatalf("Expected message when creating exec command with no Cmd specified") } } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index e22f67d2b50de..a0f029d40fadf 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/http" "net/url" "os/exec" "strings" @@ -11,10 +12,9 @@ import ( ) func (s *DockerSuite) TestLegacyImages(c *check.C) { - _, body, err := sockRequest("GET", "/v1.6/images/json", nil) - if err != nil { - c.Fatalf("Error on GET: %s", err) - } + status, body, err := sockRequest("GET", "/v1.6/images/json", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) images := []types.LegacyImage{} if err = json.Unmarshal(body, &images); err != nil { @@ -40,10 +40,10 @@ func (s *DockerSuite) TestApiImagesFilter(c *check.C) { getImages := func(filter string) []image { v := url.Values{} v.Set("filter", filter) - _, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) - if err != nil { - c.Fatal(err) - } + status, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + var images []image if err := json.Unmarshal(b, &images); err != nil { c.Fatal(err) @@ -76,20 +76,20 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { id := strings.TrimSpace(out) defer deleteImages("saveandload") - _, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") - if err != nil { - c.Fatal(err) - } + status, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + defer body.Close() if out, err := exec.Command(dockerBinary, "rmi", id).CombinedOutput(); err != nil { c.Fatal(err, out) } - _, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") - if err != nil { - c.Fatal(err) - } + status, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + defer loadBody.Close() inspectOut, err := exec.Command(dockerBinary, "inspect", "--format='{{ .Id }}'", id).CombinedOutput() diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go index 67967ab2a67c1..4084289102932 100644 --- a/integration-cli/docker_api_info_test.go +++ b/integration-cli/docker_api_info_test.go @@ -10,10 +10,9 @@ import ( func (s *DockerSuite) TestInfoApi(c *check.C) { endpoint := "/info" - statusCode, body, err := sockRequest("GET", endpoint, nil) - if err != nil || statusCode != http.StatusOK { - c.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) - } + status, body, err := sockRequest("GET", endpoint, nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) // always shown fields stringsToCheck := []string{ diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index 982962261390d..b90bdc7120444 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/http" "os/exec" "strings" @@ -26,10 +27,9 @@ func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) { if testVersion != "latest" { endpoint = "/" + testVersion + endpoint } - _, body, err := sockRequest("GET", endpoint, nil) - if err != nil { - c.Fatalf("sockRequest failed for %s version: %v", testVersion, err) - } + status, body, err := sockRequest("GET", endpoint, nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) var inspectJSON map[string]interface{} if err = json.Unmarshal(body, &inspectJSON); err != nil { diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index bbf9d17cbd971..bf0e1fbf49a3f 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -17,11 +17,9 @@ func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { c.Fatal(out, err) } - statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) - - if err != nil || statusCode != http.StatusOK { - c.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) - } + status, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) if !bytes.Contains(body, []byte(name)) { c.Fatalf("Expected %s, got %s", name, string(body[:])) @@ -35,11 +33,9 @@ func (s *DockerSuite) TestLogsApiNoStdoutNorStderr(c *check.C) { c.Fatal(out, err) } - statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) - - if err == nil || statusCode != http.StatusBadRequest { - c.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) - } + status, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) + c.Assert(status, check.Equals, http.StatusBadRequest) + c.Assert(err, check.IsNil) expected := "Bad parameters: you must choose at least one stream" if !bytes.Contains(body, []byte(expected)) { diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 6f5019b6dad17..6d5528069952b 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -17,10 +17,9 @@ func (s *DockerSuite) TestResizeApiResponse(c *check.C) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - _, _, err = sockRequest("POST", endpoint, nil) - if err != nil { - c.Fatalf("resize Request failed %v", err) - } + status, _, err := sockRequest("POST", endpoint, nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) } func (s *DockerSuite) TestResizeApiHeightWidthNoInt(c *check.C) { @@ -33,12 +32,8 @@ func (s *DockerSuite) TestResizeApiHeightWidthNoInt(c *check.C) { endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) - if err == nil { - c.Fatal("Expected resize Request to fail") - } - if status != http.StatusInternalServerError { - c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) - } + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) } func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) { @@ -57,10 +52,10 @@ func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) { } endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - _, body, err := sockRequest("POST", endpoint, nil) - if err == nil { - c.Fatalf("resize should fail when container is not started") - } + status, body, err := sockRequest("POST", endpoint, nil) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + if !strings.Contains(string(body), "Cannot resize container") && !strings.Contains(string(body), cleanedContainerID) { c.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body)) } diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go index d1ea6b9f896c8..b756794c265ee 100644 --- a/integration-cli/docker_api_version_test.go +++ b/integration-cli/docker_api_version_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/http" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" @@ -9,10 +10,10 @@ import ( ) func (s *DockerSuite) TestGetVersion(c *check.C) { - _, body, err := sockRequest("GET", "/version", nil) - if err != nil { - c.Fatal(err) - } + status, body, err := sockRequest("GET", "/version", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + var v types.Version if err := json.Unmarshal(body, &v); err != nil { c.Fatal(err) diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 8668bc70b6e80..c330bb76ab053 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -60,13 +60,8 @@ func (s *DockerSuite) TestRmRunningContainerCheckError409(c *check.C) { endpoint := "/containers/foo" status, _, err := sockRequest("DELETE", endpoint, nil) - - if err == nil { - c.Fatalf("Expected error, can't rm a running container") - } else if status != http.StatusConflict { - c.Fatalf("Expected error to contain '409 Conflict' but found %s", err) - } - + c.Assert(status, check.Equals, http.StatusConflict) + c.Assert(err, check.IsNil) } func (s *DockerSuite) TestRmForceRemoveRunningContainer(c *check.C) { diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 5e15404842f06..20e87244e7e4f 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -350,9 +350,6 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io defer client.Close() return resp.Body.Close() }) - if resp.StatusCode != http.StatusOK { - return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status) - } return resp.StatusCode, body, err } @@ -1062,10 +1059,9 @@ func daemonTime(c *check.C) time.Time { return time.Now() } - _, body, err := sockRequest("GET", "/info", nil) - if err != nil { - c.Fatalf("daemonTime: failed to get /info: %v", err) - } + status, body, err := sockRequest("GET", "/info", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) type infoJSON struct { SystemTime string diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 7499fc50688e6..cc451bd886481 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -58,8 +58,8 @@ var ( func() bool { if daemonExecDriver == "" { // get daemon info - _, body, err := sockRequest("GET", "/info", nil) - if err != nil { + status, body, err := sockRequest("GET", "/info", nil) + if err != nil || status != http.StatusOK { log.Fatalf("sockRequest failed for /info: %v", err) } From 47e5acfbaefc45e536b953af6bf8a3993669c816 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 14 Apr 2015 08:38:34 +0800 Subject: [PATCH 271/332] add devices cgroup check and errors Signed-off-by: Qiang Huang --- pkg/sysinfo/sysinfo.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 76a61fa95f059..0c1ae874388f6 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -58,5 +58,11 @@ func New(quiet bool) *SysInfo { } else { sysInfo.AppArmor = true } + + // Check if Devices cgroup is mounted, it is hard requirement for container security. + if _, err := cgroups.FindCgroupMountpoint("devices"); err != nil { + logrus.Fatalf("Error mounting devices cgroup: %v", err) + } + return sysInfo } From 66acef865d7504605572b0d9566d9e250cf19bc2 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Mon, 13 Apr 2015 22:33:52 +0800 Subject: [PATCH 272/332] clean up viz code Signed-off-by: Ma Shimiao --- api/client/images.go | 255 +++++++++---------------------------------- api/server/server.go | 10 -- 2 files changed, 54 insertions(+), 211 deletions(-) diff --git a/api/client/images.go b/api/client/images.go index 32440d48d1261..e39c473749e41 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/url" - "strings" "text/tabwriter" "time" @@ -18,74 +17,6 @@ import ( "github.com/docker/docker/utils" ) -// FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) walkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { - length := len(images) - if length > 1 { - for index, image := range images { - if index+1 == length { - printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.ID]; exists { - cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) - } - } else { - printNode(cli, noTrunc, image, prefix+"\u251C─") - if subimages, exists := byParent[image.ID]; exists { - cli.walkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) - } - } - } - } else { - for _, image := range images { - printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.ID]; exists { - cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) - } - } - } -} - -// FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) printVizNode(noTrunc bool, image *types.Image, prefix string) { - var ( - imageID string - parentID string - ) - if noTrunc { - imageID = image.ID - parentID = image.ParentId - } else { - imageID = stringid.TruncateID(image.ID) - parentID = stringid.TruncateID(image.ParentId) - } - if parentID == "" { - fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) - } else { - fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) - } - if image.RepoTags[0] != ":" { - fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", - imageID, imageID, strings.Join(image.RepoTags, "\\n")) - } -} - -// FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) printTreeNode(noTrunc bool, image *types.Image, prefix string) { - var imageID string - if noTrunc { - imageID = image.ID - } else { - imageID = stringid.TruncateID(image.ID) - } - - fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.VirtualSize))) - if image.RepoTags[0] != ":" { - fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ", ")) - } else { - fmt.Fprint(cli.out, "\n") - } -} - // CmdImages lists the images in a specified repository, or all top-level images if no repository is specified. // // Usage: docker images [OPTIONS] [REPOSITORY] @@ -95,9 +26,6 @@ func (cli *DockerCli) CmdImages(args ...string) error { all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests") - // FIXME: --viz and --tree are deprecated. Remove them in a future version. - 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") flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") @@ -116,158 +44,83 @@ 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 { - v := url.Values{ - "all": []string{"1"}, - } - if len(imageFilterArgs) > 0 { - filterJSON, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } - v.Set("filters", filterJSON) - } - - rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) + v := url.Values{} + if len(imageFilterArgs) > 0 { + filterJSON, err := filters.ToParam(imageFilterArgs) if err != nil { return err } + v.Set("filters", filterJSON) + } - images := []types.Image{} - err = json.NewDecoder(rdr).Decode(&images) - if err != nil { - return err - } - - var ( - printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string) - startImage *types.Image - - roots = []*types.Image{} - byParent = make(map[string][]*types.Image) - ) - - for _, image := range images { - if image.ParentId == "" { - roots = append(roots, &image) - } else { - if children, exists := byParent[image.ParentId]; exists { - children = append(children, &image) - } else { - byParent[image.ParentId] = []*types.Image{&image} - } - } + if cmd.NArg() == 1 { + // FIXME rename this parameter, to not be confused with the filters flag + v.Set("filter", matchName) + } + if *all { + v.Set("all", "1") + } - if matchName != "" { - if matchName == image.ID || matchName == stringid.TruncateID(image.ID) { - startImage = &image - } + rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) + if err != nil { + return err + } - for _, repotag := range image.RepoTags { - if repotag == matchName { - startImage = &image - } - } - } - } + images := []types.Image{} + if err := json.NewDecoder(rdr).Decode(&images); err != nil { + return err + } - if *flViz { - fmt.Fprintf(cli.out, "digraph docker {\n") - printNode = (*DockerCli).printVizNode + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + if !*quiet { + if *showDigests { + fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") } else { - printNode = (*DockerCli).printTreeNode + fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") } + } - if startImage != nil { - root := []*types.Image{startImage} - cli.walkTree(*noTrunc, root, byParent, "", printNode) - } else if matchName == "" { - cli.walkTree(*noTrunc, roots, byParent, "", printNode) - } - if *flViz { - fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") - } - } else { - v := url.Values{} - if len(imageFilterArgs) > 0 { - filterJSON, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } - v.Set("filters", filterJSON) + for _, image := range images { + ID := image.ID + if !*noTrunc { + ID = stringid.TruncateID(ID) } - if cmd.NArg() == 1 { - // FIXME rename this parameter, to not be confused with the filters flag - v.Set("filter", matchName) - } - if *all { - v.Set("all", "1") - } + repoTags := image.RepoTags + repoDigests := image.RepoDigests - rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) - if err != nil { - return err + if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { + // dangling image - clear out either repoTags or repoDigsts so we only show it once below + repoDigests = []string{} } - images := []types.Image{} - err = json.NewDecoder(rdr).Decode(&images) - if err != nil { - return err - } - - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - if !*quiet { - if *showDigests { - fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") + // combine the tags and digests lists + tagsAndDigests := append(repoTags, repoDigests...) + for _, repoAndRef := range tagsAndDigests { + repo, ref := parsers.ParseRepositoryTag(repoAndRef) + // default tag and digest to none - if there's a value, it'll be set below + tag := "" + digest := "" + if utils.DigestReference(ref) { + digest = ref } else { - fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") - } - } - - for _, image := range images { - ID := image.ID - if !*noTrunc { - ID = stringid.TruncateID(ID) - } - - repoTags := image.RepoTags - repoDigests := image.RepoDigests - - if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { - // dangling image - clear out either repoTags or repoDigsts so we only show it once below - repoDigests = []string{} + tag = ref } - // combine the tags and digests lists - tagsAndDigests := append(repoTags, repoDigests...) - for _, repoAndRef := range tagsAndDigests { - repo, ref := parsers.ParseRepositoryTag(repoAndRef) - // default tag and digest to none - if there's a value, it'll be set below - tag := "" - digest := "" - if utils.DigestReference(ref) { - digest = ref - } else { - tag = ref - } - - if !*quiet { - if *showDigests { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) - } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) - } + if !*quiet { + if *showDigests { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } else { - fmt.Fprintln(w, ID) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } + } else { + fmt.Fprintln(w, ID) } } + } - if !*quiet { - w.Flush() - } + if !*quiet { + w.Flush() } return nil } diff --git a/api/server/server.go b/api/server/server.go index 830e731a7ecb0..b5f978e8445d2 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -406,15 +406,6 @@ func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w ht return writeJSON(w, http.StatusOK, legacyImages) } -func (s *Server) getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version.GreaterThan("1.6") { - w.WriteHeader(http.StatusNotFound) - return fmt.Errorf("This is now implemented in the client.") - } - eng.ServeHTTP(w, r) - return nil -} - func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") @@ -1588,7 +1579,6 @@ func createRouter(s *Server, eng *engine.Engine) *mux.Router { "/info": s.getInfo, "/version": s.getVersion, "/images/json": s.getImagesJSON, - "/images/viz": s.getImagesViz, "/images/search": s.getImagesSearch, "/images/get": s.getImagesGet, "/images/{name:.*}/get": s.getImagesGet, From 667b1e220cf82fb77fd776426a4b712ae5fee0ae Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Wed, 15 Apr 2015 08:16:00 +0800 Subject: [PATCH 273/332] simplify memory limit check If memory cgroup is mounted, memory limit is always supported, no need to check if these files are exist. Signed-off-by: Qiang Huang --- pkg/sysinfo/sysinfo.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 0c1ae874388f6..195a03e9a8ed8 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -23,20 +23,16 @@ func New(quiet bool) *SysInfo { sysInfo := &SysInfo{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { if !quiet { - logrus.Warnf("%v", err) + logrus.Warnf("Your kernel does not support cgroup memory limit: %v", err) } } else { - _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) - _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) - sysInfo.MemoryLimit = err1 == nil && err2 == nil - if !sysInfo.MemoryLimit && !quiet { - logrus.Warn("Your kernel does not support cgroup memory limit.") - } + // If memory cgroup is mounted, MemoryLimit is always enabled. + sysInfo.MemoryLimit = true - _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) - sysInfo.SwapLimit = err == nil + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) + sysInfo.SwapLimit = err1 == nil if !sysInfo.SwapLimit && !quiet { - logrus.Warn("Your kernel does not support cgroup swap limit.") + logrus.Warn("Your kernel does not support swap memory limit.") } } From 5f4fb8be006c0ffeff2671e5752111e543e07d9f Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Fri, 24 Apr 2015 08:54:08 +0800 Subject: [PATCH 274/332] Add cpu cfs quota to build Signed-off-by: Lei Jitang --- api/client/build.go | 2 ++ api/server/server.go | 1 + builder/evaluator.go | 1 + builder/internals.go | 1 + builder/job.go | 2 ++ contrib/completion/bash/docker | 2 +- docs/man/docker-build.1.md | 1 + integration-cli/docker_cli_build_test.go | 15 ++++++++------- 8 files changed, 17 insertions(+), 8 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 63cc63bc9e795..fb022e38d9484 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -55,6 +55,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit") flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") @@ -281,6 +282,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("cpusetcpus", *flCPUSetCpus) v.Set("cpusetmems", *flCPUSetMems) v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) + v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10)) v.Set("memory", strconv.FormatInt(memory, 10)) v.Set("memswap", strconv.FormatInt(memorySwap, 10)) diff --git a/api/server/server.go b/api/server/server.go index 830e731a7ecb0..cdc6c181548b8 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1343,6 +1343,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R buildConfig.MemorySwap = int64Value(r, "memswap") buildConfig.Memory = int64Value(r, "memory") buildConfig.CpuShares = int64Value(r, "cpushares") + buildConfig.CpuQuota = int64Value(r, "cpuquota") buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") buildConfig.CpuSetMems = r.FormValue("cpusetmems") diff --git a/builder/evaluator.go b/builder/evaluator.go index 2f9d4ff85b759..9a2b57a8f93ea 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -124,6 +124,7 @@ type Builder struct { cpuSetCpus string cpuSetMems string cpuShares int64 + cpuQuota int64 memory int64 memorySwap int64 diff --git a/builder/internals.go b/builder/internals.go index 731ca84fede08..ba7d45bcb18cc 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -547,6 +547,7 @@ func (b *Builder) create() (*daemon.Container, error) { hostConfig := &runconfig.HostConfig{ CpuShares: b.cpuShares, + CpuQuota: b.cpuQuota, CpusetCpus: b.cpuSetCpus, CpusetMems: b.cpuSetMems, Memory: b.memory, diff --git a/builder/job.go b/builder/job.go index 115d89a4b9ef7..acffa8b4607b8 100644 --- a/builder/job.go +++ b/builder/job.go @@ -49,6 +49,7 @@ type Config struct { Memory int64 MemorySwap int64 CpuShares int64 + CpuQuota int64 CpuSetCpus string CpuSetMems string AuthConfig *cliconfig.AuthConfig @@ -169,6 +170,7 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { ConfigFile: buildConfig.ConfigFile, dockerfileName: buildConfig.DockerfileName, cpuShares: buildConfig.CpuShares, + cpuQuota: buildConfig.CpuQuota, cpuSetCpus: buildConfig.CpuSetCpus, cpuSetMems: buildConfig.CpuSetMems, memory: buildConfig.Memory, diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 7f87e50f5d453..f3b83315873f4 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -279,7 +279,7 @@ _docker_build() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--cpu-shares -c --cpuset-cpus --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--cpu-shares -c --cpuset-cpus --cpu-quota --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) ) ;; *) local counter="$(__docker_pos_first_nonflag '--tag|-t')" diff --git a/docs/man/docker-build.1.md b/docs/man/docker-build.1.md index fe6250fc195dc..4a8eba67dfe1f 100644 --- a/docs/man/docker-build.1.md +++ b/docs/man/docker-build.1.md @@ -17,6 +17,7 @@ docker-build - Build a new image from the source code at PATH [**-m**|**--memory**[=*MEMORY*]] [**--memory-swap**[=*MEMORY-SWAP*]] [**-c**|**--cpu-shares**[=*0*]] +[**--cpu-quota**[=*0*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] PATH | URL | - diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 70e4ba114c24f..72d15c177bbff 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5371,7 +5371,7 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { c.Fatal(err) } - cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "-t", name, ".") + cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "--cpu-quota=8000", "-t", name, ".") cmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(cmd) @@ -5388,6 +5388,7 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { CpusetCpus string CpusetMems string CpuShares int64 + CpuQuota int64 } cfg, err := inspectFieldJSON(cID, "HostConfig") @@ -5399,9 +5400,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c1); err != nil { c.Fatal(err, cfg) } - if c1.Memory != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { - c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - c1.Memory, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) + if c1.Memory != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 || c1.CpuQuota != 8000 { + c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d, CpuQuota: %d", + c1.Memory, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares, c1.CpuQuota) } // Make sure constraints aren't saved to image @@ -5415,9 +5416,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c2); err != nil { c.Fatal(err, cfg) } - if c2.Memory == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { - c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - c2.Memory, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) + if c2.Memory == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 || c2.CpuQuota == 8000 { + c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d, CpuQuota: %d", + c2.Memory, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares, c2.CpuQuota) } } From 493437616d1aabef50768d71ce786595f7295554 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 20 Apr 2015 10:36:52 +1000 Subject: [PATCH 275/332] make space for DHE menu item again Signed-off-by: Sven Dowideit --- docs/mkdocs.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index df9d95997ce33..c62e589f293b6 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -195,17 +195,17 @@ pages: # Project: - ['project/index.md', '**HIDDEN**'] -- ['project/who-written-for.md', 'Contributor Guide', 'README first'] -- ['project/software-required.md', 'Contributor Guide', 'Get required software'] -- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] -- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] -- ['project/test-and-docs.md', 'Contributor Guide', 'Run tests and test documentation'] -- ['project/make-a-contribution.md', 'Contributor Guide', 'Understand contribution workflow'] -- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] -- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] -- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] -- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] -- ['project/advanced-contributing.md', 'Contributor Guide', 'Advanced contributing'] -- ['project/get-help.md', 'Contributor Guide', 'Where to get help'] -- ['project/coding-style.md', 'Contributor Guide', 'Coding style guide'] -- ['project/doc-style.md', 'Contributor Guide', 'Documentation style guide'] +- ['project/who-written-for.md', 'Contribute', 'README first'] +- ['project/software-required.md', 'Contribute', 'Get required software'] +- ['project/set-up-git.md', 'Contribute', 'Configure Git for contributing'] +- ['project/set-up-dev-env.md', 'Contribute', 'Work with a development container'] +- ['project/test-and-docs.md', 'Contribute', 'Run tests and test documentation'] +- ['project/make-a-contribution.md', 'Contribute', 'Understand contribution workflow'] +- ['project/find-an-issue.md', 'Contribute', 'Find an issue'] +- ['project/work-issue.md', 'Contribute', 'Work on an issue'] +- ['project/create-pr.md', 'Contribute', 'Create a pull request'] +- ['project/review-pr.md', 'Contribute', 'Participate in the PR review'] +- ['project/advanced-contributing.md', 'Contribute', 'Advanced contributing'] +- ['project/get-help.md', 'Contribute', 'Where to get help'] +- ['project/coding-style.md', 'Contribute', 'Coding style guide'] +- ['project/doc-style.md', 'Contribute', 'Documentation style guide'] From b0ad95daa84354c4b41679d0182fafbd259e5a69 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 16 Apr 2015 15:04:47 +1000 Subject: [PATCH 276/332] Copy over the DHE documentation for release to docs.docker.com Signed-off-by: Sven Dowideit --- docs/mkdocs.yml | 12 +- .../docker-hub-enterprise/admin-metrics.png | Bin 0 -> 60141 bytes .../admin-settings-http.png | Bin 0 -> 24230 bytes docs/sources/docker-hub-enterprise/admin.png | Bin 0 -> 66488 bytes .../docker-hub-enterprise/adminguide.md | 103 ++++++ .../assets/admin-logs.png | Bin 0 -> 161230 bytes .../assets/admin-metrics.png | Bin 0 -> 74062 bytes .../admin-settings-authentication-basic.png | Bin 0 -> 23600 bytes .../admin-settings-authentication-ldap.png | Bin 0 -> 25353 bytes .../assets/admin-settings-authentication.png | Bin 0 -> 13472 bytes .../assets/admin-settings-http-unlicensed.png | Bin 0 -> 21971 bytes .../assets/admin-settings-http.png | Bin 0 -> 20618 bytes .../assets/admin-settings-license.png | Bin 0 -> 14936 bytes .../assets/admin-settings-security.png | Bin 0 -> 21807 bytes .../assets/admin-settings-storage.png | Bin 0 -> 41579 bytes .../assets/admin-settings.png | Bin 0 -> 26041 bytes .../assets/console-pull.png | Bin 0 -> 35398 bytes .../assets/console-push.png | Bin 0 -> 49729 bytes ...b-org-enterprise-license-CSDE-dropdown.png | Bin 0 -> 28680 bytes .../docker-hub-org-enterprise-license.png | Bin 0 -> 27642 bytes .../assets/jenkins-plugins.png | Bin 0 -> 45860 bytes .../assets/jenkins-ui.png | Bin 0 -> 42805 bytes .../docker-hub-enterprise/configuration.md | 311 +++++++++++++++++ docs/sources/docker-hub-enterprise/index.md | 50 +++ .../docker-hub-enterprise/install-config.md | 8 - docs/sources/docker-hub-enterprise/install.md | 312 ++++++++++++++++++ .../docker-hub-enterprise/quick-start.md | 308 +++++++++++++++++ docs/sources/docker-hub-enterprise/support.md | 14 + docs/sources/docker-hub-enterprise/usage.md | 9 - .../docker-hub-enterprise/userguide.md | 130 ++++++++ 30 files changed, 1236 insertions(+), 21 deletions(-) create mode 100644 docs/sources/docker-hub-enterprise/admin-metrics.png create mode 100644 docs/sources/docker-hub-enterprise/admin-settings-http.png create mode 100644 docs/sources/docker-hub-enterprise/admin.png create mode 100644 docs/sources/docker-hub-enterprise/adminguide.md create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-logs.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-metrics.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-basic.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-ldap.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-authentication.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-http-unlicensed.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-http.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-license.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-security.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-storage.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings.png create mode 100755 docs/sources/docker-hub-enterprise/assets/console-pull.png create mode 100755 docs/sources/docker-hub-enterprise/assets/console-push.png create mode 100644 docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license-CSDE-dropdown.png create mode 100644 docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license.png create mode 100755 docs/sources/docker-hub-enterprise/assets/jenkins-plugins.png create mode 100755 docs/sources/docker-hub-enterprise/assets/jenkins-ui.png create mode 100644 docs/sources/docker-hub-enterprise/configuration.md create mode 100644 docs/sources/docker-hub-enterprise/index.md delete mode 100644 docs/sources/docker-hub-enterprise/install-config.md create mode 100644 docs/sources/docker-hub-enterprise/install.md create mode 100644 docs/sources/docker-hub-enterprise/quick-start.md create mode 100644 docs/sources/docker-hub-enterprise/support.md delete mode 100644 docs/sources/docker-hub-enterprise/usage.md create mode 100644 docs/sources/docker-hub-enterprise/userguide.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c62e589f293b6..e425175f61657 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -78,10 +78,14 @@ pages: - ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] - ['docker-hub/official_repos.md', 'Docker Hub', 'Official repo guidelines'] -# Docker Hub Enterprise -#- ['docker-hub-enterprise/index.md', '**HIDDEN**' ] -#- ['docker-hub-enterprise/install-config.md', 'Docker Hub Enterprise', 'Installation and Configuration' ] -#- ['docker-hub-enterprise/usage.md', 'Docker Hub Enterprise', 'User Guide' ] +# Docker Hub Enterprise: +- ['docker-hub-enterprise/index.md', 'Docker Hub Enterprise', 'Overview' ] +- ['docker-hub-enterprise/quick-start.md', 'Docker Hub Enterprise', 'Quick Start: Basic Workflow' ] +- ['docker-hub-enterprise/userguide.md', 'Docker Hub Enterprise', 'User Guide' ] +- ['docker-hub-enterprise/adminguide.md', 'Docker Hub Enterprise', 'Admin Guide' ] +- ['docker-hub-enterprise/install.md', 'Docker Hub Enterprise', '  Installation' ] +- ['docker-hub-enterprise/configuration.md', 'Docker Hub Enterprise', '  Configuration options' ] +- ['docker-hub-enterprise/support.md', 'Docker Hub Enterprise', 'Support' ] # Examples: - ['examples/index.md', '**HIDDEN**'] diff --git a/docs/sources/docker-hub-enterprise/admin-metrics.png b/docs/sources/docker-hub-enterprise/admin-metrics.png new file mode 100644 index 0000000000000000000000000000000000000000..21a8f74a7cab9ecad0125ba4f795ce03c3837bab GIT binary patch literal 60141 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 SCu(1X*OJA{w6zh2U4H>>>~!M* literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/adminguide.md b/docs/sources/docker-hub-enterprise/adminguide.md new file mode 100644 index 0000000000000..d471041675582 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/adminguide.md @@ -0,0 +1,103 @@ +page_title: Docker Hub Enterprise: Admin guide +page_description: Documentation describing administration of Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, hub, enterprise + +# Docker Hub Enterprise Administrator's Guide + +This guide covers tasks and functions an administrator of Docker Hub Enterprise +(DHE) will need to know about, such as reporting, logging, system management, +performance metrics, etc. +For tasks DHE users need to accomplish, such as using DHE to push and pull +images, please visit the [User's Guide](./userguide). + +## Reporting + +### System Health + +![System Health page](../assets/admin-metrics.png) + +The "System Health" tab displays resource utilization metrics for the DHE host +as well as for each of its contained services. The CPU and RAM usage meters at +the top indicate overall resource usage for the host, while detailed time-series +charts are provided below for each service. You can mouse-over the charts or +meters to see detailed data points. + +Clicking on a service name (i.e., "load_balancer", "admin_server", etc.) will +display the network, CPU, and memory (RAM) utilization data for the specified +service. See below for a +[detailed explanation of the available services](#services). + +### Logs + +![System Logs page](../assets/admin-logs.png) + +Click the "Logs" tab to view all logs related to your DHE instance. You will see +log sections on this page for each service in your DHE instance. Older or newer +logs can be loaded by scrolling up or down. See below for a +[detailed explanation of the available services](#services). + +DHE's log files can be found on the host in `/usr/local/etc/dhe/logs/`. The +files are limited to a maximum size of 64mb. They are rotated every two weeks, +when the aggregator sends logs to the collection server, or they are rotated if +a logfile would exceed 64mb without rotation. Log files are named `-`, where the "component name" is the service it +provides (`manager`, `admin-server`, etc.). + +### Usage statistics and crash reports + +During normal use, DHE generates usage statistics and crash reports. This +information is collected by Docker, Inc. to help us prioritize features, fix +bugs, and improve our products. Specifically, Docker, Inc. collects the +following information: + +* Error logs +* Crash logs + +## Emergency access to the DHE admin web interface + +If your authenticated or public access to the DHE web interface has stopped +working, but your DHE admin container is still running, you can add an +[ambassador container](https://docs.docker.com/articles/ambassador_pattern_linking/) +to get temporary unsecure access to it by running: + + $ docker run --rm -it --link docker_hub_enterprise_admin_server:admin -p 9999:80 svendowideit/ambassador + +> **Note:** This guide assumes you can run Docker commands from a machine where +> you are a member of the `docker` group, or have root privileges. Otherwise, +> you may need to add `sudo` to the example command above. + +This will give you access on port `9999` on your DHE server - `http://:9999/admin/`. + +## Services + +DHE runs several Docker services which are essential to its reliability and +usability. The following services are included; you can see their details by +running queries on the [System Health](#system-health) and [Logs](#logs) pages: + +* `admin_server`: Used for displaying system health, performing upgrades, +configuring settings, and viewing logs. +* `load_balancer`: Used for maintaining high availability by distributing load +to each image storage service (`image_storage_X`). +* `log_aggregator`: A microservice used for aggregating logs from each of the +other services. Handles log persistence and rotation on disk. +* `image_storage_X`: Stores Docker images using the [Docker Registry HTTP API V2](https://github.com/docker/distribution/blob/master/doc/SPEC.md). Typically, +multiple image storage services are used in order to provide greater uptime and +faster, more efficient resource utilization. + +## DHE system management + +The `dockerhubenterprise/manager` image is used to control the DHE system. This +image uses the Docker socket to orchestrate the multiple services that comprise +DHE. + + $ sudo bash -c "$(sudo docker run dockerhubenterprise/manager [COMMAND])" + +Supported commands are: `install`, `start`, `stop`, `restart`, `status`, and +`upgrade`. + +> **Note**: `sudo` is needed for `dockerhubenterprise/manager` commands to +> ensure that the Bash script is run with full access to the Docker host. + +## Next Steps + +For information on installing DHE, take a look at the [Installation instructions](./install.md). diff --git a/docs/sources/docker-hub-enterprise/assets/admin-logs.png b/docs/sources/docker-hub-enterprise/assets/admin-logs.png new file mode 100644 index 0000000000000000000000000000000000000000..76f0d19a80ce96bf2e9995a6076a0c559d7235bf GIT binary patch literal 161230 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 G#Qy*sNpWNV literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-metrics.png b/docs/sources/docker-hub-enterprise/assets/admin-metrics.png new file mode 100644 index 0000000000000000000000000000000000000000..ccec72a31a19954e2271d801599c558a8c302974 GIT binary patch literal 74062 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 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-basic.png b/docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-basic.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9dfe3513154d6a2490759c6d664337a6ab9fa1 GIT binary patch literal 23600 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 z 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 U_CsBk)|u7*y$8d&p=qD}15D}J)c^nh literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-settings-http.png b/docs/sources/docker-hub-enterprise/assets/admin-settings-http.png new file mode 100644 index 0000000000000000000000000000000000000000..d860c5088d003af2034cc24e23a885afaf1d2c12 GIT binary patch literal 20618 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 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-settings-security.png b/docs/sources/docker-hub-enterprise/assets/admin-settings-security.png new file mode 100644 index 0000000000000000000000000000000000000000..81d375040e0cdb381340c38ae89105a1ba1abf2f GIT binary patch literal 21807 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%0A$|SnVdBCcZ^>KYpZ`H*(3`n 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 OV!jjfwrES#C;tK@B42(0 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/console-pull.png b/docs/sources/docker-hub-enterprise/assets/console-pull.png new file mode 100755 index 0000000000000000000000000000000000000000..57f264f4ead39d4ce2ee921cccaeead0ab1d82da GIT binary patch literal 35398 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 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license-CSDE-dropdown.png b/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license-CSDE-dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..49dbfab8745d2b7d7d80aa5f8de8efcf9169e5cf GIT binary patch literal 28680 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 z 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 Lijqa*24DUQNu1i5 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license.png b/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license.png new file mode 100644 index 0000000000000000000000000000000000000000..3c70b747c6bc32ccbfea457f7686d8d0d5764671 GIT binary patch literal 27642 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 P2c)W`dB5bIMbQ5O!JPrS literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/jenkins-ui.png b/docs/sources/docker-hub-enterprise/assets/jenkins-ui.png new file mode 100755 index 0000000000000000000000000000000000000000..6c8bd5f722bef0ad9347a2849d1a63a10f8953d2 GIT binary patch literal 42805 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 Om$Jf(r-&!uH~$0qGi2=m literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/configuration.md b/docs/sources/docker-hub-enterprise/configuration.md new file mode 100644 index 0000000000000..6050da401a9f2 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/configuration.md @@ -0,0 +1,311 @@ +page_title: Docker Hub Enterprise: Configuration options +page_description: Configuration instructions for Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry + +# Configuration options + +This page will help you properly configure Docker Hub Enterprise (DHE) so it can +run in your environment. + +Start with DHE loaded in your browser and click the "Settings" tab to view +configuration options. You'll see options for configuring: + +* Domains and ports +* Security settings +* Storage settings +* Authentication settings +* Your DHE license + +## Domains and Ports + +![Domain and Ports page](../assets/admin-settings-http.png) + +* *Domain Name*: **required**; defaults to an empty string, the fully qualified domain name assigned to the DHE host. +* *Load Balancer HTTP Port*: defaults to 80, used as the entry point for the image storage service. To see load balancer status, you can query +http://<dhe-host>/load_balancer_status. +* *Load Balancer HTTPS Port*: defaults to 443, used as the secure entry point +for the image storage service. +* *HTTP_PROXY*: defaults to an empty string, proxy server for HTTP requests. +* *HTTPS_PROXY*: defaults to an empty string, proxy server for HTTPS requests. +* *NO_PROXY*: defaults to an empty string, proxy bypass for HTTP and HTTPS requests. + + +> **Note**: If you need DHE to re-generate a self-signed certificate at some +> point, you'll need to first delete `/usr/local/etc/dhe/ssl/server.pem`, and +> then restart the DHE containers, either by changing and saving the "Domain Name", +> or using `bash -c "$(docker run dockerhubenterprise/manager restart)"`. + + +## Security + +![Security settings page](../assets/admin-settings-security.png) + +* *SSL Certificate*: Used to enter the hash (string) from the SSL Certificate. +This cert must be accompanied by its private key, entered below. +* *Private Key*: The hash from the private key associated with the provided +SSL Certificate (as a standard x509 key pair). + +In order to run, DHE requires encrypted communications via HTTPS/SSL between (a) the DHE registry and your Docker Engine(s), and (b) between your web browser and the DHE admin server. There are a few options for setting this up: + +1. You can use the self-signed certificate DHE generates by default. +2. You can generate your own certificates using a public service or your enterprise's infrastructure. See the [Generating SSL certificates](#generating-ssl-certificates) section for the options available. + +If you are generating your own certificates, you can install them by following the instructions for +[Adding your own registry certificates to DHE](#adding-your-own-registry-certificates-to-dhe). + +On the other hand, if you choose to use the DHE-generated certificates, or the +certificates you generate yourself are not trusted by your client Docker hosts, +you will need to do one of the following: + +* [Install a registry certificate on all of your client Docker daemons](#installing-registry-certificates-on-client-docker-daemons), + +* Set your [client Docker daemons to run with an unconfirmed connection to the registry](#if-you-cant-install-the-certificates). + +### Generating SSL certificates + +There are three basic approaches to generating certificates: + +1. Most enterprises will have private key infrastructure (PKI) in place to +generate keys. Consult with your security team or whomever manages your private +key infrastructure. If you have this resource available, Docker recommends you +use it. + +2. If your enterprise can't provide keys, you can use a public Certificate +Authority (CA) like "InstantSSL.com" or "RapidSSL.com" to generate a +certificate. If your certificates are generated using a globally trusted +Certificate Authority, you won't need to install them on all of your +client Docker daemons. + +3. Use the self-signed registry certificate generated by DHE, and install it +onto the client Docker daemon hosts as shown below. + +### Adding your own Registry certificates to DHE + +Whichever method you use to generate certificates, once you have them +you can set up your DHE server to use them by navigating to the "Settings" page, +going to "Security," and putting the SSL Certificate text (including all +intermediate Certificates, starting with the host) into the +"SSL Certificate" edit box, and the previously generated Private key into +the "SSL Private Key" edit box. + +Click the "Save" button, and then wait for the DHE Admin site to restart and +reload. It should now be using the new certificate. + +Once the "Security" page has reloaded, it will show `#` hashes instead of the +certificate text you pasted in. + +If your certificate is signed by a chain of Certificate Authorities that are +already trusted by your Docker daemon servers, you can skip the "Installing +registry certificates" step below. + +### Installing Registry certificates on client Docker daemons + +If your certificates do not have a trusted Certificate Authority, you will need +to install them on each client Docker daemon host. + +The procedure for installing the DHE certificates on each Linux distribution has +slightly different steps, as shown below. + +You can test this certificate using `curl`: + +``` +$ curl https://dhe.yourdomain.com/v2/ +curl: (60) SSL certificate problem: self signed certificate +More details here: http://curl.haxx.se/docs/sslcerts.html + +curl performs SSL certificate verification by default, using a "bundle" + of Certificate Authority (CA) public keys (CA certs). If the default + bundle file isn't adequate, you can specify an alternate file + using the --cacert option. +If this HTTPS server uses a certificate signed by a CA represented in + the bundle, the certificate verification probably failed due to a + problem with the certificate (it might be expired, or the name might + not match the domain name in the URL). +If you'd like to turn off curl's verification of the certificate, use + the -k (or --insecure) option. + +$ curl --cacert /usr/local/etc/dhe/ssl/server.pem https://dhe.yourdomain.com/v2/ +{"errors":[{"code":"UNAUTHORIZED","message":"access to the requested resource is not authorized","detail":null}]} +``` + +Continue by following the steps corresponding to your chosen OS. + +#### Ubuntu/Debian + +``` + $ export DOMAIN_NAME=dhe.yourdomain.com + $ openssl s_client -connect $DOMAIN_NAME:443 -showcerts /dev/null | openssl x509 -outform PEM | tee /usr/local/share/ca-certificates/$DOMAIN_NAME.crt + $ update-ca-certificates + Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done. + Running hooks in /etc/ca-certificates/update.d....done. + $ service docker restart + docker stop/waiting + docker start/running, process 29291 +``` + +#### RHEL + +``` + $ export DOMAIN_NAME=dhe.yourdomain.com + $ openssl s_client -connect $DOMAIN_NAME:443 -showcerts /dev/null | openssl x509 -outform PEM | tee /etc/pki/ca-trust/source/anchors/$DOMAIN_NAME.crt + $ update-ca-trust + $ /bin/systemctl restart docker.service +``` + +#### Boot2Docker 1.6.0 + +Install the CA cert (or the auto-generated cert) by adding the following to +your `/var/lib/boot2docker/bootsync.sh`: + +``` +#!/bin/sh + +cat /var/lib/boot2docker/server.pem >> /etc/ssl/certs/ca-certificates.crt +``` + + +Then get the certificate from the new DHE server using: + +``` +$ openssl s_client -connect dhe.yourdomain.com:443 -showcerts /dev/null | openssl x509 -outform PEM | sudo tee -a /var/lib/boot2docker/server.pem +``` + +If your certificate chain is complicated, you may want to use the changes in +[Pull request 807](https://github.com/boot2docker/boot2docker/pull/807/files) + +Now you can either reboot your Boot2Docker virtual machine, or run the following to +install the server certificate, and then restart the Docker daemon. + +``` +$ sudo chmod 755 /var/lib/boot2docker/bootsync.sh +$ sudo /var/lib/boot2docker/bootsync.sh +$ sudo /etc/init.d/docker restart`. +``` + +### If you can't install the certificates + +If for some reason you can't install the certificate chain on a client Docker host, +or your certificates do not have a global CA, you can configure your Docker daemon to run in "insecure" mode. This is done by adding an extra flag, +`--insecure-registry host-ip|domain-name`, to your client Docker daemon startup flags. +You'll need to restart the Docker daemon for the change to take effect. + +This flag means that the communications between your Docker client and the DHE +Registry server are still encrypted, but the client Docker daemon is not +confirming that the Registry connection is not being hijacked or diverted. + +> **Note**: If you enter a "Domain Name" into the "Security" settings, it needs +> to be DNS resolvable on any client Docker daemons that are running in +> "insecure-registry" mode. + +To set the flag, follow the directions below for your operating system. + +#### Ubuntu + +On Ubuntu 14.04 LTS, you customize the Docker daemon configuration with the +`/etc/defaults/docker` file. + +Open or create the `/etc/defaults/docker` file, and add the +`--insecure-registry` flag to the `DOCKER_OPTS` setting (which may need to be +added or uncommented) as follows: + +``` +DOCKER_OPTS="--insecure-registry dhe.yourdomain.com" +``` + +Then restart the Docker daemon with `sudo service docker restart`. + +#### RHEL + +On RHEL, you customize the Docker daemon configuration with the +`/etc/sysconfig/docker` file. + +Open or create the `/etc/sysconfig/docker` file, and add the +`--insecure-registry` flag to the `OPTIONS` setting (which may need to be +added or uncommented) as follows: + +``` +OPTIONS="--insecure-registry dhe.yourdomain.com" +``` + +Then restart the Docker daemon with `sudo service docker restart`. + +### Boot2Docker + +On Boot2Docker, you customize the Docker daemon configuration with the +`/var/lib/boot2docker/profile` file. + +Open or create the `/var/lib/boot2docker/profile` file, and add an `EXTRA_ARGS` +setting as follows: + +``` +EXTRA_ARGS="--insecure-registry dhe.yourdomain.com" +``` + +Then restart the Docker daemon with `sudo /etc/init.d/docker restart`. + +## Image Storage Configuration + +DHE offers multiple methods for image storage, which are defined using specific +storage drivers. Image storage can be local, remote, or on a cloud service such +as S3. Storage drivers can be added or customized via the DHE storage driver +API. + +![Storage settings page](../assets/admin-settings-storage.png) + +* *Yaml configuration file*: This file (`/usr/local/etc/dhe/storage.yml`) is +used to configure the image storage services. The editable text of the file is +displayed in the dialog box. The schema of this file is identical to that used +by the [Registry 2.0](http://docs.docker.com/registry/configuration/). +* If you are using the file system driver to provide local image storage, you will need to specify a root directory which will get mounted as a sub-path of +`/var/local/dhe/image-storage`. The default value of this root directory is +`/local`, so the full path to it is `/var/local/dhe/image-storage/local`. + +> **Note:** +> Saving changes you've made to settings will restart the Docker Hub Enterprise +> instance. The restart may cause a brief interruption for users of the image +> storage system. + +## Authentication + +The current authentication methods are `None`, `Basic` and `LDAP`. + +The `Basic` setting includes: + +![Basic authentication settings page](../assets/admin-settings-authentication-basic.png) + +* A button to add one user, or to upload a CSV file containing username, +password pairs +* A DHE website Administrator Filter, allowing you to either +* * 'Allow all authenticated users' to log into the DHE admin web interface, or +* * 'Whitelist usernames', which allows you to restrict access to the web +interface to the listed set of users. + +The `LDAP` setting includes: + +![LDAP authentication settings page](../assets/admin-settings-authentication-ldap.png) + +* *Use StartTLS*: defaults to unchecked, check to enable StartTLS +* *LDAP Server URL*: **required**; defaults to null, LDAP server URL (e.g., - ldap://example.com) +* *User Base DN*: **required**; defaults to null, user base DN in the form +(e.g., - dc=example,dc=com) +* *User Login Attribute*: **required**; defaults to null, user login attribute +(e.g., - uid or sAMAccountName) +* *Search User DN*:** required**; defaults to null, search user DN +(e.g., - domain\username) +* *Search User Password*: **required**; defaults to null, search user password +* A *DHE Registry User filter*, allowing you to either +* * 'Allow all authenticated users' to push or pull any images, or +* * 'Filter LDAP search results', which allows you to restrict DHE registry pull +and push to users matching the LDAP filter, +* * 'Whitelist usernames', which allows you to restrict DHE registry pull and +push to the listed set of users. +* A *DHE website Administrator filter*, allowing you to either +* * 'Allow all authenticated users' to log into the DHE admin web interface, or +* * 'Filter LDAP search results', which allows you to restrict DHE admin web access to users matching the LDAP filter, +* * 'Whitelist usernames', which allows you to restrict access to the web interface to the listed set of users. + +## Next Steps + +For information on getting support for DHE, take a look at the +[Support information](./support.md). + diff --git a/docs/sources/docker-hub-enterprise/index.md b/docs/sources/docker-hub-enterprise/index.md new file mode 100644 index 0000000000000..c14bf9280fca3 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/index.md @@ -0,0 +1,50 @@ +page_title: Docker Hub Enterprise: Overview +page_description: Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry + +# Overview + +Docker Hub Enterprise (DHE) lets you run and manage your own Docker image +storage service, securely on your own infrastructure behind your company +firewall. This allows you to securely store, push, and pull the images used by +your enterprise to build, ship, and run applications. DHE also provides +monitoring and usage information to help you understand the workloads being +placed on it. + +Specifically, DHE provides: + +* An image registry to store, manage, and collaborate on Docker images +* Pluggable storage drivers +* Configuration options to let you run DHE in your particular enterprise +environment. +* Easy, transparent upgrades +* Logging, usage and system health metrics + +DHE is perfect for: + +* Providing a secure, on-premise development environment +* Creating a streamlined build pipeline +* Building a consistent, high-performance test/QA environment +* Managing image deployment + +DHE is built on [version 2 of the Docker registry](https://github.com/docker/distribution). + +## Documentation + +The following documentation for DHE is available: + +* **Overview** This page. +* [**Quick Start: Basic User Workflow**](./quick-start.md) Go here to learn the +fundamentals of how DHE works and how you can set up a simple, but useful +workflow. +* [**User Guide**](./userguide.md) Go here to learn about using DHE from day to +day. +* [**Administrator Guide**](./adminguide.md) Go here if you are an administrator +responsible for running and maintaining DHE. +* [**Installation**](install.md) Go here for the steps you'll need to install +DHE and get it working. +* [**Configuration**](./configuration.md) Go here to find out details about +setting up and configuring DHE for your particular environment. +* [**Support**](./support.md) Go here for information on getting support for +DHE. + diff --git a/docs/sources/docker-hub-enterprise/install-config.md b/docs/sources/docker-hub-enterprise/install-config.md deleted file mode 100644 index 81fa3041efbab..0000000000000 --- a/docs/sources/docker-hub-enterprise/install-config.md +++ /dev/null @@ -1,8 +0,0 @@ -page_title: Using Docker Hub Enterprise installation -page_description: Docker Hub Enterprise installation -page_keywords: docker hub enterprise - -# Docker Hub Enterprise installation - -Documenation coming soon. - diff --git a/docs/sources/docker-hub-enterprise/install.md b/docs/sources/docker-hub-enterprise/install.md new file mode 100644 index 0000000000000..84f9a321b3810 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/install.md @@ -0,0 +1,312 @@ +page_title: Docker Hub Enterprise: Install +page_description: Installation instructions for Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry + +# Install + +## Overview + +This document describes the process of obtaining, installing, and securing +Docker Hub Enterprise (DHE). DHE is installed from Docker containers. Once +installed, you will need to select a method of securing it. This doc will +explain the options you have for security and help you find the resources needed +to configure it according to your chosen method. More configuration details can +be found in the [DHE Configuration page](./configuration.md). + +Specifically, installation requires completion of these steps, in order: + +1. Acquire a license by purchasing DHE or requesting a trial license. +2. Install the commercially supported Docker Engine. +3. Install DHE +4. Add your license to your DHE instance + +## Licensing + +In order to run DHE, you will need to acquire a license, either by purchasing +DHE or requesting a trial license. The license will be associated with your +Docker Hub account or Docker Hub organization (so if you don't have an account, +you'll need to set one up, which can be done at the same time as your license +request). To get your license or start your trial, please contact our +[sales department](mailto:sales@docker.com). Upon completion of your purchase or +request, you will receive an email with further instructions for licensing your +copy of DHE. + +## Prerequisites + +DHE requires the following: + +* Commercially supported Docker Engine 1.6.0 or later running on an +Ubuntu 14.04 LTS, RHEL 7.1 or RHEL 7.0 host. (See below for instructions on how +to install the commercially supported Docker Engine.) + +> **Note:** In order to remain in compliance with your DHE support agreement, +> you must use the current version of commercially supported Docker Engine. +> Running the regular, open source version of Engine is **not** supported. + +* Your Docker daemon needs to be listening to the Unix socket (the default) so +that it can be bind-mounted into the DHE management containers, allowing +DHE to manage itself and its updates. For this reason, your DHE host will also +need internet connectivity so it can access the updates. + +* Your host also needs to have TCP ports `80` and `443` available for the DHE +container port mapping. + +* You will also need the Docker Hub user-name and password used when obtaining +the DHE license (or the user-name of an administrator of the Hub organization +that obtained an Enterprise license). + +## Installing the Commercially Supported Docker Engine + +Since DHE is installed using Docker, the commercially supported Docker Engine +must be installed first. This is done with an RPM or DEB repository, which you +set up using a Bash script downloaded from the [Docker Hub](https://hub.docker.com). + +### Download the commercially supported Docker Engine installation script + +To download the commercially supported Docker Engine Bash installation script, +log in to the [Docker Hub](https://hub.docker.com) with the user-name used to +obtain your license . Once you're logged in, go to the +["Enterprise Licenses"](https://registry.hub.docker.com/account/licenses/) page +in your Hub account's "Settings" section. + +Select your intended host operating system from the "Download CS Engine" drop- +down at the top right of the page and then, once the Bash setup script is +downloaded, follow the steps below appropriate for your chosen OS. + +![Docker Hub Docker engine install dropdown](../assets/docker-hub-org-enterprise-license-CSDE-dropdown.png) + +### RHEL 7.0/7.1 installation + +First, copy the downloaded Bash setup script to your RHEL host. Next, run the +following to install commercially supported Docker Engine and its dependencies, +and then start the Docker daemon: + +``` +$ sudo yum update && sudo yum upgrade +$ chmod 755 docker-cs-engine-rpm.sh +$ sudo ./docker-cs-engine-rpm.sh +$ sudo yum install docker-engine-cs +$ sudo systemctl enable docker.service +$ sudo systemctl start docker.service +``` + +In order to simplify using Docker, you can get non-sudo access to the Docker +socket by adding your user to the `docker` group, then logging out and back in +again: + +``` +$ sudo usermod -a -G docker $USER +$ exit +``` + +> **Note**: you may need to reboot your server to update its RHEL kernel. + +### Ubuntu 14.04 LTS installation + +First, copy the downloaded Bash setup script to your Ubuntu host. Next, run the +following to install commercially supported Docker Engine and its dependencies: + +``` +$ sudo apt-get update && sudo apt-get upgrade +$ chmod 755 docker-cs-engine-deb.sh +$ sudo ./docker-cs-engine-deb.sh +$ sudo apt-get install docker-engine-cs +``` + +In order to simplify using Docker, you can get non-sudo access to the Docker +socket by adding your user to the `docker` group, then logging out and back in +again: + +``` +$ sudo usermod -a -G docker $USER +$ exit +``` + +> **Note**: you may need to reboot your server to update its LTS kernel. + +## Installing Docker Hub Enterprise + +Once the commercially supported Docker Engine is installed, you can install DHE +itself. DHE is a self-installing application built and distributed using Docker +and the [Docker Hub](https://registry.hub.docker.com/). It is able to restart +and reconfigure itself using the Docker socket that is bind-mounted to its +container. + + +Start installing DHE by running the "dockerhubenterprise/manager" container: + +``` + $ sudo bash -c "$(sudo docker run dockerhubenterprise/manager install)" +``` + +> **Note**: `sudo` is needed for `dockerhubenterprise/manager` commands to +> ensure that the Bash script is run with full access to the Docker host. + +You can also find this command on the "Enterprise Licenses" section of your Hub +user profile. The command will execute a shell script that creates the needed +directories and then runs Docker to pull DHE's images and run its containers. + +Depending on your internet connection, this process may take several minutes to +complete. + +A successful installation will pull a large number of Docker images and should +display output similar to: + +``` +$ sudo bash -c "$(sudo docker run dockerhubenterprise/manager install)" +Unable to find image 'dockerhubenterprise/manager:latest' locally +Pulling repository dockerhubenterprise/manager +c46d58daad7d: Pulling image (latest) from dockerhubenterprise/manager +c46d58daad7d: Pulling image (latest) from dockerhubenterprise/manager +c46d58daad7d: Pulling dependent layers +511136ea3c5a: Download complete +fa4fd76b09ce: Pulling metadata +fa4fd76b09ce: Pulling fs layer +ff2996b1faed: Download complete +... +fd7612809d57: Pulling metadata +fd7612809d57: Pulling fs layer +fd7612809d57: Download complete +c46d58daad7d: Pulling metadata +c46d58daad7d: Pulling fs layer +c46d58daad7d: Download complete +c46d58daad7d: Download complete +Status: Downloaded newer image for dockerhubenterprise/manager:latest +Unable to find image 'dockerhubenterprise/manager:1.0.0_8ce62a61e058' locally +Pulling repository dockerhubenterprise/manager +c46d58daad7d: Download complete +511136ea3c5a: Download complete +fa4fd76b09ce: Download complete +1c8294cc5160: Download complete +117ee323aaa9: Download complete +2d24f826cb16: Download complete +33bfc1956932: Download complete +48f0dd6c9414: Download complete +65c30f72ecb2: Download complete +d4b29764d0d3: Download complete +5654f4fe5384: Download complete +9b9faa6ecd11: Download complete +0c275f56ca5c: Download complete +ff2996b1faed: Download complete +fd7612809d57: Download complete +Status: Image is up to date for dockerhubenterprise/manager:1.0.0_8ce62a61e058 +INFO [1.0.0_8ce62a61e058] Attempting to connect to docker engine dockerHost="unix:///var/run/docker.sock" +INFO [1.0.0_8ce62a61e058] Running install command +<...output truncated...> +Creating container docker_hub_enterprise_load_balancer with docker daemon unix:///var/run/docker.sock +Starting container docker_hub_enterprise_load_balancer with docker daemon unix:///var/run/docker.sock +Bringing up docker_hub_enterprise_log_aggregator. +Creating container docker_hub_enterprise_log_aggregator with docker daemon unix:///var/run/docker.sock +Starting container docker_hub_enterprise_log_aggregator with docker daemon unix:///var/run/docker.sock +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +0168f37b6221 dockerhubenterprise/log-aggregator:1.0.0_8ce62a61e058 "log-aggregator" 4 seconds ago Up 4 seconds docker_hub_enterprise_log_aggregator +b51c73bebe8b dockerhubenterprise/nginx:1.0.0_8ce62a61e058 "nginxWatcher" 4 seconds ago Up 4 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp docker_hub_enterprise_load_balancer +e8327864356b dockerhubenterprise/admin-server:1.0.0_8ce62a61e058 "server" 5 seconds ago Up 5 seconds 80/tcp docker_hub_enterprise_admin_server +52885a6e830a dockerhubenterprise/auth_server:alpha-a5a2af8a555e "garant --authorizat 6 seconds ago Up 5 seconds 8080/tcp +``` + +Once this process completes, you should be able to manage and configure your DHE +instance by pointing your browser to `https:///`. + +Your browser will warn you that this is an unsafe site, with a self-signed, +untrusted certificate. This is normal and expected; allow this connection +temporarily. + +### Setting the DHE Domain Name + +The DHE Administrator site will also warn that the "Domain Name" is not set. Go +to the "Settings" tab, and set the "Domain Name" to the full host-name of your +DHE server. +Hitting the "Save and Restart DHE Server" button will generate a new certificate, which will be used +by both the DHE Administrator web interface and the DHE Registry server. + +After the server restarts, you will again need to allow the connection to the untrusted DHE web admin site. + +![http settings page](../assets/admin-settings-http-unlicensed.png) + +Lastly, you will see a warning notifying you that this instance of DHE is +unlicensed. You'll correct this in the next step. + +### Add your license + +The DHE registry services will not start until you add your license. +To do that, you'll first download your license from the Docker Hub and then +upload it to your DHE web admin server. Follow these steps: + +1. If needed, log back into the [Docker Hub](https://hub.docker.com) + using the user-name you used when obtaining your license. Go to "Settings" (in + the menu under your user-name, top right) to get to your account settings, and + then click on "Enterprise Licenses" in the side bar at left. + +2. You'll see a list of available licenses. Click on the download button to + obtain the license file you'd like to use. + ![Download DHE license](../assets/docker-hub-org-enterprise-license.png) + +3. Next, go to your DHE instance in your browser and click on the Settings tab + and then the "License" tab. Click on the "Upload license file" button, which + will open a standard file browser. Locate and select the license file you + downloaded in step 2, above. Approve the selection to close the dialog. + ![http settings page](../assets/admin-settings-license.png) + +4. Click the "Save and Restart DHE" button, which will quit DHE and then restart it, registering + the new license. + +5. Verify the acceptance of the license by confirming that the "unlicensed copy" +warning is no longer present. + +### Securing DHE + +Securing DHE is **required**. You will not be able to push or pull from DHE until you secure it. + +There are several options and methods for securing DHE. For more information, +see the [configuration documentation](./configuration.md#security) + +### Using DHE to push and pull images + +Now that you have DHE configured with a "Domain Name" and have your client +Docker daemons configured with the required security settings, you can test your +setup by following the instructions for +[Using DHE to Push and pull images](./userguide.md#using-dhe-to-push-and-pull-images). + +### DHE web interface and registry authentication + +By default, there is no authentication set on either the DHE web admin +interface or the DHE registry. You can restrict access using an in-DHE +configured set of users (and passwords), or you can configure DHE to use LDAP- +based authentication. + +See [DHE Authentication settings](./configuration.md#authentication) for more +details. + +# Upgrading + +DHE has been designed to allow on-the-fly software upgrades. Start by +clicking on the "System Health" tab. In the upper, right-hand side of the +dashboard, below the navigation bar, you'll see the currently installed version +(e.g., `Current Version: 0.1.12345`). + +If your DHE instance is the latest available, you will also see the message: +"System Up to Date." + +If there is an upgrade available, you will see the message "System Update +Available!" alongside a button labeled "Update to Version X.XX". To upgrade, DHE +will pull new DHE container images from the Docker Hub. If you have not already +connected to Docker Hub, DHE will prompt you to log in. + +The upgrade process requires a small amount of downtime to complete. To complete +the upgrade, DHE will: +* Connect to the Docker Hub to pull new container images with the new version of +DHE. +* Deploy those containers +* Shut down the old containers +* Resolve any necessary links/urls. + +Assuming you have a decent internet connection, the entire upgrade process +should complete within a few minutes. + +## Next Steps + +For information on configuring DHE for your environment, take a look at the +[Configuration instructions](./configuration.md). + diff --git a/docs/sources/docker-hub-enterprise/quick-start.md b/docs/sources/docker-hub-enterprise/quick-start.md new file mode 100644 index 0000000000000..a813deb076c3f --- /dev/null +++ b/docs/sources/docker-hub-enterprise/quick-start.md @@ -0,0 +1,308 @@ +page_title: Docker Hub Enterprise: Quick-start: Basic Workflow +page_description: Brief tutorial on the basics of Docker Hub Enterprise user workflow +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry, image, repository + + +# Docker Hub Enterprise Quick Start: Basic User Workflow + +## Overview + +This Quick Start Guide will give you a hands-on look at the basics of using +Docker Hub Enterprise (DHE), Docker’s on-premise image storage application. +This guide will walk you through using DHE to complete a typical, and critical, +part of building a development pipeline: setting up a Jenkins instance. Once you +complete the task, you should have a good idea of how DHE works and how it might +be useful to you. + +Specifically, this guide demonstrates the process of retrieving the +[official Docker image for Jenkins](https://registry.hub.docker.com/_/jenkins/), +customizing it to suit your needs, and then hosting it on your private instance +of DHE located inside your enterprise’s firewalled environment. Your developers +will then be able to retrieve the custom Jenkins image in order to use it to +build CI/CD infrastructure for their projects, no matter the platform they’re +working from, be it a laptop, a VM, or a cloud provider. + +The guide will walk you through the following steps: + +1. Pulling the official Jenkins image from the public Docker Hub +2. Customizing the Jenkins image to suit your needs +3. Pushing the customized image to DHE +4. Pulling the customized image from DHE +4. Launching a container from the custom image +5. Using the new Jenkins container + +You should be able to complete this guide in about thirty minutes. + +> **Note:** This guide assumes you have installed a working instance of DHE +> reachable at dhe.yourdomain.com. If you need help installing and configuring +> DHE, please consult the +[installation instructions](./install.md). + + +## Pulling the official Jenkins image + +> **Note:** This guide assumes you are familiar with basic Docker concepts such +> as images, containers, and registries. If you need to learn more about Docker +> fundamentals, please consult the +> [Docker user guide](http://docs.docker.com/userguide/). + +First, you will retrieve a copy of the official Jenkins image from the Docker Hub. From the CLI of a machine running the Docker Engine on your network, use +the +[`docker pull`](https://docs.docker.com/reference/commandline/cli/#pull) +command to pull the public Jenkins image. + + $ docker pull jenkins + +> **Note:** This guide assumes you can run Docker commands from a machine where +> you are a member of the `docker` group, or have root privileges. Otherwise, you may +> need to add `sudo` to the example commands below. + +Docker will start the process of pulling the image from the Hub. Once it has completed, the Jenkins image should be visible in the output of a [`docker images`](https://docs.docker.com/reference/commandline/cli/#images) command: + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + jenkins latest 1a7cc22b0ee9 6 days ago 662 MB + +> **Note:** Because the `pull` command did not specify any tags, it will pull +> the latest version of the public Jenkins image. If your enterprise environment +> requires you to use a specific version, add the tag for the version you need +> (e.g., `jenkins:1.565`). + +## Customizing the Jenkins image + +Now that you have a local copy of the Jenkins image, you’ll customize it so that +the containers it builds will integrate with your infrastructure. To do this, +you’ll create a custom Docker image that adds a Jenkins plugin that provides +fine grained user management. You’ll also configure Jenkins to be more secure by +disabling HTTP access and forcing it to use HTTPS. +You’ll do this by using a `Dockerfile` and the `docker build` command. + +> **Note:** These are obviously just a couple of examples of the many ways you +> can modify and configure Jenkins. Feel free to add or substitute whatever +> customization is necessary to run Jenkins in your environment. + +### Creating a `build` context + +In order to add the new plugin and configure HTTPS access to the custom Jenkins +image, you need to: + +1. Create text file that defines the new plugin +2. Create copies of the private key and certificate + +All of the above files need to be in the same directory as the Dockerfile you +will create in the next step. + +1. Create a build directory called `build`, and change to that new directory: + + $ mkdir build && cd build + +In this directory, create a new file called `plugins` and add the following +line: + + role-strategy:2.2.0 + +(The plugin version used above was the latest version at the time of writing.) + +2. You will also need to make copies of the server’s private key and certificate. Give the copies the following names — `https.key` and `https.pem`. + +> **Note:** Because creating new keys varies widely by platform and +> implementation, this guide won’t cover key generation. We assume you have +> access to existing keys. If you don’t have access, or can’t generate keys +> yourself, feel free to skip the steps involving them and HTTPS config. The +> guide will still walk you through building a custom Jenkins image and pushing +> and pulling that image using DHE. + +### Creating a Dockerfile + +In the same directory as the `plugins` file and the private key and certificate, +create a new [`Dockerfile`](https://docs.docker.com/reference/builder/) with the +following contents: + + FROM jenkins + + #New plugins must be placed in the plugins file + COPY plugins /usr/share/jenkins/plugins + + #The plugins.sh script will install new plugins + RUN /usr/local/bin/plugins.sh /usr/share/jenkins/plugins + + #Copy private key and cert to image + COPY https.pem /var/lib/jenkins/cert + COPY https.key /var/lib/jenkins/pk + + #Configure HTTP off and HTTPS on, using port 1973 + ENV JENKINS_OPTS --httpPort=-1 --httpsPort=1973 --httpsCertificate=/var/lib/jenkins/cert --httpsPrivateKey=/var/lib/jenkins/pk + +The first `COPY` instruction in the above will copy the `plugin` file created +earlier into the `/usr/share/jenkins` directory within the custom image you are +defining with the `Dockerfile`. + +The `RUN` instruction will execute the `/usr/local/bin/plugins.sh` script with +the newly copied `plugins` file, which will install the listed plugin. + +The next two `COPY` instructions copy the server’s private key and certificate +into the required directories within the new image. + +The `ENV` instruction creates an environment variable called `JENKINS_OPT` in +the image you are about to create. This environment variable will be present in +any containers launched form the image and contains the required settings to +tell Jenkins to disable HTTP and operate over HTTPS. + +> **Note:** You can specify any valid port number as part of the `JENKINS_OPT` +> environment variable declared above. The value `1973` used in the example is +> arbitrary. + +The `Dockerfile`, the `plugins` file, as well as the private key and +certificate, must all be in the same directory because the `docker build` +command uses the directory that contains the `Dockerfile` as its “build +context”. Only files contained within that “build context” will be included in +the image being built. + +### Building your custom image + +Now that the `Dockerfile`, the `plugins` file, and the files required for HTTPS +operation are created in your current working directory, you can build your +custom image using the +[`docker build` command](https://docs.docker.com/reference/commandline/cli/#build): + + docker build -t dhe.yourdomain.com/ci-infrastructure/jnkns-img . + +> **Note:** Don’t miss the period (`.`) at the end of the command above. This +> tells the `docker build` command to use the current working directory as the +> "build context". + +This command will build a new Docker image called `jnkns-img` which is based on +the public Jenkins image you pulled earlier, but contains all of your +customization. + +Please note the use of the `-t` flag in the `docker build` command above. The +`-t` flag lets you tag an image so it can be pushed to a custom repository. In +the example above, the new image is tagged so it can be pushed to the +`ci-infrastructure` Repository within the `dhe.yourdomain.com` registry (your +local DHE instance). This will be important when you need to `push` the +customized image to DHE later. + +A `docker images` command will now show the custom image alongside the Jenkins +image pulled earlier: + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + dhe.yourdomain.com/ci-infrastructure/jnkns-img latest fc0ab3008d40 2 minutes ago 674.5 MB + jenkins latest 1a7cc22b0ee9 6 days ago 662 MB + +## Pushing to Docker Hub Enterprise + +Now that you’ve create the custom image, it can be pushed to DHE using the +[`docker push`command](https://docs.docker.com/reference/commandline/cli/#push): + + $ docker push dhe.yourdomain.com/ci-infrastructure/jnkns-img + 511136ea3c5a: Image successfully pushed + 848d84b4b2ab: Image successfully pushed + 71d9d77ae89e: Image already exists + + 492ed3875e3e: Image successfully pushed + fc0ab3008d40: Image successfully pushed + +You can view the traffic throughput while the custom image is being pushed from +the `System Health` tab in DHE: + +![DHE console push throughput](../assets/console-push.png) + +Once the image is successfully pushed, it can be downloaded, or pulled, by any +Docker host that has access to DHE. + +## Pulling from Docker Hub Enterprise +To pull the `jnkns-img` image from DHE, run the +[`docker pull`](https://docs.docker.com/reference/commandline/cli/#pull) +command from any Docker Host that has access to your DHE instance: + + $ docker pull dhe.yourdomain.com/ci-infrastructure/jnkns-img + latest: Pulling from dhe.yourdomain.com/ci-infrastructure/jnkns-img + 511136ea3c5a: Pull complete + 848d84b4b2ab: Pull complete + 71d9d77ae89e: Pull complete + + 492ed3875e3e: Pull complete + fc0ab3008d40: Pull complete + dhe.yourdomain.com/ci-infrastructure/jnkns-img:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security. + Status: Downloaded newer image for dhe.yourdomain.com/ci-infrastructure/jnkns-img:latest + +You can view the traffic throughput while the custom image is being pulled from +the `System Health` tab in DHE: + +![DHE console pull throughput](../assets/console-pull.png) + +Now that the `jnkns-img` image has been pulled locally from DHE, you can view it +in the output of the `docker images` command: + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + dhe.yourdomain.com/ci-infrastructure/jnkns-img latest fc0ab3008d40 8 minutes ago 674.5 MB + +## Launching a custom Jenkins container + +Now that you’ve successfully pulled the customized Jenkins image from DHE, you +can create a container from it with the +[`docker run` command](https://docs.docker.com/reference/commandline/cli/#run): + + + $ docker run -p 1973:1973 --name jenkins01 dhe.yourdomain.com/ci-infrastructure/jnkns-img + /usr/share/jenkins/ref/init.groovy.d/tcp-slave-angent-port.groovy + /usr/share/jenkins/ref/init.groovy.d/tcp-slave-angent-port.groovy -> init.groovy.d/tcp-slave-angent-port.groovy + copy init.groovy.d/tcp-slave-angent-port.groovy to JENKINS_HOME + /usr/share/jenkins/ref/plugins/role-strategy.hpi + /usr/share/jenkins/ref/plugins/role-strategy.hpi -> plugins/role-strategy.hpi + copy plugins/role-strategy.hpi to JENKINS_HOME + /usr/share/jenkins/ref/plugins/dockerhub.hpi + /usr/share/jenkins/ref/plugins/dockerhub.hpi -> plugins/dockerhub.hpi + copy plugins/dockerhub.hpi to JENKINS_HOME + + INFO: Jenkins is fully up and running + +> **Note:** The `docker run` command above maps port 1973 in the container +> through to port 1973 on the host. This is the HTTPS port you specified in the +> Dockerfile earlier. If you specified a different HTTPS port in your +> Dockerfile, you will need to substitute this with the correct port numbers for +> your environment. + +You can view the newly launched a container, called `jenkins01`, using the +[`docker ps` command](https://docs.docker.com/reference/commandline/cli/#ps): + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS ...PORTS NAMES + 2e5d2f068504 dhe.yourdomain.com/ci-infrastructure/jnkns-img:latest "/usr/local/bin/jenk About a minute ago Up About a minute 50000/tcp, 0.0.0.0:1973->1973/tcp jenkins01 + + +## Accessing the new Jenkins container + +The previous `docker run` command mapped port `1973` on the container to port +`1973` on the Docker host, so the Jenkins Web UI can be accessed at +`https://:1973` (Don’t forget the `s` at the end of `https`.) + +> **Note:** If you are using a self-signed certificate, you may get a security +> warning from your browser telling you that the certificate is self-signed and +> not trusted. You may wish to add the certificate to the trusted store in order +> to prevent further warnings in the future. + +![Jenkins landing page](../assets/jenkins-ui.png) + +From within the Jenkins Web UI, navigate to `Manage Jenkins` (on the left-hand +pane) > `Manage Plugins` > `Installed`. The `Role-based Authorization Strategy` +plugin should be present with the `Uninstall` button available to the right. + +![Jenkins plugin manager](../assets/jenkins-plugins.png) + +In another browser session, try to access Jenkins via the default HTTP port 8080 +— `http://:8080`. This should result in a “connection timeout,” +showing that Jenkins is not available on its default port 8080 over HTTP. + +This demonstration shows your Jenkins image has been configured correctly for +HTTPS access, your new plugin was added and is ready for use, and HTTP access +has been disabled. At this point, any member of your team can use `docker pull` +to access the image from your DHE instance, allowing them to access a +configured, secured Jenkins instance that can run on any infrastructure. + +## Next Steps + +For more information on using DHE, take a look at the +[User's Guide](./userguide.md). diff --git a/docs/sources/docker-hub-enterprise/support.md b/docs/sources/docker-hub-enterprise/support.md new file mode 100644 index 0000000000000..ed60748a3a067 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/support.md @@ -0,0 +1,14 @@ +page_title: Docker Hub Enterprise: Support +page_description: Commercial Support +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry, support + +# Commercial Support + +Purchasing a DHE License or Commercial Support subscription means your questions +and issues about DHE will receive prioritized support. +You can file a ticket through [email](mailto:support@docker.com) from your +company email address, or visit our [support site](https://support.docker.com). +In either case, you'll need to verify your email address, and then you can +communicate with the support team either by email or web interface. + +**The availability of support depends on your [support subscription](https://www.docker.com/enterprise/support/)** diff --git a/docs/sources/docker-hub-enterprise/usage.md b/docs/sources/docker-hub-enterprise/usage.md deleted file mode 100644 index 252223ef7038c..0000000000000 --- a/docs/sources/docker-hub-enterprise/usage.md +++ /dev/null @@ -1,9 +0,0 @@ -page_title: Using Docker Hub Enterprise -page_description: Docker Hub Enterprise -page_keywords: docker hub enterprise - -# Docker Hub Enterprise - -Documenation coming soon. - - diff --git a/docs/sources/docker-hub-enterprise/userguide.md b/docs/sources/docker-hub-enterprise/userguide.md new file mode 100644 index 0000000000000..6d329722de8b6 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/userguide.md @@ -0,0 +1,130 @@ +page_title: Docker Hub Enterprise: User guide +page_description: Documentation describing basic use of Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, hub, enterprise + + +# Docker Hub Enterprise User's Guide + +This guide covers tasks and functions a user of Docker Hub Enterprise (DHE) will +need to know about, such as pushing or pulling images, etc. For tasks DHE +administrators need to accomplish, such as configuring or monitoring DHE, please +visit the [Administrator's Guide](./adminguide.md). + +## Using DHE to push and pull images + +The primary use case for DHE users is to push and pull images to and from the +DHE image storage service. The following instructions describe these procedures. + +> **Note**: If your DHE instance has authentication enabled, you will need to +>use your command line to `docker login ` (e.g., `docker login +> dhe.yourdomain.com`). +> +> Failures due to unauthenticated `docker push` and `docker pull` commands will +> look like : +> +> $ docker pull dhe.yourdomain.com/hello-world +> Pulling repository dhe.yourdomain.com/hello-world +> FATA[0001] Error: image hello-world:latest not found +> +> $ docker push dhe.yourdomain.com/hello-world +> The push refers to a repository [dhe.yourdomain.com/hello-world] (len: 1) +> e45a5af57b00: Image push failed +> FATA[0001] Error pushing to registry: token auth attempt for registry https://dhe.yourdomain.com/v2/: https://> dhe.yourdomain.com/auth/v2/token/?scope=repository%3Ahello-world%3Apull%2Cpush&service=dhe.yourdomain.com > request failed with status: 401 Unauthorized + + +1. Pull the `hello-world` official image from the Docker Hub. By default, if +Docker can't find an image locally, it will attempt to pull the image from the +Docker Hub. + + `$ docker pull hello-world` + +2. List your available images. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + hello-world latest e45a5af57b00 3 months ago 910 B + + Your list should include the `hello-world` image from the earlier run. + +3. Re-tag the `hello-world` image so that it refers to your DHE server. + + `$ docker tag hello-world:latest dhe.yourdomain.com/demouser/hello-mine:latest` + + The command labels a `hello-world:latest` image using a new tag in the + `[REGISTRYHOST/][USERNAME/]NAME[:TAG]` format. The `REGISTRYHOST` in this + case is the DHE server, `dhe.yourdomain.com`, and the `USERNAME` is + `demouser`. + +4. List your new image. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + hello-world latest e45a5af57b00 3 months ago 910 B + dhe.yourdomain.com/demouser/hello-mine latest e45a5af57b00 3 months ago 910 B + + You should see your new image label in the listing, with the same `IMAGE ID` + as the Official image. + +5. Push this new image to your DHE server. + + `$ docker push dhe.yourdomain.com/demouser/hello-mine:latest` + +6. Set up a test of DHE by removing all images from your local environment: + + `$ docker rmi -f $(docker images -q -a)` + + This command is for illustrative purposes only: removing the image forces + any subsequent `run` to pull from a remote registry (such as DHE) rather + than from a local cache. If you run `docker images` after this you should + not see any instance of `hello-world` or `hello-mine` in your images list. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + +7. Try running `hello-mine`. + + $ docker run hello-mine + Unable to find image 'hello-mine:latest' locally + Pulling repository hello-mine + FATA[0007] Error: image library/hello-mine:latest not found + + The `run` command fails because your new image doesn't exist on the Docker Hub. + +8. Run `hello-mine` again, this time pointing it to pull from DHE: + + $ docker run dhe.yourdomain.com/demouser/hello-mine + latest: Pulling from dhe.yourdomain.com/demouser/hello-mine + 511136ea3c5a: Pull complete + 31cbccb51277: Pull complete + e45a5af57b00: Already exists + Digest: sha256:45f0de377f861694517a1440c74aa32eecc3295ea803261d62f950b1b757bed1 + Status: Downloaded newer image for dhe.yourdomain.com/demouser/hello-mine:latest + + If you run `docker images` after this you'll see a `hello-mine` image. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + dhe.yourdomain.com/demouser/hello-mine latest e45a5af57b00 3 months ago 910 B + +> **Note**: If the Docker daemon on which you are running `docker push` doesn't +> have the right certificates set up, you will get an error similar to: +> +> $ docker push dhe.yourdomain.com/demouser/hello-world +> FATA[0000] Error response from daemon: v1 ping attempt failed with error: Get https://dhe.yourdomain.com/v1/_ping: x509: certificate signed by unknown authority. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry dhe.yourdomain.com` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/dhe.yourdomain.com/ca.crt + +9. You have now successfully created a custom image, `hello-mine`, tagged it, + and pushed it to the image storage provided by your DHE instance. You then + pulled that image back down from DHE and onto your machine, where you can + use it to create a container containing the "Hello World" application.. + +## Next Steps + +For information on administering DHE, take a look at the [Administrator's Guide](./adminguide.md). + + + From 4377ebd6a758278c1766006c7eb8b777fa175719 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Fri, 6 Mar 2015 12:53:06 +1100 Subject: [PATCH 277/332] *: expose getResourcePath and getRootResourcePath wrappers Due to the importance of path safety, the internal sanitisation wrappers for volumes and containers should be exposed so other parts of Docker can benefit from proper path sanitisation. Signed-off-by: Aleksa Sarai (github: cyphar) --- daemon/container.go | 47 ++++++++++++++++++++++++++++++++++----------- daemon/volumes.go | 10 +++++----- volumes/volume.go | 34 +++++++++++++++++++++++++++----- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index bdfcbf4477863..8eb35c9f52392 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -211,12 +211,37 @@ func (container *Container) LogEvent(action string) { ) } -func (container *Container) getResourcePath(path string) (string, error) { +// Evaluates `path` in the scope of the container's basefs, with proper path +// sanitisation. Symlinks are all scoped to the basefs of the container, as +// though the container's basefs was `/`. +// +// The basefs of a container is the host-facing path which is bind-mounted as +// `/` inside the container. This method is essentially used to access a +// particular path inside the container as though you were a process in that +// container. +// +// NOTE: The returned path is *only* safely scoped inside the container's basefs +// if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (container *Container) GetResourcePath(path string) (string, error) { cleanPath := filepath.Join("/", path) return symlink.FollowSymlinkInScope(filepath.Join(container.basefs, cleanPath), container.basefs) } -func (container *Container) getRootResourcePath(path string) (string, error) { +// Evaluates `path` in the scope of the container's root, with proper path +// sanitisation. Symlinks are all scoped to the root of the container, as +// though the container's root was `/`. +// +// The root of a container is the host-facing configuration metadata directory. +// Only use this method to safely access the container's `container.json` or +// other metadata files. If in doubt, use container.GetResourcePath. +// +// NOTE: The returned path is *only* safely scoped inside the container's root +// if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (container *Container) GetRootResourcePath(path string) (string, error) { cleanPath := filepath.Join("/", path) return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root) } @@ -515,7 +540,7 @@ func (streamConfig *StreamConfig) StderrLogPipe() io.ReadCloser { } func (container *Container) buildHostnameFile() error { - hostnamePath, err := container.getRootResourcePath("hostname") + hostnamePath, err := container.GetRootResourcePath("hostname") if err != nil { return err } @@ -529,7 +554,7 @@ func (container *Container) buildHostnameFile() error { func (container *Container) buildHostsFiles(IP string) error { - hostsPath, err := container.getRootResourcePath("hosts") + hostsPath, err := container.GetRootResourcePath("hosts") if err != nil { return err } @@ -895,7 +920,7 @@ func (container *Container) Unmount() error { } func (container *Container) logPath(name string) (string, error) { - return container.getRootResourcePath(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) { @@ -907,11 +932,11 @@ func (container *Container) ReadLog(name string) (io.Reader, error) { } func (container *Container) hostConfigPath() (string, error) { - return container.getRootResourcePath("hostconfig.json") + return container.GetRootResourcePath("hostconfig.json") } func (container *Container) jsonPath() (string, error) { - return container.getRootResourcePath("config.json") + return container.GetRootResourcePath("config.json") } // This method must be exported to be used from the lxc template @@ -981,7 +1006,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { } }() - basePath, err := container.getResourcePath(resource) + basePath, err := container.GetResourcePath(resource) if err != nil { return nil, err } @@ -1083,7 +1108,7 @@ func (container *Container) setupContainerDns() error { if err != nil { return err } - container.ResolvConfPath, err = container.getRootResourcePath("resolv.conf") + container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") if err != nil { return err } @@ -1244,7 +1269,7 @@ func (container *Container) initializeNetworking() error { return err } - hostsPath, err := container.getRootResourcePath("hosts") + hostsPath, err := container.GetRootResourcePath("hosts") if err != nil { return err } @@ -1375,7 +1400,7 @@ func (container *Container) setupWorkingDirectory() error { if container.Config.WorkingDir != "" { container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) - pth, err := container.getResourcePath(container.Config.WorkingDir) + pth, err := container.GetResourcePath(container.Config.WorkingDir) if err != nil { return err } diff --git a/daemon/volumes.go b/daemon/volumes.go index 4d15023ba717c..79d83504a236c 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -47,7 +47,7 @@ func (container *Container) createVolumes() error { continue } - realPath, err := container.getResourcePath(path) + realPath, err := container.GetResourcePath(path) if err != nil { return err } @@ -336,7 +336,7 @@ func (container *Container) mountVolumes() error { return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest) } - destPath, err := container.getResourcePath(dest) + destPath, err := container.GetResourcePath(dest) if err != nil { return err } @@ -347,7 +347,7 @@ func (container *Container) mountVolumes() error { } for _, mnt := range container.specialMounts() { - destPath, err := container.getResourcePath(mnt.Destination) + destPath, err := container.GetResourcePath(mnt.Destination) if err != nil { return err } @@ -360,7 +360,7 @@ func (container *Container) mountVolumes() error { func (container *Container) unmountVolumes() { for dest := range container.Volumes { - destPath, err := container.getResourcePath(dest) + destPath, err := container.GetResourcePath(dest) if err != nil { logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) continue @@ -372,7 +372,7 @@ func (container *Container) unmountVolumes() { } for _, mnt := range container.specialMounts() { - destPath, err := container.getResourcePath(mnt.Destination) + destPath, err := container.GetResourcePath(mnt.Destination) if err != nil { logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) continue diff --git a/volumes/volume.go b/volumes/volume.go index 87aa4ad25afaa..283bc9bca8edc 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -114,14 +114,38 @@ func (v *Volume) FromDisk() error { } func (v *Volume) jsonPath() (string, error) { - return v.getRootResourcePath("config.json") + return v.GetRootResourcePath("config.json") } -func (v *Volume) getRootResourcePath(path string) (string, error) { + +// Evalutes `path` in the scope of the volume's root path, with proper path +// sanitisation. Symlinks are all scoped to the root of the volume, as +// though the volume's root was `/`. +// +// The volume's root path is the host-facing path of the root of the volume's +// mountpoint inside a container. +// +// NOTE: The returned path is *only* safely scoped inside the volume's root +// if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (v *Volume) GetResourcePath(path string) (string, error) { cleanPath := filepath.Join("/", path) - return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath) + return symlink.FollowSymlinkInScope(filepath.Join(v.Path, cleanPath), v.Path) } -func (v *Volume) getResourcePath(path string) (string, error) { +// Evalutes `path` in the scope of the volume's config path, with proper path +// sanitisation. Symlinks are all scoped to the root of the config path, as +// though the config path was `/`. +// +// The config path of a volume is not exposed to the container and is just used +// to store volume configuration options and other internal information. If in +// doubt, you probably want to just use v.GetResourcePath. +// +// NOTE: The returned path is *only* safely scoped inside the volume's config +// path if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (v *Volume) GetRootResourcePath(path string) (string, error) { cleanPath := filepath.Join("/", path) - return symlink.FollowSymlinkInScope(filepath.Join(v.Path, cleanPath), v.Path) + return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath) } From b7c3c0cb6988c9a7864649bfe458217336c5fc51 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Fri, 20 Mar 2015 19:59:15 +1100 Subject: [PATCH 278/332] *: switch to Get(Root)?ResourcePath where appropriate Several parts of the codebase didn't use the correct path sanitisation wrappers. Now that the wrappers have been exposed, use those. Signed-off-by: Aleksa Sarai (github: cyphar) --- builder/internals.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/builder/internals.go b/builder/internals.go index ba7d45bcb18cc..2fe7ca8badf70 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -32,7 +32,6 @@ import ( "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" @@ -646,14 +645,12 @@ func (b *Builder) addContext(container *daemon.Container, orig, dest string, dec err error destExists = true origPath = path.Join(b.contextPath, orig) - destPath = path.Join(container.RootfsPath(), dest) + destPath string ) - if destPath != container.RootfsPath() { - destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath()) - if err != nil { - return err - } + destPath, err = container.GetResourcePath(dest) + if err != nil { + return err } // Preserve the trailing '/' From c21d408ad24cf8e2b5bd761d562fae7e3ae1bc54 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 24 Apr 2015 17:03:33 +0200 Subject: [PATCH 279/332] Add coverage on pkg/archive Add tests on: - changes.go - archive.go - wrap.go Should fix #11603 as the coverage is now 81.2% on the ``pkg/archive`` package. There is still room for improvement though :). Signed-off-by: Vincent Demeester --- pkg/archive/archive_test.go | 309 ++++++++++++++++++++++++++++++++++++ pkg/archive/changes_test.go | 152 ++++++++++++++++++ pkg/archive/wrap_test.go | 98 ++++++++++++ 3 files changed, 559 insertions(+) create mode 100644 pkg/archive/wrap_test.go diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index dabb0d504bc13..ae9b5a8cd2520 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -207,6 +207,315 @@ func TestCmdStreamGood(t *testing.T) { } } +func TestUntarPathWithInvalidDest(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempFolder) + invalidDestFolder := path.Join(tempFolder, "invalidDest") + // Create a src file + srcFile := path.Join(tempFolder, "src") + _, err = os.Create(srcFile) + if err != nil { + t.Fatalf("Fail to create the source file") + } + err = UntarPath(srcFile, invalidDestFolder) + if err == nil { + t.Fatalf("UntarPath with invalid destination path should throw an error.") + } +} + +func TestUntarPathWithInvalidSrc(t *testing.T) { + dest, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + defer os.RemoveAll(dest) + err = UntarPath("/invalid/path", dest) + if err == nil { + t.Fatalf("UntarPath with invalid src path should throw an error.") + } +} + +func TestUntarPath(t *testing.T) { + tmpFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpFolder) + srcFile := path.Join(tmpFolder, "src") + tarFile := path.Join(tmpFolder, "src.tar") + os.Create(path.Join(tmpFolder, "src")) + cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile) + _, err = cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + destFolder := path.Join(tmpFolder, "dest") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatalf("Fail to create the destination file") + } + err = UntarPath(tarFile, destFolder) + if err != nil { + t.Fatalf("UntarPath shouldn't throw an error, %s.", err) + } + expectedFile := path.Join(destFolder, srcFile) + _, err = os.Stat(expectedFile) + if err != nil { + t.Fatalf("Destination folder should contain the source file but did not.") + } +} + +// Do the same test as above but with the destination as file, it should fail +func TestUntarPathWithDestinationFile(t *testing.T) { + tmpFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpFolder) + srcFile := path.Join(tmpFolder, "src") + tarFile := path.Join(tmpFolder, "src.tar") + os.Create(path.Join(tmpFolder, "src")) + cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile) + _, err = cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + destFile := path.Join(tmpFolder, "dest") + _, err = os.Create(destFile) + if err != nil { + t.Fatalf("Fail to create the destination file") + } + err = UntarPath(tarFile, destFile) + if err == nil { + t.Fatalf("UntarPath should throw an error if the destination if a file") + } +} + +// Do the same test as above but with the destination folder already exists +// and the destination file is a directory +// It's working, see https://github.com/docker/docker/issues/10040 +func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) { + tmpFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpFolder) + srcFile := path.Join(tmpFolder, "src") + tarFile := path.Join(tmpFolder, "src.tar") + os.Create(srcFile) + cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile) + _, err = cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + destFolder := path.Join(tmpFolder, "dest") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatalf("Fail to create the destination folder") + } + // Let's create a folder that will has the same path as the extracted file (from tar) + destSrcFileAsFolder := path.Join(destFolder, srcFile) + err = os.MkdirAll(destSrcFileAsFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = UntarPath(tarFile, destFolder) + if err != nil { + t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder") + } +} + +func TestCopyWithTarInvalidSrc(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(nil) + } + destFolder := path.Join(tempFolder, "dest") + invalidSrc := path.Join(tempFolder, "doesnotexists") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = CopyWithTar(invalidSrc, destFolder) + if err == nil { + t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") + } +} + +func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(nil) + } + srcFolder := path.Join(tempFolder, "src") + inexistentDestFolder := path.Join(tempFolder, "doesnotexists") + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = CopyWithTar(srcFolder, inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") + } + _, err = os.Stat(inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder should create it.") + } +} + +// Test CopyWithTar with a file as src +func TestCopyWithTarSrcFile(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + srcFolder := path.Join(folder, "src") + src := path.Join(folder, path.Join("src", "src")) + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(src, []byte("content"), 0777) + err = CopyWithTar(src, dest) + if err != nil { + t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) + } + _, err = os.Stat(dest) + // FIXME Check the content + if err != nil { + t.Fatalf("Destination file should be the same as the source.") + } +} + +// Test CopyWithTar with a folder as src +func TestCopyWithTarSrcFolder(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + src := path.Join(folder, path.Join("src", "folder")) + err = os.MkdirAll(src, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(path.Join(src, "file"), []byte("content"), 0777) + err = CopyWithTar(src, dest) + if err != nil { + t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) + } + _, err = os.Stat(dest) + // FIXME Check the content (the file inside) + if err != nil { + t.Fatalf("Destination folder should contain the source file but did not.") + } +} + +func TestCopyFileWithTarInvalidSrc(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempFolder) + destFolder := path.Join(tempFolder, "dest") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatal(err) + } + invalidFile := path.Join(tempFolder, "doesnotexists") + err = CopyFileWithTar(invalidFile, destFolder) + if err == nil { + t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") + } +} + +func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(nil) + } + defer os.RemoveAll(tempFolder) + srcFile := path.Join(tempFolder, "src") + inexistentDestFolder := path.Join(tempFolder, "doesnotexists") + _, err = os.Create(srcFile) + if err != nil { + t.Fatal(err) + } + err = CopyFileWithTar(srcFile, inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") + } + _, err = os.Stat(inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder should create it.") + } + // FIXME Test the src file and content +} + +func TestCopyFileWithTarSrcFolder(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + src := path.Join(folder, "srcfolder") + err = os.MkdirAll(src, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + err = CopyFileWithTar(src, dest) + if err == nil { + t.Fatalf("CopyFileWithTar should throw an error with a folder.") + } +} + +func TestCopyFileWithTarSrcFile(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + srcFolder := path.Join(folder, "src") + src := path.Join(folder, path.Join("src", "src")) + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(src, []byte("content"), 0777) + err = CopyWithTar(src, dest+"/") + if err != nil { + t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err) + } + _, err = os.Stat(dest) + if err != nil { + t.Fatalf("Destination folder should contain the source file but did not.") + } +} + func TestTarFiles(t *testing.T) { // try without hardlinks if err := checkNoChanges(1000, false); err != nil { diff --git a/pkg/archive/changes_test.go b/pkg/archive/changes_test.go index 53ec575b67ab3..290b2dd40222d 100644 --- a/pkg/archive/changes_test.go +++ b/pkg/archive/changes_test.go @@ -6,6 +6,7 @@ import ( "os/exec" "path" "sort" + "syscall" "testing" "time" ) @@ -91,17 +92,130 @@ func createSampleDir(t *testing.T, root string) { } } +func TestChangeString(t *testing.T) { + modifiyChange := Change{"change", ChangeModify} + toString := modifiyChange.String() + if toString != "C change" { + t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString) + } + addChange := Change{"change", ChangeAdd} + toString = addChange.String() + if toString != "A change" { + t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString) + } + deleteChange := Change{"change", ChangeDelete} + toString = deleteChange.String() + if toString != "D change" { + t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString) + } +} + +func TestChangesWithNoChanges(t *testing.T) { + rwLayer, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rwLayer) + layer, err := ioutil.TempDir("", "docker-changes-test-layer") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(layer) + createSampleDir(t, layer) + changes, err := Changes([]string{layer}, rwLayer) + if err != nil { + t.Fatal(err) + } + if len(changes) != 0 { + t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes)) + } +} + +func TestChangesWithChanges(t *testing.T) { + rwLayer, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rwLayer) + // Create a folder + dir1 := path.Join(rwLayer, "dir1") + os.MkdirAll(dir1, 0740) + deletedFile := path.Join(dir1, ".wh.file1-2") + ioutil.WriteFile(deletedFile, []byte{}, 0600) + modifiedFile := path.Join(dir1, "file1-1") + ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444) + // Let's add a subfolder for a newFile + subfolder := path.Join(dir1, "subfolder") + os.MkdirAll(subfolder, 0740) + newFile := path.Join(subfolder, "newFile") + ioutil.WriteFile(newFile, []byte{}, 0740) + // Let's create folders that with have the role of layers with the same data + layer, err := ioutil.TempDir("", "docker-changes-test-layer") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(layer) + createSampleDir(t, layer) + os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740) + + // Let's modify modtime for dir1 to be sure it's the same for the two layer (to not having false positive) + fi, err := os.Stat(dir1) + if err != nil { + return + } + mtime := fi.ModTime() + stat := fi.Sys().(*syscall.Stat_t) + atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + + layerDir1 := path.Join(layer, "dir1") + os.Chtimes(layerDir1, atime, mtime) + + changes, err := Changes([]string{layer}, rwLayer) + if err != nil { + t.Fatal(err) + } + + sort.Sort(changesByPath(changes)) + + expectedChanges := []Change{ + {"/dir1/file1-1", ChangeModify}, + {"/dir1/file1-2", ChangeDelete}, + {"/dir1/subfolder", ChangeModify}, + {"/dir1/subfolder/newFile", ChangeAdd}, + } + + for i := 0; i < max(len(changes), len(expectedChanges)); i++ { + if i >= len(expectedChanges) { + t.Fatalf("unexpected change %s\n", changes[i].String()) + } + if i >= len(changes) { + t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) + } + if changes[i].Path == expectedChanges[i].Path { + if changes[i] != expectedChanges[i] { + t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) + } + } else if changes[i].Path < expectedChanges[i].Path { + t.Fatalf("unexpected change %s\n", changes[i].String()) + } else { + t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) + } + } +} + // Create an directory, copy it, make sure we report no changes between the two func TestChangesDirsEmpty(t *testing.T) { src, err := ioutil.TempDir("", "docker-changes-test") if err != nil { t.Fatal(err) } + defer os.RemoveAll(src) createSampleDir(t, src) dst := src + "-copy" if err := copyDir(src, dst); err != nil { t.Fatal(err) } + defer os.RemoveAll(dst) changes, err := ChangesDirs(dst, src) if err != nil { t.Fatal(err) @@ -291,3 +405,41 @@ func TestApplyLayer(t *testing.T) { t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2) } } + +func TestChangesSizeWithNoChanges(t *testing.T) { + size := ChangesSize("/tmp", nil) + if size != 0 { + t.Fatalf("ChangesSizes with no changes should be 0, was %d", size) + } +} + +func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) { + changes := []Change{ + {Path: "deletedPath", Kind: ChangeDelete}, + } + size := ChangesSize("/tmp", changes) + if size != 0 { + t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) + } +} + +func TestChangesSize(t *testing.T) { + parentPath, err := ioutil.TempDir("", "docker-changes-test") + defer os.RemoveAll(parentPath) + addition := path.Join(parentPath, "addition") + if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil { + t.Fatal(err) + } + modification := path.Join(parentPath, "modification") + if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil { + t.Fatal(err) + } + changes := []Change{ + {Path: "addition", Kind: ChangeAdd}, + {Path: "modification", Kind: ChangeModify}, + } + size := ChangesSize(parentPath, changes) + if size != 6 { + t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) + } +} diff --git a/pkg/archive/wrap_test.go b/pkg/archive/wrap_test.go new file mode 100644 index 0000000000000..46ab36697a75b --- /dev/null +++ b/pkg/archive/wrap_test.go @@ -0,0 +1,98 @@ +package archive + +import ( + "archive/tar" + "bytes" + "io" + "testing" +) + +func TestGenerateEmptyFile(t *testing.T) { + archive, err := Generate("emptyFile") + if err != nil { + t.Fatal(err) + } + if archive == nil { + t.Fatal("The generated archive should not be nil.") + } + + expectedFiles := [][]string{ + {"emptyFile", ""}, + } + + tr := tar.NewReader(archive) + actualFiles := make([][]string, 0, 10) + i := 0 + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + buf := new(bytes.Buffer) + buf.ReadFrom(tr) + content := buf.String() + actualFiles = append(actualFiles, []string{hdr.Name, content}) + i++ + } + if len(actualFiles) != len(expectedFiles) { + t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles)) + } + for i := 0; i < len(expectedFiles); i++ { + actual := actualFiles[i] + expected := expectedFiles[i] + if actual[0] != expected[0] { + t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0]) + } + if actual[1] != expected[1] { + t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1]) + } + } +} + +func TestGenerateWithContent(t *testing.T) { + archive, err := Generate("file", "content") + if err != nil { + t.Fatal(err) + } + if archive == nil { + t.Fatal("The generated archive should not be nil.") + } + + expectedFiles := [][]string{ + {"file", "content"}, + } + + tr := tar.NewReader(archive) + actualFiles := make([][]string, 0, 10) + i := 0 + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + buf := new(bytes.Buffer) + buf.ReadFrom(tr) + content := buf.String() + actualFiles = append(actualFiles, []string{hdr.Name, content}) + i++ + } + if len(actualFiles) != len(expectedFiles) { + t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles)) + } + for i := 0; i < len(expectedFiles); i++ { + actual := actualFiles[i] + expected := expectedFiles[i] + if actual[0] != expected[0] { + t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0]) + } + if actual[1] != expected[1] { + t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1]) + } + } +} From 36fbf4b86469ca6fe3677d47c9a1976bcdd111e4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 17 Apr 2015 15:30:22 -0700 Subject: [PATCH 280/332] Shallow clone using git to build images. Signed-off-by: David Calavera --- builder/job.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/job.go b/builder/job.go index 115d89a4b9ef7..7ea1ae30b9c19 100644 --- a/builder/job.go +++ b/builder/job.go @@ -114,7 +114,7 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { } defer os.RemoveAll(root) - if output, err := exec.Command("git", "clone", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { + if output, err := exec.Command("git", "clone", "--depth", "1", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { return fmt.Errorf("Error trying to use git: %s (%s)", err, output) } From 9fb7204a41804131c2492f9d50d7451e123a05e5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 21 Apr 2015 10:58:38 -0700 Subject: [PATCH 281/332] Do not try to shallow git history when the protocol doesn't allow it. This only happens with the old git http dumb protocol, but that's what we use in our integration tests. We check the Content-Type header advertised in http requests to make sure the http transport is the git smart transport: See this commit as a reference: https://github.com/git/git/commit/4656bf47fca857df51b5d6f4b7b052192b3b2317 Signed-off-by: David Calavera --- builder/job.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/builder/job.go b/builder/job.go index 7ea1ae30b9c19..8c031a8bd859e 100644 --- a/builder/job.go +++ b/builder/job.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "os/exec" "strings" @@ -114,7 +115,9 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { } defer os.RemoveAll(root) - if output, err := exec.Command("git", "clone", "--depth", "1", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { + clone := cloneArgs(buildConfig.RemoteURL, root) + + if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { return fmt.Errorf("Error trying to use git: %s (%s)", err, output) } @@ -239,3 +242,21 @@ func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (str return img.ID, nil } + +func cloneArgs(remoteURL, root string) []string { + args := []string{"clone", "--recursive"} + shallow := true + + if strings.HasPrefix(remoteURL, "http") { + res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) + if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { + shallow = false + } + } + + if shallow { + args = append(args, "--depth", "1") + } + + return append(args, remoteURL, root) +} From 3117bf3ef562bc43ef007d8afe3815c58aac6e85 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 21 Apr 2015 14:24:29 -0700 Subject: [PATCH 282/332] Test that we set the right arguments for git cloning depending on the transport. Signed-off-by: David Calavera --- builder/job_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 builder/job_test.go diff --git a/builder/job_test.go b/builder/job_test.go new file mode 100644 index 0000000000000..79421a06884b7 --- /dev/null +++ b/builder/job_test.go @@ -0,0 +1,56 @@ +package builder + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" +) + +func TestCloneArgsSmartHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, _ := url.Parse(server.URL) + + serverURL.Path = "/repo.git" + gitURL := serverURL.String() + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query().Get("service") + w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q)) + }) + + args := cloneArgs(gitURL, "/tmp") + exp := []string{"clone", "--recursive", "--depth", "1", gitURL, "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} + +func TestCloneArgsDumbHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, _ := url.Parse(server.URL) + + serverURL.Path = "/repo.git" + gitURL := serverURL.String() + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + }) + + args := cloneArgs(gitURL, "/tmp") + exp := []string{"clone", "--recursive", gitURL, "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} +func TestCloneArgsGit(t *testing.T) { + args := cloneArgs("git://github.com/docker/docker", "/tmp") + exp := []string{"clone", "--recursive", "--depth", "1", "git://github.com/docker/docker", "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} From 8bd5a95e1e39541dfc5dc635c5c8e7604fe10028 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 23 Apr 2015 09:35:19 -0700 Subject: [PATCH 283/332] Document the extra `depth` argument in git contexts. Signed-off-by: David Calavera --- docs/sources/reference/commandline/cli.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index a871162049b0a..26659c8ffa199 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -636,12 +636,13 @@ refer to any of the files in the context. For example, your build can use an [*ADD*](/reference/builder/#add) instruction to reference a file in the context. -The `URL` parameter can specify the location of a Git repository; in this -case, the repository is the context. The Git repository is recursively -cloned with its submodules. The system does a fresh `git clone -recursive` -in a temporary directory on your local host. Then, this clone is sent to -the Docker daemon as the context. Local clones give you the ability to -access private repositories using local user credentials, VPN's, and so forth. +The `URL` parameter can specify the location of a Git repository; +the repository acts as the build context. The system recursively clones the repository +and its submodules using a `git clone --depth 1 --recursive` command. +This command runs in a temporary directory on your local host. +After the command succeeds, the directory is sent to the Docker daemon as the context. +Local clones give you the ability to access private repositories using +local user credentials, VPN's, and so forth. Instead of specifying a context, you can pass a single Dockerfile in the `URL` or pipe the file in via `STDIN`. To pipe a Dockerfile from `STDIN`: From 1cfb307d70558110e8e88a7391ae0e6cf6ebf174 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 23 Apr 2015 10:19:34 -0700 Subject: [PATCH 284/332] Remove duplicated git clone logic. Signed-off-by: David Calavera --- api/client/build.go | 11 +----- builder/job.go | 32 +--------------- utils/git.go | 47 ++++++++++++++++++++++++ builder/job_test.go => utils/git_test.go | 2 +- 4 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 utils/git.go rename builder/job_test.go => utils/git_test.go (98%) diff --git a/api/client/build.go b/api/client/build.go index 63cc63bc9e795..eb39058b64c8e 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -95,20 +95,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } else { root := cmd.Arg(0) if urlutil.IsGitURL(root) { - remoteURL := cmd.Arg(0) - if !urlutil.IsGitTransport(remoteURL) { - remoteURL = "https://" + remoteURL - } - - root, err = ioutil.TempDir("", "docker-build-git") + root, err = utils.GitClone(root) if err != nil { return err } defer os.RemoveAll(root) - - if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil { - return fmt.Errorf("Error trying to use git: %s (%s)", err, output) - } } if _, err := os.Stat(root); err != nil { return err diff --git a/builder/job.go b/builder/job.go index 8c031a8bd859e..7991cba21c678 100644 --- a/builder/job.go +++ b/builder/job.go @@ -5,9 +5,7 @@ import ( "fmt" "io" "io/ioutil" - "net/http" "os" - "os/exec" "strings" "sync" @@ -23,6 +21,7 @@ import ( "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" ) // whitelist of commands allowed for a commit/import @@ -106,21 +105,12 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { if buildConfig.RemoteURL == "" { context = ioutil.NopCloser(buildConfig.Context) } else if urlutil.IsGitURL(buildConfig.RemoteURL) { - if !urlutil.IsGitTransport(buildConfig.RemoteURL) { - buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL - } - root, err := ioutil.TempDir("", "docker-build-git") + root, err := utils.GitClone(buildConfig.RemoteURL) if err != nil { return err } defer os.RemoveAll(root) - clone := cloneArgs(buildConfig.RemoteURL, root) - - if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { - return fmt.Errorf("Error trying to use git: %s (%s)", err, output) - } - c, err := archive.Tar(root, archive.Uncompressed) if err != nil { return err @@ -242,21 +232,3 @@ func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (str return img.ID, nil } - -func cloneArgs(remoteURL, root string) []string { - args := []string{"clone", "--recursive"} - shallow := true - - if strings.HasPrefix(remoteURL, "http") { - res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) - if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { - shallow = false - } - } - - if shallow { - args = append(args, "--depth", "1") - } - - return append(args, remoteURL, root) -} diff --git a/utils/git.go b/utils/git.go new file mode 100644 index 0000000000000..18e002d184210 --- /dev/null +++ b/utils/git.go @@ -0,0 +1,47 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "net/http" + "os/exec" + "strings" + + "github.com/docker/docker/pkg/urlutil" +) + +func GitClone(remoteURL string) (string, error) { + if !urlutil.IsGitTransport(remoteURL) { + remoteURL = "https://" + remoteURL + } + root, err := ioutil.TempDir("", "docker-build-git") + if err != nil { + return "", err + } + + clone := cloneArgs(remoteURL, root) + + if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { + return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output) + } + + return root, nil +} + +func cloneArgs(remoteURL, root string) []string { + args := []string{"clone", "--recursive"} + shallow := true + + if strings.HasPrefix(remoteURL, "http") { + res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) + if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { + shallow = false + } + } + + if shallow { + args = append(args, "--depth", "1") + } + + return append(args, remoteURL, root) +} diff --git a/builder/job_test.go b/utils/git_test.go similarity index 98% rename from builder/job_test.go rename to utils/git_test.go index 79421a06884b7..a82841ae1167e 100644 --- a/builder/job_test.go +++ b/utils/git_test.go @@ -1,4 +1,4 @@ -package builder +package utils import ( "fmt" From a9688cdca5577d6db65d76f38bcbe4c1e6f5994f Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Thu, 23 Apr 2015 14:39:31 -0700 Subject: [PATCH 285/332] Implement teardown removeAllImages Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 1 + integration-cli/docker_api_containers_test.go | 1 - integration-cli/docker_api_images_test.go | 2 - integration-cli/docker_cli_build_test.go | 186 ------------------ integration-cli/docker_cli_by_digest_test.go | 8 - integration-cli/docker_cli_commit_test.go | 16 -- integration-cli/docker_cli_create_test.go | 1 - integration-cli/docker_cli_events_test.go | 3 - .../docker_cli_export_import_test.go | 4 - integration-cli/docker_cli_history_test.go | 2 - integration-cli/docker_cli_images_test.go | 9 - integration-cli/docker_cli_import_test.go | 1 - integration-cli/docker_cli_ps_test.go | 1 - integration-cli/docker_cli_pull_test.go | 5 - integration-cli/docker_cli_push_test.go | 4 - integration-cli/docker_cli_rm_test.go | 1 - integration-cli/docker_cli_run_test.go | 8 - integration-cli/docker_cli_save_load_test.go | 7 - integration-cli/docker_cli_tag_test.go | 10 - integration-cli/docker_utils.go | 49 +++++ 20 files changed, 50 insertions(+), 269 deletions(-) diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 07bb9315966f0..f6dbdb8a92cb3 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -30,6 +30,7 @@ type DockerSuite struct { func (s *DockerSuite) TearDownTest(c *check.C) { deleteAllContainers() + deleteAllImages() s.TimerSuite.TearDownTest(c) } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 24efbb7a27cb1..f14bd91c15e14 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -632,7 +632,6 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { if err := json.Unmarshal(b, &img); err != nil { c.Fatal(err) } - defer deleteImages(img.Id) cmd, err := inspectField(img.Id, "Config.Cmd") if err != nil { diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index a0f029d40fadf..ac3ad55d7eeab 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -30,7 +30,6 @@ func (s *DockerSuite) TestApiImagesFilter(c *check.C) { name := "utest:tag1" name2 := "utest/docker:tag2" name3 := "utest:5000/docker:tag3" - defer deleteImages(name, name2, name3) for _, n := range []string{name, name2, name3} { if out, err := exec.Command(dockerBinary, "tag", "busybox", n).CombinedOutput(); err != nil { c.Fatal(err, out) @@ -74,7 +73,6 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { c.Fatal(err) } id := strings.TrimSpace(out) - defer deleteImages("saveandload") status, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") c.Assert(status, check.Equals, http.StatusOK) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 72d15c177bbff..6d6805aef5480 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -27,7 +27,6 @@ import ( func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) { name := "testbuildjsonemptyrun" - defer deleteImages(name) _, err := buildImage( name, @@ -45,7 +44,6 @@ func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) { func (s *DockerSuite) TestBuildEmptyWhitespace(c *check.C) { name := "testbuildemptywhitespace" - defer deleteImages(name) _, err := buildImage( name, @@ -65,7 +63,6 @@ func (s *DockerSuite) TestBuildEmptyWhitespace(c *check.C) { func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) { name := "testbuildshcmdjsonentrypoint" - defer deleteImages(name) _, err := buildImage( name, @@ -99,7 +96,6 @@ func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM scratch @@ -123,7 +119,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM scratch @@ -153,7 +148,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM scratch @@ -183,7 +177,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -200,7 +193,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) ctx, err := fakeContext(` FROM scratch @@ -236,8 +228,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) - _, err := buildImage(name, ` FROM busybox @@ -305,8 +295,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { name := "testbuildhandleescapes" - defer deleteImages(name) - _, err := buildImage(name, ` FROM scratch @@ -395,8 +383,6 @@ func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) { name := "testbuildonbuildlowercase" name2 := "testbuildonbuildlowercase2" - defer deleteImages(name, name2) - _, err := buildImage(name, ` FROM busybox @@ -427,7 +413,6 @@ func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) { func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) { name := "testbuildenvescapes" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -450,7 +435,6 @@ func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) { func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) { name := "testbuildenvoverwrite" - defer deleteImages(name) _, err := buildImage(name, ` @@ -478,8 +462,6 @@ func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenmaintainerinsourceimage" - defer deleteImages("onbuild") - defer deleteImages(name) createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) @@ -510,8 +492,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check. func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenfrominsourceimage" - defer deleteImages("onbuild") - defer deleteImages(name) createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) @@ -542,8 +522,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenChainedInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenchainedinsourceimage" - defer deleteImages("onbuild") - defer deleteImages(name) createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) @@ -576,9 +554,6 @@ func (s *DockerSuite) TestBuildOnBuildCmdEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" - defer deleteImages(name2) - defer deleteImages(name1) - _, err := buildImage(name1, ` FROM busybox ONBUILD CMD ["hello world"] @@ -611,9 +586,6 @@ func (s *DockerSuite) TestBuildOnBuildEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" - defer deleteImages(name2) - defer deleteImages(name1) - _, err := buildImage(name1, ` FROM busybox ONBUILD ENTRYPOINT ["echo"]`, @@ -642,7 +614,6 @@ ONBUILD ENTRYPOINT ["echo"]`, func (s *DockerSuite) TestBuildCacheADD(c *check.C) { name := "testbuildtwoimageswithadd" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "robots.txt": "hello", "index.html": "world", @@ -677,7 +648,6 @@ func (s *DockerSuite) TestBuildCacheADD(c *check.C) { func (s *DockerSuite) TestBuildLastModified(c *check.C) { name := "testbuildlastmodified" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "file": "hello", @@ -743,7 +713,6 @@ RUN ls -le /file` func (s *DockerSuite) TestBuildSixtySteps(c *check.C) { name := "foobuildsixtysteps" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\n"+strings.Repeat("ADD foo /\n", 60), map[string]string{ "foo": "test1", @@ -760,7 +729,6 @@ func (s *DockerSuite) TestBuildSixtySteps(c *check.C) { func (s *DockerSuite) TestBuildAddSingleFileToRoot(c *check.C) { name := "testaddimg" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -786,7 +754,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte // Issue #3960: "ADD src ." hangs func (s *DockerSuite) TestBuildAddSingleFileToWorkdir(c *check.C) { name := "testaddsinglefiletoworkdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox ADD test_file .`, map[string]string{ @@ -813,7 +780,6 @@ ADD test_file .`, func (s *DockerSuite) TestBuildAddSingleFileToExistDir(c *check.C) { name := "testaddsinglefiletoexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -847,7 +813,6 @@ func (s *DockerSuite) TestBuildCopyAddMultipleFiles(c *check.C) { defer server.Close() name := "testcopymultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -884,7 +849,6 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' func (s *DockerSuite) TestBuildAddMultipleFilesToFile(c *check.C) { name := "testaddmultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD file1.txt file2.txt test `, @@ -906,7 +870,6 @@ func (s *DockerSuite) TestBuildAddMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFile(c *check.C) { name := "testjsonaddmultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD ["file1.txt", "file2.txt", "test"] `, @@ -928,7 +891,6 @@ func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildAddMultipleFilesToFileWild(c *check.C) { name := "testaddmultiplefilestofilewild" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD file*.txt test `, @@ -950,7 +912,6 @@ func (s *DockerSuite) TestBuildAddMultipleFilesToFileWild(c *check.C) { func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFileWild(c *check.C) { name := "testjsonaddmultiplefilestofilewild" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD ["file*.txt", "test"] `, @@ -972,7 +933,6 @@ func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFileWild(c *check.C) { func (s *DockerSuite) TestBuildCopyMultipleFilesToFile(c *check.C) { name := "testcopymultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch COPY file1.txt file2.txt test `, @@ -994,7 +954,6 @@ func (s *DockerSuite) TestBuildCopyMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildJSONCopyMultipleFilesToFile(c *check.C) { name := "testjsoncopymultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch COPY ["file1.txt", "file2.txt", "test"] `, @@ -1016,7 +975,6 @@ func (s *DockerSuite) TestBuildJSONCopyMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildAddFileWithWhitespace(c *check.C) { name := "testaddfilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN mkdir "/test dir" RUN mkdir "/test_dir" @@ -1052,7 +1010,6 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, func (s *DockerSuite) TestBuildCopyFileWithWhitespace(c *check.C) { name := "testcopyfilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN mkdir "/test dir" RUN mkdir "/test_dir" @@ -1088,7 +1045,6 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, func (s *DockerSuite) TestBuildAddMultipleFilesToFileWithWhitespace(c *check.C) { name := "testaddmultiplefilestofilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox ADD [ "test file1", "test file2", "test" ] `, @@ -1110,7 +1066,6 @@ func (s *DockerSuite) TestBuildAddMultipleFilesToFileWithWhitespace(c *check.C) func (s *DockerSuite) TestBuildCopyMultipleFilesToFileWithWhitespace(c *check.C) { name := "testcopymultiplefilestofilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY [ "test file1", "test file2", "test" ] `, @@ -1132,7 +1087,6 @@ func (s *DockerSuite) TestBuildCopyMultipleFilesToFileWithWhitespace(c *check.C) func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { name := "testcopywildcard" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "robots.txt": "hello", "index.html": "world", @@ -1183,7 +1137,6 @@ func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) { name := "testcopywildcardnofind" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY file*.txt /tmp/ `, nil) @@ -1204,7 +1157,6 @@ func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) { func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { name := "testcopywildcardcache" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY file1.txt /tmp/`, map[string]string{ @@ -1238,7 +1190,6 @@ func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { func (s *DockerSuite) TestBuildAddSingleFileToNonExistingDir(c *check.C) { name := "testaddsinglefiletononexistingdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1264,7 +1215,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildAddDirContentToRoot(c *check.C) { name := "testadddircontenttoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1288,7 +1238,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildAddDirContentToExistingDir(c *check.C) { name := "testadddircontenttoexistingdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1314,7 +1263,6 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, func (s *DockerSuite) TestBuildAddWholeDirToRoot(c *check.C) { name := "testaddwholedirtoroot" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1342,7 +1290,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte // Testing #5941 func (s *DockerSuite) TestBuildAddEtcToRoot(c *check.C) { name := "testaddetctoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD . /`, map[string]string{ @@ -1361,7 +1308,6 @@ ADD . /`, // Testing #9401 func (s *DockerSuite) TestBuildAddPreservesFilesSpecialBits(c *check.C) { name := "testaddpreservesfilesspecialbits" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox ADD suidbin /usr/bin/suidbin RUN chmod 4755 /usr/bin/suidbin @@ -1384,7 +1330,6 @@ RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`, func (s *DockerSuite) TestBuildCopySingleFileToRoot(c *check.C) { name := "testcopysinglefiletoroot" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1410,7 +1355,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte // Issue #3960: "ADD src ." hangs - adapted for COPY func (s *DockerSuite) TestBuildCopySingleFileToWorkdir(c *check.C) { name := "testcopysinglefiletoworkdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY test_file .`, map[string]string{ @@ -1437,7 +1381,6 @@ COPY test_file .`, func (s *DockerSuite) TestBuildCopySingleFileToExistDir(c *check.C) { name := "testcopysinglefiletoexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1463,7 +1406,6 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' func (s *DockerSuite) TestBuildCopySingleFileToNonExistDir(c *check.C) { name := "testcopysinglefiletononexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1488,7 +1430,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildCopyDirContentToRoot(c *check.C) { name := "testcopydircontenttoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1512,7 +1453,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildCopyDirContentToExistDir(c *check.C) { name := "testcopydircontenttoexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1538,7 +1478,6 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, func (s *DockerSuite) TestBuildCopyWholeDirToRoot(c *check.C) { name := "testcopywholedirtoroot" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1565,7 +1504,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte func (s *DockerSuite) TestBuildCopyEtcToRoot(c *check.C) { name := "testcopyetctoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch COPY . /`, map[string]string{ @@ -1583,7 +1521,6 @@ COPY . /`, func (s *DockerSuite) TestBuildCopyDisallowRemote(c *check.C) { name := "testcopydisallowremote" - defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch COPY https://index.docker.io/robots.txt /`, true) @@ -1604,7 +1541,6 @@ func (s *DockerSuite) TestBuildAddBadLinks(c *check.C) { var ( name = "test-link-absolute" ) - defer deleteImages(name) ctx, err := fakeContext(dockerfile, nil) if err != nil { c.Fatal(err) @@ -1692,7 +1628,6 @@ func (s *DockerSuite) TestBuildAddBadLinksVolume(c *check.C) { name = "test-link-absolute-volume" dockerfile = "" ) - defer deleteImages(name) tempDir, err := ioutil.TempDir("", "test-link-absolute-volume-temp-") if err != nil { @@ -1737,7 +1672,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { { name := "testbuildinaccessiblefiles" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"fileWithoutReadAccess": "foo"}) if err != nil { c.Fatal(err) @@ -1770,7 +1704,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { } { name := "testbuildinaccessibledirectory" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"directoryWeCantStat/bar": "foo"}) if err != nil { c.Fatal(err) @@ -1809,7 +1742,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { } { name := "testlinksok" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", nil) if err != nil { c.Fatal(err) @@ -1829,7 +1761,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { } { name := "testbuildignoredinaccessible" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{ "directoryWeCantStat/bar": "foo", @@ -1867,7 +1798,6 @@ func (s *DockerSuite) TestBuildForceRm(c *check.C) { c.Fatalf("failed to get the container count: %s", err) } name := "testbuildforcerm" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nRUN true\nRUN thiswillfail", nil) if err != nil { c.Fatal(err) @@ -1903,7 +1833,6 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { defer wg.Wait() name := "testbuildcancelation" - defer deleteImages(name) // (Note: one year, will never finish) ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil) @@ -2018,7 +1947,6 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { func (s *DockerSuite) TestBuildRm(c *check.C) { name := "testbuildrm" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD foo /\nADD foo /", map[string]string{"foo": "bar"}) if err != nil { c.Fatal(err) @@ -2112,7 +2040,6 @@ func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { "/test8]": emptyMap, } ) - defer deleteImages(name) _, err := buildImage(name, `FROM scratch VOLUME /test1 @@ -2146,7 +2073,6 @@ func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { func (s *DockerSuite) TestBuildMaintainer(c *check.C) { name := "testbuildmaintainer" expected := "dockerio" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch MAINTAINER dockerio`, @@ -2166,7 +2092,6 @@ func (s *DockerSuite) TestBuildMaintainer(c *check.C) { func (s *DockerSuite) TestBuildUser(c *check.C) { name := "testbuilduser" expected := "dockerio" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd @@ -2188,7 +2113,6 @@ func (s *DockerSuite) TestBuildUser(c *check.C) { func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) { name := "testbuildrelativeworkdir" expected := "/test2/test3" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN [ "$PWD" = '/' ] @@ -2214,7 +2138,6 @@ func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) { func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) { name := "testbuildworkdirwithenvvariables" expected := "/test1/test2" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ENV DIRPATH /test1 @@ -2236,7 +2159,6 @@ func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) { func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) { name := "testbuildrelativecopy" - defer deleteImages(name) dockerfile := ` FROM busybox WORKDIR /test1 @@ -2276,7 +2198,6 @@ func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) { func (s *DockerSuite) TestBuildEnv(c *check.C) { name := "testbuildenv" expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ENV PATH /test:$PATH @@ -2299,7 +2220,6 @@ func (s *DockerSuite) TestBuildContextCleanup(c *check.C) { testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" - defer deleteImages(name) entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { c.Fatalf("failed to list contents of tmp dir: %s", err) @@ -2325,7 +2245,6 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" - defer deleteImages(name) entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { c.Fatalf("failed to list contents of tmp dir: %s", err) @@ -2350,7 +2269,6 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { func (s *DockerSuite) TestBuildCmd(c *check.C) { name := "testbuildcmd" expected := "{[/bin/echo Hello World]}" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch CMD ["/bin/echo", "Hello World"]`, @@ -2370,7 +2288,6 @@ func (s *DockerSuite) TestBuildCmd(c *check.C) { func (s *DockerSuite) TestBuildExpose(c *check.C) { name := "testbuildexpose" expected := "map[2375/tcp:{}]" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch EXPOSE 2375`, @@ -2413,7 +2330,6 @@ func (s *DockerSuite) TestBuildExposeMorePorts(c *check.C) { tmpl.Execute(buf, portList) name := "testbuildexpose" - defer deleteImages(name) _, err := buildImage(name, buf.String(), true) if err != nil { c.Fatal(err) @@ -2458,7 +2374,6 @@ func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { id1 := buildID("testbuildexpose1", "80 2375") id2 := buildID("testbuildexpose2", "2375 80") - defer deleteImages("testbuildexpose1", "testbuildexpose2") if id1 != id2 { c.Errorf("EXPOSE should invalidate the cache only when ports actually changed") } @@ -2467,7 +2382,6 @@ func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { name := "testbuildexposeuppercaseproto" expected := "map[5678/udp:{}]" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch EXPOSE 5678/UDP`, @@ -2488,7 +2402,6 @@ func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { // start building docker file with ip:hostPort:containerPort name := "testbuildexpose" expected := "map[5678/tcp:{}]" - defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch EXPOSE 192.168.1.2:2375:5678`, @@ -2513,7 +2426,6 @@ func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { name := "testbuildentrypointinheritance" name2 := "testbuildentrypointinheritance2" - defer deleteImages(name, name2) _, err := buildImage(name, `FROM busybox @@ -2554,7 +2466,6 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { name := "testbuildentrypoint" - defer deleteImages(name) expected := "{[]}" _, err := buildImage(name, @@ -2577,7 +2488,6 @@ func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildEntrypoint(c *check.C) { name := "testbuildentrypoint" expected := "{[/bin/echo]}" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch ENTRYPOINT ["/bin/echo"]`, @@ -2617,7 +2527,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { if err != nil { c.Fatalf("build failed to complete: %s, %v", out1, err) } - defer deleteImages(name1) } { name2 := "testonbuildtrigger2" @@ -2634,7 +2543,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { if err != nil { c.Fatalf("build failed to complete: %s, %v", out2, err) } - defer deleteImages(name2) } { name3 := "testonbuildtrigger3" @@ -2652,7 +2560,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { c.Fatalf("build failed to complete: %s, %v", out3, err) } - defer deleteImages(name3) } // ONBUILD should be run in second build. @@ -2669,7 +2576,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { func (s *DockerSuite) TestBuildWithCache(c *check.C) { name := "testbuildwithcache" - defer deleteImages(name) id1, err := buildImage(name, `FROM scratch MAINTAINER dockerio @@ -2696,7 +2602,6 @@ func (s *DockerSuite) TestBuildWithCache(c *check.C) { func (s *DockerSuite) TestBuildWithoutCache(c *check.C) { name := "testbuildwithoutcache" name2 := "testbuildwithoutcache2" - defer deleteImages(name, name2) id1, err := buildImage(name, `FROM scratch MAINTAINER dockerio @@ -2723,8 +2628,6 @@ func (s *DockerSuite) TestBuildWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildConditionalCache(c *check.C) { name := "testbuildconditionalcache" - name2 := "testbuildconditionalcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox @@ -2761,13 +2664,11 @@ func (s *DockerSuite) TestBuildConditionalCache(c *check.C) { if id3 != id2 { c.Fatal("Should have used the cache") } - } func (s *DockerSuite) TestBuildADDLocalFileWithCache(c *check.C) { name := "testbuildaddlocalfilewithcache" name2 := "testbuildaddlocalfilewithcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox MAINTAINER dockerio @@ -2796,7 +2697,6 @@ func (s *DockerSuite) TestBuildADDLocalFileWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDMultipleLocalFileWithCache(c *check.C) { name := "testbuildaddmultiplelocalfilewithcache" name2 := "testbuildaddmultiplelocalfilewithcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox MAINTAINER dockerio @@ -2825,7 +2725,6 @@ func (s *DockerSuite) TestBuildADDMultipleLocalFileWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDLocalFileWithoutCache(c *check.C) { name := "testbuildaddlocalfilewithoutcache" name2 := "testbuildaddlocalfilewithoutcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox MAINTAINER dockerio @@ -2854,7 +2753,6 @@ func (s *DockerSuite) TestBuildADDLocalFileWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildCopyDirButNotFile(c *check.C) { name := "testbuildcopydirbutnotfile" name2 := "testbuildcopydirbutnotfile2" - defer deleteImages(name, name2) dockerfile := ` FROM scratch COPY dir /tmp/` @@ -2888,7 +2786,6 @@ func (s *DockerSuite) TestBuildADDCurrentDirWithCache(c *check.C) { name3 := name + "3" name4 := name + "4" name5 := name + "5" - defer deleteImages(name, name2, name3, name4, name5) dockerfile := ` FROM scratch MAINTAINER dockerio @@ -2950,7 +2847,6 @@ func (s *DockerSuite) TestBuildADDCurrentDirWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDCurrentDirWithoutCache(c *check.C) { name := "testbuildaddcurrentdirwithoutcache" name2 := "testbuildaddcurrentdirwithoutcache2" - defer deleteImages(name, name2) dockerfile := ` FROM scratch MAINTAINER dockerio @@ -2977,7 +2873,6 @@ func (s *DockerSuite) TestBuildADDCurrentDirWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildADDRemoteFileWithCache(c *check.C) { name := "testbuildaddremotefilewithcache" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3010,7 +2905,6 @@ func (s *DockerSuite) TestBuildADDRemoteFileWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDRemoteFileWithoutCache(c *check.C) { name := "testbuildaddremotefilewithoutcache" name2 := "testbuildaddremotefilewithoutcache2" - defer deleteImages(name, name2) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3046,8 +2940,6 @@ func (s *DockerSuite) TestBuildADDRemoteFileMTime(c *check.C) { name3 := name + "3" name4 := name + "4" - defer deleteImages(name, name2, name3, name4) - files := map[string]string{"baz": "hello"} server, err := fakeStorage(files) if err != nil { @@ -3115,7 +3007,6 @@ func (s *DockerSuite) TestBuildADDRemoteFileMTime(c *check.C) { func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithCache(c *check.C) { name := "testbuildaddlocalandremotefilewithcache" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3167,7 +3058,6 @@ CMD ["cat", "/foo"]`, } name := "contexttar" buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") - defer deleteImages(name) buildCmd.Stdin = context if out, _, err := runCommandWithOutput(buildCmd); err != nil { @@ -3194,15 +3084,12 @@ func (s *DockerSuite) TestBuildNoContext(c *check.C) { if out, _ := dockerCmd(c, "run", "--rm", "nocontext"); out != "ok\n" { c.Fatalf("run produced invalid output: %q, expected %q", out, "ok") } - - deleteImages("nocontext") } // TODO: TestCaching func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithoutCache(c *check.C) { name := "testbuildaddlocalandremotefilewithoutcache" name2 := "testbuildaddlocalandremotefilewithoutcache2" - defer deleteImages(name, name2) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3237,7 +3124,6 @@ func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) { name := "testbuildimg" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox:latest @@ -3269,7 +3155,6 @@ func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) { // utilizing cache func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { name := "testbuildcmdcleanup" - defer deleteImages(name) if _, err := buildImage(name, `FROM busybox RUN echo "hello"`, @@ -3303,7 +3188,6 @@ func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { func (s *DockerSuite) TestBuildForbiddenContextPath(c *check.C) { name := "testbuildforbidpath" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD ../../ test/ `, @@ -3325,7 +3209,6 @@ func (s *DockerSuite) TestBuildForbiddenContextPath(c *check.C) { func (s *DockerSuite) TestBuildADDFileNotFound(c *check.C) { name := "testbuildaddnotfound" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD foo /usr/local/bar`, map[string]string{"bar": "hello"}) @@ -3344,7 +3227,6 @@ func (s *DockerSuite) TestBuildADDFileNotFound(c *check.C) { func (s *DockerSuite) TestBuildInheritance(c *check.C) { name := "testbuildinheritance" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -3384,7 +3266,6 @@ func (s *DockerSuite) TestBuildInheritance(c *check.C) { func (s *DockerSuite) TestBuildFails(c *check.C) { name := "testbuildfails" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN sh -c "exit 23"`, @@ -3400,7 +3281,6 @@ func (s *DockerSuite) TestBuildFails(c *check.C) { func (s *DockerSuite) TestBuildFailsDockerfileEmpty(c *check.C) { name := "testbuildfails" - defer deleteImages(name) _, err := buildImage(name, ``, true) if err != nil { if !strings.Contains(err.Error(), "The Dockerfile (Dockerfile) cannot be empty") { @@ -3413,7 +3293,6 @@ func (s *DockerSuite) TestBuildFailsDockerfileEmpty(c *check.C) { func (s *DockerSuite) TestBuildOnBuild(c *check.C) { name := "testbuildonbuild" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD RUN touch foobar`, @@ -3432,7 +3311,6 @@ func (s *DockerSuite) TestBuildOnBuild(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenChained(c *check.C) { name := "testbuildonbuildforbiddenchained" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD ONBUILD RUN touch foobar`, @@ -3448,7 +3326,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenChained(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenFrom(c *check.C) { name := "testbuildonbuildforbiddenfrom" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD FROM scratch`, @@ -3464,7 +3341,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenFrom(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainer(c *check.C) { name := "testbuildonbuildforbiddenmaintainer" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD MAINTAINER docker.io`, @@ -3481,7 +3357,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainer(c *check.C) { // gh #2446 func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) { name := "testbuildaddtosymlinkdest" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN mkdir /foo RUN ln -s /foo /bar @@ -3502,7 +3377,6 @@ func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) { func (s *DockerSuite) TestBuildEscapeWhitespace(c *check.C) { name := "testbuildescaping" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -3526,7 +3400,6 @@ docker.com>" func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { // Verify that strings that look like ints are still passed as strings name := "testbuildstringing" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -3546,7 +3419,6 @@ func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { func (s *DockerSuite) TestBuildDockerignore(c *check.C) { name := "testbuilddockerignore" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /bla @@ -3576,7 +3448,6 @@ func (s *DockerSuite) TestBuildDockerignore(c *check.C) { func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { name := "testbuilddockerignorecleanpaths" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3598,7 +3469,6 @@ func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3627,7 +3497,6 @@ func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3658,7 +3527,6 @@ func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringDockerignore(c *check.C) { name := "testbuilddockerignoredockerignore" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3682,7 +3550,6 @@ func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) { var id2 string name := "testbuilddockerignoretouchdockerfile" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/` @@ -3732,7 +3599,6 @@ func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { name := "testbuilddockerignorewholedir" - defer deleteImages(name) dockerfile := ` FROM busybox COPY . / @@ -3754,7 +3620,6 @@ func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { func (s *DockerSuite) TestBuildLineBreak(c *check.C) { name := "testbuildlinebreak" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN sh -c 'echo root:testpass \ @@ -3770,7 +3635,6 @@ RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, func (s *DockerSuite) TestBuildEOLInLine(c *check.C) { name := "testbuildeolinline" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN sh -c 'echo root:testpass > /tmp/passwd' @@ -3786,7 +3650,6 @@ RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, func (s *DockerSuite) TestBuildCommentsShebangs(c *check.C) { name := "testbuildcomments" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox # This is an ordinary comment. @@ -3805,7 +3668,6 @@ RUN [ "$(/hello.sh)" = "hello world" ]`, func (s *DockerSuite) TestBuildUsersAndGroups(c *check.C) { name := "testbuildusers" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox @@ -3868,7 +3730,6 @@ RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/10 func (s *DockerSuite) TestBuildEnvUsage(c *check.C) { name := "testbuildenvusage" - defer deleteImages(name) dockerfile := `FROM busybox ENV HOME /root ENV PATH $HOME/bin:$PATH @@ -3904,7 +3765,6 @@ RUN [ "$ghi" = "def" ] func (s *DockerSuite) TestBuildEnvUsage2(c *check.C) { name := "testbuildenvusage2" - defer deleteImages(name) dockerfile := `FROM busybox ENV abc=def RUN [ "$abc" = "def" ] @@ -4007,7 +3867,6 @@ RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ] func (s *DockerSuite) TestBuildAddScript(c *check.C) { name := "testbuildaddscript" - defer deleteImages(name) dockerfile := ` FROM busybox ADD test /test @@ -4030,7 +3889,6 @@ RUN [ "$(cat /testfile)" = 'test!' ]` func (s *DockerSuite) TestBuildAddTar(c *check.C) { name := "testbuildaddtar" - defer deleteImages(name) ctx := func() *FakeContext { dockerfile := ` @@ -4085,7 +3943,6 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi` func (s *DockerSuite) TestBuildAddTarXz(c *check.C) { name := "testbuildaddtarxz" - defer deleteImages(name) ctx := func() *FakeContext { dockerfile := ` @@ -4136,7 +3993,6 @@ func (s *DockerSuite) TestBuildAddTarXz(c *check.C) { func (s *DockerSuite) TestBuildAddTarXzGz(c *check.C) { name := "testbuildaddtarxzgz" - defer deleteImages(name) ctx := func() *FakeContext { dockerfile := ` @@ -4195,7 +4051,6 @@ func (s *DockerSuite) TestBuildAddTarXzGz(c *check.C) { func (s *DockerSuite) TestBuildFromGIT(c *check.C) { name := "testbuildfromgit" - defer deleteImages(name) git, err := fakeGIT("repo", map[string]string{ "Dockerfile": `FROM busybox ADD first /first @@ -4223,7 +4078,6 @@ func (s *DockerSuite) TestBuildFromGIT(c *check.C) { func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { name := "testbuildcmdcleanuponentrypoint" - defer deleteImages(name) if _, err := buildImage(name, `FROM scratch CMD ["test"] @@ -4256,7 +4110,6 @@ func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildClearCmd(c *check.C) { name := "testbuildclearcmd" - defer deleteImages(name) _, err := buildImage(name, `From scratch ENTRYPOINT ["/bin/bash"] @@ -4276,7 +4129,6 @@ func (s *DockerSuite) TestBuildClearCmd(c *check.C) { func (s *DockerSuite) TestBuildEmptyCmd(c *check.C) { name := "testbuildemptycmd" - defer deleteImages(name) if _, err := buildImage(name, "FROM scratch\nMAINTAINER quux\n", true); err != nil { c.Fatal(err) } @@ -4291,14 +4143,10 @@ func (s *DockerSuite) TestBuildEmptyCmd(c *check.C) { func (s *DockerSuite) TestBuildOnBuildOutput(c *check.C) { name := "testbuildonbuildparent" - defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nONBUILD RUN echo foo\n", true); err != nil { c.Fatal(err) } - childname := "testbuildonbuildchild" - defer deleteImages(childname) - _, out, err := buildImageWithOut(name, "FROM "+name+"\nMAINTAINER quux\n", true) if err != nil { c.Fatal(err) @@ -4312,7 +4160,6 @@ func (s *DockerSuite) TestBuildOnBuildOutput(c *check.C) { func (s *DockerSuite) TestBuildInvalidTag(c *check.C) { name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200) - defer deleteImages(name) _, out, err := buildImageWithOut(name, "FROM scratch\nMAINTAINER quux\n", true) // if the error doesnt check for illegal tag name, or the image is built // then this should fail @@ -4323,7 +4170,6 @@ func (s *DockerSuite) TestBuildInvalidTag(c *check.C) { func (s *DockerSuite) TestBuildCmdShDashC(c *check.C) { name := "testbuildcmdshc" - defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD echo cmd\n", true); err != nil { c.Fatal(err) } @@ -4346,7 +4192,6 @@ func (s *DockerSuite) TestBuildCmdSpaces(c *check.C) { // the arg separator to make sure ["echo","hi"] and ["echo hi"] don't // look the same name := "testbuildcmdspaces" - defer deleteImages(name) var id1 string var id2 string var err error @@ -4380,7 +4225,6 @@ func (s *DockerSuite) TestBuildCmdSpaces(c *check.C) { func (s *DockerSuite) TestBuildCmdJSONNoShDashC(c *check.C) { name := "testbuildcmdjson" - defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD [\"echo\", \"cmd\"]", true); err != nil { c.Fatal(err) } @@ -4400,7 +4244,6 @@ func (s *DockerSuite) TestBuildCmdJSONNoShDashC(c *check.C) { func (s *DockerSuite) TestBuildErrorInvalidInstruction(c *check.C) { name := "testbuildignoreinvalidinstruction" - defer deleteImages(name) out, _, err := buildImageWithOut(name, "FROM busybox\nfoo bar", true) if err == nil { @@ -4410,7 +4253,6 @@ func (s *DockerSuite) TestBuildErrorInvalidInstruction(c *check.C) { } func (s *DockerSuite) TestBuildEntrypointInheritance(c *check.C) { - defer deleteImages("parent", "child") if _, err := buildImage("parent", ` FROM busybox @@ -4447,8 +4289,6 @@ func (s *DockerSuite) TestBuildEntrypointInheritanceInspect(c *check.C) { expected = `["/bin/sh","-c","echo quux"]` ) - defer deleteImages(name, name2) - if _, err := buildImage(name, "FROM busybox\nENTRYPOINT /foo/bar", true); err != nil { c.Fatal(err) } @@ -4481,7 +4321,6 @@ func (s *DockerSuite) TestBuildEntrypointInheritanceInspect(c *check.C) { func (s *DockerSuite) TestBuildRunShEntrypoint(c *check.C) { name := "testbuildentrypoint" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ENTRYPOINT /bin/echo`, @@ -4500,7 +4339,6 @@ func (s *DockerSuite) TestBuildRunShEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) { name := "testbuildexoticshellinterpolation" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -4534,7 +4372,6 @@ func (s *DockerSuite) TestBuildVerifySingleQuoteFails(c *check.C) { // as a "string" insead of "JSON array" and pass it on to "sh -c" and // it should barf on it. name := "testbuildsinglequotefails" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox @@ -4550,7 +4387,6 @@ func (s *DockerSuite) TestBuildVerifySingleQuoteFails(c *check.C) { func (s *DockerSuite) TestBuildVerboseOut(c *check.C) { name := "testbuildverboseout" - defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM busybox @@ -4568,7 +4404,6 @@ RUN echo 123`, func (s *DockerSuite) TestBuildWithTabs(c *check.C) { name := "testbuildwithtabs" - defer deleteImages(name) _, err := buildImage(name, "FROM busybox\nRUN echo\tone\t\ttwo", true) if err != nil { @@ -4588,7 +4423,6 @@ func (s *DockerSuite) TestBuildWithTabs(c *check.C) { func (s *DockerSuite) TestBuildLabels(c *check.C) { name := "testbuildlabel" expected := `{"License":"GPL","Vendor":"Acme"}` - defer deleteImages(name) _, err := buildImage(name, `FROM busybox LABEL Vendor=Acme @@ -4608,7 +4442,6 @@ func (s *DockerSuite) TestBuildLabels(c *check.C) { func (s *DockerSuite) TestBuildLabelsCache(c *check.C) { name := "testbuildlabelcache" - defer deleteImages(name) id1, err := buildImage(name, `FROM busybox @@ -4659,7 +4492,6 @@ func (s *DockerSuite) TestBuildStderr(c *check.C) { // This test just makes sure that no non-error output goes // to stderr name := "testbuildstderr" - defer deleteImages(name) _, _, stderr, err := buildImageWithStdoutStderr(name, "FROM busybox\nRUN echo one", true) if err != nil { @@ -4685,7 +4517,6 @@ func (s *DockerSuite) TestBuildChownSingleFile(c *check.C) { testRequires(c, UnixCli) // test uses chown: not available on windows name := "testbuildchownsinglefile" - defer deleteImages(name) ctx, err := fakeContext(` FROM busybox @@ -4765,7 +4596,6 @@ func (s *DockerSuite) TestBuildSymlinkBreakout(c *check.C) { func (s *DockerSuite) TestBuildXZHost(c *check.C) { name := "testbuildxzhost" - defer deleteImages(name) ctx, err := fakeContext(` FROM busybox @@ -4796,7 +4626,6 @@ func (s *DockerSuite) TestBuildVolumesRetainContents(c *check.C) { name = "testbuildvolumescontent" expected = "some text" ) - defer deleteImages(name) ctx, err := fakeContext(` FROM busybox COPY content /foo/file @@ -4929,7 +4758,6 @@ func (s *DockerSuite) TestBuildRenamedDockerfile(c *check.C) { func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) { testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows - defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox RUN echo from dockerfile`, @@ -4954,7 +4782,6 @@ func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) { func (s *DockerSuite) TestBuildWithTwoDockerfiles(c *check.C) { testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows - defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox RUN echo from Dockerfile`, @@ -4978,7 +4805,6 @@ RUN echo from Dockerfile`, } func (s *DockerSuite) TestBuildFromURLWithF(c *check.C) { - defer deleteImages("test1") server, err := fakeStorage(map[string]string{"baz": `FROM busybox RUN echo from baz @@ -5013,7 +4839,6 @@ RUN echo from Dockerfile`, } func (s *DockerSuite) TestBuildFromStdinWithF(c *check.C) { - defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox RUN echo from Dockerfile`, @@ -5121,8 +4946,6 @@ func (s *DockerSuite) TestBuildDockerfileOutsideContext(c *check.C) { if err == nil { c.Fatalf("Expected error. Out: %s", out) } - deleteImages(name) - } func (s *DockerSuite) TestBuildSpaces(c *check.C) { @@ -5134,7 +4957,6 @@ func (s *DockerSuite) TestBuildSpaces(c *check.C) { ) name := "testspaces" - defer deleteImages(name) ctx, err := fakeContext("FROM busybox\nCOPY\n", map[string]string{ "Dockerfile": "FROM busybox\nCOPY\n", @@ -5199,7 +5021,6 @@ func (s *DockerSuite) TestBuildSpaces(c *check.C) { func (s *DockerSuite) TestBuildSpacesWithQuotes(c *check.C) { // Test to make sure that spaces in quotes aren't lost name := "testspacesquotes" - defer deleteImages(name) dockerfile := `FROM busybox RUN echo " \ @@ -5275,7 +5096,6 @@ func (s *DockerSuite) TestBuildMissingArgs(c *check.C) { } func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) { - defer deleteImages("sc") _, out, err := buildImageWithOut("sc", "FROM scratch", true) if err == nil { c.Fatalf("Build was supposed to fail") @@ -5286,7 +5106,6 @@ func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) { } func (s *DockerSuite) TestBuildDotDotFile(c *check.C) { - defer deleteImages("sc") ctx, err := fakeContext("FROM busybox\n", map[string]string{ "..gitme": "", @@ -5302,7 +5121,6 @@ func (s *DockerSuite) TestBuildDotDotFile(c *check.C) { } func (s *DockerSuite) TestBuildNotVerbose(c *check.C) { - defer deleteImages("verbose") ctx, err := fakeContext("FROM busybox\nENV abc=hi\nRUN echo $abc there", map[string]string{}) if err != nil { @@ -5337,8 +5155,6 @@ func (s *DockerSuite) TestBuildNotVerbose(c *check.C) { func (s *DockerSuite) TestBuildRUNoneJSON(c *check.C) { name := "testbuildrunonejson" - defer deleteImages(name, "hello-world") - ctx, err := fakeContext(`FROM hello-world:frozen RUN [ "/hello" ]`, map[string]string{}) if err != nil { @@ -5361,7 +5177,6 @@ RUN [ "/hello" ]`, map[string]string{}) func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { name := "testbuildresourceconstraints" - defer deleteImages(name, "hello-world") ctx, err := fakeContext(` FROM hello-world:frozen @@ -5425,7 +5240,6 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) { name := "testbuildemptystringvolume" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index bd4518434183d..6470d974cf4b8 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -33,7 +33,6 @@ func setupImageWithTag(tag string) (string, error) { if out, _, err := runCommandWithOutput(cmd); err != nil { return "", fmt.Errorf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoAndTag) // delete the container as we don't need it any more if err := deleteContainer(containerName); err != nil { @@ -77,7 +76,6 @@ func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { if err != nil { c.Fatalf("error pulling by tag: %s, %v", out, err) } - defer deleteImages(repoName) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) @@ -108,7 +106,6 @@ func (s *DockerSuite) TestPullByDigest(c *check.C) { if err != nil { c.Fatalf("error pulling by digest: %s, %v", out, err) } - defer deleteImages(imageReference) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) @@ -248,7 +245,6 @@ func (s *DockerSuite) TestBuildByDigest(c *check.C) { // do the build name := "buildbydigest" - defer deleteImages(name) _, err = buildImage(name, fmt.Sprintf( `FROM %s CMD ["/bin/echo", "Hello World"]`, imageReference), @@ -340,7 +336,6 @@ func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { defer setupRegistry(c)() - defer deleteImages(repoName+":tag1", repoName+":tag2") // setup image1 digest1, err := setupImageWithTag("tag1") @@ -348,7 +343,6 @@ func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { c.Fatalf("error setting up image: %v", err) } imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1) - defer deleteImages(imageReference1) c.Logf("imageReference1 = %s", imageReference1) // pull image1 by digest @@ -377,7 +371,6 @@ func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { c.Fatalf("error setting up image: %v", err) } imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2) - defer deleteImages(imageReference2) c.Logf("imageReference2 = %s", imageReference2) // pull image1 by digest @@ -508,7 +501,6 @@ func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { c.Fatalf("error pulling by digest: %s, %v", out, err) } // just in case... - defer deleteImages(imageReference) imageID, err := inspectField(imageReference, ".Id") if err != nil { diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index a75621f3a3de6..1544b3aace4bb 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -33,10 +33,6 @@ func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) { if out, _, err = runCommandWithOutput(inspectCmd); err != nil { c.Fatalf("failed to inspect image: %s, %v", out, err) } - - deleteContainer(cleanedContainerID) - deleteImages(cleanedImageID) - } func (s *DockerSuite) TestCommitWithoutPause(c *check.C) { @@ -65,10 +61,6 @@ func (s *DockerSuite) TestCommitWithoutPause(c *check.C) { if out, _, err = runCommandWithOutput(inspectCmd); err != nil { c.Fatalf("failed to inspect image: %s, %v", out, err) } - - deleteContainer(cleanedContainerID) - deleteImages(cleanedImageID) - } //test commit a paused container should not unpause it after commit @@ -92,8 +84,6 @@ func (s *DockerSuite) TestCommitPausedContainer(c *check.C) { if err != nil { c.Fatalf("failed to commit container to image: %s, %v", out, err) } - cleanedImageID := strings.TrimSpace(out) - defer deleteImages(cleanedImageID) cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Paused}}", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(cmd) @@ -120,7 +110,6 @@ func (s *DockerSuite) TestCommitNewFile(c *check.C) { c.Fatal(err) } imageID = strings.Trim(imageID, "\r\n") - defer deleteImages(imageID) cmd = exec.Command(dockerBinary, "run", imageID, "cat", "/foo") @@ -161,7 +150,6 @@ func (s *DockerSuite) TestCommitHardlink(c *check.C) { c.Fatal(imageID, err) } imageID = strings.Trim(imageID, "\r\n") - defer deleteImages(imageID) cmd = exec.Command(dockerBinary, "run", "-t", "hardlinks", "ls", "-di", "file1", "file2") secondOuput, _, err := runCommandWithOutput(cmd) @@ -185,7 +173,6 @@ func (s *DockerSuite) TestCommitHardlink(c *check.C) { } func (s *DockerSuite) TestCommitTTY(c *check.C) { - defer deleteImages("ttytest") cmd := exec.Command(dockerBinary, "run", "-t", "--name", "tty", "busybox", "/bin/ls") if _, err := runCommand(cmd); err != nil { @@ -220,7 +207,6 @@ func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) { } imageID = strings.Trim(imageID, "\r\n") - defer deleteImages(imageID) cmd = exec.Command(dockerBinary, "run", "bindtest", "true") @@ -248,7 +234,6 @@ func (s *DockerSuite) TestCommitChange(c *check.C) { c.Fatal(imageId, err) } imageId = strings.Trim(imageId, "\r\n") - defer deleteImages(imageId) expected := map[string]string{ "Config.ExposedPorts": "map[8080/tcp:{}]", @@ -274,7 +259,6 @@ func (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) { id := strings.TrimSpace(out) dockerCmd(c, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") - defer deleteImages("commit-test") out, _ = dockerCmd(c, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 10c499982e07c..646a8eafe023b 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -259,7 +259,6 @@ func (s *DockerSuite) TestCreateLabels(c *check.C) { func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { imageName := "testcreatebuildlabel" - defer deleteImages(imageName) _, err := buildImage(imageName, `FROM busybox LABEL k1=v1 k2=v2`, diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 35a9c0a213546..dfe35b14ca082 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -144,7 +144,6 @@ func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) { func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) { name := "testimageevents" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch MAINTAINER "docker"`, @@ -180,8 +179,6 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) { since := daemonTime(c).Unix() testRequires(c, Network) - defer deleteImages("hello-world") - pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { c.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err) diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index bc7b16356d94d..3370a96761cab 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -12,8 +12,6 @@ import ( func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { containerID := "testexportcontainerandimportimage" - defer deleteImages("repo/testexp:v1") - runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { @@ -51,8 +49,6 @@ func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) { containerID := "testexportcontainerwithoutputandimportimage" - defer deleteImages("repo/testexp:v1") - runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 8de4008314877..d229f1a8c58f8 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -14,7 +14,6 @@ import ( // sort is not predictable it doesn't always fail. func (s *DockerSuite) TestBuildHistory(c *check.C) { name := "testbuildhistory" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN echo "A" RUN echo "B" @@ -85,7 +84,6 @@ func (s *DockerSuite) TestHistoryNonExistentImage(c *check.C) { func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { name := "testhistoryimagewithcomment" - defer deleteImages(name) // make a image through docker commit [ -m messages ] //runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index cf219e88b85a9..0ab6462509340 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -26,9 +26,6 @@ func (s *DockerSuite) TestImagesEnsureImageIsListed(c *check.C) { } func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) { - defer deleteImages("order:test_a") - defer deleteImages("order:test_c") - defer deleteImages("order:test_b") id1, err := buildImage("order:test_a", `FROM scratch MAINTAINER dockerio1`, true) @@ -80,9 +77,6 @@ func (s *DockerSuite) TestImagesFilterLabel(c *check.C) { imageName1 := "images_filter_test1" imageName2 := "images_filter_test2" imageName3 := "images_filter_test3" - defer deleteImages(imageName1) - defer deleteImages(imageName2) - defer deleteImages(imageName3) image1ID, err := buildImage(imageName1, `FROM scratch LABEL match me`, true) @@ -130,7 +124,6 @@ func (s *DockerSuite) TestImagesFilterLabel(c *check.C) { func (s *DockerSuite) TestImagesFilterSpaceTrimCase(c *check.C) { imageName := "images_filter_test" - defer deleteImages(imageName) buildImage(imageName, `FROM scratch RUN touch /test/foo @@ -189,7 +182,6 @@ func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) { c.Fatalf("error tagging foobox: %s", err) } imageId := stringid.TruncateID(strings.TrimSpace(out)) - defer deleteImages(imageId) // overwrite the tag, making the previous image dangling cmd = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox") @@ -197,7 +189,6 @@ func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) { if err != nil { c.Fatalf("error tagging foobox: %s", err) } - defer deleteImages("foobox") cmd = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true") out, _, err = runCommandWithOutput(cmd) diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go index 02b857bfd958e..201dbaa580e7f 100644 --- a/integration-cli/docker_cli_import_test.go +++ b/integration-cli/docker_cli_import_test.go @@ -27,7 +27,6 @@ func (s *DockerSuite) TestImportDisplay(c *check.C) { c.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out) } image := strings.TrimSpace(out) - defer deleteImages(image) runCmd = exec.Command(dockerBinary, "run", "--rm", image, "true") out, _, err = runCommandWithOutput(runCmd) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index ca2d67bc96c2b..881f02d4fe74d 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -560,7 +560,6 @@ func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { func (s *DockerSuite) TestPsRightTagName(c *check.C) { tag := "asybox:shmatest" - defer deleteImages(tag) if out, err := exec.Command(dockerBinary, "tag", "busybox", tag).CombinedOutput(); err != nil { c.Fatalf("Failed to tag image: %s, out: %q", err, out) } diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 9dc9920cac745..bdb55f35d0bce 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -13,7 +13,6 @@ func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) - defer deleteImages(repoName) repos := []string{} for _, tag := range []string{"recent", "fresh"} { @@ -25,7 +24,6 @@ func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil { c.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out) } - defer deleteImages(repo) if out, err := exec.Command(dockerBinary, "push", repo).CombinedOutput(); err != nil { c.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) } @@ -61,7 +59,6 @@ func (s *DockerSuite) TestPullVerified(c *check.C) { // unless keychain is manually updated to contain the daemon's sign key. verifiedName := "hello-world" - defer deleteImages(verifiedName) // pull it expected := "The image you are pulling has been verified" @@ -88,8 +85,6 @@ func (s *DockerSuite) TestPullVerified(c *check.C) { func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) { testRequires(c, Network) - defer deleteImages("hello-world") - pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { c.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 3fc1ae3a0c8e6..44d34dd637e86 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -22,7 +22,6 @@ func (s *DockerSuite) TestPushBusyboxImage(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { @@ -77,12 +76,10 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd1); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoTag1) tagCmd2 := exec.Command(dockerBinary, "tag", "busybox", repoTag2) if out, _, err := runCommandWithOutput(tagCmd2); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoTag2) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { @@ -97,7 +94,6 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if err := pushCmd.Start(); err != nil { diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index c330bb76ab053..b8d1b843d1502 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -87,7 +87,6 @@ func (s *DockerSuite) TestRmContainerOrphaning(c *check.C) { // build first dockerfile img1, err := buildImage(img, dockerfile1, true) - defer deleteImages(img1) if err != nil { c.Fatalf("Could not build image %s: %v", img, err) } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 8da6f3b879dd5..342137c477a88 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -447,7 +447,6 @@ func (s *DockerSuite) TestRunCreateVolumesInSymlinkDir(c *check.C) { if _, err := buildImage(name, dockerFile, false); err != nil { c.Fatal(err) } - defer deleteImages(name) out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/test/test", name)) if err != nil { @@ -604,7 +603,6 @@ func (s *DockerSuite) TestRunCreateVolume(c *check.C) { // Note that this bug happens only with symlinks with a target that starts with '/'. func (s *DockerSuite) TestRunCreateVolumeWithSymlink(c *check.C) { image := "docker-test-createvolumewithsymlink" - defer deleteImages(image) buildCmd := exec.Command(dockerBinary, "build", "-t", image, "-") buildCmd.Stdin = strings.NewReader(`FROM busybox @@ -644,7 +642,6 @@ func (s *DockerSuite) TestRunCreateVolumeWithSymlink(c *check.C) { // Tests that a volume path that has a symlink exists in a container mounting it with `--volumes-from`. func (s *DockerSuite) TestRunVolumesFromSymlinkPath(c *check.C) { name := "docker-test-volumesfromsymlinkpath" - defer deleteImages(name) buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") buildCmd.Stdin = strings.NewReader(`FROM busybox @@ -1746,7 +1743,6 @@ func (s *DockerSuite) TestRunState(c *check.C) { // Test for #1737 func (s *DockerSuite) TestRunCopyVolumeUidGid(c *check.C) { name := "testrunvolumesuidgid" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd @@ -1772,7 +1768,6 @@ func (s *DockerSuite) TestRunCopyVolumeUidGid(c *check.C) { // Test for #1582 func (s *DockerSuite) TestRunCopyVolumeContent(c *check.C) { name := "testruncopyvolumecontent" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN mkdir -p /hello/local && echo hello > /hello/local/world`, @@ -1794,7 +1789,6 @@ func (s *DockerSuite) TestRunCopyVolumeContent(c *check.C) { func (s *DockerSuite) TestRunCleanupCmdOnEntrypoint(c *check.C) { name := "testrunmdcleanuponentrypoint" - defer deleteImages(name) if _, err := buildImage(name, `FROM busybox ENTRYPOINT ["echo"] @@ -2349,7 +2343,6 @@ func (s *DockerSuite) TestRunCreateVolumeEtc(c *check.C) { } func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) { - defer deleteImages("dataimage") if _, err := buildImage("dataimage", `FROM busybox RUN mkdir -p /foo @@ -2430,7 +2423,6 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { true); err != nil { c.Fatal(err) } - defer deleteImages("run_volumes_clean_paths") cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "-v", "/bar/", "--name", "dark_helmet", "run_volumes_clean_paths") if out, _, err := runCommandWithOutput(cmd); err != nil { diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index f74f69fcc14d3..fe6bf2bfc24ab 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -131,7 +131,6 @@ func (s *DockerSuite) TestSaveSingleTag(c *check.C) { repoName := "foobar-save-single-tag-test" tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", fmt.Sprintf("%v:latest", repoName)) - defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } @@ -157,7 +156,6 @@ func (s *DockerSuite) TestSaveImageId(c *check.C) { repoName := "foobar-save-image-id-test" tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName)) - defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } @@ -264,7 +262,6 @@ func (s *DockerSuite) TestSaveMultipleNames(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } - defer deleteImages(repoName + "-one") // Make two images tagCmd = exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-two:latest", repoName)) @@ -272,7 +269,6 @@ func (s *DockerSuite) TestSaveMultipleNames(c *check.C) { if err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } - defer deleteImages(repoName + "-two") out, _, err = runCommandPipelineWithOutput( exec.Command(dockerBinary, "save", fmt.Sprintf("%v-one", repoName), fmt.Sprintf("%v-two:latest", repoName)), @@ -311,9 +307,7 @@ func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { tagBar := repoName + ":bar" idFoo := makeImage("busybox:latest", tagFoo) - defer deleteImages(idFoo) idBar := makeImage("busybox:latest", tagBar) - defer deleteImages(idBar) deleteImages(repoName) @@ -358,7 +352,6 @@ func (s *DockerSuite) TestSaveDirectoryPermissions(c *check.C) { os.Mkdir(extractionDirectory, 0777) defer os.RemoveAll(tmpDir) - defer deleteImages(name) _, err = buildImage(name, `FROM busybox RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index 1b4d36b7bf3d4..35225f9c1e5ec 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -18,9 +18,6 @@ func (s *DockerSuite) TestTagUnprefixedRepoByName(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatal(out, err) } - - deleteImages("testfoobarbaz") - } // tagging an image by ID in a new unprefixed repo should work @@ -36,9 +33,6 @@ func (s *DockerSuite) TestTagUnprefixedRepoByID(c *check.C) { if out, _, err = runCommandWithOutput(tagCmd); err != nil { c.Fatal(out, err) } - - deleteImages("testfoobarbaz") - } // ensure we don't allow the use of invalid repository names; these tag operations should fail @@ -104,8 +98,6 @@ func (s *DockerSuite) TestTagExistedNameWithoutForce(c *check.C) { if err == nil || !strings.Contains(out, "Conflict: Tag test is already set to image") { c.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed") } - deleteImages("busybox:test") - } // tag an image with an existed tag name with -f option should work @@ -122,8 +114,6 @@ func (s *DockerSuite) TestTagExistedNameWithForce(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatal(out, err) } - deleteImages("busybox:test") - } // ensure tagging using official names works diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 20e87244e7e4f..688de68a1c90f 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -396,6 +396,55 @@ func deleteAllContainers() error { return nil } +var protectedImages = map[string]struct{}{} + +func init() { + out, err := exec.Command(dockerBinary, "images").CombinedOutput() + if err != nil { + panic(err) + } + lines := strings.Split(string(out), "\n")[1:] + for _, l := range lines { + if l == "" { + continue + } + fields := strings.Fields(l) + imgTag := fields[0] + ":" + fields[1] + protectedImages[imgTag] = struct{}{} + } +} + +func deleteAllImages() error { + out, err := exec.Command(dockerBinary, "images").CombinedOutput() + if err != nil { + return err + } + lines := strings.Split(string(out), "\n")[1:] + var imgs []string + for _, l := range lines { + if l == "" { + continue + } + fields := strings.Fields(l) + imgTag := fields[0] + ":" + fields[1] + if _, ok := protectedImages[imgTag]; !ok { + if fields[0] == "" { + imgs = append(imgs, fields[2]) + continue + } + imgs = append(imgs, imgTag) + } + } + if len(imgs) == 0 { + return nil + } + args := append([]string{"rmi", "-f"}, imgs...) + if err := exec.Command(dockerBinary, args...).Run(); err != nil { + return err + } + return nil +} + func getPausedContainers() (string, error) { getPausedContainersCmd := exec.Command(dockerBinary, "ps", "-f", "status=paused", "-q", "-a") out, exitCode, err := runCommandWithOutput(getPausedContainersCmd) From 07795c3f5db3aa8ff592b0606ab76d32db4c6d25 Mon Sep 17 00:00:00 2001 From: Daniel Antlinger Date: Thu, 23 Apr 2015 11:43:08 -0700 Subject: [PATCH 286/332] fix runtime issue for TestEventsFilterContainer Signed-off-by: Daniel Antlinger Signed-off-by: Alexander Morozov --- integration-cli/docker_cli_events_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index b8e24260a72f7..16e03bfe33ad6 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -322,12 +322,15 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) { nameID := make(map[string]string) for _, name := range []string{"container_1", "container_2"} { - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "true")) + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")) + if err != nil { + c.Fatalf("Error: %v, Output: %s", err, out) + } + id, err := inspectField(name, "Id") if err != nil { c.Fatal(err) } - nameID[name] = strings.TrimSpace(out) - waitInspect(name, "{{.State.Runing }}", "false", 5) + nameID[name] = id } until := fmt.Sprintf("%d", daemonTime(c).Unix()) From 11a5f1af01842acf9a25ccebb59f36472ae51170 Mon Sep 17 00:00:00 2001 From: Anton Tiurin Date: Fri, 24 Apr 2015 00:39:05 +0300 Subject: [PATCH 287/332] statsCollector: fix data race in run() statsCollector.publishers must be protected to prevent modifications during the iteration in run(). Being locked for a long time is bad, so pairs of containers & publishers (pointers) are copied to release the lock fast. Signed-off-by: Anton Tiurin --- daemon/stats_collector.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/daemon/stats_collector.go b/daemon/stats_collector.go index 5677a8634a6f3..22239743a69d5 100644 --- a/daemon/stats_collector.go +++ b/daemon/stats_collector.go @@ -76,22 +76,42 @@ func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) { } func (s *statsCollector) run() { + type publishersPair struct { + container *Container + publisher *pubsub.Publisher + } + // we cannot determine the capacity here. + // it will grow enough in first iteration + var pairs []publishersPair + for range time.Tick(s.interval) { + systemUsage, err := s.getSystemCpuUsage() + if err != nil { + logrus.Errorf("collecting system cpu usage: %v", err) + continue + } + + // it does not make sense in the first iteration, + // but saves allocations in further iterations + pairs = pairs[:0] + + s.m.Lock() for container, publisher := range s.publishers { - systemUsage, err := s.getSystemCpuUsage() - if err != nil { - logrus.Errorf("collecting system cpu usage for %s: %v", container.ID, err) - continue - } - stats, err := container.Stats() + // copy pointers here to release the lock ASAP + pairs = append(pairs, publishersPair{container, publisher}) + } + s.m.Unlock() + + for _, pair := range pairs { + stats, err := pair.container.Stats() if err != nil { if err != execdriver.ErrNotRunning { - logrus.Errorf("collecting stats for %s: %v", container.ID, err) + logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err) } continue } stats.SystemUsage = systemUsage - publisher.Publish(stats) + pair.publisher.Publish(stats) } } } From 887ad57cfa46df542001a1bb4e3ab052ce5f5ed9 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 24 Apr 2015 11:05:17 -0700 Subject: [PATCH 288/332] Use -f for rm instead of kill before Signed-off-by: Alexander Morozov --- integration-cli/docker_utils.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 20e87244e7e4f..907be173c2b26 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -361,9 +361,7 @@ func readBody(b io.ReadCloser) ([]byte, error) { func deleteContainer(container string) error { container = strings.TrimSpace(strings.Replace(container, "\n", " ", -1)) - killArgs := strings.Split(fmt.Sprintf("kill %v", container), " ") - runCommand(exec.Command(dockerBinary, killArgs...)) - rmArgs := strings.Split(fmt.Sprintf("rm -v %v", container), " ") + rmArgs := strings.Split(fmt.Sprintf("rm -fv %v", container), " ") exitCode, err := runCommand(exec.Command(dockerBinary, rmArgs...)) // set error manually if not set if exitCode != 0 && err == nil { From 366ee6bdfa9d2c87d006a162fc951ecc0f68051a Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Fri, 24 Apr 2015 12:23:54 -0700 Subject: [PATCH 289/332] Expose ParseRestartPolicy ParseRestartPolicy is useful function for third party go programs to use so that they can parse the restart policy in the same way that Docker does Signed-off-by: Darren Shepherd --- runconfig/parse.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runconfig/parse.go b/runconfig/parse.go index 2cdb2d331d727..47feac866a636 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -277,7 +277,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err) } - restartPolicy, err := parseRestartPolicy(*flRestartPolicy) + restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) if err != nil { return nil, nil, cmd, err } @@ -374,8 +374,8 @@ func convertKVStringsToMap(values []string) map[string]string { return result } -// parseRestartPolicy returns the parsed policy or an error indicating what is incorrect -func parseRestartPolicy(policy string) (RestartPolicy, error) { +// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect +func ParseRestartPolicy(policy string) (RestartPolicy, error) { p := RestartPolicy{} if policy == "" { From 9bea123bddb9d2ce8b2aa517c2a2a4b269ed39bc Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 24 Apr 2015 13:16:51 -0700 Subject: [PATCH 290/332] Not protect dangling images for integration-cli Signed-off-by: Alexander Morozov --- integration-cli/docker_utils.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 929426b4f1ad4..51ecacb3327eb 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -408,7 +408,10 @@ func init() { } fields := strings.Fields(l) imgTag := fields[0] + ":" + fields[1] - protectedImages[imgTag] = struct{}{} + // just for case if we have dangling images in tested daemon + if imgTag != ":" { + protectedImages[imgTag] = struct{}{} + } } } From f696b1071a296bee1f4ac7cafa807ce337fb9f2c Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 24 Apr 2015 14:16:56 -0700 Subject: [PATCH 291/332] Implement DockerRegistrySuite in integration-cli To avoid manually creating and destroying registrys in tests. Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 25 +++++++++- integration-cli/docker_cli_by_digest_test.go | 48 ++++---------------- integration-cli/docker_cli_pull_test.go | 5 +- integration-cli/docker_cli_push_test.go | 22 +++------ integration-cli/docker_utils.go | 5 +- 5 files changed, 43 insertions(+), 62 deletions(-) diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index f6dbdb8a92cb3..533ac127c4468 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -24,6 +24,10 @@ func (s *TimerSuite) TearDownTest(c *check.C) { fmt.Printf("%-60s%.2f\n", c.TestName(), time.Since(s.start).Seconds()) } +func init() { + check.Suite(&DockerSuite{}) +} + type DockerSuite struct { TimerSuite } @@ -34,4 +38,23 @@ func (s *DockerSuite) TearDownTest(c *check.C) { s.TimerSuite.TearDownTest(c) } -var _ = check.Suite(&DockerSuite{}) +func init() { + check.Suite(&DockerRegistrySuite{ + ds: &DockerSuite{}, + }) +} + +type DockerRegistrySuite struct { + ds *DockerSuite + reg *testRegistryV2 +} + +func (s *DockerRegistrySuite) SetUpTest(c *check.C) { + s.reg = setupRegistry(c) + s.ds.SetUpTest(c) +} + +func (s *DockerRegistrySuite) TearDownTest(c *check.C) { + s.reg.Close() + s.ds.TearDownTest(c) +} diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index 6470d974cf4b8..b9b319cf94a41 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -62,9 +62,7 @@ func setupImageWithTag(tag string) (string, error) { return pushDigest, nil } -func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -88,12 +86,9 @@ func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { if pushDigest != pullDigest { c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - } -func (s *DockerSuite) TestPullByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -118,12 +113,9 @@ func (s *DockerSuite) TestPullByDigest(c *check.C) { if pushDigest != pullDigest { c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - } -func (s *DockerSuite) TestCreateByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestCreateByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -145,12 +137,9 @@ func (s *DockerSuite) TestCreateByDigest(c *check.C) { if res != imageReference { c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - } -func (s *DockerSuite) TestRunByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestRunByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -181,12 +170,9 @@ func (s *DockerSuite) TestRunByDigest(c *check.C) { if res != imageReference { c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - } -func (s *DockerSuite) TestRemoveImageByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestRemoveImageByDigest(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -217,12 +203,9 @@ func (s *DockerSuite) TestRemoveImageByDigest(c *check.C) { } else if !strings.Contains(err.Error(), "No such image") { c.Fatalf("expected 'No such image' output, got %v", err) } - } -func (s *DockerSuite) TestBuildByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestBuildByDigest(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -262,12 +245,9 @@ func (s *DockerSuite) TestBuildByDigest(c *check.C) { if res != imageID { c.Fatalf("Image %s, expected %s", res, imageID) } - } -func (s *DockerSuite) TestTagByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestTagByDigest(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -302,12 +282,9 @@ func (s *DockerSuite) TestTagByDigest(c *check.C) { if tagID != expectedID { c.Fatalf("expected image id %q, got %q", expectedID, tagID) } - } -func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestListImagesWithoutDigests(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -334,8 +311,7 @@ func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { } -func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { - defer setupRegistry(c)() +func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { // setup image1 digest1, err := setupImageWithTag("tag1") @@ -482,12 +458,9 @@ func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { if !busyboxRe.MatchString(out) { c.Fatalf("expected %q: %s", busyboxRe.String(), out) } - } -func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -511,5 +484,4 @@ func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { if _, err := runCommand(cmd); err != nil { c.Fatalf("error deleting image by id: %v", err) } - } diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index bdb55f35d0bce..a3ded8f038da8 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -9,9 +9,7 @@ import ( ) // See issue docker/docker#8141 -func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repos := []string{} @@ -48,7 +46,6 @@ func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { c.Fatalf("Image %v shouldn't have been pulled down", repo) } } - } // pulling library/hello-world should show verified message diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 44d34dd637e86..8f7ee31588b72 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -13,9 +13,7 @@ import ( ) // pulling an image from the central registry should work -func (s *DockerSuite) TestPushBusyboxImage(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it to the private registry tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) @@ -37,9 +35,7 @@ func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) { } } -func (s *DockerSuite) TestPushUntagged(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) expected := "Repository does not exist" @@ -51,9 +47,7 @@ func (s *DockerSuite) TestPushUntagged(c *check.C) { } } -func (s *DockerSuite) TestPushBadTag(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL) expected := "does not exist" @@ -65,9 +59,7 @@ func (s *DockerSuite) TestPushBadTag(c *check.C) { } } -func (s *DockerSuite) TestPushMultipleTags(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL) repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL) @@ -87,8 +79,7 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { } } -func (s *DockerSuite) TestPushInterrupt(c *check.C) { - defer setupRegistry(c)() +func (s *DockerRegistrySuite) TestPushInterrupt(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image and upload it to the private registry if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { @@ -118,8 +109,7 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { } } -func (s *DockerSuite) TestPushEmptyLayer(c *check.C) { - defer setupRegistry(c)() +func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL) emptyTarball, err := ioutil.TempFile("", "empty_tarball") if err != nil { diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 929426b4f1ad4..931f3e2697d0f 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -1125,7 +1125,7 @@ func daemonTime(c *check.C) time.Time { return dt } -func setupRegistry(c *check.C) func() { +func setupRegistry(c *check.C) *testRegistryV2 { testRequires(c, RegistryHosting) reg, err := newTestRegistryV2(c) if err != nil { @@ -1143,8 +1143,7 @@ func setupRegistry(c *check.C) func() { if err != nil { c.Fatal("Timeout waiting for test registry to become available") } - - return func() { reg.Close() } + return reg } // appendBaseEnv appends the minimum set of environment variables to exec the From 29a3bbf2b375111b9494df1ce5ee457508f7acad Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Mon, 20 Apr 2015 13:05:48 -0700 Subject: [PATCH 292/332] Eliminate json.Marshal from graph/export.go and volumes/volume.go Fixes #12531 Fixes #12530 Signed-off-by: Ankush Agarwal --- graph/export.go | 11 +++++++++-- volumes/volume.go | 13 +++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/graph/export.go b/graph/export.go index 00cfa8975a5a5..91382c60e5444 100644 --- a/graph/export.go +++ b/graph/export.go @@ -85,8 +85,15 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { } // write repositories, if there is something to write if len(rootRepoMap) > 0 { - rootRepoJson, _ := json.Marshal(rootRepoMap) - if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil { + f, err := os.OpenFile(path.Join(tempdir, "repositories"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + f.Close() + return err + } + if err := json.NewEncoder(f).Encode(rootRepoMap); err != nil { + return err + } + if err := f.Close(); err != nil { return err } } else { diff --git a/volumes/volume.go b/volumes/volume.go index 87aa4ad25afaa..16446926550d9 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -2,7 +2,6 @@ package volumes import ( "encoding/json" - "io/ioutil" "os" "path/filepath" "sync" @@ -81,17 +80,19 @@ func (v *Volume) ToDisk() error { } func (v *Volume) toDisk() error { - data, err := json.Marshal(v) + jsonPath, err := v.jsonPath() if err != nil { return err } - - pth, err := v.jsonPath() + f, err := os.OpenFile(jsonPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } - - return ioutil.WriteFile(pth, data, 0666) + if err := json.NewEncoder(f).Encode(v); err != nil { + f.Close() + return err + } + return f.Close() } func (v *Volume) FromDisk() error { From ae9905ef9c5e8fe793e6a6269bb720618f4fcaed Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 24 Apr 2015 16:52:32 -0700 Subject: [PATCH 293/332] Fixed typo 'configuring' Signed-off-by: John Howard --- daemon/networkdriver/bridge/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index f1397dc3268b2..04e3737dcafaf 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -233,7 +233,7 @@ func InitDriver(config *Config) error { // Configure iptables for link support if config.EnableIptables { if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { - logrus.Errorf("Error configuing iptables: %s", err) + logrus.Errorf("Error configuring iptables: %s", err) return err } // call this on Firewalld reload From 2188c8d2f41f4905411a40c30f4c703d7f4e0fcf Mon Sep 17 00:00:00 2001 From: Sergey Alekseev Date: Sat, 25 Apr 2015 11:28:39 +0300 Subject: [PATCH 294/332] Fix documentation typo in articles/basics.md Signed-off-by: Sergey Alekseev --- docs/sources/articles/basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/articles/basics.md b/docs/sources/articles/basics.md index 94264ece64e88..7d7c154091b7c 100644 --- a/docs/sources/articles/basics.md +++ b/docs/sources/articles/basics.md @@ -172,7 +172,7 @@ will be stored (as a diff). See which images you already have using the # Commit your container to a new named image $ docker commit - # List your containers + # List your images $ docker images You now have an image state from which you can create new instances. From 8f752ffeafd2f8c08035a5e39220fe17c9309fd7 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 24 Apr 2015 19:57:04 +0800 Subject: [PATCH 295/332] Add test for REST API container rename Signed-off-by: Hu Keping --- integration-cli/docker_api_containers_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 24efbb7a27cb1..172e3e9935af1 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -841,3 +841,22 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { c.Assert(status, check.Equals, http.StatusInternalServerError) c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } + +func (s *DockerSuite) TestContainerApiRename(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + containerID := strings.TrimSpace(out) + newName := "new_name" + stringid.GenerateRandomID() + statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/rename?name="+newName, nil) + + // 204 No Content is expected, not 200 + c.Assert(statusCode, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + name, err := inspectField(containerID, "Name") + if name != "/"+newName { + c.Fatalf("Failed to rename container, expected %v, got %v. Container rename API failed", newName, name) + } +} From bc149be69c5ad22f82269427dd4b4aed9df4fa40 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Sat, 25 Apr 2015 04:42:43 -0700 Subject: [PATCH 296/332] A fix for = in env values in linked containers Closes: #12763 Signed-off-by: Doug Davis --- integration-cli/docker_cli_links_test.go | 21 +++++++++++++++++++++ links/links.go | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 95b340be2dc4f..6bb173c10cc20 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -310,3 +310,24 @@ func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) } } + +func (s *DockerSuite) TestLinksEnvs(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "-e", "e1=", "-e", "e2=v2", "-e", "e3=v3=v3", "--name=first", "busybox", "top") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + c.Fatalf("Run of first failed: %s\n%s", out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--name=second", "--link=first:first", "busybox", "env") + + out, stde, rc, err := runCommandWithStdoutStderr(runCmd) + if err != nil || rc != 0 { + c.Fatalf("run of 2nd failed: rc: %d, out: %s\n err: %s", rc, out, stde) + } + + if !strings.Contains(out, "FIRST_ENV_e1=\n") || + !strings.Contains(out, "FIRST_ENV_e2=v2") || + !strings.Contains(out, "FIRST_ENV_e3=v3=v3") { + c.Fatalf("Incorrect output: %s", out) + } +} diff --git a/links/links.go b/links/links.go index 8bbacdd3d7527..935bff4ae3911 100644 --- a/links/links.go +++ b/links/links.go @@ -107,8 +107,8 @@ func (l *Link) ToEnv() []string { if l.ChildEnvironment != nil { for _, v := range l.ChildEnvironment { - parts := strings.Split(v, "=") - if len(parts) != 2 { + parts := strings.SplitN(v, "=", 2) + if len(parts) < 2 { continue } // Ignore a few variables that are added during docker build (and not really relevant to linked containers) From cd4f507b42e800d148b211ca2c780d01192a9041 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Sat, 25 Apr 2015 05:46:47 -0700 Subject: [PATCH 297/332] Fix race condition in API commit test Signed-off-by: Doug Davis --- integration-cli/docker_api_containers_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index f14bd91c15e14..50958a6cd984f 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -614,14 +614,14 @@ func (s *DockerSuite) TestContainerApiTop(c *check.C) { } func (s *DockerSuite) TestContainerApiCommit(c *check.C) { - out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput() + cName := "testapicommit" + out, err := exec.Command(dockerBinary, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput() if err != nil { c.Fatal(err, out) } - id := strings.TrimSpace(string(out)) name := "testcommit" + stringid.GenerateRandomID() - status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) + status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+cName, nil) c.Assert(status, check.Equals, http.StatusCreated) c.Assert(err, check.IsNil) From fa9299f4c03cf097887db26dadcc63c711c925f1 Mon Sep 17 00:00:00 2001 From: Ed Costello Date: Sat, 25 Apr 2015 14:57:01 -0400 Subject: [PATCH 298/332] Copy edits for typos Signed-off-by: Ed Costello --- docs/sources/articles/b2d_volume_resize.md | 2 +- docs/sources/articles/networking.md | 2 +- docs/sources/articles/puppet.md | 2 +- docs/sources/articles/security.md | 2 +- docs/sources/installation/SUSE.md | 2 +- docs/sources/project/create-pr.md | 2 +- docs/sources/project/review-pr.md | 2 +- docs/sources/project/test-and-docs.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.10.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.11.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.12.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.13.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.14.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.15.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.16.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.17.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.18.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.19.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.9.md | 2 +- docs/sources/reference/builder.md | 8 ++++---- docs/sources/reference/run.md | 2 +- 21 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/sources/articles/b2d_volume_resize.md b/docs/sources/articles/b2d_volume_resize.md index 65238c669b579..53c8590955724 100644 --- a/docs/sources/articles/b2d_volume_resize.md +++ b/docs/sources/articles/b2d_volume_resize.md @@ -60,7 +60,7 @@ You might need to create the bus before you can add the ISO. ## 5. Add the new VDI image In the settings for the Boot2Docker image in VirtualBox, remove the VMDK image -from the SATA contoller and add the VDI image. +from the SATA controller and add the VDI image. diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 18529a0864208..823b450c75656 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -576,7 +576,7 @@ As soon as the router wants to send an IPv6 packet to the first container it will transmit a neighbor solicitation request, asking, who has `2001:db8::c009`? But it will get no answer because noone on this subnet has this address. The container with this address is hidden behind the Docker host. -The Docker host has to listen to neighbor solication requests for the container +The Docker host has to listen to neighbor solicitation requests for the container address and send a response that itself is the device that is responsible for the address. This is done by a Kernel feature called `NDP Proxy`. You can enable it by executing diff --git a/docs/sources/articles/puppet.md b/docs/sources/articles/puppet.md index 50504cd475b21..a1b3d273a4e33 100644 --- a/docs/sources/articles/puppet.md +++ b/docs/sources/articles/puppet.md @@ -1,5 +1,5 @@ page_title: Using Puppet -page_description: Installating and using Puppet +page_description: Installing and using Puppet page_keywords: puppet, installation, usage, docker, documentation # Using Puppet diff --git a/docs/sources/articles/security.md b/docs/sources/articles/security.md index 39a247c38e9e0..42d15e88c0452 100644 --- a/docs/sources/articles/security.md +++ b/docs/sources/articles/security.md @@ -249,7 +249,7 @@ may still be utilized by Docker containers on supported kernels, by directly using the clone syscall, or utilizing the 'unshare' utility. Using this, some users may find it possible to drop more capabilities from their process as user namespaces provide -an artifical capabilities set. Likewise, however, this artifical +an artificial capabilities set. Likewise, however, this artificial capabilities set may require use of 'capsh' to restrict the user-namespace capabilities set when using 'unshare'. diff --git a/docs/sources/installation/SUSE.md b/docs/sources/installation/SUSE.md index 2a0aa91d9f331..756ed6b5c1430 100644 --- a/docs/sources/installation/SUSE.md +++ b/docs/sources/installation/SUSE.md @@ -8,7 +8,7 @@ Docker is available in **openSUSE 12.3 and later**. Please note that due to its current limitations Docker is able to run only **64 bit** architecture. Docker is not part of the official repositories of openSUSE 12.3 and -openSUSE 13.1. Hence it is neccessary to add the [Virtualization +openSUSE 13.1. Hence it is necessary to add the [Virtualization repository](https://build.opensuse.org/project/show/Virtualization) from [OBS](https://build.opensuse.org/) to install the `docker` package. diff --git a/docs/sources/project/create-pr.md b/docs/sources/project/create-pr.md index e9123c463f378..613ab691123d1 100644 --- a/docs/sources/project/create-pr.md +++ b/docs/sources/project/create-pr.md @@ -77,7 +77,7 @@ Always rebase and squash your commits before making a pull request. `git commit -s` - Make sure your message includes Date: Wed, 22 Apr 2015 00:47:51 +0200 Subject: [PATCH 299/332] Replace json.Unmarshal with json.Decoder().Decode() Signed-off-by: Antonio Murdaca --- builder/parser/line_parsers.go | 2 +- daemon/execdriver/lxc/init.go | 8 ++++---- graph/load.go | 26 +++++++++++++++----------- graph/tags.go | 5 +++-- pkg/parsers/filters/parse.go | 3 +-- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/builder/parser/line_parsers.go b/builder/parser/line_parsers.go index 5f65a8762fa2f..8db360ca3753a 100644 --- a/builder/parser/line_parsers.go +++ b/builder/parser/line_parsers.go @@ -233,7 +233,7 @@ func parseString(rest string) (*Node, map[string]bool, error) { // parseJSON converts JSON arrays to an AST. func parseJSON(rest string) (*Node, map[string]bool, error) { var myJson []interface{} - if err := json.Unmarshal([]byte(rest), &myJson); err != nil { + if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJson); err != nil { return nil, nil, err } diff --git a/daemon/execdriver/lxc/init.go b/daemon/execdriver/lxc/init.go index 6cdbf775ea90c..eca1c02e21d68 100644 --- a/daemon/execdriver/lxc/init.go +++ b/daemon/execdriver/lxc/init.go @@ -4,7 +4,6 @@ import ( "encoding/json" "flag" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -107,12 +106,13 @@ func getArgs() *InitArgs { func setupEnv(args *InitArgs) error { // Get env var env []string - content, err := ioutil.ReadFile(".dockerenv") + dockerenv, err := os.Open(".dockerenv") if err != nil { return fmt.Errorf("Unable to load environment variables: %v", err) } - if err := json.Unmarshal(content, &env); err != nil { - return fmt.Errorf("Unable to unmarshal environment variables: %v", err) + defer dockerenv.Close() + if err := json.NewDecoder(dockerenv).Decode(&env); err != nil { + return fmt.Errorf("Unable to decode environment variables: %v", err) } // Propagate the plugin-specific container env variable env = append(env, "container="+os.Getenv("container")) diff --git a/graph/load.go b/graph/load.go index be968bab5b3e2..d978b1ee8e8ce 100644 --- a/graph/load.go +++ b/graph/load.go @@ -58,22 +58,26 @@ func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { } } - repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) - if err == nil { - repositories := map[string]Repository{} - if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { + reposJSONFile, err := os.Open(path.Join(tmpImageDir, "repo", "repositories")) + if err != nil { + if !os.IsNotExist(err) { return err } + return nil + } + defer reposJSONFile.Close() - for imageName, tagMap := range repositories { - for tag, address := range tagMap { - if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil { - return err - } + repositories := map[string]Repository{} + if err := json.NewDecoder(reposJSONFile).Decode(&repositories); err != nil { + return err + } + + for imageName, tagMap := range repositories { + for tag, address := range tagMap { + if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil { + return err } } - } else if !os.IsNotExist(err) { - return err } return nil diff --git a/graph/tags.go b/graph/tags.go index 39f0ffc29a93b..abffe2f562c76 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -115,11 +115,12 @@ func (store *TagStore) save() error { } func (store *TagStore) reload() error { - jsonData, err := ioutil.ReadFile(store.path) + f, err := os.Open(store.path) if err != nil { return err } - if err := json.Unmarshal(jsonData, store); err != nil { + defer f.Close() + if err := json.NewDecoder(f).Decode(&store); err != nil { return err } return nil diff --git a/pkg/parsers/filters/parse.go b/pkg/parsers/filters/parse.go index 9c056bb3cf34a..df5486d515801 100644 --- a/pkg/parsers/filters/parse.go +++ b/pkg/parsers/filters/parse.go @@ -58,8 +58,7 @@ func FromParam(p string) (Args, error) { if len(p) == 0 { return args, nil } - err := json.Unmarshal([]byte(p), &args) - if err != nil { + if err := json.NewDecoder(strings.NewReader(p)).Decode(&args); err != nil { return nil, err } return args, nil From ba79f0ca1f389e3c87c19b2c326b3b6afbebc8ef Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Sun, 26 Apr 2015 10:32:43 -0700 Subject: [PATCH 300/332] Adding James and theJeztah to the list Signed-off-by: Mary Anthony --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index b708af7745b89..5c02fa671be1c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -409,6 +409,8 @@ made through a pull request. "fredlf", "james", "moxiegirl", + "thaJeztah", + "jamtur01", "spf13", "sven" ] From cfd0f53b03d26c370e37abf651d7f481ee8c6912 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Sun, 26 Apr 2015 15:36:01 -0400 Subject: [PATCH 301/332] Add missing API docs about filtering by label. Signed-off-by: Daniel Nephin --- docs/sources/reference/api/docker_remote_api_v1.18.md | 2 ++ docs/sources/reference/api/docker_remote_api_v1.19.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index c97fe9c612188..a91ca8417a121 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -91,6 +91,7 @@ Query Parameters: - **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters: - exited=<int> -- containers with exit code of <int> - status=(restarting|running|paused|exited) + - label=`key` or `key=value` of a container label Status Codes: @@ -1191,6 +1192,7 @@ 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. Available filters: - dangling=true + - label=`key` or `key=value` of an image label ### Build image from a Dockerfile diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 8c27de1591c35..cede2e1073dd7 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -91,6 +91,7 @@ Query Parameters: - **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters: - exited=<int> -- containers with exit code of <int> - status=(restarting|running|paused|exited) + - label=`key` or `key=value` of a container label Status Codes: @@ -1194,6 +1195,7 @@ 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. Available filters: - dangling=true + - label=`key` or `key=value` of an image label ### Build image from a Dockerfile From 6d9439c627e36349679249d991400da45eb75587 Mon Sep 17 00:00:00 2001 From: He Simei Date: Mon, 27 Apr 2015 12:41:03 +0800 Subject: [PATCH 302/332] remove useless http call from export Signed-off-by: He Simei --- api/client/export.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/api/client/export.go b/api/client/export.go index 8f1642f609da9..1ff46f9b5701d 100644 --- a/api/client/export.go +++ b/api/client/export.go @@ -3,7 +3,6 @@ package client import ( "errors" "io" - "net/url" "os" flag "github.com/docker/docker/pkg/mflag" @@ -34,19 +33,9 @@ func (cli *DockerCli) CmdExport(args ...string) error { return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") } - if len(cmd.Args()) == 1 { - image := cmd.Arg(0) - if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil { - return err - } - } else { - v := url.Values{} - for _, arg := range cmd.Args() { - v.Add("names", arg) - } - if err := cli.stream("GET", "/containers/get?"+v.Encode(), nil, output, nil); err != nil { - return err - } + image := cmd.Arg(0) + if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil { + return err } return nil From accbbfeae4c2f3ceb68060e08e46e382cdefc975 Mon Sep 17 00:00:00 2001 From: jmzwcn Date: Mon, 27 Apr 2015 15:50:47 +0800 Subject: [PATCH 303/332] Remove dead code from daemon/daemon.go fix #12492 Signed-off-by: Daniel Zhang --- daemon/daemon.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index d186854ad8c5f..99f5ae6364f41 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -225,7 +225,6 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err if container.IsRunning() { logrus.Debugf("killing old running container %s", container.ID) - existingPid := container.Pid container.SetStopped(&execdriver.ExitStatus{ExitCode: 0}) // We only have to handle this for lxc because the other drivers will ensure that @@ -237,11 +236,6 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err cmd := &execdriver.Command{ ID: container.ID, } - var err error - cmd.ProcessConfig.Process, err = os.FindProcess(existingPid) - if err != nil { - logrus.Debugf("cannot find existing process for %d", existingPid) - } daemon.execDriver.Terminate(cmd) } From c7b2632dc8550c8aa80ebd8b229809ac37fa53e1 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 27 Apr 2015 13:56:55 +0200 Subject: [PATCH 304/332] Remove c.Fatal from goroutine in TestGetContainersAttachWebsocket Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_attach_test.go | 36 ++++++++++++++++------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/integration-cli/docker_api_attach_test.go b/integration-cli/docker_api_attach_test.go index ce7f85d306814..c784d5c369cef 100644 --- a/integration-cli/docker_api_attach_test.go +++ b/integration-cli/docker_api_attach_test.go @@ -40,24 +40,38 @@ func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) { expected := []byte("hello") actual := make([]byte, len(expected)) - outChan := make(chan string) + + outChan := make(chan error) go func() { - if _, err := ws.Read(actual); err != nil { - c.Fatal(err) - } - outChan <- "done" + _, err := ws.Read(actual) + outChan <- err + close(outChan) }() - inChan := make(chan string) + inChan := make(chan error) go func() { - if _, err := ws.Write(expected); err != nil { + _, err := ws.Write(expected) + inChan <- err + close(inChan) + }() + + select { + case err := <-inChan: + if err != nil { c.Fatal(err) } - inChan <- "done" - }() + case <-time.After(5 * time.Second): + c.Fatal("Timeout writing to ws") + } - <-inChan - <-outChan + select { + case err := <-outChan: + if err != nil { + c.Fatal(err) + } + case <-time.After(5 * time.Second): + c.Fatal("Timeout reading from ws") + } if !bytes.Equal(expected, actual) { c.Fatal("Expected output on websocket to match input") From f30d1c1835618eadea5d0a68d1301dffd9f09b22 Mon Sep 17 00:00:00 2001 From: Aidan Hobson Sayers Date: Mon, 27 Apr 2015 01:07:30 +0100 Subject: [PATCH 305/332] Prevent deadlock on attempt to use own net Signed-off-by: Aidan Hobson Sayers --- daemon/container.go | 3 +++ integration-cli/docker_cli_run_test.go | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/daemon/container.go b/daemon/container.go index bdfcbf4477863..01eef4d31d206 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1515,6 +1515,9 @@ func (container *Container) getNetworkedContainer() (*Container, error) { if err != nil { return nil, err } + if container == nc { + return nil, fmt.Errorf("cannot join own network") + } if !nc.IsRunning() { return nil, fmt.Errorf("cannot join network of a non running container: %s", parts[1]) } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 342137c477a88..b7961126cc607 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2657,6 +2657,14 @@ func (s *DockerSuite) TestContainerNetworkMode(c *check.C) { } } +func (s *DockerSuite) TestContainerNetworkModeToSelf(c *check.C) { + cmd := exec.Command(dockerBinary, "run", "--name=me", "--net=container:me", "busybox", "true") + out, _, err := runCommandWithOutput(cmd) + if err == nil || !strings.Contains(out, "cannot join own network") { + c.Fatalf("using container net mode to self should result in an error") + } +} + func (s *DockerSuite) TestRunModePidHost(c *check.C) { testRequires(c, NativeExecDriver, SameHostDaemon) From 9e0ffae864d6679084e84e2e27cbb9d350f6dbae Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 21 Apr 2015 21:51:41 -0400 Subject: [PATCH 306/332] remove some uneeded sleeps in tests Signed-off-by: Brian Goff --- integration-cli/docker_api_logs_test.go | 40 +++++++++++----- integration-cli/docker_cli_wait_test.go | 61 +++++++++++++++++-------- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index bf0e1fbf49a3f..a1723ef21a9f5 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -1,28 +1,46 @@ package main import ( + "bufio" "bytes" "fmt" "net/http" "os/exec" + "strings" + "time" "github.com/go-check/check" ) func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { - name := "logs_test" - - runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name) - if out, _, err := runCommandWithOutput(runCmd); err != nil { - c.Fatal(out, err) + out, _ := dockerCmd(c, "run", "-d", "-t", "busybox", "/bin/sh", "-c", "while true; do echo hello; sleep 1; done") + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + c.Fatal(err) } - status, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) - c.Assert(status, check.Equals, http.StatusOK) - c.Assert(err, check.IsNil) - - if !bytes.Contains(body, []byte(name)) { - c.Fatalf("Expected %s, got %s", name, string(body[:])) + type logOut struct { + out string + status int + err error + } + chLog := make(chan logOut) + + go func() { + statusCode, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", id), nil, "") + out, _ := bufio.NewReader(body).ReadString('\n') + chLog <- logOut{strings.TrimSpace(out), statusCode, err} + }() + + select { + case l := <-chLog: + c.Assert(l.status, check.Equals, http.StatusOK) + c.Assert(l.err, check.IsNil) + if !strings.HasSuffix(l.out, "hello") { + c.Fatalf("expected log output to container 'hello', but it does not") + } + case <-time.After(2 * time.Second): + c.Fatal("timeout waiting for logs to exit") } } diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go index 09d0272bcf03b..21f04faf0f8cd 100644 --- a/integration-cli/docker_cli_wait_test.go +++ b/integration-cli/docker_cli_wait_test.go @@ -44,19 +44,29 @@ func (s *DockerSuite) TestWaitNonBlockedExitZero(c *check.C) { // blocking wait with 0 exit code func (s *DockerSuite) TestWaitBlockedExitZero(c *check.C) { + out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "trap 'exit 0' SIGTERM; while true; do sleep 0.01; done") + containerID := strings.TrimSpace(out) - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10") - out, _, err := runCommandWithOutput(runCmd) - if err != nil { - c.Fatal(out, err) + if err := waitRun(containerID); err != nil { + c.Fatal(err) } - containerID := strings.TrimSpace(out) - runCmd = exec.Command(dockerBinary, "wait", containerID) - out, _, err = runCommandWithOutput(runCmd) + chWait := make(chan string) + go func() { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "wait", containerID)) + chWait <- out + }() - if err != nil || strings.TrimSpace(out) != "0" { - c.Fatal("failed to set up container", out, err) + time.Sleep(100 * time.Millisecond) + dockerCmd(c, "stop", containerID) + + select { + case status := <-chWait: + if strings.TrimSpace(status) != "0" { + c.Fatalf("expected exit 0, got %s", status) + } + case <-time.After(2 * time.Second): + c.Fatal("timeout waiting for `docker wait` to exit") } } @@ -97,19 +107,30 @@ func (s *DockerSuite) TestWaitNonBlockedExitRandom(c *check.C) { // blocking wait with random exit code func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) { - - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10; exit 99") - out, _, err := runCommandWithOutput(runCmd) - if err != nil { - c.Fatal(out, err) - } + out, _ := dockerCmd(c, "run", "-d", "busybox", "sh", "-c", "trap 'exit 99' SIGTERM; while true; do sleep 0.01; done") containerID := strings.TrimSpace(out) + if err := waitRun(containerID); err != nil { + c.Fatal(err) + } + if err := waitRun(containerID); err != nil { + c.Fatal(err) + } - runCmd = exec.Command(dockerBinary, "wait", containerID) - out, _, err = runCommandWithOutput(runCmd) + chWait := make(chan string) + go func() { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "wait", containerID)) + chWait <- out + }() - if err != nil || strings.TrimSpace(out) != "99" { - c.Fatal("failed to set up container", out, err) - } + time.Sleep(100 * time.Millisecond) + dockerCmd(c, "stop", containerID) + select { + case status := <-chWait: + if strings.TrimSpace(status) != "99" { + c.Fatalf("expected exit 99, got %s", status) + } + case <-time.After(2 * time.Second): + c.Fatal("timeout waiting for `docker wait` to exit") + } } From 29f379ea6e2a68d8ac4bf41d3559a52066a275a0 Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Mon, 27 Apr 2015 20:54:51 +0800 Subject: [PATCH 307/332] improve docker man page fix issue #12708: "docker man page is misleading about format of commands" Signed-off-by: Zhang Wei --- docs/man/docker.1.md | 114 ++++++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 38 deletions(-) diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 0196b6364eef6..75b9b80cc1749 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -128,120 +128,158 @@ unix://[/path/to/socket] to use. Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver. # COMMANDS -**docker-attach(1)** +**attach** Attach to a running container + See **docker-attach(1)** for full documentation on the **attach** command. -**docker-build(1)** +**build** Build an image from a Dockerfile + See **docker-build(1)** for full documentation on the **build** command. -**docker-commit(1)** +**commit** Create a new image from a container's changes + See **docker-commit(1)** for full documentation on the **commit** command. -**docker-cp(1)** +**cp** Copy files/folders from a container's filesystem to the host + See **docker-cp(1)** for full documentation on the **cp** command. -**docker-create(1)** +**create** Create a new container + See **docker-create(1)** for full documentation on the **create** command. -**docker-diff(1)** +**diff** Inspect changes on a container's filesystem + See **docker-diff(1)** for full documentation on the **diff** command. -**docker-events(1)** +**events** Get real time events from the server + See **docker-events(1)** for full documentation on the **events** command. -**docker-exec(1)** +**exec** Run a command in a running container + See **docker-exec(1)** for full documentation on the **exec** command. -**docker-export(1)** +**export** Stream the contents of a container as a tar archive + See **docker-export(1)** for full documentation on the **export** command. -**docker-history(1)** +**history** Show the history of an image + See **docker-history(1)** for full documentation on the **history** command. -**docker-images(1)** +**images** List images + See **docker-images(1)** for full documentation on the **images** command. -**docker-import(1)** +**import** Create a new filesystem image from the contents of a tarball + See **docker-import(1)** for full documentation on the **import** command. -**docker-info(1)** +**info** Display system-wide information + See **docker-info(1)** for full documentation on the **info** command. -**docker-inspect(1)** +**inspect** Return low-level information on a container or image + See **docker-inspect(1)** for full documentation on the **inspect** command. -**docker-kill(1)** +**kill** Kill a running container (which includes the wrapper process and everything inside it) + See **docker-kill(1)** for full documentation on the **kill** command. -**docker-load(1)** +**load** Load an image from a tar archive + See **docker-load(1)** for full documentation on the **load** command. -**docker-login(1)** +**login** Register or login to a Docker Registry + See **docker-login(1)** for full documentation on the **login** command. -**docker-logout(1)** +**logout** Log the user out of a Docker Registry + See **docker-logout(1)** for full documentation on the **logout** command. -**docker-logs(1)** +**logs** Fetch the logs of a container + See **docker-logs(1)** for full documentation on the **logs** command. -**docker-pause(1)** +**pause** Pause all processes within a container + See **docker-pause(1)** for full documentation on the **pause** command. -**docker-port(1)** +**port** Lookup the public-facing port which is NAT-ed to PRIVATE_PORT + See **docker-port(1)** for full documentation on the **port** command. -**docker-ps(1)** +**ps** List containers + See **docker-ps(1)** for full documentation on the **ps** command. -**docker-pull(1)** +**pull** Pull an image or a repository from a Docker Registry + See **docker-pull(1)** for full documentation on the **pull** command. -**docker-push(1)** +**push** Push an image or a repository to a Docker Registry + See **docker-push(1)** for full documentation on the **push** command. -**docker-restart(1)** +**restart** Restart a running container + See **docker-restart(1)** for full documentation on the **restart** command. -**docker-rm(1)** +**rm** Remove one or more containers + See **docker-rm(1)** for full documentation on the **rm** command. -**docker-rmi(1)** +**rmi** Remove one or more images + See **docker-rmi(1)** for full documentation on the **rmi** command. -**docker-run(1)** +**run** Run a command in a new container + See **docker-run(1)** for full documentation on the **run** command. -**docker-save(1)** +**save** Save an image to a tar archive + See **docker-save(1)** for full documentation on the **save** command. -**docker-search(1)** +**search** Search for an image in the Docker index + See **docker-search(1)** for full documentation on the **search** command. -**docker-start(1)** +**start** Start a stopped container + See **docker-start(1)** for full documentation on the **start** command. -**docker-stats(1)** +**stats** Display a live stream of one or more containers' resource usage statistics + See **docker-stats(1)** for full documentation on the **stats** command. -**docker-stop(1)** +**stop** Stop a running container + See **docker-stop(1)** for full documentation on the **stop** command. -**docker-tag(1)** +**tag** Tag an image into a repository + See **docker-tag(1)** for full documentation on the **tag** command. -**docker-top(1)** +**top** Lookup the running processes of a container + See **docker-top(1)** for full documentation on the **top** command. -**docker-unpause(1)** +**unpause** Unpause all processes within a container + See **docker-unpause(1)** for full documentation on the **unpause** command. -**docker-version(1)** +**version** Show the Docker version information + See **docker-version(1)** for full documentation on the **version** command. -**docker-wait(1)** +**wait** Block until a container stops, then print its exit code + See **docker-wait(1)** for full documentation on the **wait** command. # STORAGE DRIVER OPTIONS From ab97303caed2a7be474e2c4ff7a55af9d6c8c3cc Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 27 Apr 2015 08:38:01 -0700 Subject: [PATCH 308/332] Windows: Info no containerized check Signed-off-by: John Howard --- daemon/info.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/daemon/info.go b/daemon/info.go index 270abda598c11..df1c0530ccc0a 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -33,11 +33,15 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { if s, err := operatingsystem.GetOperatingSystem(); err == nil { operatingSystem = s } - if inContainer, err := operatingsystem.IsContainerized(); err != nil { - logrus.Errorf("Could not determine if daemon is containerized: %v", err) - operatingSystem += " (error determining if containerized)" - } else if inContainer { - operatingSystem += " (containerized)" + + // Don't do containerized check on Windows + if runtime.GOOS != "windows" { + if inContainer, err := operatingsystem.IsContainerized(); err != nil { + logrus.Errorf("Could not determine if daemon is containerized: %v", err) + operatingSystem += " (error determining if containerized)" + } else if inContainer { + operatingSystem += " (containerized)" + } } meminfo, err := system.ReadMemInfo() From ba1725a94ee75603d3a21e0580be3954fc689137 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 27 Apr 2015 09:25:38 -0700 Subject: [PATCH 309/332] Windows: Refactor volumes Signed-off-by: John Howard --- daemon/volumes.go | 16 ---------------- daemon/volumes_linux.go | 24 ++++++++++++++++++++++++ daemon/volumes_windows.go | 8 ++++++++ 3 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 daemon/volumes_linux.go create mode 100644 daemon/volumes_windows.go diff --git a/daemon/volumes.go b/daemon/volumes.go index 4d15023ba717c..ede1eef73c91a 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" - "github.com/docker/docker/pkg/system" ) type volumeMount struct { @@ -314,21 +313,6 @@ func copyExistingContents(source, destination string) error { 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 { - stat, err := system.Stat(source) - if 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())) -} - func (container *Container) mountVolumes() error { for dest, source := range container.Volumes { v := container.daemon.volumes.Get(source) diff --git a/daemon/volumes_linux.go b/daemon/volumes_linux.go new file mode 100644 index 0000000000000..93fea816598a2 --- /dev/null +++ b/daemon/volumes_linux.go @@ -0,0 +1,24 @@ +// +build !windows + +package daemon + +import ( + "os" + + "github.com/docker/docker/pkg/system" +) + +// copyOwnership copies the permissions and uid:gid of the source file +// into the destination file +func copyOwnership(source, destination string) error { + stat, err := system.Stat(source) + if 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())) +} diff --git a/daemon/volumes_windows.go b/daemon/volumes_windows.go new file mode 100644 index 0000000000000..ca1199a542d15 --- /dev/null +++ b/daemon/volumes_windows.go @@ -0,0 +1,8 @@ +// +build windows + +package daemon + +// Not supported on Windows +func copyOwnership(source, destination string) error { + return nil +} From bb1c576eb3024f7fb4242d76dc835b7c4dd85c5b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 27 Apr 2015 18:33:08 +0200 Subject: [PATCH 310/332] Expose whole Response struct in sockRequestRaw Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_containers_test.go | 50 +++++++++---------- integration-cli/docker_api_images_test.go | 8 +-- integration-cli/docker_api_logs_test.go | 12 ++--- integration-cli/docker_utils.go | 16 +++--- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e7ce0f3f59bd6..cd04c38dcf9f4 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -339,8 +339,8 @@ func (s *DockerSuite) TestBuildApiDockerfilePath(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - status, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(err, check.IsNil) out, err := readBody(body) @@ -365,8 +365,8 @@ RUN find /tmp/`, } defer server.Close() - status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -393,8 +393,8 @@ RUN echo from dockerfile`, } defer git.Close() - status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -421,8 +421,8 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -450,8 +450,8 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -483,8 +483,8 @@ func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - status, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(err, check.IsNil) out, err := readBody(body) @@ -720,7 +720,7 @@ func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { "Image": "busybox", } - create := func(ct string) (int, io.ReadCloser, error) { + create := func(ct string) (*http.Response, io.ReadCloser, error) { jsonData := bytes.NewBuffer(nil) if err := json.NewEncoder(jsonData).Encode(config); err != nil { c.Fatal(err) @@ -729,21 +729,21 @@ func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { } // Try with no content-type - status, body, err := create("") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err := create("") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) body.Close() // Try with wrong content-type - status, body, err = create("application/xml") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err = create("application/xml") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) body.Close() // now application/json - status, body, err = create("application/json") - c.Assert(status, check.Equals, http.StatusCreated) + res, body, err = create("application/json") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusCreated) body.Close() } @@ -774,8 +774,8 @@ func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { "NetworkDisabled":false, "OnBuild":null}` - status, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") - c.Assert(status, check.Equals, http.StatusCreated) + res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusCreated) c.Assert(err, check.IsNil) b, err := readBody(body) @@ -808,13 +808,13 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - status, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + res, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } @@ -831,13 +831,13 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - status, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") + res, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index ac3ad55d7eeab..e88fbaeaad9c3 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -74,9 +74,9 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { } id := strings.TrimSpace(out) - status, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusOK) defer body.Close() @@ -84,9 +84,9 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { c.Fatal(err, out) } - status, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") - c.Assert(status, check.Equals, http.StatusOK) + res, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusOK) defer loadBody.Close() diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index a1723ef21a9f5..f9284494d2e88 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -20,22 +20,22 @@ func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { } type logOut struct { - out string - status int - err error + out string + res *http.Response + err error } chLog := make(chan logOut) go func() { - statusCode, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", id), nil, "") + res, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", id), nil, "") out, _ := bufio.NewReader(body).ReadString('\n') - chLog <- logOut{strings.TrimSpace(out), statusCode, err} + chLog <- logOut{strings.TrimSpace(out), res, err} }() select { case l := <-chLog: - c.Assert(l.status, check.Equals, http.StatusOK) c.Assert(l.err, check.IsNil) + c.Assert(l.res.StatusCode, check.Equals, http.StatusOK) if !strings.HasSuffix(l.out, "hello") { c.Fatalf("expected log output to container 'hello', but it does not") } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 5d2a537e1d6b8..8386bb59ff962 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -313,20 +313,20 @@ func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) return -1, nil, err } - status, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") + res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") if err != nil { b, _ := ioutil.ReadAll(body) - return status, b, err + return -1, b, err } var b []byte b, err = readBody(body) - return status, b, err + return res.StatusCode, b, err } -func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io.ReadCloser, error) { +func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) { c, err := sockConn(time.Duration(10 * time.Second)) if err != nil { - return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err) + return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err) } client := httputil.NewClientConn(c, nil) @@ -334,7 +334,7 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io req, err := http.NewRequest(method, endpoint, data) if err != nil { client.Close() - return -1, nil, fmt.Errorf("could not create new request: %v", err) + return nil, nil, fmt.Errorf("could not create new request: %v", err) } if ct != "" { @@ -344,14 +344,14 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io resp, err := client.Do(req) if err != nil { client.Close() - return -1, nil, fmt.Errorf("could not perform request: %v", err) + return nil, nil, fmt.Errorf("could not perform request: %v", err) } body := ioutils.NewReadCloserWrapper(resp.Body, func() error { defer client.Close() return resp.Body.Close() }) - return resp.StatusCode, body, err + return resp, body, nil } func readBody(b io.ReadCloser) ([]byte, error) { From b6cfe7ca07722cee22345602595fc8fb928dbe79 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Sun, 26 Apr 2015 20:58:31 -0400 Subject: [PATCH 311/332] Do Debian installation verification with hello-world image. The hello-world image is recommended as a verification image and it is smaller than Ubuntu. Signed-off-by: Matt McCormick --- docs/sources/installation/debian.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index aeee1ecb1f680..da9e5f59b132b 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -28,9 +28,10 @@ To install the latest Debian package (may not be the latest Docker release): To verify that everything has worked as expected: - $ sudo docker run -i -t ubuntu /bin/bash + $ sudo docker run --rm hello-world -Which should download the `ubuntu` image, and then start `bash` in a container. +This command downloads and runs the `hello-world` image in a container. When the +container runs, it prints an informational message. Then, it exits. > **Note**: > If you want to enable memory and swap accounting see From 40779b28bb04dca6178ba17eaeb93265e2974ee1 Mon Sep 17 00:00:00 2001 From: Lorenzo Fontana Date: Mon, 27 Apr 2015 21:12:23 +0200 Subject: [PATCH 312/332] Parallelize TestEventsLimit Signed-off-by: Lorenzo Fontana --- integration-cli/docker_cli_events_test.go | 25 +++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 5f3616b21dd43..b1cc26b9a1b98 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -7,6 +7,7 @@ import ( "regexp" "strconv" "strings" + "sync" "time" "github.com/go-check/check" @@ -65,9 +66,29 @@ func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) { } func (s *DockerSuite) TestEventsLimit(c *check.C) { - for i := 0; i < 30; i++ { - dockerCmd(c, "run", "busybox", "echo", strconv.Itoa(i)) + + var waitGroup sync.WaitGroup + errChan := make(chan error, 17) + + args := []string{"run", "--rm", "busybox", "true"} + for i := 0; i < 17; i++ { + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + err := exec.Command(dockerBinary, args...).Run() + errChan <- err + }() + } + + waitGroup.Wait() + close(errChan) + + for err := range errChan { + if err != nil { + c.Fatalf("%q failed with error: %v", strings.Join(args, " "), err) + } } + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ := runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") From 844538142d95c1b7dda1bb2903179510105fe9b5 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 26 Apr 2015 18:50:25 +0200 Subject: [PATCH 313/332] Small if err cleaning Signed-off-by: Antonio Murdaca --- api/client/build.go | 2 +- api/client/diff.go | 3 +-- api/client/history.go | 3 +-- api/client/ps.go | 3 +-- api/client/rmi.go | 3 +-- api/client/search.go | 3 +-- api/client/top.go | 3 +-- api/common.go | 3 +-- api/server/server.go | 7 +++---- builder/internals.go | 11 +++++++++-- cliconfig/config.go | 3 +-- daemon/container.go | 3 +-- daemon/daemon.go | 3 +-- daemon/exec.go | 3 +-- daemon/graphdriver/devmapper/deviceset.go | 13 ++++++------- graph/graph.go | 5 ++--- graph/pull.go | 6 ++---- graph/push.go | 3 +-- image/image.go | 3 +-- integration-cli/docker_api_containers_test.go | 8 ++++---- integration-cli/utils.go | 3 +-- pkg/jsonlog/jsonlog_marshalling.go | 3 +-- pkg/mflag/flag.go | 3 +-- pkg/system/lstat.go | 3 +-- pkg/system/stat_linux.go | 3 +-- pkg/timeoutconn/timeoutconn.go | 3 +-- registry/session.go | 3 +-- registry/session_v2.go | 4 +--- trust/trusts.go | 9 +++------ 29 files changed, 51 insertions(+), 74 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 800e04ac9b94d..e83de976beb18 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -173,7 +173,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { includes = append(includes, ".dockerignore", *dockerfileName) } - if err = utils.ValidateContextDirectory(root, excludes); err != nil { + if err := utils.ValidateContextDirectory(root, excludes); err != nil { return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err) } options := &archive.TarOptions{ diff --git a/api/client/diff.go b/api/client/diff.go index a22734d046f05..6000c6b388f66 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -31,8 +31,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { } changes := []types.ContainerChange{} - err = json.NewDecoder(rdr).Decode(&changes) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&changes); err != nil { return err } diff --git a/api/client/history.go b/api/client/history.go index 79c6f3f7a6244..31b8535031f54 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -30,8 +30,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { } history := []types.ImageHistory{} - err = json.NewDecoder(rdr).Decode(&history) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&history); err != nil { return err } diff --git a/api/client/ps.go b/api/client/ps.go index 44f5ff0d21ea2..6c40c6867b871 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -92,8 +92,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { } containers := []types.Container{} - err = json.NewDecoder(rdr).Decode(&containers) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&containers); err != nil { return err } diff --git a/api/client/rmi.go b/api/client/rmi.go index 11c9ff32d04d8..a8590dc8203fd 100644 --- a/api/client/rmi.go +++ b/api/client/rmi.go @@ -37,8 +37,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error { encounteredError = fmt.Errorf("Error: failed to remove one or more images") } else { dels := []types.ImageDelete{} - err = json.NewDecoder(rdr).Decode(&dels) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&dels); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") continue diff --git a/api/client/search.go b/api/client/search.go index 4e493b234ac42..e606d479f14d2 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -51,8 +51,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { } results := ByStars{} - err = json.NewDecoder(rdr).Decode(&results) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&results); err != nil { return err } diff --git a/api/client/top.go b/api/client/top.go index 4975f47597f9e..ee16fdbf605ae 100644 --- a/api/client/top.go +++ b/api/client/top.go @@ -31,8 +31,7 @@ func (cli *DockerCli) CmdTop(args ...string) error { } procList := types.ContainerProcessList{} - err = json.NewDecoder(stream).Decode(&procList) - if err != nil { + if err := json.NewDecoder(stream).Decode(&procList); err != nil { return err } diff --git a/api/common.go b/api/common.go index cb627824e96ab..4a9523cd45c97 100644 --- a/api/common.go +++ b/api/common.go @@ -107,8 +107,7 @@ func MatchesContentType(contentType, expectedType string) bool { // LoadOrCreateTrustKey attempts to load the libtrust key at the given path, // otherwise generates a new one func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) { - err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700) - if err != nil { + if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil { return nil, err } trustKey, err := libtrust.LoadKeyFile(trustKeyPath) diff --git a/api/server/server.go b/api/server/server.go index 1e951d36c1e0d..61e8162659562 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -277,8 +277,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, if vars == nil { return fmt.Errorf("Missing parameter") } - err := parseForm(r) - if err != nil { + if err := parseForm(r); err != nil { return err } @@ -289,7 +288,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, if sigStr := vars["signal"]; sigStr != "" { // Check if we passed the signal as a number: // The largest legal signal is 31, so let's parse on 5 bits - sig, err = strconv.ParseUint(sigStr, 10, 5) + sig, err := strconv.ParseUint(sigStr, 10, 5) if err != nil { // The signal is not a number, treat it as a string (either like // "KILL" or like "SIGKILL") @@ -301,7 +300,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, } } - if err = s.daemon.ContainerKill(name, sig); err != nil { + if err := s.daemon.ContainerKill(name, sig); err != nil { return err } diff --git a/builder/internals.go b/builder/internals.go index ba7d45bcb18cc..53542a669b7e8 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -148,8 +148,15 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp // do the copy (e.g. hash value if cached). Don't actually do // the copy until we've looked at all src files for _, orig := range args[0 : len(args)-1] { - err := calcCopyInfo(b, cmdName, ©Infos, orig, dest, allowRemote, allowDecompression) - if err != nil { + if err := calcCopyInfo( + b, + cmdName, + ©Infos, + orig, + dest, + allowRemote, + allowDecompression, + ); err != nil { return err } } diff --git a/cliconfig/config.go b/cliconfig/config.go index 19a92fbd85424..2a27589d20dd9 100644 --- a/cliconfig/config.go +++ b/cliconfig/config.go @@ -166,8 +166,7 @@ func (configFile *ConfigFile) Save() error { return err } - err = ioutil.WriteFile(configFile.filename, data, 0600) - if err != nil { + if err := ioutil.WriteFile(configFile.filename, data, 0600); err != nil { return err } diff --git a/daemon/container.go b/daemon/container.go index 01eef4d31d206..10d6b4cd80881 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -149,8 +149,7 @@ func (container *Container) toDisk() error { return err } - err = ioutil.WriteFile(pth, data, 0666) - if err != nil { + if err := ioutil.WriteFile(pth, data, 0666); err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 99f5ae6364f41..f12ed21119c58 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1181,8 +1181,7 @@ func tempDir(rootDir string) (string, error) { if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" { tmpDir = filepath.Join(rootDir, "tmp") } - err := os.MkdirAll(tmpDir, 0700) - return tmpDir, err + return tmpDir, os.MkdirAll(tmpDir, 0700) } func checkKernel() error { diff --git a/daemon/exec.go b/daemon/exec.go index 22872adc44e89..9aa102690f4ac 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -214,8 +214,7 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout // the exitStatus) even after the cmd is done running. go func() { - err := container.Exec(execConfig) - if err != nil { + if err := container.Exec(execConfig); err != nil { execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err) } }() diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index b5d67fa119d0c..42b9d76bedf09 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -218,7 +218,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { } defer file.Close() - if err = file.Truncate(size); err != nil { + if err := file.Truncate(size); err != nil { return "", err } } @@ -697,7 +697,7 @@ func (devices *DeviceSet) setupBaseImage() error { logrus.Debugf("Creating filesystem on base device-mapper thin volume") - if err = devices.activateDeviceIfNeeded(info); err != nil { + if err := devices.activateDeviceIfNeeded(info); err != nil { return err } @@ -706,7 +706,7 @@ func (devices *DeviceSet) setupBaseImage() error { } info.Initialized = true - if err = devices.saveMetadata(info); err != nil { + if err := devices.saveMetadata(info); err != nil { info.Initialized = false return err } @@ -1099,14 +1099,14 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // If we didn't just create the data or metadata image, we need to // load the transaction id and migrate old metadata if !createdLoopback { - if err = devices.initMetaData(); err != nil { + if err := devices.initMetaData(); err != nil { return err } } // Right now this loads only NextDeviceId. If there is more metadata // down the line, we might have to move it earlier. - if err = devices.loadDeviceSetMetaData(); err != nil { + if err := devices.loadDeviceSetMetaData(); err != nil { return err } @@ -1528,8 +1528,7 @@ func (devices *DeviceSet) MetadataDevicePath() string { func (devices *DeviceSet) getUnderlyingAvailableSpace(loopFile string) (uint64, error) { buf := new(syscall.Statfs_t) - err := syscall.Statfs(loopFile, buf) - if err != nil { + if err := syscall.Statfs(loopFile, buf); err != nil { logrus.Warnf("Couldn't stat loopfile filesystem %v: %v", loopFile, err) return 0, err } diff --git a/graph/graph.go b/graph/graph.go index 5159a93223dd6..9b2d7c2ee9634 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -348,9 +348,8 @@ func (graph *Graph) Delete(name string) error { tmp, err := graph.Mktemp("") graph.idIndex.Delete(id) if err == nil { - err = os.Rename(graph.ImageRoot(id), tmp) - // On err make tmp point to old dir and cleanup unused tmp dir - if err != nil { + if err := os.Rename(graph.ImageRoot(id), tmp); err != nil { + // On err make tmp point to old dir and cleanup unused tmp dir os.RemoveAll(tmp) tmp = graph.ImageRoot(id) } diff --git a/graph/pull.go b/graph/pull.go index 0ebf75abb7c80..c3c064fc5842a 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -537,8 +537,7 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis di.err <- downloadFunc(di) }(&downloads[i]) } else { - err := downloadFunc(&downloads[i]) - if err != nil { + if err := downloadFunc(&downloads[i]); err != nil { return false, err } } @@ -548,8 +547,7 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis for i := len(downloads) - 1; i >= 0; i-- { d := &downloads[i] if d.err != nil { - err := <-d.err - if err != nil { + if err := <-d.err; err != nil { return false, err } } diff --git a/graph/push.go b/graph/push.go index 62ff94e0c41b9..1b33288d8fedf 100644 --- a/graph/push.go +++ b/graph/push.go @@ -367,8 +367,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o logrus.Debugf("Pushing layer: %s", layer.ID) if layer.Config != nil && metadata.Image != layer.ID { - err = runconfig.Merge(&metadata, layer.Config) - if err != nil { + if err := runconfig.Merge(&metadata, layer.Config); err != nil { return err } } diff --git a/image/image.go b/image/image.go index 90714d6dbe773..a34d2b940849f 100644 --- a/image/image.go +++ b/image/image.go @@ -268,8 +268,7 @@ func NewImgJSON(src []byte) (*Image, error) { func ValidateID(id string) error { validHex := regexp.MustCompile(`^([a-f0-9]{64})$`) if ok := validHex.MatchString(id); !ok { - err := fmt.Errorf("image ID '%s' is invalid", id) - return err + return fmt.Errorf("image ID '%s' is invalid", id) } return nil } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e7ce0f3f59bd6..a26afb16241df 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -176,7 +176,7 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { c.Fatal(out, err) } - name := "testing" + name := "TestContainerApiStartDupVolumeBinds" config := map[string]interface{}{ "Image": "busybox", "Volumes": map[string]struct{}{volPath: {}}, @@ -620,7 +620,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { c.Fatal(err, out) } - name := "testcommit" + stringid.GenerateRandomID() + name := "TestContainerApiCommit" status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+cName, nil) c.Assert(status, check.Equals, http.StatusCreated) c.Assert(err, check.IsNil) @@ -842,12 +842,12 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { } func (s *DockerSuite) TestContainerApiRename(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") + runCmd := exec.Command(dockerBinary, "run", "--name", "TestContainerApiRename", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) c.Assert(err, check.IsNil) containerID := strings.TrimSpace(out) - newName := "new_name" + stringid.GenerateRandomID() + newName := "TestContainerApiRenameNew" statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/rename?name="+newName, nil) // 204 No Content is expected, not 200 diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 4ca7158aeb7bd..f0de79ea8f071 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -169,8 +169,7 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in } func unmarshalJSON(data []byte, result interface{}) error { - err := json.Unmarshal(data, result) - if err != nil { + if err := json.Unmarshal(data, result); err != nil { return err } diff --git a/pkg/jsonlog/jsonlog_marshalling.go b/pkg/jsonlog/jsonlog_marshalling.go index 6244eb01a4fc2..abaa8a73baab6 100644 --- a/pkg/jsonlog/jsonlog_marshalling.go +++ b/pkg/jsonlog/jsonlog_marshalling.go @@ -65,8 +65,7 @@ import ( func (mj *JSONLog) MarshalJSON() ([]byte, error) { var buf bytes.Buffer buf.Grow(1024) - err := mj.MarshalJSONBuf(&buf) - if err != nil { + if err := mj.MarshalJSONBuf(&buf); err != nil { return nil, err } return buf.Bytes(), nil diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index f2da1cd1b91aa..f0d20d99b06c3 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -486,8 +486,7 @@ func (f *FlagSet) Set(name, value string) error { if !ok { return fmt.Errorf("no such flag -%v", name) } - err := flag.Value.Set(value) - if err != nil { + if err := flag.Value.Set(value); err != nil { return err } if f.actual == nil { diff --git a/pkg/system/lstat.go b/pkg/system/lstat.go index a966cd4881b2b..d0e43b3709784 100644 --- a/pkg/system/lstat.go +++ b/pkg/system/lstat.go @@ -12,8 +12,7 @@ import ( // Throws an error if the file does not exist func Lstat(path string) (*Stat_t, error) { s := &syscall.Stat_t{} - err := syscall.Lstat(path, s) - if err != nil { + if err := syscall.Lstat(path, s); err != nil { return nil, err } return fromStatT(s) diff --git a/pkg/system/stat_linux.go b/pkg/system/stat_linux.go index 928ba89e698d4..3899b3e0eeac7 100644 --- a/pkg/system/stat_linux.go +++ b/pkg/system/stat_linux.go @@ -20,8 +20,7 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { // Throws an error if the file does not exist func Stat(path string) (*Stat_t, error) { s := &syscall.Stat_t{} - err := syscall.Stat(path, s) - if err != nil { + if err := syscall.Stat(path, s); err != nil { return nil, err } return fromStatT(s) diff --git a/pkg/timeoutconn/timeoutconn.go b/pkg/timeoutconn/timeoutconn.go index 3a554559a4a97..d9534b5da7500 100644 --- a/pkg/timeoutconn/timeoutconn.go +++ b/pkg/timeoutconn/timeoutconn.go @@ -17,8 +17,7 @@ type conn struct { func (c *conn) Read(b []byte) (int, error) { if c.timeout > 0 { - err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) - if err != nil { + if err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout)); err != nil { return 0, err } } diff --git a/registry/session.go b/registry/session.go index f7358bc1025d6..e65f82cd6103e 100644 --- a/registry/session.go +++ b/registry/session.go @@ -597,8 +597,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) } result := new(SearchResults) - err = json.NewDecoder(res.Body).Decode(result) - return result, err + return result, json.NewDecoder(res.Body).Decode(result) } func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig { diff --git a/registry/session_v2.go b/registry/session_v2.go index a14e434acf43e..4188e505bda51 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -387,10 +387,8 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) } - decoder := json.NewDecoder(res.Body) var remote remoteTags - err = decoder.Decode(&remote) - if err != nil { + if err := json.NewDecoder(res.Body).Decode(&remote); err != nil { return nil, fmt.Errorf("Error while decoding the http response: %s", err) } return remote.Tags, nil diff --git a/trust/trusts.go b/trust/trusts.go index c4a2f4158b21f..885127ee5d6f1 100644 --- a/trust/trusts.go +++ b/trust/trusts.go @@ -62,8 +62,7 @@ func NewTrustStore(path string) (*TrustStore, error) { baseEndpoints: endpoints, } - err = t.reload() - if err != nil { + if err := t.reload(); err != nil { return nil, err } @@ -170,8 +169,7 @@ func (t *TrustStore) fetch() { continue } // TODO check if value differs - err = ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600) - if err != nil { + if err := ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600); err != nil { logrus.Infof("Error writing trust graph statement: %s", err) } fetchCount++ @@ -180,8 +178,7 @@ func (t *TrustStore) fetch() { if fetchCount > 0 { go func() { - err := t.reload() - if err != nil { + if err := t.reload(); err != nil { logrus.Infof("Reload of trust graph failed: %s", err) } }() From 3941623fbc3fa724d61f53121513ffd87d03b61c Mon Sep 17 00:00:00 2001 From: David Mackey Date: Mon, 27 Apr 2015 13:33:30 -0700 Subject: [PATCH 314/332] trivial: typo cleanup Signed-off-by: David Mackey --- builder/internals.go | 2 +- contrib/docker-device-tool/device_tool.go | 2 +- daemon/daemon.go | 2 +- daemon/networkdriver/ipallocator/allocator_test.go | 2 +- engine/streams_test.go | 2 +- integration-cli/docker_api_containers_test.go | 2 +- integration-cli/docker_cli_attach_test.go | 4 ++-- integration-cli/docker_cli_attach_unix_test.go | 8 ++++---- integration-cli/docker_cli_build_test.go | 4 ++-- integration-cli/docker_cli_commit_test.go | 2 +- integration-cli/docker_cli_daemon_test.go | 2 +- integration-cli/docker_cli_ps_test.go | 2 +- integration-cli/docker_cli_push_test.go | 4 ++-- integration-cli/docker_cli_rmi_test.go | 2 +- integration-cli/docker_cli_run_test.go | 4 ++-- integration-cli/docker_cli_run_unix_test.go | 4 ++-- integration-cli/docker_cli_save_load_test.go | 2 +- integration-cli/docker_cli_start_test.go | 2 +- integration/api_test.go | 4 ++-- integration/container_test.go | 2 +- integration/runtime_test.go | 2 +- integration/utils.go | 2 +- pkg/archive/archive_windows_test.go | 2 +- pkg/graphdb/graphdb_test.go | 4 ++-- pkg/term/winconsole/console_windows_test.go | 2 +- registry/registry_test.go | 2 +- runconfig/config_test.go | 2 +- runconfig/merge.go | 2 +- volumes/repository.go | 2 +- 29 files changed, 39 insertions(+), 39 deletions(-) diff --git a/builder/internals.go b/builder/internals.go index ba7d45bcb18cc..d373de6c17040 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -483,7 +483,7 @@ func (b *Builder) processImageFrom(img *imagepkg.Image) error { fmt.Fprintf(b.ErrStream, "# Executing %d build triggers\n", nTriggers) } - // Copy the ONBUILD triggers, and remove them from the config, since the config will be commited. + // Copy the ONBUILD triggers, and remove them from the config, since the config will be committed. onBuildTriggers := b.Config.OnBuild b.Config.OnBuild = []string{} diff --git a/contrib/docker-device-tool/device_tool.go b/contrib/docker-device-tool/device_tool.go index 9ad094a341a77..0a0b0803d39ac 100644 --- a/contrib/docker-device-tool/device_tool.go +++ b/contrib/docker-device-tool/device_tool.go @@ -125,7 +125,7 @@ func main() { err = devices.ResizePool(size) if err != nil { - fmt.Println("Error resizeing pool: ", err) + fmt.Println("Error resizing pool: ", err) os.Exit(1) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 99f5ae6364f41..45a5af28f569b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -823,7 +823,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService // Load storage driver driver, err := graphdriver.New(config.Root, config.GraphOptions) if err != nil { - return nil, fmt.Errorf("error intializing graphdriver: %v", err) + return nil, fmt.Errorf("error initializing graphdriver: %v", err) } logrus.Debugf("Using graph driver %s", driver) // register cleanup for graph driver diff --git a/daemon/networkdriver/ipallocator/allocator_test.go b/daemon/networkdriver/ipallocator/allocator_test.go index fffe6e3389c6f..6c5c0e4dbcc4e 100644 --- a/daemon/networkdriver/ipallocator/allocator_test.go +++ b/daemon/networkdriver/ipallocator/allocator_test.go @@ -601,7 +601,7 @@ func TestRegisterBadTwice(t *testing.T) { Mask: []byte{255, 255, 255, 248}, } if err := a.RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered { - t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err) + t.Fatalf("Expected ErrNetworkAlreadyRegistered error, got %v", err) } } diff --git a/engine/streams_test.go b/engine/streams_test.go index 476a721baf71e..c22338a32e752 100644 --- a/engine/streams_test.go +++ b/engine/streams_test.go @@ -182,7 +182,7 @@ func TestInputAddEmpty(t *testing.T) { t.Fatal(err) } if len(data) > 0 { - t.Fatalf("Read from empty input shoul yield no data") + t.Fatalf("Read from empty input should yield no data") } } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e7ce0f3f59bd6..9a2300270d03f 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -643,7 +643,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { // sanity check, make sure the image is what we think it is out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput() if err != nil { - c.Fatalf("error checking commited image: %v - %q", err, string(out)) + c.Fatalf("error checking committed image: %v - %q", err, string(out)) } } diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 11ae1584ad2b2..4dd45ddff3c5b 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -161,7 +161,7 @@ func (s *DockerSuite) TestAttachDisconnect(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } if err := stdin.Close(); err != nil { @@ -174,7 +174,7 @@ func (s *DockerSuite) TestAttachDisconnect(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } } diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index 5567c92a0b943..bae83d994a064 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -172,7 +172,7 @@ func (s *DockerSuite) TestAttachDetach(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } // escape sequence @@ -195,7 +195,7 @@ func (s *DockerSuite) TestAttachDetach(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } go func() { @@ -243,7 +243,7 @@ func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } // escape sequence @@ -266,7 +266,7 @@ func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } go func() { diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 6d6805aef5480..695e4cd6e6dd1 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -3408,7 +3408,7 @@ func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { out, rc, err := runCommandWithOutput(exec.Command(dockerBinary, "inspect", name)) if rc != 0 || err != nil { - c.Fatalf("Unexcepted error from inspect: rc: %v err: %v", rc, err) + c.Fatalf("Unexpected error from inspect: rc: %v err: %v", rc, err) } if !strings.Contains(out, "\"123\"") { @@ -5033,7 +5033,7 @@ RUN echo " \ expecting := "\n foo \n" if !strings.Contains(out, expecting) { - c.Fatalf("Bad output: %q expecting to contian %q", out, expecting) + c.Fatalf("Bad output: %q expecting to contain %q", out, expecting) } } diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 1544b3aace4bb..391cd4ebc5a34 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -262,7 +262,7 @@ func (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) { out, _ = dockerCmd(c, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { - c.Fatal("run config in commited container was not merged") + c.Fatal("run config in committed container was not merged") } type cfg struct { diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 2a945827e1caa..034c17ebbb8ac 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -860,7 +860,7 @@ func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { if err := d1.Start(); err == nil { d1.Stop() - c.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) + c.Fatalf("It should not be successful to start daemon with wrong key: %v", err) } content, _ := ioutil.ReadFile(d1.logFile.Name()) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 881f02d4fe74d..271051815a4e9 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -547,7 +547,7 @@ func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { } ids = strings.Split(strings.TrimSpace(out), "\n") if len(ids) != 2 { - c.Fatalf("Should be 2 zero exited containerst got %d", len(ids)) + c.Fatalf("Should be 2 zero exited containers got %d", len(ids)) } if ids[0] != secondNonZero { c.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0]) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 8f7ee31588b72..69a05ed821c78 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -41,7 +41,7 @@ func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) { expected := "Repository does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: output %q", out) } else if !strings.Contains(out, expected) { c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } @@ -53,7 +53,7 @@ func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) { expected := "does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: output %q", out) } else if !strings.Contains(out, expected) { c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 234fa22f0a413..9dc2ee297a8d7 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -108,7 +108,7 @@ func (s *DockerSuite) TestRmiImgIDForce(c *check.C) { runCmd = exec.Command(dockerBinary, "rmi", imgID) out, _, err = runCommandWithOutput(runCmd) if err == nil || !strings.Contains(out, fmt.Sprintf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", imgID)) { - c.Fatalf("rmi tagged in mutiple repos should have failed without force:%s, %v", out, err) + c.Fatalf("rmi tagged in multiple repos should have failed without force:%s, %v", out, err) } dockerCmd(c, "rmi", "-f", imgID) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index b7961126cc607..c3b25558d9ec7 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -329,7 +329,7 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", cID) ip, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - c.Fatalf("faild to inspect container: %v, output: %q", err, ip) + c.Fatalf("failed to inspect container: %v, output: %q", err, ip) } ip = strings.TrimSpace(ip) cmd = exec.Command(dockerBinary, "run", "--link", cID+":test", "busybox", "/bin/cat", "/etc/hosts") @@ -2067,7 +2067,7 @@ func (s *DockerSuite) TestRunCidFileCleanupIfEmpty(c *check.C) { if err == nil { c.Fatalf("Run without command must fail. out=%s", out) } else if !strings.Contains(out, "No command specified") { - c.Fatalf("Run without command failed with wrong outpuc. out=%s\nerr=%v", out, err) + c.Fatalf("Run without command failed with wrong output. out=%s\nerr=%v", out, err) } if _, err := os.Stat(tmpCidFile); err == nil { diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 74fae1735259d..43fa821509afc 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -213,7 +213,7 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } // escape sequence @@ -236,7 +236,7 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } go func() { diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index fe6bf2bfc24ab..f83f6645ac616 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -333,7 +333,7 @@ func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { sort.Strings(actual) sort.Strings(expected) if !reflect.DeepEqual(expected, actual) { - c.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected) + c.Fatalf("archive does not contains the right layers: got %v, expected %v", actual, expected) } } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 52afac1af1a07..13ecedd57c8d3 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -205,7 +205,7 @@ func (s *DockerSuite) TestStartMultipleContainers(c *check.C) { c.Fatal("Container should be stopped") } - // start all the three containers, container `child_first` start first which should be faild + // start all the three containers, container `child_first` start first which should be failed // container 'parent' start second and then start container 'child_second' cmd = exec.Command(dockerBinary, "start", "child_first", "parent", "child_second") out, _, err = runCommandWithOutput(cmd) diff --git a/integration/api_test.go b/integration/api_test.go index 614966f6e5f1d..e45fa97e8288e 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -434,7 +434,7 @@ func TestGetEnabledCors(t *testing.T) { t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth\", %s found.", allowHeaders) } if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" { - t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) + t.Errorf("Expected header Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) } } @@ -648,7 +648,7 @@ func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) { } if c.HostConfig.Binds[0] != "/tmp:/foo" { - t.Fatal("Chunked encoding not properly handled, execpted binds to be /tmp:/foo, got:", c.HostConfig.Binds[0]) + t.Fatal("Chunked encoding not properly handled, expected binds to be /tmp:/foo, got:", c.HostConfig.Binds[0]) } } diff --git a/integration/container_test.go b/integration/container_test.go index 01078734cfc89..9256e9997f2a4 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -213,7 +213,7 @@ func BenchmarkRunParallel(b *testing.B) { return } // if string(output) != "foo" { - // complete <- fmt.Errorf("Unexecpted output: %v", string(output)) + // complete <- fmt.Errorf("Unexpected output: %v", string(output)) // } if err := daemon.Rm(container); err != nil { complete <- err diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 82f21b70083c0..a2f22072c36b8 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -837,7 +837,7 @@ func TestDestroyWithInitLayer(t *testing.T) { // Make sure that the container does not exist in the driver if _, err := driver.Get(container.ID, ""); err == nil { - t.Fatal("Conttainer should not exist in the driver") + t.Fatal("Container should not exist in the driver") } // Make sure that the init layer is removed from the driver diff --git a/integration/utils.go b/integration/utils.go index 1d27cd6e42234..62e02e9bb141f 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -41,7 +41,7 @@ func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container { }) if container == nil { - t.Fatal("An error occured while waiting for the container to start") + t.Fatal("An error occurred while waiting for the container to start") } return container diff --git a/pkg/archive/archive_windows_test.go b/pkg/archive/archive_windows_test.go index b33e0fb0055a5..72bc71e06b413 100644 --- a/pkg/archive/archive_windows_test.go +++ b/pkg/archive/archive_windows_test.go @@ -20,7 +20,7 @@ func TestCanonicalTarNameForPath(t *testing.T) { if out, err := CanonicalTarNameForPath(v.in); err != nil && !v.shouldFail { t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err) } else if v.shouldFail && err == nil { - t.Fatalf("canonical path call should have pailed with error. in=%s out=%s", v.in, out) + t.Fatalf("canonical path call should have failed with error. in=%s out=%s", v.in, out) } else if !v.shouldFail && out != v.expected { t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out) } diff --git a/pkg/graphdb/graphdb_test.go b/pkg/graphdb/graphdb_test.go index 12dd524ed5342..1cd223bd9cd89 100644 --- a/pkg/graphdb/graphdb_test.go +++ b/pkg/graphdb/graphdb_test.go @@ -52,7 +52,7 @@ func TestGetRootEntity(t *testing.T) { t.Fatal("Entity should not be nil") } if e.ID() != "0" { - t.Fatalf("Enity id should be 0, got %s", e.ID()) + t.Fatalf("Entity id should be 0, got %s", e.ID()) } } @@ -74,7 +74,7 @@ func TestSetDuplicateEntity(t *testing.T) { t.Fatal(err) } if _, err := db.Set("/foo", "43"); err == nil { - t.Fatalf("Creating an entry with a duplciate path did not cause an error") + t.Fatalf("Creating an entry with a duplicate path did not cause an error") } } diff --git a/pkg/term/winconsole/console_windows_test.go b/pkg/term/winconsole/console_windows_test.go index ee9d96834b854..edb5d6f66123a 100644 --- a/pkg/term/winconsole/console_windows_test.go +++ b/pkg/term/winconsole/console_windows_test.go @@ -18,7 +18,7 @@ func helpsTestParseInt16OrDefault(t *testing.T, expectedValue int16, shouldFail t.Errorf(format, args) } if expectedValue != value { - t.Errorf("The value returned does not macth expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value) + t.Errorf("The value returned does not match expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value) t.Errorf(format, args) } } diff --git a/registry/registry_test.go b/registry/registry_test.go index b4bd4ee724c79..3f63eb6e257fa 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -736,7 +736,7 @@ func TestSearchRepositories(t *testing.T) { } assertEqual(t, results.NumResults, 1, "Expected 1 search results") assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query") - assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars") + assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars") } func TestValidRemoteName(t *testing.T) { diff --git a/runconfig/config_test.go b/runconfig/config_test.go index e36dacbf44795..87fc6c6aaca34 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -104,7 +104,7 @@ func TestParseRunVolumes(t *testing.T) { if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil { t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds) } else if _, exists := config.Volumes["/tmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Recevied %v", config.Volumes) + t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes) } else if _, exists := config.Volumes["/var"]; !exists { t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes) } diff --git a/runconfig/merge.go b/runconfig/merge.go index ce6697dbfc6cf..9c9a3b4367553 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -41,7 +41,7 @@ func Merge(userConf, imageConf *Config) error { } if len(imageConf.PortSpecs) > 0 { // FIXME: I think we can safely remove this. Leaving it for now for the sake of reverse-compat paranoia. - logrus.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) + logrus.Debugf("Migrating image port specs to container: %s", strings.Join(imageConf.PortSpecs, ", ")) if userConf.ExposedPorts == nil { userConf.ExposedPorts = make(nat.PortSet) } diff --git a/volumes/repository.go b/volumes/repository.go index 0dac3753dad6a..71d6c0ad60afa 100644 --- a/volumes/repository.go +++ b/volumes/repository.go @@ -58,7 +58,7 @@ func (r *Repository) newVolume(path string, writable bool) (*Volume, error) { path = filepath.Clean(path) // Ignore the error here since the path may not exist - // Really just want to make sure the path we are using is real(or non-existant) + // Really just want to make sure the path we are using is real(or nonexistent) if cleanPath, err := filepath.EvalSymlinks(path); err == nil { path = cleanPath } From c3c08f76bec023218b632e4c688ff9fcda11fcef Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 27 Apr 2015 16:10:59 -0400 Subject: [PATCH 315/332] Fix undead containers When a container has errors on removal, it gets flagged as dead. If you `docker rm -f` a dead container the container is dereffed from the daemon and doesn't show up on `docker ps` anymore... except that the container JSON file may still be lingering around and becomes undead when you restart the daemon. Signed-off-by: Brian Goff --- daemon/delete.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/delete.go b/daemon/delete.go index d398741d75678..464193b283ab1 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -129,6 +129,7 @@ func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err erro if err != nil && forceRemove { daemon.idIndex.Delete(container.ID) daemon.containers.Delete(container.ID) + os.RemoveAll(container.root) } }() From 57464c32b99b36a2963fb37da86b9870c9a56145 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Sat, 25 Apr 2015 19:47:42 -0700 Subject: [PATCH 316/332] Implement daemon suite for integration-cli For creating and stopping test daemons automatically. Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 21 ++ integration-cli/docker_cli_daemon_test.go | 387 +++++++++------------- integration-cli/docker_cli_exec_test.go | 15 +- integration-cli/docker_cli_proxy_test.go | 5 +- 4 files changed, 178 insertions(+), 250 deletions(-) diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 533ac127c4468..202799cbf153b 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -58,3 +58,24 @@ func (s *DockerRegistrySuite) TearDownTest(c *check.C) { s.reg.Close() s.ds.TearDownTest(c) } + +func init() { + check.Suite(&DockerDaemonSuite{ + ds: &DockerSuite{}, + }) +} + +type DockerDaemonSuite struct { + ds *DockerSuite + d *Daemon +} + +func (s *DockerDaemonSuite) SetUpTest(c *check.C) { + s.d = NewDaemon(c) + s.ds.SetUpTest(c) +} + +func (s *DockerDaemonSuite) TearDownTest(c *check.C) { + s.d.Stop() + s.ds.TearDownTest(c) +} diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 034c17ebbb8ac..e099995ad3aed 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -16,25 +16,23 @@ import ( "github.com/go-check/check" ) -func (s *DockerSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top1: err=%v\n%s", err, out) } // --restart=no by default - if out, err := d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top2: err=%v\n%s", err, out) } testRun := func(m map[string]bool, prefix string) { var format string for cont, shouldRun := range m { - out, err := d.Cmd("ps") + out, err := s.d.Cmd("ps") if err != nil { c.Fatalf("Could not run ps: err=%v\n%q", err, out) } @@ -51,34 +49,30 @@ func (s *DockerSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { testRun(map[string]bool{"top1": true, "top2": true}, "") - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } - testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ") - } -func (s *DockerSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil { c.Fatal(err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatal(err) } - if _, err := d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil { + if _, err := s.d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil { c.Fatal(err) } - if out, err := d.Cmd("rm", "-fv", "volrestarttest2"); err != nil { + if out, err := s.d.Cmd("rm", "-fv", "volrestarttest2"); err != nil { c.Fatal(err, out) } - v, err := d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1") + v, err := s.d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1") if err != nil { c.Fatal(err) } @@ -87,30 +81,25 @@ func (s *DockerSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { if _, err := os.Stat(volumes["/foo"]); err != nil { c.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err) } - } -func (s *DockerSuite) TestDaemonStartIptablesFalse(c *check.C) { - d := NewDaemon(c) - if err := d.Start("--iptables=false"); err != nil { +func (s *DockerDaemonSuite) TestDaemonStartIptablesFalse(c *check.C) { + if err := s.d.Start("--iptables=false"); err != nil { c.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err) } - d.Stop() - } // Issue #8444: If docker0 bridge is modified (intentionally or unintentionally) and // no longer has an IP associated, we should gracefully handle that case and associate // an IP with it rather than fail daemon start -func (s *DockerSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { - d := NewDaemon(c) +func (s *DockerDaemonSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { // rather than depending on brctl commands to verify docker0 is created and up // let's start the daemon and stop it, and then make a modification to run the // actual test - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatalf("Could not stop daemon: %v", err) } @@ -121,27 +110,18 @@ func (s *DockerSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { c.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr) } - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { warning := "**WARNING: Docker bridge network in bad state--delete docker0 bridge interface to fix" c.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning) } - - // cleanup - stop the daemon if test passed - if err := d.Stop(); err != nil { - c.Fatalf("Could not stop daemon: %v", err) - } - } -func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { - - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonIptablesClean(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top: %s, %v", out, err) } @@ -157,7 +137,7 @@ func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatalf("Could not stop daemon: %v", err) } @@ -171,18 +151,14 @@ func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { if strings.Contains(out, ipTablesSearchString) { c.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out) } - } -func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { - - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonIptablesCreate(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top: %s, %v", out, err) } @@ -198,12 +174,12 @@ func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } // make sure the container is not running - runningOut, err := d.Cmd("inspect", "--format='{{.State.Running}}'", "top") + runningOut, err := s.d.Cmd("inspect", "--format='{{.State.Running}}'", "top") if err != nil { c.Fatalf("Could not inspect on container: %s, %v", out, err) } @@ -221,69 +197,64 @@ func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { if !strings.Contains(out, ipTablesSearchString) { c.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out) } - } -func (s *DockerSuite) TestDaemonLoggingLevel(c *check.C) { - d := NewDaemon(c) - - if err := d.Start("--log-level=bogus"); err == nil { - c.Fatal("Daemon should not have been able to start") - } +func (s *DockerDaemonSuite) TestDaemonLogLevelWrong(c *check.C) { + c.Assert(s.d.Start("--log-level=bogus"), check.NotNil, check.Commentf("Daemon shouldn't start with wrong log level")) +} - d = NewDaemon(c) - if err := d.Start("--log-level=debug"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLogLevelDebug(c *check.C) { + if err := s.d.Start("--log-level=debug"); err != nil { c.Fatal(err) } - d.Stop() - content, _ := ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("--log-level=fatal"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLogLevelFatal(c *check.C) { + // we creating new daemons to create new logFile + if err := s.d.Start("--log-level=fatal"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if strings.Contains(string(content), `level=debug`) { c.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("-D"); err != nil { +func (s *DockerDaemonSuite) TestDaemonFlagD(c *check.C) { + if err := s.d.Start("-D"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("--debug"); err != nil { +func (s *DockerDaemonSuite) TestDaemonFlagDebug(c *check.C) { + if err := s.d.Start("--debug"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("--debug", "--log-level=fatal"); err != nil { +func (s *DockerDaemonSuite) TestDaemonFlagDebugLogLevelFatal(c *check.C) { + if err := s.d.Start("--debug", "--log-level=fatal"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) } - } -func (s *DockerSuite) TestDaemonAllocatesListeningPort(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonAllocatesListeningPort(c *check.C) { listeningPorts := [][]string{ {"0.0.0.0", "0.0.0.0", "5678"}, {"127.0.0.1", "127.0.0.1", "1234"}, @@ -295,31 +266,25 @@ func (s *DockerSuite) TestDaemonAllocatesListeningPort(c *check.C) { cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2])) } - d := NewDaemon(c) - if err := d.StartWithBusybox(cmdArgs...); err != nil { + if err := s.d.StartWithBusybox(cmdArgs...); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() for _, hostDirective := range listeningPorts { - output, err := d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true") + output, err := s.d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true") if err == nil { c.Fatalf("Container should not start, expected port already allocated error: %q", output) } else if !strings.Contains(output, "port is already allocated") { c.Fatalf("Expected port is already allocated error: %q", output) } } - } // #9629 -func (s *DockerSuite) TestDaemonVolumesBindsRefs(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonVolumesBindsRefs(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() tmp, err := ioutil.TempDir(os.TempDir(), "") if err != nil { @@ -331,28 +296,26 @@ func (s *DockerSuite) TestDaemonVolumesBindsRefs(c *check.C) { c.Fatal(err) } - if out, err := d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil { + if out, err := s.d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil { c.Fatal(err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatal(err) } - if out, err := d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil { + if out, err := s.d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil { c.Fatal(err, out) } - } -func (s *DockerSuite) TestDaemonKeyGeneration(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonKeyGeneration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") - d := NewDaemon(c) - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } - d.Stop() + s.d.Stop() k, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { @@ -363,10 +326,9 @@ func (s *DockerSuite) TestDaemonKeyGeneration(c *check.C) { if len(kid) != 59 { c.Fatalf("Bad key ID: %s", kid) } - } -func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonKeyMigration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") k1, err := libtrust.GenerateECP256PrivateKey() @@ -380,11 +342,10 @@ func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { c.Fatalf("Error saving private key: %s", err) } - d := NewDaemon(c) - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } - d.Stop() + s.d.Stop() k2, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { @@ -393,29 +354,25 @@ func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { if k1.KeyID() != k2.KeyID() { c.Fatalf("Key not migrated") } - } // Simulate an older daemon (pre 1.3) coming up with volumes specified in containers // without corresponding volume json -func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { - d := NewDaemon(c) - +func (s *DockerDaemonSuite) TestDaemonUpgradeWithVolumes(c *check.C) { graphDir := filepath.Join(os.TempDir(), "docker-test") defer os.RemoveAll(graphDir) - if err := d.StartWithBusybox("-g", graphDir); err != nil { + if err := s.d.StartWithBusybox("-g", graphDir); err != nil { c.Fatal(err) } - defer d.Stop() tmpDir := filepath.Join(os.TempDir(), "test") defer os.RemoveAll(tmpDir) - if out, err := d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil { + if out, err := s.d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil { c.Fatal(err, out) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatal(err) } @@ -430,7 +387,7 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { c.Fatal(err) } - if err := d.Start("-g", graphDir); err != nil { + if err := s.d.Start("-g", graphDir); err != nil { c.Fatal(err) } @@ -447,7 +404,7 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { } // Now with just removing the volume config and not the volume data - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatal(err) } @@ -455,7 +412,7 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { c.Fatal(err) } - if err := d.Start("-g", graphDir); err != nil { + if err := s.d.Start("-g", graphDir); err != nil { c.Fatal(err) } @@ -467,45 +424,37 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { if len(dir) == 0 { c.Fatalf("expected volumes config dir to contain data for new volume") } - } // GH#11320 - verify that the daemon exits on failure properly // Note that this explicitly tests the conflict of {-b,--bridge} and {--bip} options as the means // to get a daemon init failure; no other tests for -b/--bip conflict are therefore required -func (s *DockerSuite) TestDaemonExitOnFailure(c *check.C) { - d := NewDaemon(c) - defer d.Stop() - +func (s *DockerDaemonSuite) TestDaemonExitOnFailure(c *check.C) { //attempt to start daemon with incorrect flags (we know -b and --bip conflict) - if err := d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil { + if err := s.d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil { //verify we got the right error if !strings.Contains(err.Error(), "Daemon exited and never started") { c.Fatalf("Expected daemon not to start, got %v", err) } // look in the log and make sure we got the message that daemon is shutting down - runCmd := exec.Command("grep", "Error starting daemon", d.LogfileName()) + runCmd := exec.Command("grep", "Error starting daemon", s.d.LogfileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { c.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) } } else { //if we didn't get an error and the daemon is running, this is a failure - d.Stop() c.Fatal("Conflicting options should cause the daemon to error out with a failure") } - } -func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { testRequires(c, NativeExecDriver) - d := NewDaemon(c) - if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil { + if err := s.d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)") + out, err := s.d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)") if err != nil { c.Fatal(out, err) } @@ -525,11 +474,11 @@ func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { } // Now restart daemon with a new default - if err := d.Restart("--default-ulimit", "nofile=43"); err != nil { + if err := s.d.Restart("--default-ulimit", "nofile=43"); err != nil { c.Fatal(err) } - out, err = d.Cmd("start", "-a", "test") + out, err = s.d.Cmd("start", "-a", "test") if err != nil { c.Fatal(err) } @@ -547,53 +496,46 @@ func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { if nproc != "2048" { c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) } - } // #11315 -func (s *DockerSuite) TestDaemonRestartRenameContainer(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartRenameContainer(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() - if out, err := d.Cmd("run", "--name=test", "busybox"); err != nil { + if out, err := s.d.Cmd("run", "--name=test", "busybox"); err != nil { c.Fatal(err, out) } - if out, err := d.Cmd("rename", "test", "test2"); err != nil { + if out, err := s.d.Cmd("rename", "test", "test2"); err != nil { c.Fatal(err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatal(err) } - if out, err := d.Cmd("start", "test2"); err != nil { + if out, err := s.d.Cmd("start", "test2"); err != nil { c.Fatal(err, out) } - } -func (s *DockerSuite) TestDaemonLoggingDriverDefault(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefault(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { c.Fatal(err) @@ -621,72 +563,63 @@ func (s *DockerSuite) TestDaemonLoggingDriverDefault(c *check.C) { } } -func (s *DockerSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } } -func (s *DockerSuite) TestDaemonLoggingDriverNone(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox("--log-driver=none"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverNone(c *check.C) { + if err := s.d.StartWithBusybox("--log-driver=none"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } } -func (s *DockerSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox("--log-driver=none"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { + if err := s.d.StartWithBusybox("--log-driver=none"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { c.Fatal(err) @@ -714,20 +647,17 @@ func (s *DockerSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { } } -func (s *DockerSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox("--log-driver=none"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { + if err := s.d.StartWithBusybox("--log-driver=none"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - out, err = d.Cmd("logs", id) + out, err = s.d.Cmd("logs", id) if err == nil { c.Fatalf("Logs should fail with \"none\" driver") } @@ -736,54 +666,50 @@ func (s *DockerSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { } } -func (s *DockerSuite) TestDaemonDots(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonDots(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() // Now create 4 containers - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - d.Stop() + s.d.Stop() - d.Start("--log-level=debug") - d.Stop() - content, _ := ioutil.ReadFile(d.logFile.Name()) + s.d.Start("--log-level=debug") + s.d.Stop() + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if strings.Contains(string(content), "....") { c.Fatalf("Debug level should not have ....\n%s", string(content)) } - d.Start("--log-level=error") - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + s.d.Start("--log-level=error") + s.d.Stop() + content, _ = ioutil.ReadFile(s.d.logFile.Name()) if strings.Contains(string(content), "....") { c.Fatalf("Error level should not have ....\n%s", string(content)) } - d.Start("--log-level=info") - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + s.d.Start("--log-level=info") + s.d.Stop() + content, _ = ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), "....") { c.Fatalf("Info level should have ....\n%s", string(content)) } - } -func (s *DockerSuite) TestDaemonUnixSockCleanedUp(c *check.C) { - d := NewDaemon(c) +func (s *DockerDaemonSuite) TestDaemonUnixSockCleanedUp(c *check.C) { dir, err := ioutil.TempDir("", "socket-cleanup-test") if err != nil { c.Fatal(err) @@ -791,26 +717,24 @@ func (s *DockerSuite) TestDaemonUnixSockCleanedUp(c *check.C) { defer os.RemoveAll(dir) sockPath := filepath.Join(dir, "docker.sock") - if err := d.Start("--host", "unix://"+sockPath); err != nil { + if err := s.d.Start("--host", "unix://"+sockPath); err != nil { c.Fatal(err) } - defer d.Stop() if _, err := os.Stat(sockPath); err != nil { c.Fatal("socket does not exist") } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatal(err) } if _, err := os.Stat(sockPath); err == nil || !os.IsNotExist(err) { c.Fatal("unix socket is not cleaned up") } - } -func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonwithwrongkey(c *check.C) { type Config struct { Crv string `json:"crv"` D string `json:"d"` @@ -821,12 +745,11 @@ func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { } os.Remove("/etc/docker/key.json") - d := NewDaemon(c) - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Failed to start daemon: %v", err) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatalf("Could not stop daemon: %v", err) } @@ -855,46 +778,41 @@ func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { c.Fatalf("Error ioutil.WriteFile: %s", err) } - d1 := NewDaemon(c) defer os.Remove("/etc/docker/key.json") - if err := d1.Start(); err == nil { - d1.Stop() + if err := s.d.Start(); err == nil { c.Fatalf("It should not be successful to start daemon with wrong key: %v", err) } - content, _ := ioutil.ReadFile(d1.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), "Public Key ID does not match") { c.Fatal("Missing KeyID message from daemon logs") } - } -func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartKillWait(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - out, err := d.Cmd("run", "-id", "busybox", "/bin/cat") + out, err := s.d.Cmd("run", "-id", "busybox", "/bin/cat") if err != nil { c.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) } containerID := strings.TrimSpace(out) - if out, err := d.Cmd("kill", containerID); err != nil { + if out, err := s.d.Cmd("kill", containerID); err != nil { c.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } errchan := make(chan error) go func() { - if out, err := d.Cmd("wait", containerID); err != nil { + if out, err := s.d.Cmd("wait", containerID); err != nil { errchan <- fmt.Errorf("%v:\n%s", err, out) } close(errchan) @@ -908,26 +826,23 @@ func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { c.Fatal(err) } } - } // TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint -func (s *DockerSuite) TestHttpsInfo(c *check.C) { +func (s *DockerDaemonSuite) TestHttpsInfo(c *check.C) { const ( testDaemonHttpsAddr = "localhost:4271" ) - d := NewDaemon(c) - if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() //force tcp protocol host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-cert.pem", "--tlskey", "fixtures/https/client-key.pem"} - out, err := d.CmdWithArgs(daemonArgs, "info") + out, err := s.d.CmdWithArgs(daemonArgs, "info") if err != nil { c.Fatalf("Error Occurred: %s and output: %s", err, out) } @@ -935,22 +850,20 @@ func (s *DockerSuite) TestHttpsInfo(c *check.C) { // TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint // by using a rogue client certificate and checks that it fails with the expected error. -func (s *DockerSuite) TestHttpsInfoRogueCert(c *check.C) { +func (s *DockerDaemonSuite) TestHttpsInfoRogueCert(c *check.C) { const ( errBadCertificate = "remote error: bad certificate" testDaemonHttpsAddr = "localhost:4271" ) - d := NewDaemon(c) - if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() //force tcp protocol host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} - out, err := d.CmdWithArgs(daemonArgs, "info") + out, err := s.d.CmdWithArgs(daemonArgs, "info") if err == nil || !strings.Contains(out, errBadCertificate) { c.Fatalf("Expected err: %s, got instead: %s and output: %s", errBadCertificate, err, out) } @@ -958,22 +871,20 @@ func (s *DockerSuite) TestHttpsInfoRogueCert(c *check.C) { // TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint // which provides a rogue server certificate and checks that it fails with the expected error -func (s *DockerSuite) TestHttpsInfoRogueServerCert(c *check.C) { +func (s *DockerDaemonSuite) TestHttpsInfoRogueServerCert(c *check.C) { const ( errCaUnknown = "x509: certificate signed by unknown authority" testDaemonRogueHttpsAddr = "localhost:4272" ) - d := NewDaemon(c) - if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-rogue-cert.pem", + if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-rogue-cert.pem", "--tlskey", "fixtures/https/server-rogue-key.pem", "-H", testDaemonRogueHttpsAddr); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() //force tcp protocol host := fmt.Sprintf("tcp://%s", testDaemonRogueHttpsAddr) daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} - out, err := d.CmdWithArgs(daemonArgs, "info") + out, err := s.d.CmdWithArgs(daemonArgs, "info") if err == nil || !strings.Contains(out, errCaUnknown) { c.Fatalf("Expected err: %s, got instead: %s and output: %s", errCaUnknown, err, out) } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index c6910d4321570..cbdd5975654ee 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -117,28 +117,26 @@ func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { } -func (s *DockerSuite) TestExecAfterDaemonRestart(c *check.C) { +func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) { testRequires(c, SameHostDaemon) - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top: err=%v\n%s", err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } - if out, err := d.Cmd("start", "top"); err != nil { + if out, err := s.d.Cmd("start", "top"); err != nil { c.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) } - out, err := d.Cmd("exec", "top", "echo", "hello") + out, err := s.d.Cmd("exec", "top", "echo", "hello") if err != nil { c.Fatalf("Could not exec on container top: err=%v\n%s", err, out) } @@ -147,7 +145,6 @@ func (s *DockerSuite) TestExecAfterDaemonRestart(c *check.C) { if outStr != "hello" { c.Errorf("container should've printed hello, instead printed %q", outStr) } - } // Regression test for #9155, #9044 diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go index c07ed64068517..8b55c67d81445 100644 --- a/integration-cli/docker_cli_proxy_test.go +++ b/integration-cli/docker_cli_proxy_test.go @@ -22,7 +22,7 @@ func (s *DockerSuite) TestCliProxyDisableProxyUnixSock(c *check.C) { // Can't use localhost here since go has a special case to not use proxy if connecting to localhost // See https://golang.org/pkg/net/http/#ProxyFromEnvironment -func (s *DockerSuite) TestCliProxyProxyTCPSock(c *check.C) { +func (s *DockerDaemonSuite) TestCliProxyProxyTCPSock(c *check.C) { testRequires(c, SameHostDaemon) // get the IP to use to connect since we can't use localhost addrs, err := net.InterfaceAddrs() @@ -43,8 +43,7 @@ func (s *DockerSuite) TestCliProxyProxyTCPSock(c *check.C) { c.Fatal("could not find ip to connect to") } - d := NewDaemon(c) - if err := d.Start("-H", "tcp://"+ip+":2375"); err != nil { + if err := s.d.Start("-H", "tcp://"+ip+":2375"); err != nil { c.Fatal(err) } From d4bbbe58ddd5916c5f7d253a90fd97e3a4fd59bf Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 28 Jan 2015 14:54:25 -0800 Subject: [PATCH 317/332] Add docs for `--exec-opt` and setting `native.cgroupdriver`. update man pages. update bash completion. Docker-DCO-1.1-Signed-off-by: Jessica Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- contrib/completion/bash/docker | 1 + contrib/completion/fish/docker.fish | 1 + docs/man/docker.1.md | 15 +++++++++++++++ docs/sources/reference/commandline/cli.md | 21 +++++++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f3b83315873f4..5b7a102a68396 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1151,6 +1151,7 @@ _docker() { --dns --dns-search --exec-driver -e + --exec-opt --fixed-cidr --fixed-cidr-v6 --graph -g diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index d3237588effe8..c5359118538fc 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -51,6 +51,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -s d -l daemon -d 'Enable complete -c docker -f -n '__fish_docker_no_subcommand' -l dns -d 'Force Docker to use specific DNS servers' complete -c docker -f -n '__fish_docker_no_subcommand' -l dns-search -d 'Force Docker to use specific DNS search domains' complete -c docker -f -n '__fish_docker_no_subcommand' -s e -l exec-driver -d 'Force the Docker runtime to use a specific exec driver' +complete -c docker -f -n '__fish_docker_no_subcommand' -l exec-opt -d 'Set exec driver options' complete -c docker -f -n '__fish_docker_no_subcommand' -l fixed-cidr -d 'IPv4 subnet for fixed IPs (e.g. 10.20.0.0/16)' complete -c docker -f -n '__fish_docker_no_subcommand' -l fixed-cidr-v6 -d 'IPv6 subnet for fixed IPs (e.g.: 2001:a02b/48)' complete -c docker -f -n '__fish_docker_no_subcommand' -s G -l group -d 'Group to assign the unix socket specified by -H when running in daemon mode' diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 0196b6364eef6..8ee5bc3cf86ea 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -124,6 +124,9 @@ unix://[/path/to/socket] to use. **-v**, **--version**=*true*|*false* Print version information and quit. Default is false. +**--exec-opt**=[] + Set exec driver options. See EXEC DRIVER OPTIONS. + **--selinux-enabled**=*true*|*false* Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver. @@ -319,6 +322,18 @@ for data and metadata: --storage-opt dm.metadatadev=/dev/vdc \ --storage-opt dm.basesize=20G +# EXEC DRIVER OPTIONS + +Options to the exec-driver can be specified with the **--exec-opt** flags. The +only driver accepting options is the *native* (libcontainer) driver. Therefore +use these flags with **-s=**native. + +The following is the only *native* option: + +#### native.cgroupdriver +Specifies the management of the container's cgroups. As of now the only viable +options are `cgroupfs` and `systemd`. The option will always fallback to `cgroupfs`. + #### Client For specific client examples please see the man page for the specific Docker command. For example: diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ffa199..e2d073a42d32e 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -452,6 +452,27 @@ https://linuxcontainers.org/) via the `lxc` execution driver, however, this is not where the primary development of new functionality is taking place. Add `-e lxc` to the daemon flags to use the `lxc` execution driver. +#### Exec driver options + +Particular exec-driver can be configured with options specified with +`--exec-opt` flags. The only driver accepting options is `native` +(libcontainer) as of now. All its options are prefixed with `native`. + +Currently supported options are: + + * `native.cgroupdriver` + + Specifies the management of the container's cgroups. As of now the only + viable options are `cgroupfs` and `systemd`. The option will always + fallback to `cgroupfs`. By default, if no option is specified, the + execdriver will try `systemd` and fallback to `cgroupfs`. Same applies if + `systemd` is passed as the `cgroupdriver` but is not capable of being used. + + Example use: + + $ sudo docker -d --exec-opt native.cgroupdriver=cgroupfs + + ### Daemon DNS options From 2afcd10202283478cbafb21e8c5f90f1236acccc Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 6 Apr 2015 11:47:55 -0700 Subject: [PATCH 318/332] option to configure cgroups Signed-off-by: Jessica Frazelle --- daemon/config.go | 2 ++ daemon/daemon.go | 2 +- daemon/execdriver/execdrivers/execdrivers.go | 4 +-- daemon/execdriver/native/driver.go | 38 +++++++++++++++++++- docs/man/docker.1.md | 14 ++++---- docs/sources/reference/commandline/cli.md | 32 +++++++---------- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 952fb5f743474..43b08531b52e1 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -29,6 +29,7 @@ type Config struct { GraphDriver string GraphOptions []string ExecDriver string + ExecOptions []string Mtu int SocketGroup string EnableCors bool @@ -70,6 +71,7 @@ func (config *Config) InstallFlags() { flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API") opts.IPVar(&config.Bridge.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports") opts.ListVar(&config.GraphOptions, []string{"-storage-opt"}, "Set storage driver options") + opts.ListVar(&config.ExecOptions, []string{"-exec-opt"}, "Set exec driver options") // FIXME: why the inconsistency between "hosts" and "sockets"? opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use") opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use") diff --git a/daemon/daemon.go b/daemon/daemon.go index 109ee35cb3822..05de402174c82 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -942,7 +942,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService sysInfo := sysinfo.New(false) const runDir = "/var/run/docker" - ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, config.Root, sysInitPath, sysInfo) + ed, err := execdrivers.NewDriver(config.ExecDriver, config.ExecOptions, runDir, config.Root, sysInitPath, sysInfo) if err != nil { return nil, err } diff --git a/daemon/execdriver/execdrivers/execdrivers.go b/daemon/execdriver/execdrivers/execdrivers.go index f6f97c930266d..dde0be1f0f463 100644 --- a/daemon/execdriver/execdrivers/execdrivers.go +++ b/daemon/execdriver/execdrivers/execdrivers.go @@ -10,7 +10,7 @@ import ( "github.com/docker/docker/pkg/sysinfo" ) -func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { +func NewDriver(name string, options []string, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { switch name { case "lxc": // we want to give the lxc driver the full docker root because it needs @@ -18,7 +18,7 @@ func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) ( // to be backwards compatible return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor) case "native": - return native.NewDriver(path.Join(root, "execdriver", "native"), initPath) + return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, options) } return nil, fmt.Errorf("unknown exec driver %s", name) } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index ad13e1c1eb700..afc3f1e45ee4f 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -8,12 +8,14 @@ import ( "os" "os/exec" "path/filepath" + "strings" "sync" "syscall" "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" + "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/reexec" sysinfo "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/term" @@ -39,7 +41,7 @@ type driver struct { sync.Mutex } -func NewDriver(root, initPath string) (*driver, error) { +func NewDriver(root, initPath string, options []string) (*driver, error) { meminfo, err := sysinfo.ReadMemInfo() if err != nil { return nil, err @@ -52,11 +54,45 @@ func NewDriver(root, initPath string) (*driver, error) { if err := apparmor.InstallDefaultProfile(); err != nil { return nil, err } + + // choose cgroup manager + // this makes sure there are no breaking changes to people + // who upgrade from versions without native.cgroupdriver opt cgm := libcontainer.Cgroupfs if systemd.UseSystemd() { cgm = libcontainer.SystemdCgroups } + // parse the options + for _, option := range options { + key, val, err := parsers.ParseKeyValueOpt(option) + if err != nil { + return nil, err + } + key = strings.ToLower(key) + switch key { + case "native.cgroupdriver": + // override the default if they set options + switch val { + case "systemd": + if systemd.UseSystemd() { + cgm = libcontainer.SystemdCgroups + } else { + // warn them that they chose the wrong driver + logrus.Warn("You cannot use systemd as native.cgroupdriver, using cgroupfs instead") + } + case "cgroupfs": + cgm = libcontainer.Cgroupfs + default: + return nil, fmt.Errorf("Unknown native.cgroupdriver given %q. try cgroupfs or systemd", val) + } + default: + return nil, fmt.Errorf("Unknown option %s\n", key) + } + } + + logrus.Debugf("Using %v as native.cgroupdriver", cgm) + f, err := libcontainer.New( root, cgm, diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 8ee5bc3cf86ea..794fb16883694 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -324,15 +324,15 @@ for data and metadata: # EXEC DRIVER OPTIONS -Options to the exec-driver can be specified with the **--exec-opt** flags. The -only driver accepting options is the *native* (libcontainer) driver. Therefore -use these flags with **-s=**native. - -The following is the only *native* option: +Use the **--exec-opt** flags to specify options to the exec-driver. The only +driver that accepts this flag is the *native* (libcontainer) driver. As a +result, you must also specify **-s=**native for this option to have effect. The +following is the only *native* option: #### native.cgroupdriver -Specifies the management of the container's cgroups. As of now the only viable -options are `cgroupfs` and `systemd`. The option will always fallback to `cgroupfs`. +Specifies the management of the container's `cgroups`. You can specify +`cgroupfs` or `systemd`. If you specify `systemd` and it is not available, the +system uses `cgroupfs`. #### Client For specific client examples please see the man page for the specific Docker diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e2d073a42d32e..2348ff549c270 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -442,7 +442,7 @@ Currently supported options are: > Otherwise, set this flag for migrating existing Docker daemons to a > daemon with a supported environment. -### Docker exec-driver option +### Docker execdriver option The Docker daemon uses a specifically built `libcontainer` execution driver as its interface to the Linux kernel `namespaces`, `cgroups`, and `SELinux`. @@ -452,27 +452,21 @@ https://linuxcontainers.org/) via the `lxc` execution driver, however, this is not where the primary development of new functionality is taking place. Add `-e lxc` to the daemon flags to use the `lxc` execution driver. -#### Exec driver options +#### Options for the native execdriver -Particular exec-driver can be configured with options specified with -`--exec-opt` flags. The only driver accepting options is `native` -(libcontainer) as of now. All its options are prefixed with `native`. - -Currently supported options are: - - * `native.cgroupdriver` - - Specifies the management of the container's cgroups. As of now the only - viable options are `cgroupfs` and `systemd`. The option will always - fallback to `cgroupfs`. By default, if no option is specified, the - execdriver will try `systemd` and fallback to `cgroupfs`. Same applies if - `systemd` is passed as the `cgroupdriver` but is not capable of being used. - - Example use: - - $ sudo docker -d --exec-opt native.cgroupdriver=cgroupfs +You can configure the `native` (libcontainer) execdriver using options specified +with the `--exec-opt` flag. All the flag's options have the `native` prefix. A +single `native.cgroupdriver` option is available. +The `native.cgroupdriver` option specifies the management of the container's +cgroups. You can specify `cgroupfs` or `systemd`. If you specify `systemd` and +it is not available, the system uses `cgroupfs`. By default, if no option is +specified, the execdriver first tries `systemd` and falls back to `cgroupfs`. +This example sets the execdriver to `cgroupfs`: + $ sudo docker -d --exec-opt native.cgroupdriver=cgroupfs + +Setting this option applies to all containers the daemon launches. ### Daemon DNS options From af8efab7561484debffa3f6782eaea199fa22506 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Tue, 10 Mar 2015 11:21:09 -0700 Subject: [PATCH 319/332] add initial docs for windows client testing Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) docs: crop and optimize images Cropped and optimized images. Signed-off-by: Sebastiaan van Stijn Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Testing and updating the Windows material Signed-off-by: Mary Anthony checkpoint Signed-off-by: Mary Anthony Fitting the Windows specific material into the contributor guide Signed-off-by: Mary Anthony Entering James comments Signed-off-by: Mary Anthony --- docs/mkdocs.yml | 33 +-- docs/sources/project/images/git_bash.png | Bin 0 -> 39522 bytes docs/sources/project/images/include_gcc.png | Bin 0 -> 16446 bytes docs/sources/project/images/path_variable.png | Bin 0 -> 59864 bytes .../project/images/windows-env-vars.png | Bin 0 -> 120562 bytes docs/sources/project/images/windows-mingw.png | Bin 0 -> 230524 bytes docs/sources/project/set-up-git.md | 5 +- docs/sources/project/software-req-win.md | 258 ++++++++++++++++++ docs/sources/project/software-required.md | 5 +- docs/sources/project/test-and-docs.md | 40 +++ 10 files changed, 322 insertions(+), 19 deletions(-) create mode 100644 docs/sources/project/images/git_bash.png create mode 100644 docs/sources/project/images/include_gcc.png create mode 100644 docs/sources/project/images/path_variable.png create mode 100644 docs/sources/project/images/windows-env-vars.png create mode 100644 docs/sources/project/images/windows-mingw.png create mode 100644 docs/sources/project/software-req-win.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e425175f61657..a67910b214109 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -34,6 +34,7 @@ pages: - ['installation/ubuntulinux.md', 'Installation', 'Ubuntu'] - ['installation/mac.md', 'Installation', 'Mac OS X'] - ['installation/windows.md', 'Installation', 'Microsoft Windows'] +- ['installation/testing-windows-docker-client.md', 'Installation', 'Building and testing the Windows Docker client'] - ['installation/amazon.md', 'Installation', 'Amazon EC2'] - ['installation/archlinux.md', 'Installation', 'Arch Linux'] - ['installation/binaries.md', 'Installation', 'Binaries'] @@ -195,21 +196,21 @@ pages: - ['terms/image.md', '**HIDDEN**'] - - # Project: - ['project/index.md', '**HIDDEN**'] -- ['project/who-written-for.md', 'Contribute', 'README first'] -- ['project/software-required.md', 'Contribute', 'Get required software'] -- ['project/set-up-git.md', 'Contribute', 'Configure Git for contributing'] -- ['project/set-up-dev-env.md', 'Contribute', 'Work with a development container'] -- ['project/test-and-docs.md', 'Contribute', 'Run tests and test documentation'] -- ['project/make-a-contribution.md', 'Contribute', 'Understand contribution workflow'] -- ['project/find-an-issue.md', 'Contribute', 'Find an issue'] -- ['project/work-issue.md', 'Contribute', 'Work on an issue'] -- ['project/create-pr.md', 'Contribute', 'Create a pull request'] -- ['project/review-pr.md', 'Contribute', 'Participate in the PR review'] -- ['project/advanced-contributing.md', 'Contribute', 'Advanced contributing'] -- ['project/get-help.md', 'Contribute', 'Where to get help'] -- ['project/coding-style.md', 'Contribute', 'Coding style guide'] -- ['project/doc-style.md', 'Contribute', 'Documentation style guide'] +- ['project/who-written-for.md', 'Contributor', 'README first'] +- ['project/software-required.md', 'Contributor', 'Get required software for Linux or OS X'] +- ['project/software-req-win.md', 'Contributor', 'Get required software for Windows'] +- ['project/set-up-git.md', 'Contributor', 'Configure Git for contributing'] +- ['project/set-up-dev-env.md', 'Contributor', 'Work with a development container'] +- ['project/test-and-docs.md', 'Contributor', 'Run tests and test documentation'] +- ['project/make-a-contribution.md', 'Contributor', 'Understand contribution workflow'] +- ['project/find-an-issue.md', 'Contributor', 'Find an issue'] +- ['project/work-issue.md', 'Contributor', 'Work on an issue'] +- ['project/create-pr.md', 'Contributor', 'Create a pull request'] +- ['project/review-pr.md', 'Contributor', 'Participate in the PR review'] +- ['project/advanced-contributing.md', 'Contributor', 'Advanced contributing'] +- ['project/get-help.md', 'Contributor', 'Where to get help'] +- ['project/coding-style.md', 'Contributor', 'Coding style guide'] +- ['project/doc-style.md', 'Contributor', 'Documentation style guide'] + diff --git a/docs/sources/project/images/git_bash.png b/docs/sources/project/images/git_bash.png new file mode 100644 index 0000000000000000000000000000000000000000..153fd2fbb92c9b10f120eb38041ede9ce96f570a GIT binary patch literal 39522 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�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 literal 0 HcmV?d00001 diff --git a/docs/sources/project/images/include_gcc.png b/docs/sources/project/images/include_gcc.png new file mode 100644 index 0000000000000000000000000000000000000000..e48f50cdf99f2d77733ea50235feab3003c58a5e GIT binary patch literal 16446 zcmc&+<8vinw2jS)ZB2}ceZz^JiEZ2FBsZSeHYO9>wr%sq&daafm-ipMFWr4=*Ew}g z*RI}sueEkZD#}YDBj6)|fq@}QONl9ifq@f$J$u2yd|fTq_?f-scYL~U%7{h?OG(i>9u847jc4-QZlpO+Wu|$@m<>&4 zZ7z%@11dnh7W0N1t2+B@nW-*j-=kv?Oc*l5--~;=kH$^9FGbs^f!Gq5#w+eohCmWv zU2y)6tH+z#jo!tF_>)T~p2j8zA$AD?J`i!`?8q3D?V;nV=6`%{=aFF==L)61{c0{0 zWIK-aG?6OST(#if#WBsHp9~w}^q)3_FK+3q&A;p8K%AfJsgP!#mt17DPuIs<6LEI$ znW^DF$S`y0PiCI@`dk|IBMTwgg%iO*vV)3)Av2l`S@84jc|9k4rq74@C#lgO%jPWD z5~OJ|foJ;yz)86J8N`eQ_?qipYu;dZi7Wzjg3w*on$3WYd7`=*t)&#b5p(_boqr_bF zodPaleOqTzPG8&v8eIq+*ulEMXnZLJ71w}Rav%ZgS47fX5+(6PXuP+_s5 zW!nDT2SJmVV7{uFEbFgP+r?0K%Vj8Kg=mU!E;dlK9_^Vg%{&u-Resn_og~Xv=;QO!~5Z#6%%M}v2FSWviKnR?}1Si76u+4JXy?d$( zhmp^t^ep`mc{o2?2&Eu{uJVip(M1jgap%?EZijJrH3m?YV$mby!z4HVXf~j7Dlcad zqyzUSoQGX(rJJu4Er6P;BX{M0Zl15^a>fFV?cI;;{p}mDw40;)0qj8{%5>n-nZ|`| z0qf>*s=C0j%sB=fbe}5V+>9%#_a|x7OBan8SnIc3p51Z}_+vzLyaI zM)NktR+J4r%$nf!)fx11ah%OHS2=LayQ8r%Ixcuv>~X7I^9--jxl=+MG=6l{)%=A) zbiM&yCo~8WYwTkg=op!=uXdS7g~p&M29

{0NO1)Qesh-^#1Zg(2RWErK}|tEP;> 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

{{VD31C$dDSwVl{n2aBK7$F@ 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⁢~Guti;Ek` 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

Cpt}Nwl-z6!Ma5V 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

mQC}VKu+7F>Yf$5kX)xe7RcM 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%0A=aq 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

    w^*@oxBDMMj0#l7z9L?-RgwoeU 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 LFIp*V81TOU7pe6i literal 0 HcmV?d00001 diff --git a/docs/sources/project/set-up-git.md b/docs/sources/project/set-up-git.md index 1c8b511d0c934..d67ff817c6144 100644 --- a/docs/sources/project/set-up-git.md +++ b/docs/sources/project/set-up-git.md @@ -46,9 +46,12 @@ target="_blank">docker/docker repository. that instead. You'll need to convert what you see in the guide to what is appropriate to your tool. -5. Open a terminal window on your local host and change to your home directory. In Windows, you'll work in your Boot2Docker window instead of Powershell or cmd. +5. Open a terminal window on your local host and change to your home directory. $ cd ~ + + In Windows, you'll work in your Boot2Docker window instead of Powershell or + a `cmd` window. 6. Create a `repos` directory. diff --git a/docs/sources/project/software-req-win.md b/docs/sources/project/software-req-win.md new file mode 100644 index 0000000000000..a7f1378929ec3 --- /dev/null +++ b/docs/sources/project/software-req-win.md @@ -0,0 +1,258 @@ +page_title: Set up for development on Windows +page_description: How to set up a server to test Docker Windows client +page_keywords: development, inception, container, image Dockerfile, dependencies, Go, artifacts, windows + + +# Get the required software for Windows + +This page explains how to get the software you need to use a a Windows Server +2012 or Windows 8 machine for Docker development. Before you begin contributing +you must have: + +- a GitHub account +- Git for Windows (msysGit) +- TDM-GCC, a compiler suite for Windows +- MinGW (tar and xz) +- Go language + +> **Note**: This installation prcedure refers to the `C:\` drive. If you system's main drive +is `D:\` you'll need to substitute that in where appropriate in these +instructions. + +### Get a GitHub account + +To contribute to the Docker project, you will need a GitHub account. A free account is +fine. All the Docker project repositories are public and visible to everyone. + +You should also have some experience using both the GitHub application and `git` +on the command line. + +## Install Git for Windows + +Git for Windows includes several tools including msysGit, which is a build +environment. The environment contains the tools you need for development such as +Git and a Git Bash shell. + +1. Browse to the [Git for Windows](https://msysgit.github.io/) download page. + +2. Click **Download**. + + Windows prompts you to save the file to your machine. + +3. Run the saved file. + + The system displays the **Git Setup** wizard. + +4. Click the **Next** button to move through the wizard and accept all the defaults. + +5. Click **Finish** when you are done. + +## Installing TDM-GCC + +TDM-GCC is a compiler suite for Windows. You'll use this suite to compile the +Docker Go code as you develop. + +1. Browse to + [tdm-gcc download page](http://tdm-gcc.tdragon.net/download). + +2. Click on the lastest 64-bit version of the package. + + Windows prompts you to save the file to your machine + +3. Set up the suite by running the downloaded file. + + The system opens the **TDM-GCC Setup** wizard. + +4. Click **Create**. + +5. Click the **Next** button to move through the wizard and accept all the defaults. + +6. Click **Finish** when you are done. + + +## Installing MinGW (tar and xz) + +MinGW is a minimalist port of the GNU Compiler Collection (GCC). In this +procedure, you first download and install the MinGW installation manager. Then, +you use the manager to install the `tar` and `xz` tools from the collection. + +1. Browse to MinGW + [SourceForge](http://sourceforge.net/projects/mingw/). + +2. Click **Download**. + + Windows prompts you to save the file to your machine + +3. Run the downloaded file. + + The system opens the **MinGW Installation Manager Setup Tool** + +4. Choose **Install** install the MinGW Installation Manager. + +5. Press **Continue**. + + The system installs and then opens the MinGW Installation Manager. + +6. Press **Continue** after the install completes to open the manager. + +7. Select **All Packages > MSYS Base System** from the left hand menu. + + The system displays the available packages. + +8. Click on the the **msys-tar bin** package and choose **Mark for Installation**. + +9. Click on the **msys-xz bin** package and choose **Mark for Installation**. + +10. Select **Installation > Apply Changes**, to install the selected packages. + + The system displays the **Schedule of Pending Actions Dialog**. + + ![windows-mingw](/project/images/windows-mingw.png) + +11. Press **Apply** + + MingGW installs the packages for you. + +12. Close the dialog and the MinGW Installation Manager. + + +## Set up your environment variables + +You'll need to add the compiler to your `Path` environment variable. + +1. Open the **Control Panel**. + +2. Choose **System and Security > System**. + +3. Click the **Advanced system settings** link in the sidebar. + + The system opens the **System Properties** dialog. + +3. Select the **Advanced** tab. + +4. Click **Environment Variables**. + + The system opens the **Environment Variables dialog** dialog. + +5. Locate the **System variables** area and scroll to the **Path** + variable. + + ![windows-mingw](/project/images/path_variable.png) + +6. Click **Edit** to edit the variable (you can also double-click it). + + The system opens the **Edit System Variable** dialog. + +7. Make sure the `Path` includes `C:\TDM-GCC64\bin` + + ![include gcc](/project/images/include_gcc.png) + + If you don't see `C:\TDM-GCC64\bin`, add it. + +8. Press **OK** to close this dialog. + +9. Press **OK** twice to close out of the remaining dialogs. + +## Install Go and cross-compile it + +In this section, you install the Go language. Then, you build the source so that it can cross-compile for `linux/amd64` architectures. + +1. Open [Go Language download](http://golang.org/dl/) page in your browser. + +2. Locate and click the latest `.msi` installer. + + The system prompts you to save the file. + +3. Run the installer. + + The system opens the **Go Programming Langauge Setup** dialog. + +4. Select all the defaults to install. + +5. Press **Finish** to close the installation dialog. + +6. Start a command prompt. + +7. Change to the Go `src` directory. + + cd c:\Go\src + +8. Set the following Go variables + + c:\Go\src> set GOOS=linux + c:\Go\src> set GOARCH=amd64 + +9. Compile the source. + + c:\Go\src> make.bat + + Compiling the source also adds a number of variables to your Windows environment. + +## Get the Docker repository + +In this step, you start a Git `bash` terminal and get the Docker source code from +Github. + +1. Locate the **Git Bash** program and start it. + + Recall that **Git Bash** came with the Git for Windows installation. **Git + Bash** just as it sounds allows you to run a Bash terminal on Windows. + + ![Git Bash](/project/images/git_bash.png) + +2. Change to the root directory. + + $ cd /c/ + +3. Make a `gopath` directory. + + $ mkdir gopath + +4. Go get the `docker/docker` repository. + + $ go.exe get github.com/docker/docker package github.com/docker/docker + imports github.com/docker/docker + imports github.com/docker/docker: no buildable Go source files in C:\gopath\src\github.com\docker\docker + + In the next steps, you create environment variables for you Go paths. + +5. Open the **Control Panel** on your system. + +6. Choose **System and Security > System**. + +7. Click the **Advanced system settings** link in the sidebar. + + The system opens the **System Properties** dialog. + +8. Select the **Advanced** tab. + +9. Click **Environment Variables**. + + The system opens the **Environment Variables dialog** dialog. + +10. Locate the **System variables** area and scroll to the **Path** + variable. + +11. Click **New**. + + Now you are going to create some new variables. These paths you'll create in the next procedure; but you can set them now. + +12. Enter `GOPATH` for the **Variable Name**. + +13. For the **Variable Value** enter the following: + + C:\gopath;C:\gopath\src\github.com\docker\docker\vendor + + +14. Press **OK** to close this dialog. + + The system adds `GOPATH` to the list of **System Variables**. + +15. Press **OK** twice to close out of the remaining dialogs. + + +## Where to go next + +In the next section, you'll [learn how to set up and configure Git for +contributing to Docker](/project/set-up-git/). \ No newline at end of file diff --git a/docs/sources/project/software-required.md b/docs/sources/project/software-required.md index 08a4243aec685..15b9a693526b9 100644 --- a/docs/sources/project/software-required.md +++ b/docs/sources/project/software-required.md @@ -2,9 +2,10 @@ page_title: Get the required software page_description: Describes the software required to contribute to Docker page_keywords: GitHub account, repository, Docker, Git, Go, make, -# Get the required software +# Get the required software for Linux or OS X -Before you begin contributing you must have: +This page explains how to get the software you need to use a Linux or OS X +machine for Docker development. Before you begin contributing you must have: * a GitHub account * `git` diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index f11049047172d..23b6b0914d6ea 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -230,6 +230,46 @@ with new memory settings. 6. Restart your container and try your test again. +## Testing just the Windows client + +This explains how to test the Windows client on a Windows server set up as a +development environment. You'll use the **Git Bash** came with the Git for +Windows installation. **Git Bash** just as it sounds allows you to run a Bash +terminal on Windows. + +1. If you don't have one, start a Git Bash terminal. + + ![Git Bash](/project/images/git_bash.png) + +2. Change to the `docker` source directory. + + $ cd /c/gopath/src/github.com/docker/docker + +3. Set `DOCKER_CLIENTONLY` as follows: + + $ export DOCKER_CLIENTONLY=1 + + This ensures you are building only the client binary instead of both the + binary and the daemon. + +4. Set `DOCKER_TEST_HOST` to the `tcp://IP_ADDRESS:2376` value; substitute your +machine's actual IP address, for example: + + $ export DOCKER_TEST_HOST=tcp://263.124.23.200:2376 + +5. Make the binary and the test: + + $ hack/make.sh binary test-integration-cli + + Many tests are skipped on Windows for various reasons. You see which tests + were skipped by re-running the make and passing in the + `TESTFLAGS='-test.v'` value. + + +You can now choose to make changes to the Docker source or the tests. If you +make any changes just run these commands again. + + ## Build and test the documentation The Docker documentation source files are under `docs/sources`. The content is From 8f52eb7b827d658d6974056460afd722a5cb040f Mon Sep 17 00:00:00 2001 From: Peter Salvatore Date: Tue, 21 Apr 2015 23:36:27 -0400 Subject: [PATCH 320/332] Rewrite Official Repositories page. The existing page is focused on listing a set of requirements for proposing a new repository. This information has become outdated and is duplicated in the `docker-library/official-images` and `docker-library/docs` GitHub repositories. This PR rewrites the Official Repositories page to describe what they actually are, and defers to GitHub/IRC for the subset of users that are interested in contributing. I also removed the requirement to contact partners@docker.com and made it optional to reduce the barrier to entry. Signed-off-by: Peter Salvatore --- docs/mkdocs.yml | 2 +- docs/sources/articles/baseimages.md | 2 +- .../articles/dockerfile_best-practices.md | 4 +- docs/sources/docker-hub/official_repos.md | 289 +++++++----------- docs/sources/docker-hub/repos.md | 12 +- docs/sources/terms/repository.md | 2 +- docs/sources/userguide/dockerimages.md | 10 +- docs/sources/userguide/dockerrepos.md | 12 +- 8 files changed, 125 insertions(+), 208 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e425175f61657..a83f281e589f2 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -76,7 +76,7 @@ pages: - ['docker-hub/accounts.md', 'Docker Hub', 'Accounts'] - ['docker-hub/repos.md', 'Docker Hub', 'Repositories'] - ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] -- ['docker-hub/official_repos.md', 'Docker Hub', 'Official repo guidelines'] +- ['docker-hub/official_repos.md', 'Docker Hub', 'Official Repositories'] # Docker Hub Enterprise: - ['docker-hub-enterprise/index.md', 'Docker Hub Enterprise', 'Overview' ] diff --git a/docs/sources/articles/baseimages.md b/docs/sources/articles/baseimages.md index a54f5307ad570..a1a7665b704e8 100644 --- a/docs/sources/articles/baseimages.md +++ b/docs/sources/articles/baseimages.md @@ -65,4 +65,4 @@ There are lots more resources available to help you write your 'Dockerfile`. * There's a [complete guide to all the instructions](/reference/builder/) available for use in a `Dockerfile` in the reference section. * To help you write a clear, readable, maintainable `Dockerfile`, we've also written a [`Dockerfile` Best Practices guide](/articles/dockerfile_best-practices). -* If you're working on an Official Repo, be sure to check out the [Official Repo Guidelines](/docker-hub/official_repos/). +* If your goal is to create a new Official Repository, be sure to read up on Docker's [Official Repositories](/docker-hub/official_repos/). diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 425eb86583ac3..04e77fd62c3e6 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -419,9 +419,9 @@ fail catastrophically if the new build's context is missing the resource being added. Adding a separate tag, as recommended above, will help mitigate this by allowing the `Dockerfile` author to make a choice. -## Examples for official repositories +## Examples for Official Repositories -These Official Repos have exemplary `Dockerfile`s: +These Official Repositories have exemplary `Dockerfile`s: * [Go](https://registry.hub.docker.com/_/golang/) * [Perl](https://registry.hub.docker.com/_/perl/) diff --git a/docs/sources/docker-hub/official_repos.md b/docs/sources/docker-hub/official_repos.md index a101d88c1dc62..eb73b4bc20117 100644 --- a/docs/sources/docker-hub/official_repos.md +++ b/docs/sources/docker-hub/official_repos.md @@ -1,189 +1,106 @@ -page_title: Guidelines for official repositories on Docker Hub +page_title: Official Repositories on Docker Hub page_description: Guidelines for Official Repositories on Docker Hub page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, official, image, documentation -# Guidelines for creating and documenting official repositories - -## Introduction - -You’ve been given the job of creating an image for an Official Repository -hosted on [Docker Hub Registry](https://registry.hub.docker.com/). These are -our guidelines for getting that task done. Even if you’re not -planning to create an Official Repo, you can think of these guidelines as best -practices for image creation generally. - -This document consists of two major sections: - -* A list of expected files, resources and supporting items for your image, -along with best practices for creating those items -* Examples embodying those practices - -## Expected files and resources - -### A Git repository - -Your image needs to live in a Git repository, preferably on GitHub. (If you’d -like to use a different provider, please [contact us](mailto:feedback@docker.com) -directly.) Docker **strongly** recommends that this repo be publicly -accessible. - -If the repo is private or has otherwise limited access, you must provide a -means of at least “read-only” access for both general users and for the -docker-library maintainers, who need access for review and building purposes. - -### A Dockerfile - -Complete information on `Dockerfile`s can be found in the [Reference section](https://docs.docker.com/reference/builder/). -We also have a page discussing [best practices for writing `Dockerfile`s](/articles/dockerfile_best-practices). -Your `Dockerfile` should adhere to the following: - -* It must be written either by using `FROM scratch` or be based on another, -established Official Image. -* It must follow `Dockerfile` best practices. These are discussed on the -[best practices page](/articles/dockerfile_best-practices). In addition, -Docker engineer Michael Crosby has some good tips for `Dockerfiles` in -this [blog post](http://crosbymichael.com/dockerfile-best-practices-take-2.html). - -While [`ONBUILD` triggers](https://docs.docker.com/reference/builder/#onbuild) -are not required, if you choose to use them you should: - -* Build both `ONBUILD` and non-`ONBUILD` images, with the `ONBUILD` image -built `FROM` the non-`ONBUILD` image. -* The `ONBUILD` image should be specifically tagged, for example, `ruby: -latest`and `ruby:onbuild`, or `ruby:2` and `ruby:2-onbuild` - -### A short description - -Include a brief description of your image (in plaintext). Only one description -is required; you don’t need additional descriptions for each tag. The file -should also: - -* Be named `README-short.txt` -* Reside in the repo for the “latest” tag -* Not exceed 100 characters - -### A logo - -Include a logo of your company or the product (png format preferred). Only one -logo is required; you don’t need additional logo files for each tag. The logo -file should have the following characteristics: - -* Be named `logo.png` -* Should reside in the repo for the “latest” tag -* Should fit inside a 200px square, maximized in one dimension (preferably the -width) -* Square or wide (landscape) is preferred over tall (portrait), but exceptions -can be made based on the logo needed - -### A long description - -Include a comprehensive description of your image (in Markdown format, GitHub -flavor preferred). Only one description is required; you don’t need additional -descriptions for each tag. The file should also: - -* Be named `README.md` -* Reside in the repo for the “latest” tag -* Be no longer than absolutely necessary, while still addressing all the -content requirements - -In terms of content, the long description must include the following sections: - -* Overview & links -* How-to/usage -* Issues & contributions - -#### Overview and links - -This section should provide: - -* an overview of the software contained in the image, similar to the -introduction in a Wikipedia entry - -* a selection of links to outside resources that help to describe the software - -* a *mandatory* link to the `Dockerfile` - -#### How-to/usage - -A section that describes how to run and use the image, including common use -cases and example `Dockerfile`s (if applicable). Try to provide clear, step-by- -step instructions wherever possible. - -##### Issues and contributions - -In this section, point users to any resources that can help them contribute to -the project. Include contribution guidelines and any specific instructions -related to your development practices. Include a link to -[Docker’s resources for contributors](https://docs.docker.com/contributing/contributing/). -Be sure to include contact info, handles, etc. for official maintainers. - -Also include information letting users know where they can go for help and how -they can file issues with the repo. Point them to any specific IRC channels, -issue trackers, contacts, additional “how-to” information or other resources. - -### License - -Include a file, `LICENSE`, of any applicable license. Docker recommends using -the license of the software contained in the image, provided it allows Docker, -Inc. to legally build and distribute the image. Otherwise, Docker recommends -adopting the [Expat license](http://directory.fsf.org/wiki/License:Expat) -(a.k.a., the MIT or X11 license). - -## Examples - -Below are sample short and long description files for an imaginary image -containing Ruby on Rails. - -### Short description - -`README-short.txt` - -`Ruby on Rails is an open-source application framework written in Ruby. It emphasizes best practices such as convention over configuration, active record pattern, and the model-view-controller pattern.` - -### Long description - -`README.md` - -```markdown -# What is Ruby on Rails - -Ruby on Rails, often simply referred to as Rails, is an open source web application framework which runs via the Ruby programming language. It is a full-stack framework: it allows creating pages and applications that gather information from the web server, talk to or query the database, and render templates out of the box. As a result, Rails features a routing system that is independent of the web server. - -> [wikipedia.org/wiki/Ruby_on_Rails](https://en.wikipedia.org/wiki/Ruby_on_Rails) - -# How to use this image - -## Create a `Dockerfile` in your rails app project - - FROM rails:onbuild - -Put this file in the root of your app, next to the `Gemfile`. - -This image includes multiple `ONBUILD` triggers so that should be all that you need for most applications. The build will `ADD . /usr/src/app`, `RUN bundle install`, `EXPOSE 3000`, and set the default command to `rails server`. - -Then build and run the Docker image. - - docker build -t my-rails-app . - docker run --name some-rails-app -d my-rails-app - -Test it by visiting `http://container-ip:3000` in a browser. On the other hand, if you need access outside the host on port 8080: - - docker run --name some-rails-app -p 8080:3000 -d my-rails-app - -Then go to `http://localhost:8080` or `http://host-ip:8080` in a browser. -``` - -For more examples, take a look at these repos: - -* [Go](https://github.com/docker-library/golang) -* [PostgreSQL](https://github.com/docker-library/postgres) -* [Buildpack-deps](https://github.com/docker-library/buildpack-deps) -* ["Hello World" minimal container](https://github.com/docker-library/hello-world) -* [Node](https://github.com/docker-library/node) - -## Submit your repo - -Once you've checked off everything in these guidelines, and are confident your -image is ready for primetime, please contact us at -[partners@docker.com](mailto:partners@docker.com) to have your project -considered for the Official Repos program. +# Official Repositories on Docker Hub + +The Docker [Official Repositories](http://registry.hub.docker.com/official) are +a curated set of Docker repositories that are promoted on Docker Hub and +supported by Docker, Inc. They are designed to: + +* Provide essential base OS repositories (for example, + [`ubuntu`](https://registry.hub.docker.com/_/ubuntu/), + [`centos`](https://registry.hub.docker.com/_/centos/)) that serve as the + starting point for the majority of users. + +* Provide drop-in solutions for popular programming language runtimes, data + stores, and other services, similar to what a Platform-as-a-Service (PAAS) + would offer. + +* Exemplify [`Dockerfile` best practices](/articles/dockerfile_best-practices) + and provide clear documentation to serve as a reference for other `Dockerfile` + authors. + +* Ensure that security updates are applied in a timely manner. This is + particularly important as many Official Repositories are some of the most + popular on Docker Hub. + +* Provide a channel for software vendors to redistribute up-to-date and + supported versions of their products. Organization accounts on Docker Hub can + also serve this purpose, without the careful review or restrictions on what + can be published. + +Docker, Inc. sponsors a dedicated team that is responsible for reviewing and +publishing all Official Repositories content. This team works in collaboration +with upstream software maintainers, security experts, and the broader Docker +community. + +While it is preferrable to have upstream software authors maintaining their +corresponding Official Repositories, this is not a strict requirement. Creating +and maintaining images for Official Repositories is a public process. It takes +place openly on GitHub where participation is encouraged. Anyone can provide +feedback, contribute code, suggest process changes, or even propose a new +Official Repository. + +## Should I use Official Repositories? + +New Docker users are encouraged to use the Official Repositories in their +projects. These repositories have clear documentation, promote best practices, +and are designed for the most common use cases. Advanced users are encouraged to +review the Official Repositories as part of their `Dockerfile` learning process. + +A common rationale for diverging from Official Repositories is to optimize for +image size. For instance, many of the programming language stack images contain +a complete build toolchain to support installation of modules that depend on +optimized code. An advanced user could build a custom image with just the +necessary pre-compiled libraries to save space. + +A number of language stacks such as +[`python`](https://registry.hub.docker.com/_/python/) and +[`ruby`](https://registry.hub.docker.com/_/ruby/) have `-slim` tag variants +designed to fill the need for optimization. Even when these "slim" variants are +insufficient, it is still recommended to inherit from an Official Repository +base OS image to leverage the ongoing maintenance work, rather than duplicating +these efforts. + +## How can I get involved? + +All Official Repositories contain a **User Feedback** section in their +documentation which covers the details for that specific repository. In most +cases, the GitHub repository which contains the Dockerfiles for an Official +Repository also has an active issue tracker. General feedback and support +questions should be directed to `#docker-library` on Freenode IRC. + +## How do I create a new Official Repository? + +From a high level, an Official Repository starts out as a proposal in the form +of a set of GitHub pull requests. You'll find detailed and objective proposal +requirements in the following GitHub repositories: + +* [docker-library/official-images](https://github.com/docker-library/official-images) + +* [docker-library/docs](https://github.com/docker-library/docs) + +The Official Repositories team, with help from community contributors, formally +review each proposal and provide feedback to the author. This initial review +process may require a bit of back and forth before the proposal is accepted. + +There are also subjective considerations during the review process. These +subjective concerns boil down to the basic question: "is this image generally +useful?" For example, the [`python`](https://registry.hub.docker.com/_/python/) +Official Repository is "generally useful" to the large Python developer +community, whereas an obscure text adventure game written in Python last week is +not. + +When a new proposal is accepted, the author becomes responsibile for keeping +their images up-to-date and responding to user feedback. The Official +Repositories team becomes responsibile for publishing the images and +documentation on Docker Hub. Updates to the Official Repository follow the same +pull request process, though with less review. The Official Repositories team +ultimately acts as a gatekeeper for all changes, which helps mitigate the risk +of quality and security issues from being introduced. + +> **Note**: If you are interested in proposing an Official Repository, but would +> like to discuss it with Docker, Inc. privately first, please send your +> inquiries to partners@docker.com. There is no fast-track or pay-for-status +> option. diff --git a/docs/sources/docker-hub/repos.md b/docs/sources/docker-hub/repos.md index 0a2fa6550094a..a48040fb55806 100644 --- a/docs/sources/docker-hub/repos.md +++ b/docs/sources/docker-hub/repos.md @@ -51,10 +51,10 @@ private to public. You can also collaborate on Docker Hub with organizations and groups. You can read more about that [here](accounts/). -## Official repositories +## Official Repositories -The Docker Hub contains a number of [official -repositories](http://registry.hub.docker.com/official). These are +The Docker Hub contains a number of [Official +Repositories](http://registry.hub.docker.com/official). These are certified repositories from vendors and contributors to Docker. They contain Docker images from vendors like Canonical, Oracle, and Red Hat that you can use to build applications and services. @@ -63,9 +63,9 @@ If you use Official Repositories you know you're using a supported, optimized and up-to-date image to power your applications. > **Note:** -> If you would like to contribute an official repository for your -> organization, product or team you can see more information -> [here](https://github.com/docker/stackbrew). +> If you would like to contribute an Official Repository for your +> organization, see [Official Repositories on Docker +> Hub](/docker-hub/official_repos) for more information. ## Private repositories diff --git a/docs/sources/terms/repository.md b/docs/sources/terms/repository.md index 84963b4bf94a1..4b8579924fbf3 100644 --- a/docs/sources/terms/repository.md +++ b/docs/sources/terms/repository.md @@ -29,7 +29,7 @@ A Fully Qualified Image Name (FQIN) can be made up of 3 parts: 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. +reserved for [Official Repositories](/docker-hub/official_repos). For more information see [*Working with Repositories*](/userguide/dockerrepos/#working-with-the-repository) diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index 6219466546cf6..c29b01032c94b 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -131,11 +131,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 Automated build statuses. Official repositories are built and -maintained by the [Stackbrew](https://github.com/docker/stackbrew) project, -and Automated repositories are [Automated Builds]( -/userguide/dockerrepos/#automated-builds) that allow you to validate the source -and content of an image. +the Official and Automated build statuses. +[Official Repositories](/docker-hub/official_repos) are a carefully curated set +of Docker repositories supported by Docker, Inc. Automated repositories are +[Automated Builds](/userguide/dockerrepos/#automated-builds) that allow you to +validate the source and content of an image. We've reviewed the images available to use and we decided to use the `training/sinatra` image. So far we've seen two types of images repositories, diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md index efa6ca3d0df22..8fc2ba637fdfb 100644 --- a/docs/sources/userguide/dockerrepos.md +++ b/docs/sources/userguide/dockerrepos.md @@ -51,12 +51,12 @@ name, user name, or description: 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, named `tianon/`, while the first result, -`centos`, doesn't explicitly list a repository which means that it comes from the -trusted top-level namespace. The `/` character separates a user's -repository from the image name. +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, named +`tianon/`, while the first result, `centos`, doesn't explicitly list a +repository which means that it comes from the trusted top-level namespace for +[Official Repositories](/docker-hub/official_repos). The `/` character separates +a user's repository from the image name. Once you've found the image you want, you can download it with `docker pull `: From c7812f01c7269c713c2243fe8a69b55cdb77b72a Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 28 Apr 2015 18:51:04 +0800 Subject: [PATCH 321/332] fix a minor inspect format issue Before, inspect cont1 cont2 shows: [{ xxx } ,{ xxx } ] After, it shows: [ { xxx } ,{ xxx } ] Because `func (*Encoder) Encode` always followed by a newline character, so it's difficult to put '}' and ']' one the same line. To get symmetry, above is our choice. Signed-off-by: Qiang Huang --- api/client/inspect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index db281795cdb42..0f327cb4db8d2 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -34,7 +34,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } indented := new(bytes.Buffer) - indented.WriteByte('[') + indented.WriteString("[\n") status := 0 isImage := false From ed40c0a9a48e54dbe341ad718d9362a268467378 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Apr 2015 11:03:05 -0400 Subject: [PATCH 322/332] rhel.md: update RHEL6 from 6.5 to 6.6 Signed-off-by: Vincent Batts --- docs/sources/installation/rhel.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 7be8debce5b1c..b312d57d15c9c 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -7,7 +7,7 @@ page_keywords: Docker, Docker documentation, requirements, linux, rhel Docker is supported on the following versions of RHEL: - [*Red Hat Enterprise Linux 7 (64-bit)*](#red-hat-enterprise-linux-7-installation) -- [*Red Hat Enterprise Linux 6.5 (64-bit)*](#red-hat-enterprise-linux-6.5-installation) or later +- [*Red Hat Enterprise Linux 6.6 (64-bit)*](#red-hat-enterprise-linux-66-installation) or later ## Kernel support @@ -41,14 +41,14 @@ Portal](https://access.redhat.com/). Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -## Red Hat Enterprise Linux 6.5 installation +## Red Hat Enterprise Linux 6.6 installation You will need **64 bit** [RHEL -6.5](https://access.redhat.com/site/articles/3078#RHEL6) or later, with +6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with a RHEL 6 kernel version 2.6.32-431 or higher as this has specific kernel fixes to allow Docker to work. -Docker is available for **RHEL6.5** on EPEL. Please note that +Docker is available for **RHEL6.6** on EPEL. 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. From 179b6ddc353fd766125df19bf61b00a39a13cd04 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Apr 2015 11:04:32 -0400 Subject: [PATCH 323/332] rhel.md: bump the kernel version for RHEL6 Closes #9856 Signed-off-by: Vincent Batts --- docs/sources/installation/rhel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index b312d57d15c9c..610a300c596b0 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -45,7 +45,7 @@ Please continue with the [Starting the Docker daemon](#starting-the-docker-daemo You will need **64 bit** [RHEL 6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with -a RHEL 6 kernel version 2.6.32-431 or higher as this has specific kernel +a RHEL 6 kernel version 2.6.32-504.16.2 or higher as this has specific kernel fixes to allow Docker to work. Docker is available for **RHEL6.6** on EPEL. Please note that From 9b365e0845bf8e74cef23db2233e721b90ae4339 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Apr 2015 14:44:56 -0400 Subject: [PATCH 324/332] rhel.md: adding link to most recent issue Signed-off-by: Vincent Batts --- docs/sources/installation/rhel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 610a300c596b0..b3bd7aa1d0852 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -46,7 +46,7 @@ Please continue with the [Starting the Docker daemon](#starting-the-docker-daemo You will need **64 bit** [RHEL 6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with a RHEL 6 kernel version 2.6.32-504.16.2 or higher as this has specific kernel -fixes to allow Docker to work. +fixes to allow Docker to work. Related issues: [#9856](https://github.com/docker/docker/issues/9856). Docker is available for **RHEL6.6** on EPEL. Please note that this package is part of [Extra Packages for Enterprise Linux From 6fd8e485c85c4f8ca62578d0840bdeddc4cba151 Mon Sep 17 00:00:00 2001 From: buddhamagnet Date: Thu, 9 Apr 2015 20:07:06 +0100 Subject: [PATCH 325/332] add support for exclusion rules in dockerignore Signed-off-by: Dave Goodchild --- .../articles/dockerfile_best-practices.md | 15 +- docs/sources/reference/builder.md | 97 +++++++----- docs/sources/reference/commandline/cli.md | 69 +++------ integration-cli/docker_cli_build_test.go | 63 +++++++- pkg/archive/archive.go | 11 +- pkg/fileutils/fileutils.go | 111 ++++++++++++-- pkg/fileutils/fileutils_test.go | 139 ++++++++++++++++++ 7 files changed, 400 insertions(+), 105 deletions(-) diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 425eb86583ac3..dfb265c68b476 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -32,13 +32,14 @@ ephemeral as possible. By “ephemeral,” we mean that it can be stopped and destroyed and a new one built and put in place with an absolute minimum of set-up and configuration. -### Use [a .dockerignore file](https://docs.docker.com/reference/builder/#the-dockerignore-file) - -For faster uploading and efficiency during `docker build`, you should use -a `.dockerignore` file to exclude files or directories from the build -context and final image. For example, unless`.git` is needed by your build -process or scripts, you should add it to `.dockerignore`, which can save many -megabytes worth of upload time. +### Use a .dockerignore file + +In most cases, it's best to put each Dockerfile in an empty directory. Then, +add to that directory only the files needed for building the Dockerfile. To +increase the build's performance, you can exclude files and directories by +adding a `.dockerignore` file to that directory as well. This file supports +exclusion patterns similar to `.gitignore` files. For information on creating one, +see the [.dockerignore file](../../reference/builder/#dockerignore-file). ### Avoid installing unnecessary packages diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index c5392f8474723..7dbe549237f13 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -41,10 +41,11 @@ whole context must be transferred to the daemon. The Docker CLI reports > repository, the entire contents of your hard drive will get sent to the daemon (and > thus to the machine running the daemon). You probably don't want that. -In most cases, it's best to put each Dockerfile in an empty directory, and then add only -the files needed for building that Dockerfile to that directory. To further speed up the -build, you can exclude files and directories by adding a `.dockerignore` file to the same -directory. +In most cases, it's best to put each Dockerfile in an empty directory. Then, +only add the files needed for building the Dockerfile to the directory. To +increase the build's performance, you can exclude files and directories by +adding a `.dockerignore` file to the directory. For information about how to +[create a `.dockerignore` file](#the-dockerignore-file) on this page. You can specify a repository and tag at which to save the new image if the build succeeds: @@ -169,43 +170,67 @@ will result in `def` having a value of `hello`, not `bye`. However, `ghi` will have a value of `bye` because it is not part of the same command that set `abc` to `bye`. -## The `.dockerignore` file +### .dockerignore file -If a file named `.dockerignore` exists in the source repository, then it -is interpreted as a newline-separated list of exclusion patterns. -Exclusion patterns match files or directories relative to the source repository -that will be excluded from the context. Globbing is done using Go's +If a file named `.dockerignore` exists in the root of `PATH`, then Docker +interprets it as a newline-separated list of exclusion patterns. Docker excludes +files or directories relative to `PATH` that match these exclusion patterns. If +there are any `.dockerignore` files in `PATH` subdirectories, Docker treats +them as normal files. + +Filepaths in `.dockerignore` are absolute with the current directory as the +root. Wildcards are allowed but the search is not recursive. Globbing (file name +expansion) is done using Go's [filepath.Match](http://golang.org/pkg/path/filepath#Match) rules. -> **Note**: -> The `.dockerignore` file can even be used to ignore the `Dockerfile` and -> `.dockerignore` files. This might be useful if you are copying files from -> the root of the build context into your new container but do not want to -> include the `Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`). +You can specify exceptions to exclusion rules. To do this, simply prefix a +pattern with an `!` (exclamation mark) in the same way you would in a +`.gitignore` file. Currently there is no support for regular expressions. +Formats like `[^temp*]` are ignored. -The following example shows the use of the `.dockerignore` file to exclude the -`.git` directory from the context. Its effect can be seen in the changed size of -the uploaded context. +The following is an example `.dockerignore` file: + +``` + */temp* + */*/temp* + temp? + *.md + !LICENCSE.md +``` + +This file causes the following build behavior: + +| Rule | Behavior | +|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `*/temp*` | Exclude all files with names starting with`temp` in any subdirectory below the root directory. For example, a file named`/somedir/temporary.txt` is ignored. | +| `*/*/temp*` | Exclude files starting with name `temp` from any subdirectory that is two levels below the root directory. For example, the file `/somedir/subdir/temporary.txt` is ignored. | +| `temp?` | Exclude the files that match the pattern in the root directory. For example, the files `tempa`, `tempb` in the root directory are ignored. | +| `*.md ` | Exclude all markdown files. | +| `!LICENSE.md` | Exception to the exclude all Markdown files is this file, `LICENSE.md`, include this file in the build. | + +The placement of `!` exception rules influences the matching algorithm; the +last line of the `.dockerignore` that matches a particular file determines +whether it is included or excluded. In the above example, the `LICENSE.md` file +matches both the `*.md` and `!LICENSE.md` rule. If you reverse the lines in the +example: + +``` + */temp* + */*/temp* + temp? + !LICENCSE.md + *.md +``` + +The build would exclude `LICENSE.md` because the last `*.md` rule adds all +Markdown files back onto the ignore list. The `!LICENSE.md` rule has no effect +because the subsequent `*.md` rule overrides it. + +You can even use the `.dockerignore` file to ignore the `Dockerfile` and +`.dockerignore` files. This is useful if you are copying files from the root of +the build context into your new container but do not want to include the +`Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`). - $ docker build . - Uploading context 18.829 MB - Uploading context - Step 0 : FROM busybox - ---> 769b9341d937 - Step 1 : CMD echo Hello World - ---> Using cache - ---> 99cc1ad10469 - Successfully built 99cc1ad10469 - $ echo ".git" > .dockerignore - $ docker build . - Uploading context 6.76 MB - Uploading context - Step 0 : FROM busybox - ---> 769b9341d937 - Step 1 : CMD echo Hello World - ---> Using cache - ---> 99cc1ad10469 - Successfully built 99cc1ad10469 ## FROM diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ffa199..597324eae0621 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -653,6 +653,26 @@ If you use STDIN or specify a `URL`, the system places the contents into a file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this scenario, there is no context. +By default the `docker build` command will look for a `Dockerfile` at the +root of the build context. The `-f`, `--file`, option lets you specify +the path to an alternative file to use instead. This is useful +in cases where the same set of files are used for multiple builds. The path +must be to a file within the build context. If a relative path is specified +then it must to be relative to the current directory. + +In most cases, it's best to put each Dockerfile in an empty directory. Then, add +to that directory only the files needed for building the Dockerfile. To increase +the build's performance, you can exclude files and directories by adding a +`.dockerignore` file to that directory as well. For information on creating one, +see the [.dockerignore file](../../reference/builder/#dockerignore-file). + +If the Docker client loses connection to the daemon, the build is canceled. +This happens if you interrupt the Docker client with `ctrl-c` or if the Docker +client is killed for any reason. + +> **Note:** Currently only the "run" phase of the build can be canceled until +> pull cancelation is implemented). + ### Return code On a successful build, a return code of success `0` will be returned. @@ -673,55 +693,11 @@ INFO[0000] The command [/bin/sh -c exit 13] returned a non-zero code: 13 $ echo $? 1 ``` - -### .dockerignore file - -If a file named `.dockerignore` exists in the root of `PATH` then it -is interpreted as a newline-separated list of exclusion patterns. -Exclusion patterns match files or directories relative to `PATH` that -will be excluded from the context. Globbing is done using Go's -[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules. - -Please note that `.dockerignore` files in other subdirectories are -considered as normal files. Filepaths in `.dockerignore` are absolute with -the current directory as the root. Wildcards are allowed but the search -is not recursive. - -#### Example .dockerignore file - */temp* - */*/temp* - temp? - -The first line above `*/temp*`, would ignore all files with names starting with -`temp` from any subdirectory below the root directory. For example, a file named -`/somedir/temporary.txt` would be ignored. The second line `*/*/temp*`, will -ignore files starting with name `temp` from any subdirectory that is two levels -below the root directory. For example, the file `/somedir/subdir/temporary.txt` -would get ignored in this case. The last line in the above example `temp?` -will ignore the files that match the pattern from the root directory. -For example, the files `tempa`, `tempb` are ignored from the root directory. -Currently there is no support for regular expressions. Formats -like `[^temp*]` are ignored. - -By default the `docker build` command will look for a `Dockerfile` at the -root of the build context. The `-f`, `--file`, option lets you specify -the path to an alternative file to use instead. This is useful -in cases where the same set of files are used for multiple builds. The path -must be to a file within the build context. If a relative path is specified -then it must to be relative to the current directory. - -If the Docker client loses connection to the daemon, the build is canceled. -This happens if you interrupt the Docker client with `ctrl-c` or if the Docker -client is killed for any reason. - -> **Note:** Currently only the "run" phase of the build can be canceled until -> pull cancelation is implemented). - See also: [*Dockerfile Reference*](/reference/builder). -#### Examples +### Examples $ docker build . Uploading context 10240 bytes @@ -790,7 +766,8 @@ affect the build cache. This example shows the use of the `.dockerignore` file to exclude the `.git` directory from the context. Its effect can be seen in the changed size of the -uploaded context. +uploaded context. The builder reference contains detailed information on +[creating a .dockerignore file](../../builder/#dockerignore-file) $ docker build -t vieux/apache:2.0 . diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 695e4cd6e6dd1..3343427831798 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -3427,20 +3427,29 @@ func (s *DockerSuite) TestBuildDockerignore(c *check.C) { RUN [[ ! -e /bla/src/_vendor ]] RUN [[ ! -e /bla/.gitignore ]] RUN [[ ! -e /bla/README.md ]] + RUN [[ ! -e /bla/dir/foo ]] + RUN [[ ! -e /bla/foo ]] RUN [[ ! -e /bla/.git ]]` ctx, err := fakeContext(dockerfile, map[string]string{ "Makefile": "all:", ".git/HEAD": "ref: foo", "src/x.go": "package main", "src/_vendor/v.go": "package main", + "dir/foo": "", ".gitignore": "", "README.md": "readme", - ".dockerignore": ".git\npkg\n.gitignore\nsrc/_vendor\n*.md", + ".dockerignore": ` +.git +pkg +.gitignore +src/_vendor +*.md +dir`, }) - defer ctx.Close() if err != nil { c.Fatal(err) } + defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { c.Fatal(err) } @@ -3467,6 +3476,55 @@ func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { } } +func (s *DockerSuite) TestBuildDockerignoreExceptions(c *check.C) { + name := "testbuilddockerignoreexceptions" + defer deleteImages(name) + dockerfile := ` + FROM busybox + ADD . /bla + RUN [[ -f /bla/src/x.go ]] + RUN [[ -f /bla/Makefile ]] + RUN [[ ! -e /bla/src/_vendor ]] + RUN [[ ! -e /bla/.gitignore ]] + RUN [[ ! -e /bla/README.md ]] + RUN [[ -e /bla/dir/dir/foo ]] + RUN [[ ! -e /bla/dir/foo1 ]] + RUN [[ -f /bla/dir/e ]] + RUN [[ -f /bla/dir/e-dir/foo ]] + RUN [[ ! -e /bla/foo ]] + RUN [[ ! -e /bla/.git ]]` + ctx, err := fakeContext(dockerfile, map[string]string{ + "Makefile": "all:", + ".git/HEAD": "ref: foo", + "src/x.go": "package main", + "src/_vendor/v.go": "package main", + "dir/foo": "", + "dir/foo1": "", + "dir/dir/f1": "", + "dir/dir/foo": "", + "dir/e": "", + "dir/e-dir/foo": "", + ".gitignore": "", + "README.md": "readme", + ".dockerignore": ` +.git +pkg +.gitignore +src/_vendor +*.md +dir +!dir/e* +!dir/dir/foo`, + }) + if err != nil { + c.Fatal(err) + } + defer ctx.Close() + if _, err := buildImageFromContext(name, ctx, true); err != nil { + c.Fatal(err) + } +} + func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" dockerfile := ` @@ -3607,6 +3665,7 @@ func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { ctx, err := fakeContext(dockerfile, map[string]string{ "Dockerfile": "FROM scratch", "Makefile": "all:", + ".gitignore": "", ".dockerignore": ".*\n", }) defer ctx.Close() diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 7f188975036dd..4d8d260087595 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -391,6 +391,13 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) { // TarWithOptions creates an archive from the directory at `path`, only including files whose relative // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { + + patterns, patDirs, exceptions, err := fileutils.CleanPatterns(options.ExcludePatterns) + + if err != nil { + return nil, err + } + pipeReader, pipeWriter := io.Pipe() compressWriter, err := CompressStream(pipeWriter, options.Compression) @@ -441,7 +448,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) // is asking for that file no matter what - which is true // for some files, like .dockerignore and Dockerfile (sometimes) if include != relFilePath { - skip, err = fileutils.Matches(relFilePath, options.ExcludePatterns) + skip, err = fileutils.OptimizedMatches(relFilePath, patterns, patDirs) if err != nil { logrus.Debugf("Error matching %s", relFilePath, err) return err @@ -449,7 +456,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) } if skip { - if f.IsDir() { + if !exceptions && f.IsDir() { return filepath.SkipDir } return nil diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index ef2a6523dc398..d1e130d0adbab 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -1,33 +1,120 @@ package fileutils import ( + "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" + "strings" "github.com/Sirupsen/logrus" ) -// Matches returns true if relFilePath matches any of the patterns -func Matches(relFilePath string, patterns []string) (bool, error) { - for _, exclude := range patterns { - matched, err := filepath.Match(exclude, relFilePath) +func Exclusion(pattern string) bool { + return pattern[0] == '!' +} + +func Empty(pattern string) bool { + return pattern == "" +} + +// Cleanpatterns takes a slice of patterns returns a new +// slice of patterns cleaned with filepath.Clean, stripped +// of any empty patterns and lets the caller know whether the +// slice contains any exception patterns (prefixed with !). +func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) { + // Loop over exclusion patterns and: + // 1. Clean them up. + // 2. Indicate whether we are dealing with any exception rules. + // 3. Error if we see a single exclusion marker on it's own (!). + cleanedPatterns := []string{} + patternDirs := [][]string{} + exceptions := false + for _, pattern := range patterns { + // Eliminate leading and trailing whitespace. + pattern = strings.TrimSpace(pattern) + if Empty(pattern) { + continue + } + if Exclusion(pattern) { + if len(pattern) == 1 { + logrus.Errorf("Illegal exclusion pattern: %s", pattern) + return nil, nil, false, errors.New("Illegal exclusion pattern: !") + } + exceptions = true + } + pattern = filepath.Clean(pattern) + cleanedPatterns = append(cleanedPatterns, pattern) + if Exclusion(pattern) { + pattern = pattern[1:] + } + patternDirs = append(patternDirs, strings.Split(pattern, "/")) + } + + return cleanedPatterns, patternDirs, exceptions, nil +} + +// Matches returns true if file matches any of the patterns +// and isn't excluded by any of the subsequent patterns. +func Matches(file string, patterns []string) (bool, error) { + file = filepath.Clean(file) + + if file == "." { + // Don't let them exclude everything, kind of silly. + return false, nil + } + + patterns, patDirs, _, err := CleanPatterns(patterns) + if err != nil { + return false, err + } + + return OptimizedMatches(file, patterns, patDirs) +} + +// Matches is basically the same as fileutils.Matches() but optimized for archive.go. +// It will assume that the inputs have been preprocessed and therefore the function +// doen't need to do as much error checking and clean-up. This was done to avoid +// repeating these steps on each file being checked during the archive process. +// The more generic fileutils.Matches() can't make these assumptions. +func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { + matched := false + parentPath := filepath.Dir(file) + parentPathDirs := strings.Split(parentPath, "/") + + for i, pattern := range patterns { + negative := false + + if Exclusion(pattern) { + negative = true + pattern = pattern[1:] + } + + match, err := filepath.Match(pattern, file) if err != nil { - logrus.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude) + logrus.Errorf("Error matching: %s (pattern: %s)", file, pattern) return false, err } - if matched { - if filepath.Clean(relFilePath) == "." { - logrus.Errorf("Can't exclude whole path, excluding pattern: %s", exclude) - continue + + if !match && parentPath != "." { + // Check to see if the pattern matches one of our parent dirs. + if len(patDirs[i]) <= len(parentPathDirs) { + match, _ = filepath.Match(strings.Join(patDirs[i], "/"), + strings.Join(parentPathDirs[:len(patDirs[i])], "/")) } - logrus.Debugf("Skipping excluded path: %s", relFilePath) - return true, nil } + + if match { + matched = !negative + } + } + + if matched { + logrus.Debugf("Skipping excluded path: %s", file) } - return false, nil + return matched, nil } func CopyFile(src, dst string) (int64, error) { diff --git a/pkg/fileutils/fileutils_test.go b/pkg/fileutils/fileutils_test.go index 16d00d7b95d97..ce19ffce4a66b 100644 --- a/pkg/fileutils/fileutils_test.go +++ b/pkg/fileutils/fileutils_test.go @@ -79,3 +79,142 @@ func TestReadSymlinkedDirectoryToFile(t *testing.T) { t.Errorf("failed to remove symlink: %s", err) } } + +func TestWildcardMatches(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"*"}) + if match != true { + t.Errorf("failed to get a wildcard match, got %v", match) + } +} + +// A simple pattern match should return true. +func TestPatternMatches(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"*.go"}) + if match != true { + t.Errorf("failed to get a match, got %v", match) + } +} + +// An exclusion followed by an inclusion should return true. +func TestExclusionPatternMatchesPatternBefore(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"}) + if match != true { + t.Errorf("failed to get true match on exclusion pattern, got %v", match) + } +} + +// A folder pattern followed by an exception should return false. +func TestPatternMatchesFolderExclusions(t *testing.T) { + match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"}) + if match != false { + t.Errorf("failed to get a false match on exclusion pattern, got %v", match) + } +} + +// A folder pattern followed by an exception should return false. +func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) { + match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"}) + if match != false { + t.Errorf("failed to get a false match on exclusion pattern, got %v", match) + } +} + +// A folder pattern followed by an exception should return false. +func TestPatternMatchesFolderWildcardExclusions(t *testing.T) { + match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"}) + if match != false { + t.Errorf("failed to get a false match on exclusion pattern, got %v", match) + } +} + +// A pattern followed by an exclusion should return false. +func TestExclusionPatternMatchesPatternAfter(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"}) + if match != false { + t.Errorf("failed to get false match on exclusion pattern, got %v", match) + } +} + +// A filename evaluating to . should return false. +func TestExclusionPatternMatchesWholeDirectory(t *testing.T) { + match, _ := Matches(".", []string{"*.go"}) + if match != false { + t.Errorf("failed to get false match on ., got %v", match) + } +} + +// A single ! pattern should return an error. +func TestSingleExclamationError(t *testing.T) { + _, err := Matches("fileutils.go", []string{"!"}) + if err == nil { + t.Errorf("failed to get an error for a single exclamation point, got %v", err) + } +} + +// A string preceded with a ! should return true from Exclusion. +func TestExclusion(t *testing.T) { + exclusion := Exclusion("!") + if !exclusion { + t.Errorf("failed to get true for a single !, got %v", exclusion) + } +} + +// An empty string should return true from Empty. +func TestEmpty(t *testing.T) { + empty := Empty("") + if !empty { + t.Errorf("failed to get true for an empty string, got %v", empty) + } +} + +func TestCleanPatterns(t *testing.T) { + cleaned, _, _, _ := CleanPatterns([]string{"docs", "config"}) + if len(cleaned) != 2 { + t.Errorf("expected 2 element slice, got %v", len(cleaned)) + } +} + +func TestCleanPatternsStripEmptyPatterns(t *testing.T) { + cleaned, _, _, _ := CleanPatterns([]string{"docs", "config", ""}) + if len(cleaned) != 2 { + t.Errorf("expected 2 element slice, got %v", len(cleaned)) + } +} + +func TestCleanPatternsExceptionFlag(t *testing.T) { + _, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md"}) + if !exceptions { + t.Errorf("expected exceptions to be true, got %v", exceptions) + } +} + +func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) { + _, _, exceptions, _ := CleanPatterns([]string{"docs", " !docs/README.md"}) + if !exceptions { + t.Errorf("expected exceptions to be true, got %v", exceptions) + } +} + +func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) { + _, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md "}) + if !exceptions { + t.Errorf("expected exceptions to be true, got %v", exceptions) + } +} + +func TestCleanPatternsErrorSingleException(t *testing.T) { + _, _, _, err := CleanPatterns([]string{"!"}) + if err == nil { + t.Errorf("expected error on single exclamation point, got %v", err) + } +} + +func TestCleanPatternsFolderSplit(t *testing.T) { + _, dirs, _, _ := CleanPatterns([]string{"docs/config/CONFIG.md"}) + if dirs[0][0] != "docs" { + t.Errorf("expected first element in dirs slice to be docs, got %v", dirs[0][1]) + } + if dirs[0][1] != "config" { + t.Errorf("expected first element in dirs slice to be config, got %v", dirs[0][1]) + } +} From 0e752adf550ef4891e40889ea92e12ec3d775a00 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Wed, 29 Apr 2015 19:37:20 +0800 Subject: [PATCH 326/332] Fix docker rename help not consistent with other commands Signed-off-by: Lei Jitang --- api/client/rename.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/api/client/rename.go b/api/client/rename.go index 278f471f2371d..ebe16963ddf19 100644 --- a/api/client/rename.go +++ b/api/client/rename.go @@ -1,20 +1,19 @@ package client -import "fmt" +import ( + "fmt" + + flag "github.com/docker/docker/pkg/mflag" +) // CmdRename renames a container. // // Usage: docker rename OLD_NAME NEW_NAME func (cli *DockerCli) CmdRename(args ...string) error { cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true) - if err := cmd.Parse(args); err != nil { - return nil - } + cmd.Require(flag.Exact, 2) + cmd.ParseFlags(args, true) - if cmd.NArg() != 2 { - cmd.Usage() - return nil - } oldName := cmd.Arg(0) newName := cmd.Arg(1) From 8454e1a3b24e2e076bb08a2a6b1fcb56efe2924e Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Wed, 29 Apr 2015 16:27:12 +0200 Subject: [PATCH 327/332] Add coverage on pkg/fileutils Should fix #11598 Signed-off-by: Vincent Demeester --- pkg/fileutils/fileutils.go | 10 ++- pkg/fileutils/fileutils_test.go | 137 ++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 4 deletions(-) diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index d1e130d0adbab..fdafb53c7fefa 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -118,18 +118,20 @@ func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, } func CopyFile(src, dst string) (int64, error) { - if src == dst { + cleanSrc := filepath.Clean(src) + cleanDst := filepath.Clean(dst) + if cleanSrc == cleanDst { return 0, nil } - sf, err := os.Open(src) + sf, err := os.Open(cleanSrc) if err != nil { return 0, err } defer sf.Close() - if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { + if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) { return 0, err } - df, err := os.Create(dst) + df, err := os.Create(cleanDst) if err != nil { return 0, err } diff --git a/pkg/fileutils/fileutils_test.go b/pkg/fileutils/fileutils_test.go index ce19ffce4a66b..ef931684c1281 100644 --- a/pkg/fileutils/fileutils_test.go +++ b/pkg/fileutils/fileutils_test.go @@ -1,10 +1,125 @@ package fileutils import ( + "io/ioutil" "os" + "path" "testing" ) +// CopyFile with invalid src +func TestCopyFileWithInvalidSrc(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile("/invalid/file/path", path.Join(tempFolder, "dest")) + if err == nil { + t.Fatal("Should have fail to copy an invalid src file") + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes") + } + +} + +// CopyFile with invalid dest +func TestCopyFileWithInvalidDest(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + src := path.Join(tempFolder, "file") + err = ioutil.WriteFile(src, []byte("content"), 0740) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile(src, path.Join(tempFolder, "/invalid/dest/path")) + if err == nil { + t.Fatal("Should have fail to copy an invalid src file") + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes") + } + +} + +// CopyFile with same src and dest +func TestCopyFileWithSameSrcAndDest(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + file := path.Join(tempFolder, "file") + err = ioutil.WriteFile(file, []byte("content"), 0740) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile(file, file) + if err != nil { + t.Fatal(err) + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes as it is the same file.") + } +} + +// CopyFile with same src and dest but path is different and not clean +func TestCopyFileWithSameSrcAndDestWithPathNameDifferent(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + testFolder := path.Join(tempFolder, "test") + err = os.MkdirAll(testFolder, 0740) + if err != nil { + t.Fatal(err) + } + file := path.Join(testFolder, "file") + sameFile := testFolder + "/../test/file" + err = ioutil.WriteFile(file, []byte("content"), 0740) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile(file, sameFile) + if err != nil { + t.Fatal(err) + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes as it is the same file.") + } +} + +func TestCopyFile(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + src := path.Join(tempFolder, "src") + dest := path.Join(tempFolder, "dest") + ioutil.WriteFile(src, []byte("content"), 0777) + ioutil.WriteFile(dest, []byte("destContent"), 0777) + bytes, err := CopyFile(src, dest) + if err != nil { + t.Fatal(err) + } + if bytes != 7 { + t.Fatalf("Should have written %d bytes but wrote %d", 7, bytes) + } + actual, err := ioutil.ReadFile(dest) + if err != nil { + t.Fatal(err) + } + if string(actual) != "content" { + t.Fatalf("Dest content was '%s', expected '%s'", string(actual), "content") + } +} + // Reading a symlink to a directory must return the directory func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { var err error @@ -159,6 +274,28 @@ func TestExclusion(t *testing.T) { } } +// Matches with no patterns +func TestMatchesWithNoPatterns(t *testing.T) { + matches, err := Matches("/any/path/there", []string{}) + if err != nil { + t.Fatal(err) + } + if matches { + t.Fatalf("Should not have match anything") + } +} + +// Matches with malformed patterns +func TestMatchesWithMalformedPatterns(t *testing.T) { + matches, err := Matches("/any/path/there", []string{"["}) + if err == nil { + t.Fatal("Should have failed because of a malformed syntax in the pattern") + } + if matches { + t.Fatalf("Should not have match anything") + } +} + // An empty string should return true from Empty. func TestEmpty(t *testing.T) { empty := Empty("") From 4203230cbbf46238e38099c9073bdcad5f69a63f Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 27 Apr 2015 19:29:48 +0200 Subject: [PATCH 328/332] c.Fatal won't fail and exit test inside a goroutine, errors should be handled outside with a channel Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_containers_test.go | 15 ++-- integration-cli/docker_cli_attach_test.go | 17 +++-- .../docker_cli_attach_unix_test.go | 33 +++++---- integration-cli/docker_cli_build_test.go | 71 +++++++------------ integration-cli/docker_cli_events_test.go | 39 ++++------ integration-cli/docker_cli_exec_test.go | 53 +++++++++----- integration-cli/docker_cli_logs_test.go | 23 +++--- integration-cli/docker_cli_ps_test.go | 1 - integration-cli/docker_cli_run_test.go | 55 +++++++------- integration-cli/docker_cli_run_unix_test.go | 11 +-- integration-cli/docker_cli_start_test.go | 7 +- 11 files changed, 156 insertions(+), 169 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 6cf2f9975e4db..1fec3912e6de1 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -260,15 +260,14 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } type b struct { - body []byte - err error + status int + body []byte + err error } bc := make(chan b, 1) go func() { status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) - c.Assert(status, check.Equals, http.StatusOK) - c.Assert(err, check.IsNil) - bc <- b{body, err} + bc <- b{status, body, err} }() // allow some time to stream the stats from the container @@ -283,9 +282,8 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { case <-time.After(2 * time.Second): c.Fatal("stream was not closed after container was removed") case sr := <-bc: - if sr.err != nil { - c.Fatal(sr.err) - } + c.Assert(sr.err, check.IsNil) + c.Assert(sr.status, check.Equals, http.StatusOK) dec := json.NewDecoder(bytes.NewBuffer(sr.body)) var s *types.Stats @@ -297,6 +295,7 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { } func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) { + // TODO: this test does nothing because we are c.Assert'ing in goroutine var ( name = "statscontainer" runCmd = exec.Command(dockerBinary, "create", "--name", name, "busybox", "top") diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 4dd45ddff3c5b..fc2ea1a1d1ed7 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "fmt" "io" "os/exec" "strings" @@ -89,7 +90,6 @@ func (s *DockerSuite) TestAttachMultipleAndRestart(c *check.C) { } func (s *DockerSuite) TestAttachTtyWithoutStdin(c *check.C) { - cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox") out, _, err := runCommandWithOutput(cmd) if err != nil { @@ -108,29 +108,32 @@ func (s *DockerSuite) TestAttachTtyWithoutStdin(c *check.C) { } }() - done := make(chan struct{}) + done := make(chan error) go func() { defer close(done) cmd := exec.Command(dockerBinary, "attach", id) if _, err := cmd.StdinPipe(); err != nil { - c.Fatal(err) + done <- err + return } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - c.Fatal("attach should have failed") + done <- fmt.Errorf("attach should have failed") + return } else if !strings.Contains(out, expected) { - c.Fatalf("attach failed with error %q: expected %q", out, expected) + done <- fmt.Errorf("attach failed with error %q: expected %q", out, expected) + return } }() select { - case <-done: + case err := <-done: + c.Assert(err, check.IsNil) case <-time.After(attachWait): c.Fatal("attach is running but should have failed") } - } func (s *DockerSuite) TestAttachDisconnect(c *check.C) { diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index bae83d994a064..82808a5b087f1 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -27,14 +27,14 @@ func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { c.Fatal(err) } - done := make(chan struct{}) - + errChan := make(chan error) go func() { - defer close(done) + defer close(errChan) _, tty, err := pty.Open() if err != nil { - c.Fatalf("could not open pty: %v", err) + errChan <- err + return } attachCmd := exec.Command(dockerBinary, "attach", id) attachCmd.Stdin = tty @@ -42,7 +42,8 @@ func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { attachCmd.Stderr = tty if err := attachCmd.Run(); err != nil { - c.Fatalf("attach returned error %s", err) + errChan <- err + return } }() @@ -51,7 +52,8 @@ func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(attachWait): c.Fatal("timed out without attach returning") } @@ -71,12 +73,10 @@ func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { cmd.Stdout = tty cmd.Stderr = tty - detached := make(chan struct{}) + errChan := make(chan error) go func() { - if err := cmd.Run(); err != nil { - c.Fatalf("attach returned error %s", err) - } - close(detached) + errChan <- cmd.Run() + close(errChan) }() time.Sleep(500 * time.Millisecond) @@ -87,7 +87,12 @@ func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { time.Sleep(100 * time.Millisecond) cpty.Write([]byte{17}) - <-detached + select { + case err := <-errChan: + c.Assert(err, check.IsNil) + case <-time.After(5 * time.Second): + c.Fatal("timeout while detaching") + } cpty, tty, err = pty.Open() if err != nil { @@ -119,9 +124,7 @@ func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { select { case err := <-readErr: - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) case <-time.After(2 * time.Second): c.Fatal("timeout waiting for attach read") } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 3343427831798..b74dce2cfa906 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -15,7 +15,6 @@ import ( "runtime" "strconv" "strings" - "sync" "text/template" "time" @@ -764,17 +763,17 @@ ADD test_file .`, } defer ctx.Close() - done := make(chan struct{}) + errChan := make(chan error) go func() { - if _, err := buildImageFromContext(name, ctx, true); err != nil { - c.Fatal(err) - } - close(done) + _, err := buildImageFromContext(name, ctx, true) + errChan <- err + close(errChan) }() select { case <-time.After(5 * time.Second): c.Fatal("Build with adding to workdir timed out") - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) } } @@ -1365,17 +1364,17 @@ COPY test_file .`, } defer ctx.Close() - done := make(chan struct{}) + errChan := make(chan error) go func() { - if _, err := buildImageFromContext(name, ctx, true); err != nil { - c.Fatal(err) - } - close(done) + _, err := buildImageFromContext(name, ctx, true) + errChan <- err + close(errChan) }() select { case <-time.After(5 * time.Second): c.Fatal("Build with adding to workdir timed out") - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) } } @@ -1829,9 +1828,6 @@ func (s *DockerSuite) TestBuildForceRm(c *check.C) { // * When docker events sees container start, close the "docker build" command // * Wait for docker events to emit a dying event. func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { - var wg sync.WaitGroup - defer wg.Wait() - name := "testbuildcancelation" // (Note: one year, will never finish) @@ -1849,26 +1845,21 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { containerID := make(chan string) startEpoch := daemonTime(c).Unix() + // Watch for events since epoch. + eventsCmd := exec.Command( + dockerBinary, "events", + "--since", strconv.FormatInt(startEpoch, 10)) + stdout, err := eventsCmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + if err := eventsCmd.Start(); err != nil { + c.Fatal(err) + } + defer eventsCmd.Process.Kill() - wg.Add(1) // Goroutine responsible for watching start/die events from `docker events` go func() { - defer wg.Done() - // Watch for events since epoch. - eventsCmd := exec.Command( - dockerBinary, "events", - "--since", strconv.FormatInt(startEpoch, 10)) - stdout, err := eventsCmd.StdoutPipe() - err = eventsCmd.Start() - if err != nil { - c.Fatalf("failed to start 'docker events': %s", err) - } - - go func() { - <-finish - eventsCmd.Process.Kill() - }() - cid := <-containerID matchStart := regexp.MustCompile(cid + `(.*) start$`) @@ -1886,19 +1877,13 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { close(eventDie) } } - - err = eventsCmd.Wait() - if err != nil && !IsKilled(err) { - c.Fatalf("docker events had bad exit status: %s", err) - } }() buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".") buildCmd.Dir = ctx.Dir stdoutBuild, err := buildCmd.StdoutPipe() - err = buildCmd.Start() - if err != nil { + if err := buildCmd.Start(); err != nil { c.Fatalf("failed to run build: %s", err) } @@ -1923,14 +1908,12 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { // Send a kill to the `docker build` command. // Causes the underlying build to be cancelled due to socket close. - err = buildCmd.Process.Kill() - if err != nil { + if err := buildCmd.Process.Kill(); err != nil { c.Fatalf("error killing build command: %s", err) } // Get the exit status of `docker build`, check it exited because killed. - err = buildCmd.Wait() - if err != nil && !IsKilled(err) { + if err := buildCmd.Wait(); err != nil && !IsKilled(err) { c.Fatalf("wait failed during build run: %T %s", err, err) } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index b1cc26b9a1b98..80cc0c69d54b8 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -75,8 +75,7 @@ func (s *DockerSuite) TestEventsLimit(c *check.C) { waitGroup.Add(1) go func() { defer waitGroup.Done() - err := exec.Command(dockerBinary, args...).Run() - errChan <- err + errChan <- exec.Command(dockerBinary, args...).Run() }() } @@ -229,8 +228,7 @@ func (s *DockerSuite) TestEventsImageImport(c *check.C) { if err != nil { c.Fatal(err) } - err = eventsCmd.Start() - if err != nil { + if err := eventsCmd.Start(); err != nil { c.Fatal(err) } defer eventsCmd.Process.Kill() @@ -424,30 +422,23 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) { func (s *DockerSuite) TestEventsStreaming(c *check.C) { start := daemonTime(c).Unix() - finish := make(chan struct{}) - defer close(finish) id := make(chan string) eventCreate := make(chan struct{}) eventStart := make(chan struct{}) eventDie := make(chan struct{}) eventDestroy := make(chan struct{}) - go func() { - eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10)) - stdout, err := eventsCmd.StdoutPipe() - if err != nil { - c.Fatal(err) - } - err = eventsCmd.Start() - if err != nil { - c.Fatalf("failed to start 'docker events': %s", err) - } - - go func() { - <-finish - eventsCmd.Process.Kill() - }() + eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10)) + stdout, err := eventsCmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + if err := eventsCmd.Start(); err != nil { + c.Fatalf("failed to start 'docker events': %s", err) + } + defer eventsCmd.Process.Kill() + go func() { containerID := <-id matchCreate := regexp.MustCompile(containerID + `: \(from busybox:latest\) create$`) @@ -468,11 +459,6 @@ func (s *DockerSuite) TestEventsStreaming(c *check.C) { close(eventDestroy) } } - - err = eventsCmd.Wait() - if err != nil && !IsKilled(err) { - c.Fatalf("docker events had bad exit status: %s", err) - } }() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox:latest", "true") @@ -516,5 +502,4 @@ func (s *DockerSuite) TestEventsStreaming(c *check.C) { case <-eventDestroy: // ignore, done } - } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index cbdd5975654ee..4b36d7b532142 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -74,15 +74,14 @@ func (s *DockerSuite) TestExecInteractive(c *check.C) { if err := stdin.Close(); err != nil { c.Fatal(err) } - finish := make(chan struct{}) + errChan := make(chan error) go func() { - if err := execCmd.Wait(); err != nil { - c.Fatal(err) - } - close(finish) + errChan <- execCmd.Wait() + close(errChan) }() select { - case <-finish: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(1 * time.Second): c.Fatal("docker exec failed to exit on stdin close") } @@ -278,25 +277,29 @@ func (s *DockerSuite) TestExecTtyWithoutStdin(c *check.C) { } }() - done := make(chan struct{}) + errChan := make(chan error) go func() { - defer close(done) + defer close(errChan) cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") if _, err := cmd.StdinPipe(); err != nil { - c.Fatal(err) + errChan <- err + return } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - c.Fatal("exec should have failed") + errChan <- fmt.Errorf("exec should have failed") + return } else if !strings.Contains(out, expected) { - c.Fatalf("exec failed with error %q: expected %q", out, expected) + errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) + return } }() select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(3 * time.Second): c.Fatal("exec is running but should have failed") } @@ -326,17 +329,22 @@ func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { c.Fatal(err) } - wait := make(chan struct{}) + type dstop struct { + out []byte + err error + } + + ch := make(chan dstop) go func() { - if out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput(); err != nil { - c.Fatal(out, err) - } - close(wait) + out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput() + ch <- dstop{out, err} + close(ch) }() select { case <-time.After(3 * time.Second): c.Fatal("Container stop timed out") - case <-wait: + case s := <-ch: + c.Assert(s.err, check.IsNil) } } @@ -359,6 +367,7 @@ func (s *DockerSuite) TestExecCgroup(c *check.C) { var wg sync.WaitGroup var mu sync.Mutex execCgroups := []sort.StringSlice{} + errChan := make(chan error) // exec a few times concurrently to get consistent failure for i := 0; i < 5; i++ { wg.Add(1) @@ -366,7 +375,8 @@ func (s *DockerSuite) TestExecCgroup(c *check.C) { cmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/self/cgroup") out, _, err := runCommandWithOutput(cmd) if err != nil { - c.Fatal(out, err) + errChan <- err + return } cg := sort.StringSlice(strings.Split(string(out), "\n")) @@ -377,6 +387,11 @@ func (s *DockerSuite) TestExecCgroup(c *check.C) { }() } wg.Wait() + close(errChan) + + for err := range errChan { + c.Assert(err, check.IsNil) + } for _, cg := range execCgroups { if !reflect.DeepEqual(cg, containerCgroups) { diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index a7c8916226ca6..0a3e1af981841 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -260,16 +260,15 @@ func (s *DockerSuite) TestLogsFollowStopped(c *check.C) { c.Fatal(err) } - ch := make(chan struct{}) + errChan := make(chan error) go func() { - if err := logsCmd.Wait(); err != nil { - c.Fatal(err) - } - close(ch) + errChan <- logsCmd.Wait() + close(errChan) }() select { - case <-ch: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(1 * time.Second): c.Fatal("Following logs is hanged") } @@ -298,9 +297,7 @@ func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { logCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID) stdout, err := logCmd.StdoutPipe() - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) if err := logCmd.Start(); err != nil { c.Fatal(err) @@ -308,15 +305,11 @@ func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { // First read slowly bytes1, err := consumeWithSpeed(stdout, 10, 50*time.Millisecond, stopSlowRead) - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) // After the container has finished we can continue reading fast bytes2, err := consumeWithSpeed(stdout, 32*1024, 0, nil) - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) actual := bytes1 + bytes2 expected := 200000 diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 271051815a4e9..bb34575fbaf48 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -255,7 +255,6 @@ func assertContainerList(out string, expected []string) bool { } func (s *DockerSuite) TestPsListContainersSize(c *check.C) { - cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") runCommandWithOutput(cmd) cmd = exec.Command(dockerBinary, "ps", "-s", "-n=1") diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index c3b25558d9ec7..0cf5c31eeeac9 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -756,17 +756,22 @@ func (s *DockerSuite) TestRunTwoConcurrentContainers(c *check.C) { group := sync.WaitGroup{} group.Add(2) + errChan := make(chan error, 2) for i := 0; i < 2; i++ { go func() { defer group.Done() cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") - if _, err := runCommand(cmd); err != nil { - c.Fatal(err) - } + _, err := runCommand(cmd) + errChan <- err }() } group.Wait() + close(errChan) + + for err := range errChan { + c.Assert(err, check.IsNil) + } } func (s *DockerSuite) TestRunEnvironment(c *check.C) { @@ -1851,22 +1856,20 @@ func (s *DockerSuite) TestRunExitOnStdinClose(c *check.C) { if err := stdin.Close(); err != nil { c.Fatal(err) } - finish := make(chan struct{}) + finish := make(chan error) go func() { - if err := runCmd.Wait(); err != nil { - c.Fatal(err) - } + finish <- runCmd.Wait() close(finish) }() select { - case <-finish: + case err := <-finish: + c.Assert(err, check.IsNil) case <-time.After(1 * time.Second): c.Fatal("docker run failed to exit on stdin close") } state, err := inspectField(name, "State.Running") - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) + if state != "false" { c.Fatal("Container must be stopped after stdin closing") } @@ -2762,25 +2765,29 @@ func (s *DockerSuite) TestRunPortFromDockerRangeInUse(c *check.C) { } func (s *DockerSuite) TestRunTtyWithPipe(c *check.C) { - done := make(chan struct{}) + errChan := make(chan error) go func() { - defer close(done) + defer close(errChan) cmd := exec.Command(dockerBinary, "run", "-ti", "busybox", "true") if _, err := cmd.StdinPipe(); err != nil { - c.Fatal(err) + errChan <- err + return } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - c.Fatal("run should have failed") + errChan <- fmt.Errorf("run should have failed") + return } else if !strings.Contains(out, expected) { - c.Fatalf("run failed with error %q: expected %q", out, expected) + errChan <- fmt.Errorf("run failed with error %q: expected %q", out, expected) + return } }() select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(3 * time.Second): c.Fatal("container is running but should have failed") } @@ -2875,19 +2882,19 @@ func (s *DockerSuite) TestRunAllowPortRangeThroughPublish(c *check.C) { } func (s *DockerSuite) TestRunOOMExitCode(c *check.C) { - done := make(chan struct{}) + errChan := make(chan error) go func() { - defer close(done) - + defer close(errChan) runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done") out, exitCode, _ := runCommandWithOutput(runCmd) if expected := 137; exitCode != expected { - c.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) + errChan <- fmt.Errorf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) } }() select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(30 * time.Second): c.Fatal("Timeout waiting for container to die on OOM") } @@ -3030,9 +3037,7 @@ func (s *DockerSuite) TestRunPidHostWithChildIsKillable(c *check.C) { }() select { case err := <-errchan: - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) case <-time.After(5 * time.Second): c.Fatal("Kill container timed out") } diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 43fa821509afc..59b623162de44 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -29,21 +29,22 @@ func (s *DockerSuite) TestRunRedirectStdout(c *check.C) { cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty - ch := make(chan struct{}) if err := cmd.Start(); err != nil { c.Fatalf("start err: %v", err) } + ch := make(chan error) go func() { - if err := cmd.Wait(); err != nil { - c.Fatalf("wait err=%v", err) - } + ch <- cmd.Wait() close(ch) }() select { case <-time.After(10 * time.Second): c.Fatal("command timeout") - case <-ch: + case err := <-ch: + if err != nil { + c.Fatalf("wait err=%v", err) + } } } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 13ecedd57c8d3..fddc8c97bbf0d 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -20,18 +20,19 @@ func (s *DockerSuite) TestStartAttachReturnsOnError(c *check.C) { c.Fatal("Expected error but got none") } - ch := make(chan struct{}) + ch := make(chan error) go func() { // Attempt to start attached to the container that won't start // This should return an error immediately since the container can't be started if _, err := runCommand(exec.Command(dockerBinary, "start", "-a", "test2")); err == nil { - c.Fatal("Expected error but got none") + ch <- fmt.Errorf("Expected error but got none") } close(ch) }() select { - case <-ch: + case err := <-ch: + c.Assert(err, check.IsNil) case <-time.After(time.Second): c.Fatalf("Attach did not exit properly") } From c0a1b2d6e99d5c6e29a016da39c1313a54c1cb34 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Mon, 20 Apr 2015 16:15:27 -0700 Subject: [PATCH 329/332] docs: Add more places docker.service can be at More systemd goodness. Documenting where `docker.service` lives under Ubuntu 15. Signed-off-by: Ahmet Alp Balkan --- docs/sources/articles/systemd.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/articles/systemd.md b/docs/sources/articles/systemd.md index fddd146b0702b..10baf6d6f62ea 100644 --- a/docs/sources/articles/systemd.md +++ b/docs/sources/articles/systemd.md @@ -30,8 +30,8 @@ If the `docker.service` file is set to use an `EnvironmentFile` (often pointing to `/etc/sysconfig/docker`) then you can modify the referenced file. -Or, you may need to edit the `docker.service` file, which can be in `/usr/lib/systemd/system` -or `/etc/systemd/service`. +Or, you may need to edit the `docker.service` file, which can be in +`/usr/lib/systemd/system`, `/etc/systemd/service`, or `/lib/systemd/system`. ### Runtime directory and storage driver From bedba1691eaca93f6778edf3e1eb2ae6f76dcac9 Mon Sep 17 00:00:00 2001 From: boucher Date: Thu, 9 Apr 2015 16:41:49 -0700 Subject: [PATCH 330/332] Initial attempt at adding Criu support to Docker This branch represents a merge of current Docker master with the libcontainer criu and swrk branches, along with @SaiedKazemi's original Docker criu branch. In addition to trying to get those branches working, a few features have been added tot he Criu support (in various states of completion), chiefly: being able to specify output directories and being able to leave the process running after checkpointing. --- api/client/checkpoint.go | 50 +++++++ api/client/restore.go | 48 +++++++ api/server/server.go | 86 ++++++++--- daemon/checkpoint.go | 102 +++++++++++++ daemon/container.go | 92 +++++++++++- daemon/daemon.go | 35 +++++ daemon/execdriver/driver.go | 3 + daemon/execdriver/lxc/driver.go | 8 ++ daemon/execdriver/native/driver.go | 136 +++++++++++++++++- daemon/monitor.go | 72 ++++++++++ daemon/networkdriver/bridge/driver.go | 44 +++++- daemon/networkdriver/ipallocator/allocator.go | 18 ++- daemon/state.go | 32 +++++ docker/flags.go | 4 +- 14 files changed, 692 insertions(+), 38 deletions(-) create mode 100644 api/client/checkpoint.go create mode 100644 api/client/restore.go create mode 100644 daemon/checkpoint.go diff --git a/api/client/checkpoint.go b/api/client/checkpoint.go new file mode 100644 index 0000000000000..3129c9b6c6cac --- /dev/null +++ b/api/client/checkpoint.go @@ -0,0 +1,50 @@ +package client + +import ( + "fmt" + + "github.com/docker/libcontainer" +) + +func (cli *DockerCli) CmdCheckpoint(args ...string) error { + cmd := cli.Subcmd("checkpoint", "CONTAINER [CONTAINER...]", "Checkpoint one or more running containers", true) + + var ( + flImgDir = cmd.String([]string{"-image-dir"}, "", "(optional) directory for storing checkpoint image files") + flWorkDir = cmd.String([]string{"-work-dir"}, "", "directory for storing log file") + flLeaveRunning = cmd.Bool([]string{"-leave-running"}, false, "leave the container running after checkpointing") + flCheckTcp = cmd.Bool([]string{"-allow-tcp"}, false, "allow checkpointing established tcp connections") + flExtUnix = cmd.Bool([]string{"-allow-ext-unix"}, false, "allow checkpointing external unix connections") + flShell = cmd.Bool([]string{"-allow-shell"}, false, "allow checkpointing shell jobs") + ) + + if err := cmd.ParseFlags(args, true); err != nil { + return err + } + + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + criuOpts := &libcontainer.CriuOpts{ + ImagesDirectory: *flImgDir, + WorkDirectory: *flWorkDir, + LeaveRunning: *flLeaveRunning, + TcpEstablished: *flCheckTcp, + ExternalUnixConnections: *flExtUnix, + ShellJob: *flShell, + } + + var encounteredError error + for _, name := range cmd.Args() { + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/checkpoint", criuOpts, nil)) + if err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to checkpoint one or more containers") + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/client/restore.go b/api/client/restore.go new file mode 100644 index 0000000000000..11e3a362e3fa7 --- /dev/null +++ b/api/client/restore.go @@ -0,0 +1,48 @@ +package client + +import ( + "fmt" + + "github.com/docker/libcontainer" +) + +func (cli *DockerCli) CmdRestore(args ...string) error { + cmd := cli.Subcmd("restore", "CONTAINER [CONTAINER...]", "Restore one or more checkpointed containers", true) + + var ( + flImgDir = cmd.String([]string{"-image-dir"}, "", "(optional) directory to restore image files from") + flWorkDir = cmd.String([]string{"-work-dir"}, "", "directory to store temp files and restore.log") + flCheckTcp = cmd.Bool([]string{"-allow-tcp"}, false, "allow restoring tcp connections") + flExtUnix = cmd.Bool([]string{"-allow-ext-unix"}, false, "allow restoring external unix connections") + flShell = cmd.Bool([]string{"-allow-shell"}, false, "allow restoring shell jobs") + ) + + if err := cmd.ParseFlags(args, true); err != nil { + return err + } + + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + criuOpts := &libcontainer.CriuOpts{ + ImagesDirectory: *flImgDir, + WorkDirectory: *flWorkDir, + TcpEstablished: *flCheckTcp, + ExternalUnixConnections: *flExtUnix, + ShellJob: *flShell, + } + + var encounteredError error + for _, name := range cmd.Args() { + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restore", criuOpts, nil)) + if err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to restore one or more containers") + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/server/server.go b/api/server/server.go index 61e8162659562..cfdee975d97c7 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1413,6 +1413,46 @@ func (s *Server) postContainersCopy(eng *engine.Engine, version version.Version, return nil } +func (s *Server) postContainersCheckpoint(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("checkpoint", vars["name"]) + + if err := job.DecodeEnv(r.Body); err != nil { + return err + } + + if err := job.Run(); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func (s *Server) postContainersRestore(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("restore", vars["name"]) + + if err := job.DecodeEnv(r.Body); err != nil { + return err + } + + if err := job.Run(); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil @@ -1596,28 +1636,30 @@ func createRouter(s *Server, eng *engine.Engine) *mux.Router { "/exec/{id:.*}/json": s.getExecByID, }, "POST": { - "/auth": s.postAuth, - "/commit": s.postCommit, - "/build": s.postBuild, - "/images/create": s.postImagesCreate, - "/images/load": s.postImagesLoad, - "/images/{name:.*}/push": s.postImagesPush, - "/images/{name:.*}/tag": s.postImagesTag, - "/containers/create": s.postContainersCreate, - "/containers/{name:.*}/kill": s.postContainersKill, - "/containers/{name:.*}/pause": s.postContainersPause, - "/containers/{name:.*}/unpause": s.postContainersUnpause, - "/containers/{name:.*}/restart": s.postContainersRestart, - "/containers/{name:.*}/start": s.postContainersStart, - "/containers/{name:.*}/stop": s.postContainersStop, - "/containers/{name:.*}/wait": s.postContainersWait, - "/containers/{name:.*}/resize": s.postContainersResize, - "/containers/{name:.*}/attach": s.postContainersAttach, - "/containers/{name:.*}/copy": s.postContainersCopy, - "/containers/{name:.*}/exec": s.postContainerExecCreate, - "/exec/{name:.*}/start": s.postContainerExecStart, - "/exec/{name:.*}/resize": s.postContainerExecResize, - "/containers/{name:.*}/rename": s.postContainerRename, + "/auth": s.postAuth, + "/commit": s.postCommit, + "/build": s.postBuild, + "/images/create": s.postImagesCreate, + "/images/load": s.postImagesLoad, + "/images/{name:.*}/push": s.postImagesPush, + "/images/{name:.*}/tag": s.postImagesTag, + "/containers/create": s.postContainersCreate, + "/containers/{name:.*}/kill": s.postContainersKill, + "/containers/{name:.*}/pause": s.postContainersPause, + "/containers/{name:.*}/unpause": s.postContainersUnpause, + "/containers/{name:.*}/restart": s.postContainersRestart, + "/containers/{name:.*}/start": s.postContainersStart, + "/containers/{name:.*}/stop": s.postContainersStop, + "/containers/{name:.*}/wait": s.postContainersWait, + "/containers/{name:.*}/resize": s.postContainersResize, + "/containers/{name:.*}/attach": s.postContainersAttach, + "/containers/{name:.*}/copy": s.postContainersCopy, + "/containers/{name:.*}/exec": s.postContainerExecCreate, + "/exec/{name:.*}/start": s.postContainerExecStart, + "/exec/{name:.*}/resize": s.postContainerExecResize, + "/containers/{name:.*}/rename": s.postContainerRename, + "/containers/{name:.*}/checkpoint": s.postContainersCheckpoint, + "/containers/{name:.*}/restore": s.postContainersRestore, }, "DELETE": { "/containers/{name:.*}": s.deleteContainers, diff --git a/daemon/checkpoint.go b/daemon/checkpoint.go new file mode 100644 index 0000000000000..14476fafd2386 --- /dev/null +++ b/daemon/checkpoint.go @@ -0,0 +1,102 @@ +package daemon + +import ( + "fmt" + + "github.com/docker/docker/engine" + "github.com/docker/libcontainer" +) + +// Checkpoint a running container. +func (daemon *Daemon) ContainerCheckpoint(job *engine.Job) error { + if len(job.Args) != 1 { + return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) + } + + name := job.Args[0] + container, err := daemon.Get(name) + if err != nil { + return err + } + if !container.IsRunning() { + return fmt.Errorf("Container %s not running", name) + } + + opts := &libcontainer.CriuOpts{} + + if job.EnvExists("ImagesDirectory") { + opts.ImagesDirectory = job.Getenv("ImagesDirectory") + } + if job.EnvExists("WorkDirectory") { + opts.WorkDirectory = job.Getenv("WorkDirectory") + } + if job.EnvExists("LeaveRunning") { + opts.LeaveRunning = job.GetenvBool("LeaveRunning") + } + if job.EnvExists("TcpEstablished") { + opts.TcpEstablished = job.GetenvBool("TcpEstablished") + } + if job.EnvExists("ExternalUnixConnections") { + opts.ExternalUnixConnections = job.GetenvBool("ExternalUnixConnections") + } + if job.EnvExists("ShellJob") { + opts.ShellJob = job.GetenvBool("ShellJob") + } + + if err := container.Checkpoint(opts); err != nil { + return fmt.Errorf("Cannot checkpoint container %s: %s", name, err) + } + + container.LogEvent("checkpoint") + return nil +} + +// Restore a checkpointed container. +func (daemon *Daemon) ContainerRestore(job *engine.Job) error { + if len(job.Args) != 1 { + return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) + } + + name := job.Args[0] + container, err := daemon.Get(name) + if err != nil { + return err + } + + if container.IsRunning() { + return fmt.Errorf("Container %s already running", name) + } + + // TODO: how should we handle the notion of checkpoint and keep running? + // right now, having ever been checkpointed is sufficient for our desires + // still requires manually calling stop before you are able to then restore + if !container.HasBeenCheckpointed() { + return fmt.Errorf("Container %s is not checkpointed", name) + } + + opts := &libcontainer.CriuOpts{} + + if job.EnvExists("ImagesDirectory") { + opts.ImagesDirectory = job.Getenv("ImagesDirectory") + } + if job.EnvExists("WorkDirectory") { + opts.WorkDirectory = job.Getenv("WorkDirectory") + } + if job.EnvExists("TcpEstablished") { + opts.TcpEstablished = job.GetenvBool("TcpEstablished") + } + if job.EnvExists("ExternalUnixConnections") { + opts.ExternalUnixConnections = job.GetenvBool("ExternalUnixConnections") + } + if job.EnvExists("ShellJob") { + opts.ShellJob = job.GetenvBool("ShellJob") + } + + if err = container.Restore(opts); err != nil { + container.LogEvent("die") + return fmt.Errorf("Cannot restore container %s: %s", name, err) + } + + container.LogEvent("restore") + return nil +} diff --git a/daemon/container.go b/daemon/container.go index 9bd8cc1eb90cd..308471a0ff9ec 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -14,6 +14,7 @@ import ( "syscall" "time" + "github.com/docker/libcontainer" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/devices" "github.com/docker/libcontainer/label" @@ -603,9 +604,16 @@ func (container *Container) AllocateNetwork() error { var ( err error eng = container.daemon.eng + networkSettings *network.Settings ) - networkSettings, err := bridge.Allocate(container.ID, container.Config.MacAddress, "", "") + if container.IsCheckpointed() { + // FIXME: ipv6 support... + networkSettings, err = bridge.Allocate(container.ID, container.Config.MacAddress, container.NetworkSettings.IPAddress, "", true) + } else { + networkSettings, err = bridge.Allocate(container.ID, container.Config.MacAddress, "", "", false) + } + if err != nil { return err } @@ -689,7 +697,7 @@ func (container *Container) RestoreNetwork() error { eng := container.daemon.eng // Re-allocate the interface with the same IP and MAC address. - if _, err := bridge.Allocate(container.ID, container.NetworkSettings.MacAddress, container.NetworkSettings.IPAddress, ""); err != nil { + if _, err := bridge.Allocate(container.ID, container.NetworkSettings.MacAddress, container.NetworkSettings.IPAddress, "", true); err != nil { return err } @@ -705,7 +713,11 @@ func (container *Container) RestoreNetwork() error { // cleanup releases any network resources allocated to the container along with any rules // around how containers are linked together. It also unmounts the container's root filesystem. func (container *Container) cleanup() { - container.ReleaseNetwork() + if container.IsCheckpointed() { + logrus.Debugf("not calling ReleaseNetwork() for checkpointed container %s", container.ID) + } else { + container.ReleaseNetwork() + } // Disable all active links if container.activeLinks != nil { @@ -982,6 +994,46 @@ func (container *Container) GetSize() (int64, int64) { return sizeRw, sizeRootfs } +func (container *Container) Checkpoint(opts *libcontainer.CriuOpts) error { + return container.daemon.Checkpoint(container, opts) +} + +// XXX Start() does a lot more. Not sure if we have +// to do everything it does. +func (container *Container) Restore(opts *libcontainer.CriuOpts) error { + var err error + + container.Lock() + defer container.Unlock() + + defer func() { + if err != nil { + container.cleanup() + } + }() + + if err = container.initializeNetworking(); err != nil { + return err + } + + linkedEnv, err := container.setupLinkedContainers() + if err != nil { + return err + } + + if err = container.setupWorkingDirectory(); err != nil { + return err + } + + env := container.createDaemonEnvironment(linkedEnv) + + if err = populateCommand(container, env); err != nil { + return err + } + + return container.waitForRestore(opts) +} + func (container *Container) Copy(resource string) (io.ReadCloser, error) { container.Lock() defer container.Unlock() @@ -1480,6 +1532,37 @@ func (container *Container) waitForStart() error { return err } + // FIXME? We should write to the disk after actually starting up + // becase StdFds cannot be initialized before + container.toDisk() + + return nil +} + +// Like waitForStart() but for restoring a container. +// +// XXX Does RestartPolicy apply here? +func (container *Container) waitForRestore(opts *libcontainer.CriuOpts) error { + container.monitor = newContainerMonitor(container, container.hostConfig.RestartPolicy) + + // After calling promise.Go() we'll have two goroutines: + // - The current goroutine that will block in the select + // below until restore is done. + // - A new goroutine that will restore the container and + // wait for it to exit. + select { + case <-container.monitor.restoreSignal: + if container.ExitCode != 0 { + return fmt.Errorf("restore process failed") + } + case err := <-promise.Go(func() error { return container.monitor.Restore(opts) }): + return err + } + + // FIXME? We should write to the disk after actually starting up + // becase StdFds cannot be initialized before + container.toDisk() + return nil } @@ -1490,7 +1573,8 @@ func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bind } for i := 0; i < len(binding); i++ { - b, err := bridge.AllocatePort(container.ID, port, binding[i]) + b, err := bridge.AllocatePort(container.ID, port, binding[i], container.IsCheckpointed()) + if err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 05de402174c82..7df57085cb84c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -48,6 +48,7 @@ import ( "github.com/docker/docker/trust" "github.com/docker/docker/utils" "github.com/docker/docker/volumes" + "github.com/docker/libcontainer" "github.com/go-fsnotify/fsnotify" ) @@ -296,6 +297,21 @@ func (daemon *Daemon) restore() error { logrus.Debugf("Loaded container %v", container.ID) containers[container.ID] = container + + // If the container was checkpointed, we need to reserve + // the IP address that it was using. + // + // XXX We should also reserve host ports (if any). + if container.IsCheckpointed() { + logrus.Debugf("\ncontainer %s was checkpointed", container.ID) + err = bridge.ReserveIP(container.ID, container.NetworkSettings.IPAddress) + if err != nil { + logrus.Errorf("Failed to reserve IP %s for container %s", + container.ID, container.NetworkSettings.IPAddress) + } + } else { + logrus.Debugf("IP was not reserved in restore()") + } } else { logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID) } @@ -1061,6 +1077,25 @@ func (daemon *Daemon) Unpause(c *Container) error { return nil } +func (daemon *Daemon) Checkpoint(c *Container, opts *libcontainer.CriuOpts) error { + if err := daemon.execDriver.Checkpoint(c.command, opts); err != nil { + return err + } + c.SetCheckpointed(opts.LeaveRunning) + return nil +} + +func (daemon *Daemon) Restore(c *Container, pipes *execdriver.Pipes, restoreCallback execdriver.RestoreCallback, opts *libcontainer.CriuOpts) (execdriver.ExitStatus, error) { + // Mount the container's filesystem (daemon/graphdriver/aufs/aufs.go). + _, err := daemon.driver.Get(c.ID, c.GetMountLabel()) + if err != nil { + return execdriver.ExitStatus{ExitCode: 0}, err + } + + exitCode, err := daemon.execDriver.Restore(c.command, pipes, restoreCallback, opts) + return exitCode, err +} + func (daemon *Daemon) Kill(c *Container, sig int) error { return daemon.execDriver.Kill(c.command, sig) } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index ce196df2015a3..ccbf8eb76ace5 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -22,6 +22,7 @@ import ( // Context is a generic key value pair that allows // arbatrary data to be sent type Context map[string]string +type RestoreCallback func(*ProcessConfig, int) var ( ErrNotRunning = errors.New("Container is not running") @@ -66,6 +67,8 @@ type Driver interface { Kill(c *Command, sig int) error Pause(c *Command) error Unpause(c *Command) error + Checkpoint(c *Command, opts *libcontainer.CriuOpts) error + Restore(c *Command, pipes *Pipes, restoreCallback RestoreCallback, opts *libcontainer.CriuOpts) (ExitStatus, error) Name() string // Driver name Info(id string) Info // "temporary" hack (until we move state from core to plugins) GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container. diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 15f57bfe0faff..7dd2cd1ccaed5 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -493,6 +493,14 @@ func (d *driver) Unpause(c *execdriver.Command) error { return err } +func (d *driver) Checkpoint(c *execdriver.Command, opts *libcontainer.CriuOpts) error { + return fmt.Errorf("Checkpointing lxc containers not supported yet\n") +} + +func (d *driver) Restore(c *execdriver.Command, pipes *execdriver.Pipes, restoreCallback execdriver.RestoreCallback, opts *libcontainer.CriuOpts) (execdriver.ExitStatus, error) { + return execdriver.ExitStatus{ExitCode: 0}, fmt.Errorf("Restoring lxc containers not supported yet\n") +} + func (d *driver) Terminate(c *execdriver.Command) error { return KillLxc(c.ID, 9) } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index afc3f1e45ee4f..727d142c187d2 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -41,6 +41,18 @@ type driver struct { sync.Mutex } +// FIXME: move this into libcontainer +// InitFactory returns an options func to configure a LinuxFactory with the +// provided absolute path to the init binary and arguements and a path to criu +func InitFactory(criuPath string, path string, args ...string) func(*libcontainer.LinuxFactory) error { + return func(l *libcontainer.LinuxFactory) error { + l.CriuPath = criuPath + l.InitPath = path + l.InitArgs = args + return nil + } +} + func NewDriver(root, initPath string, options []string) (*driver, error) { meminfo, err := sysinfo.ReadMemInfo() if err != nil { @@ -96,8 +108,9 @@ func NewDriver(root, initPath string, options []string) (*driver, error) { f, err := libcontainer.New( root, cgm, - libcontainer.InitPath(reexec.Self(), DriverName), + InitFactory("criu", reexec.Self(), DriverName), ) + if err != nil { return nil, err } @@ -272,6 +285,127 @@ func (d *driver) Unpause(c *execdriver.Command) error { return active.Resume() } +func (d *driver) Checkpoint(c *execdriver.Command, opts *libcontainer.CriuOpts) error { + active := d.activeContainers[c.ID] + if active == nil { + return fmt.Errorf("active container for %s does not exist", c.ID) + } + + d.Lock() + defer d.Unlock() + err := active.Checkpoint(opts) + if err != nil { + return err + } + + return nil +} + +func (d *driver) Restore(c *execdriver.Command, pipes *execdriver.Pipes, restoreCallback execdriver.RestoreCallback, opts *libcontainer.CriuOpts) (execdriver.ExitStatus, error) { + cont, err := d.factory.Load(c.ID) + + p := &libcontainer.Process{ + Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...), + Env: c.ProcessConfig.Env, + Cwd: c.WorkingDir, + User: c.ProcessConfig.User, + } + + var term execdriver.Terminal + + if c.ProcessConfig.Tty { + rootuid, err := cont.Config().HostUID() + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + cons, err := p.NewConsole(rootuid) + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + term, err = NewTtyConsole(cons, pipes, rootuid) + } else { + p.Stdout = pipes.Stdout + p.Stderr = pipes.Stderr + r, w, err := os.Pipe() + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + if pipes.Stdin != nil { + go func() { + io.Copy(w, pipes.Stdin) + w.Close() + }() + p.Stdin = r + } + term = &execdriver.StdConsole{} + } + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + + c.ProcessConfig.Terminal = term + + + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + + d.Lock() + d.activeContainers[c.ID] = cont + d.Unlock() + defer func() { + cont.Destroy() + d.cleanContainer(c.ID) + }() + + + // Since the CRIU binary exits after restoring the container, we + // need to reap its child by setting PR_SET_CHILD_SUBREAPER (36) + // so that it'll be owned by this process (Docker daemon) after restore. + // + // XXX This really belongs to where the Docker daemon starts. + if _, _, syserr := syscall.RawSyscall(syscall.SYS_PRCTL, 36, 1, 0); syserr != 0 { + return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Could not set PR_SET_CHILD_SUBREAPER (syserr %d)", syserr) + } + + if err := cont.Restore(p, opts); err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + + // FIXME: no idea if any of this is needed... + if restoreCallback != nil { + pid, err := p.Pid() + if err != nil { + p.Signal(os.Kill) + p.Wait() + return execdriver.ExitStatus{ExitCode: -1}, err + } + restoreCallback(&c.ProcessConfig, pid) + } + + oom := notifyOnOOM(cont) + waitF := p.Wait + if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) { + // we need such hack for tracking processes with inherited fds, + // because cmd.Wait() waiting for all streams to be copied + waitF = waitInPIDHost(p, cont) + } + ps, err := waitF() + if err != nil { + execErr, ok := err.(*exec.ExitError) + if !ok { + return execdriver.ExitStatus{ExitCode: -1}, err + } + ps = execErr.ProcessState + } + + cont.Destroy() + _, oomKill := <-oom + return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil +} + + + func (d *driver) Terminate(c *execdriver.Command) error { defer d.cleanContainer(c.ID) container, err := d.factory.Load(c.ID) diff --git a/daemon/monitor.go b/daemon/monitor.go index 293849dd364b4..f0c56b162a899 100644 --- a/daemon/monitor.go +++ b/daemon/monitor.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" + "github.com/docker/libcontainer" ) const defaultTimeIncrement = 100 @@ -44,6 +45,9 @@ type containerMonitor struct { // left waiting for nothing to happen during this time stopChan chan struct{} + // like startSignal but for restoring a container + restoreSignal chan struct{} + // timeIncrement is the amount of time to wait between restarts // this is in milliseconds timeIncrement int @@ -61,6 +65,7 @@ func newContainerMonitor(container *Container, policy runconfig.RestartPolicy) * timeIncrement: defaultTimeIncrement, stopChan: make(chan struct{}), startSignal: make(chan struct{}), + restoreSignal: make(chan struct{}), } } @@ -181,6 +186,50 @@ func (m *containerMonitor) Start() error { } } +// Like Start() but for restoring a container. +func (m *containerMonitor) Restore(opts *libcontainer.CriuOpts) error { + var ( + err error + // XXX The following line should be changed to + // exitStatus execdriver.ExitStatus to match Start() + exitCode execdriver.ExitStatus + afterRestore bool + ) + + defer func() { + if afterRestore { + m.container.Lock() + m.container.setStopped(&execdriver.ExitStatus{exitCode.ExitCode, false}) + defer m.container.Unlock() + } + m.Close() + }() + + // FIXME: right now if we startLogging again we get double logs after a restore + //if err := m.container.startLogging(); err != nil { + // m.resetContainer(false) + // return err + //} + + pipes := execdriver.NewPipes(m.container.stdin, m.container.stdout, m.container.stderr, m.container.Config.OpenStdin) + + m.container.LogEvent("restore") + m.lastStartTime = time.Now() + if exitCode, err = m.container.daemon.Restore(m.container, pipes, m.restoreCallback, opts); err != nil { + logrus.Errorf("Error restoring container: %s, exitCode=%d", err, exitCode) + m.container.ExitCode = -1 + m.resetContainer(false) + return err + } + afterRestore = true + + m.container.ExitCode = exitCode.ExitCode + m.resetMonitor(err == nil && exitCode.ExitCode == 0) + m.container.LogEvent("die") + m.resetContainer(true) + return err +} + // resetMonitor resets the stateful fields on the containerMonitor based on the // previous runs success or failure. Regardless of success, if the container had // an execution time of more than 10s then reset the timer back to the default @@ -267,6 +316,29 @@ func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid } } +// Like callback() but for restoring a container. +func (m *containerMonitor) restoreCallback(processConfig *execdriver.ProcessConfig, restorePid int) { + // If restorePid is 0, it means that restore failed. + if restorePid != 0 { + m.container.setRunning(restorePid) + } + + // Unblock the goroutine waiting in waitForRestore(). + select { + case <-m.restoreSignal: + default: + close(m.restoreSignal) + } + + if restorePid != 0 { + // Write config.json and hostconfig.json files + // to /var/lib/docker/containers/. + if err := m.container.ToDisk(); err != nil { + logrus.Debugf("%s", err) + } + } +} + // resetContainer resets the container's IO and ensures that the command is able to be executed again // by copying the data into a new struct // if lock is true, then container locked during reset diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 2fe04d2065592..6dca828524122 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -319,7 +319,7 @@ func InitDriver(config *Config) error { } // Block BridgeIP in IP allocator - ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) + ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP, false) if config.EnableIptables { iptables.OnReloaded(portMapper.ReMapAll) // call this on Firewalld reload @@ -516,7 +516,7 @@ func requestDefaultGateway(requestedGateway string, network *net.IPNet) (gateway return nil, fmt.Errorf("Gateway ip %s must be part of the network %s", requestedGateway, network.String()) } - ipAllocator.RequestIP(network, gateway) + ipAllocator.RequestIP(network, gateway, false) } return gateway, nil @@ -568,8 +568,23 @@ func linkLocalIPv6FromMac(mac string) (string, error) { return fmt.Sprintf("fe80::%x%x:%xff:fe%x:%x%x/64", hw[0], hw[1], hw[2], hw[3], hw[4], hw[5]), nil } +// This function is called from restore (in daemon/daemon.go) +// to reserve the IP address of a checkpointed container when +// the daemon starts. +func ReserveIP(id, ipAddr string) error { + logrus.Debugf("reserving IP %s at %v", ipAddr, bridgeIPv4Network) + ip, err := ipAllocator.RequestIP(bridgeIPv4Network, net.ParseIP(ipAddr), false) + if err != nil { + return err + } + currentInterfaces.Set(id, &networkInterface{ + IP: ip, + }) + return nil +} + // Allocate a network interface -func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Settings, error) { +func Allocate(id, requestedMac, requestedIP, requestedIPv6 string, restoring bool) (*network.Settings, error) { var ( ip net.IP mac net.HardwareAddr @@ -579,7 +594,8 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set defaultGWIPv6 net.IP ) - ip, err = ipAllocator.RequestIP(bridgeIPv4Network, net.ParseIP(requestedIP)) + ip, err = ipAllocator.RequestIP(bridgeIPv4Network, net.ParseIP(requestedIP), restoring) + if err != nil { return nil, err } @@ -587,6 +603,9 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set // If no explicit mac address was given, generate a random one. if mac, err = net.ParseMAC(requestedMac); err != nil { mac = generateMacAddr(ip) + logrus.Debugf("using generated MAC address: %v", mac) + } else { + logrus.Debugf("using requested MAC address: %v", mac) } if globalIPv6Network != nil { @@ -601,7 +620,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set } } - globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, ipv6) + globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, ipv6, restoring) if err != nil { logrus.Errorf("Allocator: RequestIP v6: %v", err) return nil, err @@ -680,7 +699,7 @@ func Release(id string) { } // Allocate an external port and map it to the interface -func AllocatePort(id string, port nat.Port, binding nat.PortBinding) (nat.PortBinding, error) { +func AllocatePort(id string, port nat.Port, binding nat.PortBinding, restoring bool) (nat.PortBinding, error) { var ( ip = defaultBindingIP proto = port.Proto() @@ -735,7 +754,18 @@ func AllocatePort(id string, port nat.Port, binding nat.PortBinding) (nat.PortBi } if err != nil { - return nat.PortBinding{}, err + // If we're restoring on the same Docker server, we + // should not error because we didn't release the port. + // + // XXX How do we handle this on a different server? + // XXX How do we make sure that the requestor is the + // right previous owner? + if restoring { + logrus.Warnf(">>> Ignoring error %s for restore", err) + err = nil + } else { + return nat.PortBinding{}, err + } } network.PortMappings = append(network.PortMappings, host) diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index 554dbdd5b1877..7ecaa3b221a98 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/daemon/networkdriver/ipallocator/allocator.go @@ -81,7 +81,7 @@ func (a *IPAllocator) RegisterSubnet(network *net.IPNet, subnet *net.IPNet) erro // 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 (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) { +func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP, restoring bool) (net.IP, error) { a.mutex.Lock() defer a.mutex.Unlock() @@ -95,7 +95,7 @@ func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) { if ip == nil { return allocated.getNextIP() } - return allocated.checkIP(ip) + return allocated.checkIP(ip, restoring) } // ReleaseIP adds the provided ip back into the pool of @@ -110,8 +110,20 @@ func (a *IPAllocator) ReleaseIP(network *net.IPNet, ip net.IP) error { return nil } -func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) { +func (allocated *allocatedMap) checkIP(ip net.IP, restoring bool) (net.IP, error) { if _, ok := allocated.p[ip.String()]; ok { + // If we're restoring on the same Docker server, we + // should not error on "ip already allocated" because + // we didn't release it. Also, if the server was restarted, + // it reserved this IP address when coming up. + // + // XXX How do we handle this on a different server? + // XXX How do we make sure that the requestor is the + // right previous owner? + if restoring { + logrus.Warnf("using already allocated ip %v", ip) + return ip, nil + } return nil, ErrIPAlreadyAllocated } diff --git a/daemon/state.go b/daemon/state.go index 4119d0e6cd924..d1f515678710f 100644 --- a/daemon/state.go +++ b/daemon/state.go @@ -13,6 +13,7 @@ type State struct { sync.Mutex Running bool Paused bool + Checkpointed bool Restarting bool OOMKilled bool removalInProgress bool // Not need for this to be persistent on disk. @@ -22,6 +23,7 @@ type State struct { Error string // contains last known error when starting the container StartedAt time.Time FinishedAt time.Time + CheckpointedAt time.Time waitChan chan struct{} } @@ -48,6 +50,10 @@ func (s *State) String() string { return "Removal In Progress" } + if s.Checkpointed { + return fmt.Sprintf("Checkpointed %s ago", units.HumanDuration(time.Now().UTC().Sub(s.CheckpointedAt))) + } + if s.Dead { return "Dead" } @@ -71,6 +77,10 @@ func (s *State) StateString() string { return "running" } + if s.Checkpointed { + return "checkpointed'" + } + if s.Dead { return "dead" } @@ -159,6 +169,7 @@ func (s *State) setRunning(pid int) { s.Running = true s.Paused = false s.Restarting = false + s.Checkpointed = false s.ExitCode = 0 s.Pid = pid s.StartedAt = time.Now().UTC() @@ -233,6 +244,27 @@ func (s *State) IsPaused() bool { return res } +func (s *State) SetCheckpointed(leaveRunning bool) { + s.Lock() + s.CheckpointedAt = time.Now().UTC() + s.Checkpointed = !leaveRunning + s.Running = leaveRunning + s.Paused = false + s.Restarting = false + // XXX Not sure if we need to close and recreate waitChan. + // close(s.waitChan) + // s.waitChan = make(chan struct{}) + s.Unlock() +} + +func (s *State) HasBeenCheckpointed() bool { + return s.CheckpointedAt != time.Time{} +} + +func (s *State) IsCheckpointed() bool { + return s.Checkpointed +} + func (s *State) SetRemovalInProgress() error { s.Lock() defer s.Unlock() diff --git a/docker/flags.go b/docker/flags.go index 7f0c10d2d3dbd..969f3571be53e 100644 --- a/docker/flags.go +++ b/docker/flags.go @@ -78,6 +78,7 @@ func init() { for _, command := range [][]string{ {"attach", "Attach to a running container"}, {"build", "Build an image from a Dockerfile"}, + {"checkpoint", "Checkpoint one or more running containers"}, {"commit", "Create a new image from a container's changes"}, {"cp", "Copy files/folders from a container's filesystem to the host path"}, {"create", "Create a new container"}, @@ -102,6 +103,7 @@ func init() { {"push", "Push an image or a repository to a Docker registry server"}, {"rename", "Rename an existing container"}, {"restart", "Restart a running container"}, + {"restore", "Restore one or more checkpointed containers"}, {"rm", "Remove one or more containers"}, {"rmi", "Remove one or more images"}, {"run", "Run a command in a new container"}, @@ -116,7 +118,7 @@ func init() { {"version", "Show the Docker version information"}, {"wait", "Block until a container stops, then print its exit code"}, } { - help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1]) + help += fmt.Sprintf(" %-11.11s%s\n", command[0], command[1]) } help += "\nRun 'docker COMMAND --help' for more information on a command." fmt.Fprintf(os.Stdout, "%s\n", help) From 5b1db18e19fc72570f88e27a6f48ab32ba465e7f Mon Sep 17 00:00:00 2001 From: boucher Date: Wed, 29 Apr 2015 19:50:19 -0700 Subject: [PATCH 331/332] Update checkpoint/restore to use the new server/daemon pattern --- api/server/server.go | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index cfdee975d97c7..0549488fcda37 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -37,6 +37,7 @@ import ( "github.com/docker/docker/pkg/version" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" + "github.com/docker/libcontainer" ) type ServerConfig struct { @@ -1420,15 +1421,25 @@ func (s *Server) postContainersCheckpoint(eng *engine.Engine, version version.Ve if err := parseForm(r); err != nil { return err } - job := eng.Job("checkpoint", vars["name"]) - if err := job.DecodeEnv(r.Body); err != nil { + criuOpts := &libcontainer.CriuOpts{} + if err := json.NewDecoder(r.Body).Decode(criuOpts); err != nil { return err } - if err := job.Run(); err != nil { + cont, err := s.daemon.Get(vars["name"]) + if err != nil { + logrus.Errorf("%v", err) + if strings.Contains(strings.ToLower(err.Error()), "no such id") { + w.WriteHeader(http.StatusNotFound) + return nil + } + } + + if err := cont.Checkpoint(criuOpts); err != nil { return err } + w.WriteHeader(http.StatusNoContent) return nil } @@ -1440,15 +1451,25 @@ func (s *Server) postContainersRestore(eng *engine.Engine, version version.Versi if err := parseForm(r); err != nil { return err } - job := eng.Job("restore", vars["name"]) - if err := job.DecodeEnv(r.Body); err != nil { + criuOpts := &libcontainer.CriuOpts{} + if err := json.NewDecoder(r.Body).Decode(criuOpts); err != nil { return err } - if err := job.Run(); err != nil { + cont, err := s.daemon.Get(vars["name"]) + if err != nil { + logrus.Errorf("%v", err) + if strings.Contains(strings.ToLower(err.Error()), "no such id") { + w.WriteHeader(http.StatusNotFound) + return nil + } + } + + if err := cont.Restore(criuOpts); err != nil { return err } + w.WriteHeader(http.StatusNoContent) return nil } From d68ea3937c7c0149a6512039238a2cdbe9a6bf5c Mon Sep 17 00:00:00 2001 From: boucher Date: Tue, 5 May 2015 19:18:20 -0700 Subject: [PATCH 332/332] Updates to docker master --- api/server/server.go | 26 ++------- daemon/checkpoint.go | 126 +++++++++++++------------------------------ daemon/monitor.go | 11 ++-- 3 files changed, 47 insertions(+), 116 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 0549488fcda37..cc2bffd992560 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1427,16 +1427,7 @@ func (s *Server) postContainersCheckpoint(eng *engine.Engine, version version.Ve return err } - cont, err := s.daemon.Get(vars["name"]) - if err != nil { - logrus.Errorf("%v", err) - if strings.Contains(strings.ToLower(err.Error()), "no such id") { - w.WriteHeader(http.StatusNotFound) - return nil - } - } - - if err := cont.Checkpoint(criuOpts); err != nil { + if err := s.daemon.ContainerCheckpoint(vars["name"], criuOpts); err != nil { return err } @@ -1452,21 +1443,12 @@ func (s *Server) postContainersRestore(eng *engine.Engine, version version.Versi return err } - criuOpts := &libcontainer.CriuOpts{} - if err := json.NewDecoder(r.Body).Decode(criuOpts); err != nil { + restoreOpts := &libcontainer.CriuOpts{} + if err := json.NewDecoder(r.Body).Decode(restoreOpts); err != nil { return err } - cont, err := s.daemon.Get(vars["name"]) - if err != nil { - logrus.Errorf("%v", err) - if strings.Contains(strings.ToLower(err.Error()), "no such id") { - w.WriteHeader(http.StatusNotFound) - return nil - } - } - - if err := cont.Restore(criuOpts); err != nil { + if err := s.daemon.ContainerRestore(vars["name"], restoreOpts); err != nil { return err } diff --git a/daemon/checkpoint.go b/daemon/checkpoint.go index 14476fafd2386..538e921539a42 100644 --- a/daemon/checkpoint.go +++ b/daemon/checkpoint.go @@ -3,100 +3,48 @@ package daemon import ( "fmt" - "github.com/docker/docker/engine" "github.com/docker/libcontainer" ) // Checkpoint a running container. -func (daemon *Daemon) ContainerCheckpoint(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } - - name := job.Args[0] - container, err := daemon.Get(name) - if err != nil { - return err - } - if !container.IsRunning() { - return fmt.Errorf("Container %s not running", name) - } - - opts := &libcontainer.CriuOpts{} - - if job.EnvExists("ImagesDirectory") { - opts.ImagesDirectory = job.Getenv("ImagesDirectory") - } - if job.EnvExists("WorkDirectory") { - opts.WorkDirectory = job.Getenv("WorkDirectory") - } - if job.EnvExists("LeaveRunning") { - opts.LeaveRunning = job.GetenvBool("LeaveRunning") - } - if job.EnvExists("TcpEstablished") { - opts.TcpEstablished = job.GetenvBool("TcpEstablished") - } - if job.EnvExists("ExternalUnixConnections") { - opts.ExternalUnixConnections = job.GetenvBool("ExternalUnixConnections") - } - if job.EnvExists("ShellJob") { - opts.ShellJob = job.GetenvBool("ShellJob") - } - - if err := container.Checkpoint(opts); err != nil { - return fmt.Errorf("Cannot checkpoint container %s: %s", name, err) - } - - container.LogEvent("checkpoint") - return nil +func (daemon *Daemon) ContainerCheckpoint(name string, opts *libcontainer.CriuOpts) error { + container, err := daemon.Get(name) + if err != nil { + return err + } + if !container.IsRunning() { + return fmt.Errorf("Container %s not running", name) + } + if err := container.Checkpoint(opts); err != nil { + return fmt.Errorf("Cannot checkpoint container %s: %s", name, err) + } + + container.LogEvent("checkpoint") + return nil } // Restore a checkpointed container. -func (daemon *Daemon) ContainerRestore(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } - - name := job.Args[0] - container, err := daemon.Get(name) - if err != nil { - return err - } - - if container.IsRunning() { - return fmt.Errorf("Container %s already running", name) - } - - // TODO: how should we handle the notion of checkpoint and keep running? - // right now, having ever been checkpointed is sufficient for our desires - // still requires manually calling stop before you are able to then restore - if !container.HasBeenCheckpointed() { - return fmt.Errorf("Container %s is not checkpointed", name) - } - - opts := &libcontainer.CriuOpts{} - - if job.EnvExists("ImagesDirectory") { - opts.ImagesDirectory = job.Getenv("ImagesDirectory") - } - if job.EnvExists("WorkDirectory") { - opts.WorkDirectory = job.Getenv("WorkDirectory") - } - if job.EnvExists("TcpEstablished") { - opts.TcpEstablished = job.GetenvBool("TcpEstablished") - } - if job.EnvExists("ExternalUnixConnections") { - opts.ExternalUnixConnections = job.GetenvBool("ExternalUnixConnections") - } - if job.EnvExists("ShellJob") { - opts.ShellJob = job.GetenvBool("ShellJob") - } - - if err = container.Restore(opts); err != nil { - container.LogEvent("die") - return fmt.Errorf("Cannot restore container %s: %s", name, err) - } - - container.LogEvent("restore") - return nil +func (daemon *Daemon) ContainerRestore(name string, opts *libcontainer.CriuOpts) error { + container, err := daemon.Get(name) + if err != nil { + return err + } + + // TODO: It's possible we only want to bypass the checkpointed check, + // I'm not sure how this will work if the container is already running + if container.IsRunning() { + return fmt.Errorf("Container %s already running", name) + } + + if !container.HasBeenCheckpointed() { + return fmt.Errorf("Container %s is not checkpointed", name) + } + + if err = container.Restore(opts); err != nil { + container.LogEvent("die") + return fmt.Errorf("Cannot restore container %s: %s", name, err) + } + + container.LogEvent("restore") + return nil } diff --git a/daemon/monitor.go b/daemon/monitor.go index f0c56b162a899..7474d68c158fd 100644 --- a/daemon/monitor.go +++ b/daemon/monitor.go @@ -195,7 +195,6 @@ func (m *containerMonitor) Restore(opts *libcontainer.CriuOpts) error { exitCode execdriver.ExitStatus afterRestore bool ) - defer func() { if afterRestore { m.container.Lock() @@ -206,10 +205,12 @@ func (m *containerMonitor) Restore(opts *libcontainer.CriuOpts) error { }() // FIXME: right now if we startLogging again we get double logs after a restore - //if err := m.container.startLogging(); err != nil { - // m.resetContainer(false) - // return err - //} + if m.container.logCopier == nil { + if err := m.container.startLogging(); err != nil { + m.resetContainer(false) + return err + } + } pipes := execdriver.NewPipes(m.container.stdin, m.container.stdout, m.container.stderr, m.container.Config.OpenStdin)

tp3@! zS2N}jUbH`*MmM9?P!EX@KDjBQION{W`^2t6U77!H-!-ydW~ZEB)t?2`R3Tqg+rl+@ z@X+8&R7o-vjv}~ze1=vUF@_+NAmzcZT5lB7z%}LgGcmY}{stkIA}*$bTEj`aSv}D* zs^e|G_I07Ic})o;b(%7N(M9RYN`DScikrjp+t+i)kh zKB>wK&k5<*CGczZ=4?qAz8^g^VJg+<=lj_pe33+3Yp5PS*vs*9zpBA%i7(1t@bLH1 zV4QlmDF{q4Rti#~`qkx8YG9rQYFaKV1VkxoYor>_>Kdn-ykW9h2)ovXU$8+F6J1gL zou=(f5*~+kYHk(>R~g2;l!QdvTUe`4cp=MxN{g=Y2$Vt{+7w7!Y5XJxWyLB!Y-DHk zi{5nk@O}ZmU+*fEJ7WBXJTVxriZx`Ac4>QTwlEW3hzNB{GCYrH;B!#l**R)7aRiHD z7HYLXn*kecl?&G{6iZWm*ybuTOk-}+bMWi2s2UzJL5nU_gNoK?_S>2W$2GUc|k6Q;N$Fs zr;ZG676M(z-A>jq2bQ@k{%lV($vBrEK!ZPOM2Vek_Z^)a^3=-GFs5i=qC3=RS#;%! z{#pP`&0vvKHFrhNJ}F5vuist94dYk?Xv}rY1hAXtgC@Mqkxw6h6-X*m6;0{N9SIk5 zjrhAap4mPOgJ=dznxSNDw(9UfL4vcAJNbfYPXyh<-MQ;=8C&(sg1teDtJl?H6-Z<5 zeupTPb5eS~(qu$wZ>b4#Zuayv5n@vnXFb6X&7gZ(cgdncHX2Un?*fn;<)BE!;>A0% zZQJLHJ^UUqvP9`5Z6Cxj^rOFW)1k0N$7edkr2?^zJ&!S?IL*_w=6Ol-1sDUVg@s2U zRnSK&IAGimIE4~F6Zzifc1MRHBYwpi;Q}j|e~8sY^~*pJXa52JpK4;~nmk|rI2CZa z>C7k7V!^{wbFtR&$?-dK?5Xyvy_=UjiiH~dA}i!g4Sk6aW(dnYxG8G`?B|&APaxXP zA6LHD^Dbvj^qnn`UM1BxXQht2yYyw3)6#6vhunB=>fglVoGul5WeOWoo6ZCsXS(O$ zd++bJoua98`Yy@LC2f+&#G8eAnQdpb>xU-CIa7nBC+4Ap?C=mjx9QO$dCA3~o22&@H)>o5D)V?w3gBYo*Ou|4fMZCV?epB6D2j z83?`hRYGW7(Obb|sm~IB7TjDO%P7t}{^S;iBSw?54gwInkH*y4nO3pBrB*~X^)!)F z6@ea()hSEyVw4TRS!k{|=`pST?<4I~tqRLp&vT#xnw0iyqqH1!KJOEehTz| z)=)h+Cq{y;yV{Q`1sm2rwP4BZ3W1N&<`5jx&USH`hY!RjDy)c>Wz^egYm+Bw4b&SN zCh4b~Um=VXN%d+4eG4ebgu3aZU%Po8Jss(q(aLl1pgDtvHn%Q!_lg-M0x+)Q$0#!1 zi7}{93OHm;h~dq@)xDiOmmAO@DeS2Q78NWmN#*%fdYal1V+^~SMD^g zl_??^6dG&Z?jGcgOhTvcH#0FK3OK8)Qi1(KB11 ztH{8&sH%!YtEhlQU98Zc5Okn(0xe9Won|wKLbOL^0>x5y%$Eh?m(UF4LE#@Z{PgXv zoV0Y6xET_ja_GkzE3RZbglp_3`cf1j_Z6d^I>gmCmTZ;_)6BHXPBgDWcEX1Wfb*M? zxiq;OUKhPXJEVH|igib-$NdR4y}I39=hvb(+<=!J@Wi=Ff`F%cSbP3a?OJ*+<`6hE zmLD}SHjmJRXSzQ5Y(#5BTH!&P1o`MdQjb6a595VXcjx7!4~ za`aM^^32k&(+3YH3FT*lJxml6C04Sm8=&m?=yP^f>mh)8B2svu-&ut?m#pnX;#X?S zXW+W-n=4uGUC-k&+0P6NoW8N4WFY@#*sS+M5oJJ%Yp+60m&Y9Sd>H&E_kh+aR?DpA z@faFYiJK^l*)}tQ=CoANPI!zGs9MshWY=ZS9c*nj+I2vW=lx%~2QW;bBfQu%U|lg zAClg-Cc80H4{MtWgon4J)KxXu7YM0 z2GIsc!0}MA%&H!}Ghb>lMI{qsOxmR^X;styv^Y=IOxq{o32G8db+I= zED8xwW;JcBsS*5d0bq46^3QMpxh2i;Y* z#OW-wC5vn_%-IggH|%`bO7fX2hiU;YL9~|a-$~%MM$%LnG;xaU(!9Isy~v1ME2w|Y zd_rT$j;*$_1pKI6A7h}oGGPjTenPc~eF${-gsM+w&43q9MVa zU`*Hb9K;3gGSe>IDhAwo*{TYVB`7wJ=3oa|)f!Jn%u|elEOSwHfCKa=OSJufyV&~d zc2!^Ra8Sf#cHRCP?`Oi!5)2_ibML$^y6|JI|LTL)cGH1R;zVsJa<)64vX#MbR?mh( zZ&nX#N>UtAz`%H`BDmoEOisc$sDB9F%KZtrYAE|d(-HJ{98f`Cr-AGp+#1x~m};rp zF!8*e&slx^&|-RRas?ZLzKd_^8{GuRpDet-!cJDwMzCzG9YIS(^c!~0k7W<(vWIQ{ z{*IcIU9!uVXF11-%M`v92I7BA%qYNs z*OAs<$Yn)?m#spHK?DSZc*^*MEu9?E@75@zHZ{=!yEhgO;%yWNRYw5cwXyGpXUsKG zkyt9k!@z#+42wjJjF})MH&0Cq`xIZ3Z5jk-U|j(T^Fc&jh?y?! zuu5mLD+O=Z{yrvlehd;QgPO z&#G$|QUF⪯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
7i@!(6dY;9m^s$c!uGTQy%^FYud$zDQM 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
dPu1mzW>&42AO9WM2>O7A=FYBr(B;XH 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 literal 0 HcmV?d00001 diff --git a/docs/sources/project/images/windows-mingw.png b/docs/sources/project/images/windows-mingw.png new file mode 100644 index 0000000000000000000000000000000000000000..09f53ba9ebf11ad1ef9790b5a5752b2480f3de7d GIT binary patch literal 230524 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