2018-02-05 16:05:59 -05:00
|
|
|
package system // import "github.com/docker/docker/api/server/router/system"
|
2015-07-28 14:35:24 -04:00
|
|
|
|
|
|
|
import (
|
2018-04-19 15:30:59 -07:00
|
|
|
"context"
|
2015-07-28 14:35:24 -04:00
|
|
|
"encoding/json"
|
2016-04-11 11:52:34 -07:00
|
|
|
"fmt"
|
2015-07-28 14:35:24 -04:00
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
"github.com/docker/docker/api/server/httputils"
|
2018-08-21 23:05:26 -07:00
|
|
|
"github.com/docker/docker/api/server/router/build"
|
2016-09-06 11:46:37 -07:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/events"
|
|
|
|
"github.com/docker/docker/api/types/filters"
|
2016-10-18 17:52:46 -07:00
|
|
|
"github.com/docker/docker/api/types/registry"
|
2021-02-23 18:21:37 +01:00
|
|
|
"github.com/docker/docker/api/types/swarm"
|
2016-09-06 11:46:37 -07:00
|
|
|
timetypes "github.com/docker/docker/api/types/time"
|
|
|
|
"github.com/docker/docker/api/types/versions"
|
2015-07-28 14:35:24 -04:00
|
|
|
"github.com/docker/docker/pkg/ioutils"
|
2021-06-23 15:26:54 +02:00
|
|
|
"github.com/pkg/errors"
|
2017-07-26 14:42:13 -07:00
|
|
|
"github.com/sirupsen/logrus"
|
2018-05-15 10:02:16 -07:00
|
|
|
"golang.org/x/sync/errgroup"
|
2015-07-28 14:35:24 -04:00
|
|
|
)
|
|
|
|
|
2015-12-03 13:11:19 -05:00
|
|
|
func optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
return nil
|
|
|
|
}
|
2015-07-28 14:35:24 -04:00
|
|
|
|
2018-08-05 18:52:35 -07:00
|
|
|
func (s *systemRouter) pingHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2019-01-14 17:00:47 +01:00
|
|
|
w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
|
|
w.Header().Add("Pragma", "no-cache")
|
|
|
|
|
2018-08-21 23:05:26 -07:00
|
|
|
builderVersion := build.BuilderVersion(*s.features)
|
|
|
|
if bv := builderVersion; bv != "" {
|
2018-08-05 18:52:35 -07:00
|
|
|
w.Header().Set("Builder-Version", string(bv))
|
|
|
|
}
|
2021-02-23 18:21:37 +01:00
|
|
|
|
|
|
|
w.Header().Set("Swarm", s.swarmStatus())
|
|
|
|
|
Add HEAD support for /_ping endpoint
Monitoring systems and load balancers are usually configured to use HEAD
requests for health monitoring. The /_ping endpoint currently does not
support this type of request, which means that those systems have fallback
to GET requests.
This patch adds support for HEAD requests on the /_ping endpoint.
Although optional, this patch also returns `Content-Type` and `Content-Length`
headers in case of a HEAD request; Refering to RFC 7231, section 4.3.2:
The HEAD method is identical to GET except that the server MUST NOT
send a message body in the response (i.e., the response terminates at
the end of the header section). The server SHOULD send the same
header fields in response to a HEAD request as it would have sent if
the request had been a GET, except that the payload header fields
(Section 3.3) MAY be omitted. This method can be used for obtaining
metadata about the selected representation without transferring the
representation data and is often used for testing hypertext links for
validity, accessibility, and recent modification.
A payload within a HEAD request message has no defined semantics;
sending a payload body on a HEAD request might cause some existing
implementations to reject the request.
The response to a HEAD request is cacheable; a cache MAY use it to
satisfy subsequent HEAD requests unless otherwise indicated by the
Cache-Control header field (Section 5.2 of [RFC7234]). A HEAD
response might also have an effect on previously cached responses to
GET; see Section 4.3.5 of [RFC7234].
With this patch applied, either `GET` or `HEAD` requests work; the only
difference is that the body is empty in case of a `HEAD` request;
curl -i --unix-socket /var/run/docker.sock http://localhost/_ping
HTTP/1.1 200 OK
Api-Version: 1.40
Cache-Control: no-cache, no-store, must-revalidate
Docker-Experimental: false
Ostype: linux
Pragma: no-cache
Server: Docker/dev (linux)
Date: Mon, 14 Jan 2019 12:35:16 GMT
Content-Length: 2
Content-Type: text/plain; charset=utf-8
OK
curl --head -i --unix-socket /var/run/docker.sock http://localhost/_ping
HTTP/1.1 200 OK
Api-Version: 1.40
Cache-Control: no-cache, no-store, must-revalidate
Content-Length: 0
Content-Type: text/plain; charset=utf-8
Docker-Experimental: false
Ostype: linux
Pragma: no-cache
Server: Docker/dev (linux)
Date: Mon, 14 Jan 2019 12:34:15 GMT
The client is also updated to use `HEAD` by default, but fallback to `GET`
if the daemon does not support this method.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-01-14 18:08:49 +01:00
|
|
|
if r.Method == http.MethodHead {
|
|
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
|
|
w.Header().Set("Content-Length", "0")
|
|
|
|
return nil
|
|
|
|
}
|
2015-12-03 13:11:19 -05:00
|
|
|
_, err := w.Write([]byte{'O', 'K'})
|
|
|
|
return err
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2021-02-23 18:21:37 +01:00
|
|
|
func (s *systemRouter) swarmStatus() string {
|
|
|
|
if s.cluster != nil {
|
|
|
|
if p, ok := s.cluster.(StatusProvider); ok {
|
|
|
|
return p.Status()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return string(swarm.LocalNodeStateInactive)
|
|
|
|
}
|
|
|
|
|
2015-12-03 13:11:19 -05:00
|
|
|
func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2019-08-29 07:44:39 +08:00
|
|
|
info := s.backend.SystemInfo()
|
|
|
|
|
2016-12-26 20:53:13 +08:00
|
|
|
if s.cluster != nil {
|
|
|
|
info.Swarm = s.cluster.Info()
|
2019-03-18 14:20:44 +01:00
|
|
|
info.Warnings = append(info.Warnings, info.Swarm.Warnings...)
|
2016-06-13 19:52:49 -07:00
|
|
|
}
|
2015-07-28 14:35:24 -04:00
|
|
|
|
2022-02-07 17:09:23 +01:00
|
|
|
version := httputils.VersionFromContext(ctx)
|
|
|
|
if versions.LessThan(version, "1.25") {
|
2016-08-15 12:13:18 -04:00
|
|
|
// TODO: handle this conversion in engine-api
|
2016-11-16 22:30:29 +01:00
|
|
|
type oldInfo struct {
|
|
|
|
*types.Info
|
|
|
|
ExecutionDriver string
|
|
|
|
}
|
|
|
|
old := &oldInfo{
|
|
|
|
Info: info,
|
2016-09-02 15:20:54 +02:00
|
|
|
ExecutionDriver: "<not supported>",
|
|
|
|
}
|
2016-11-16 22:30:29 +01:00
|
|
|
nameOnlySecurityOptions := []string{}
|
|
|
|
kvSecOpts, err := types.DecodeSecurityOptions(old.SecurityOptions)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, s := range kvSecOpts {
|
|
|
|
nameOnlySecurityOptions = append(nameOnlySecurityOptions, s.Name)
|
2016-09-02 15:20:54 +02:00
|
|
|
}
|
2016-11-16 22:30:29 +01:00
|
|
|
old.SecurityOptions = nameOnlySecurityOptions
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, old)
|
2016-08-15 12:13:18 -04:00
|
|
|
}
|
2022-02-07 17:09:23 +01:00
|
|
|
if versions.LessThan(version, "1.39") {
|
2018-07-16 16:07:47 +02:00
|
|
|
if info.KernelVersion == "" {
|
|
|
|
info.KernelVersion = "<unknown>"
|
|
|
|
}
|
|
|
|
if info.OperatingSystem == "" {
|
|
|
|
info.OperatingSystem = "<unknown>"
|
|
|
|
}
|
|
|
|
}
|
2022-02-07 17:09:23 +01:00
|
|
|
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
|
|
|
info.KernelMemory = false
|
|
|
|
}
|
2015-09-23 19:42:08 -04:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, info)
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2015-12-03 13:11:19 -05:00
|
|
|
func (s *systemRouter) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
info := s.backend.SystemVersion()
|
|
|
|
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, info)
|
|
|
|
}
|
|
|
|
|
2016-08-23 16:24:15 -07:00
|
|
|
func (s *systemRouter) getDiskUsage(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2021-06-23 15:26:54 +02:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-08-06 15:52:37 +02:00
|
|
|
version := httputils.VersionFromContext(ctx)
|
|
|
|
|
2021-06-23 15:26:54 +02:00
|
|
|
var getContainers, getImages, getVolumes, getBuildCache bool
|
2021-08-06 15:52:37 +02:00
|
|
|
typeStrs, ok := r.Form["type"]
|
|
|
|
if versions.LessThan(version, "1.42") || !ok {
|
2021-06-23 15:26:54 +02:00
|
|
|
getContainers, getImages, getVolumes, getBuildCache = true, true, true, true
|
|
|
|
} else {
|
|
|
|
for _, typ := range typeStrs {
|
|
|
|
switch types.DiskUsageObject(typ) {
|
|
|
|
case types.ContainerObject:
|
|
|
|
getContainers = true
|
|
|
|
case types.ImageObject:
|
|
|
|
getImages = true
|
|
|
|
case types.VolumeObject:
|
|
|
|
getVolumes = true
|
|
|
|
case types.BuildCacheObject:
|
|
|
|
getBuildCache = true
|
|
|
|
default:
|
|
|
|
return invalidRequestError{Err: fmt.Errorf("unknown object type: %s", typ)}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-15 10:02:16 -07:00
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
|
|
|
2021-06-23 15:26:54 +02:00
|
|
|
var systemDiskUsage *types.DiskUsage
|
|
|
|
if getContainers || getImages || getVolumes {
|
|
|
|
eg.Go(func() error {
|
|
|
|
var err error
|
|
|
|
systemDiskUsage, err = s.backend.SystemDiskUsage(ctx, DiskUsageOptions{
|
|
|
|
Containers: getContainers,
|
|
|
|
Images: getImages,
|
|
|
|
Volumes: getVolumes,
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
2018-05-15 10:02:16 -07:00
|
|
|
|
|
|
|
var buildCache []*types.BuildCache
|
2021-06-23 15:26:54 +02:00
|
|
|
if getBuildCache {
|
|
|
|
eg.Go(func() error {
|
|
|
|
var err error
|
|
|
|
buildCache, err = s.builder.DiskUsage(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error getting build cache usage")
|
|
|
|
}
|
|
|
|
if buildCache == nil {
|
|
|
|
// Ensure empty `BuildCache` field is represented as empty JSON array(`[]`)
|
|
|
|
// instead of `null` to be consistent with `Images`, `Containers` etc.
|
|
|
|
buildCache = []*types.BuildCache{}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
2018-05-15 10:02:16 -07:00
|
|
|
|
|
|
|
if err := eg.Wait(); err != nil {
|
2016-08-23 16:24:15 -07:00
|
|
|
return err
|
|
|
|
}
|
2018-05-15 10:02:16 -07:00
|
|
|
|
2021-06-23 15:26:54 +02:00
|
|
|
var builderSize int64
|
2021-08-06 15:52:37 +02:00
|
|
|
if versions.LessThan(version, "1.42") {
|
2021-07-08 13:30:05 +02:00
|
|
|
for _, b := range buildCache {
|
|
|
|
builderSize += b.Size
|
2022-08-02 09:49:29 +02:00
|
|
|
// Parents field was added in API 1.42 to replace the Parent field.
|
|
|
|
b.Parents = nil
|
2021-07-08 13:30:05 +02:00
|
|
|
}
|
2017-05-15 14:54:27 -07:00
|
|
|
}
|
2022-05-23 12:56:39 +02:00
|
|
|
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
|
|
|
for _, b := range buildCache {
|
|
|
|
// Parent field is deprecated in API v1.42 and up, as it is deprecated
|
|
|
|
// in BuildKit. Empty the field to omit it in the API response.
|
|
|
|
b.Parent = "" //nolint:staticcheck // ignore SA1019 (Parent field is deprecated)
|
|
|
|
}
|
|
|
|
}
|
2018-05-15 10:02:16 -07:00
|
|
|
|
2021-06-23 15:26:54 +02:00
|
|
|
du := types.DiskUsage{
|
|
|
|
BuildCache: buildCache,
|
|
|
|
BuilderSize: builderSize,
|
|
|
|
}
|
|
|
|
if systemDiskUsage != nil {
|
|
|
|
du.LayersSize = systemDiskUsage.LayersSize
|
|
|
|
du.Images = systemDiskUsage.Images
|
|
|
|
du.Containers = systemDiskUsage.Containers
|
|
|
|
du.Volumes = systemDiskUsage.Volumes
|
2021-07-08 13:12:13 +02:00
|
|
|
}
|
2016-08-23 16:24:15 -07:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, du)
|
|
|
|
}
|
|
|
|
|
2017-07-19 10:20:13 -04:00
|
|
|
type invalidRequestError struct {
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e invalidRequestError) Error() string {
|
|
|
|
return e.Err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e invalidRequestError) InvalidParameter() {}
|
|
|
|
|
2015-12-03 13:11:19 -05:00
|
|
|
func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
2016-04-11 11:52:34 -07:00
|
|
|
|
|
|
|
since, err := eventTime(r.Form.Get("since"))
|
2015-09-23 14:51:43 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
2016-04-11 11:52:34 -07:00
|
|
|
until, err := eventTime(r.Form.Get("until"))
|
2015-09-23 14:51:43 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2016-04-11 11:52:34 -07:00
|
|
|
var (
|
|
|
|
timeout <-chan time.Time
|
|
|
|
onlyPastEvents bool
|
|
|
|
)
|
|
|
|
if !until.IsZero() {
|
|
|
|
if until.Before(since) {
|
2017-07-19 10:20:13 -04:00
|
|
|
return invalidRequestError{fmt.Errorf("`since` time (%s) cannot be after `until` time (%s)", r.Form.Get("since"), r.Form.Get("until"))}
|
2016-04-11 11:52:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
onlyPastEvents = until.Before(now)
|
|
|
|
|
|
|
|
if !onlyPastEvents {
|
|
|
|
dur := until.Sub(now)
|
2019-01-09 10:24:03 -08:00
|
|
|
timer := time.NewTimer(dur)
|
|
|
|
defer timer.Stop()
|
|
|
|
timeout = timer.C
|
2016-04-11 11:52:34 -07:00
|
|
|
}
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2017-09-26 13:59:45 +02:00
|
|
|
ef, err := filters.FromJSON(r.Form.Get("filters"))
|
2015-07-28 14:35:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-11-02 16:11:28 -08:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
output := ioutils.NewWriteFlusher(w)
|
|
|
|
defer output.Close()
|
2015-12-19 09:43:10 -05:00
|
|
|
output.Flush()
|
2015-11-02 16:11:28 -08:00
|
|
|
|
|
|
|
enc := json.NewEncoder(output)
|
2015-07-28 14:35:24 -04:00
|
|
|
|
2016-04-11 11:52:34 -07:00
|
|
|
buffered, l := s.backend.SubscribeToEvents(since, until, ef)
|
2015-12-03 13:11:19 -05:00
|
|
|
defer s.backend.UnsubscribeFromEvents(l)
|
2015-10-12 11:54:46 -07:00
|
|
|
|
2015-11-25 21:03:10 -05:00
|
|
|
for _, ev := range buffered {
|
|
|
|
if err := enc.Encode(ev); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-11 11:52:34 -07:00
|
|
|
if onlyPastEvents {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-28 14:35:24 -04:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case ev := <-l:
|
2015-12-21 17:55:23 -05:00
|
|
|
jev, ok := ev.(events.Message)
|
2015-07-28 14:35:24 -04:00
|
|
|
if !ok {
|
2015-12-21 17:55:23 -05:00
|
|
|
logrus.Warnf("unexpected event message: %q", ev)
|
2015-07-28 14:35:24 -04:00
|
|
|
continue
|
|
|
|
}
|
2015-11-25 21:03:10 -05:00
|
|
|
if err := enc.Encode(jev); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
2016-03-31 09:48:20 -07:00
|
|
|
case <-timeout:
|
2015-07-28 14:35:24 -04:00
|
|
|
return nil
|
2016-03-25 11:33:54 -07:00
|
|
|
case <-ctx.Done():
|
|
|
|
logrus.Debug("Client context cancelled, stop sending events")
|
2015-07-28 14:35:24 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-12-03 13:11:19 -05:00
|
|
|
|
|
|
|
func (s *systemRouter) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2022-03-03 10:23:18 +01:00
|
|
|
var config *registry.AuthConfig
|
2015-12-03 13:11:19 -05:00
|
|
|
err := json.NewDecoder(r.Body).Decode(&config)
|
|
|
|
r.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-03-18 14:42:40 -07:00
|
|
|
status, token, err := s.backend.AuthenticateToRegistry(ctx, config)
|
2015-12-03 13:11:19 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-10-18 17:52:46 -07:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, ®istry.AuthenticateOKBody{
|
2016-02-23 15:18:04 -08:00
|
|
|
Status: status,
|
|
|
|
IdentityToken: token,
|
2015-12-03 13:11:19 -05:00
|
|
|
})
|
|
|
|
}
|
2016-04-11 11:52:34 -07:00
|
|
|
|
|
|
|
func eventTime(formTime string) (time.Time, error) {
|
|
|
|
t, tNano, err := timetypes.ParseTimestamps(formTime, -1)
|
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, err
|
|
|
|
}
|
|
|
|
if t == -1 {
|
|
|
|
return time.Time{}, nil
|
|
|
|
}
|
|
|
|
return time.Unix(t, tNano), nil
|
|
|
|
}
|