dotcloud/docker

View on GitHub
api/server/router/system/system_routes.go

Summary

Maintainability
C
1 day
Test Coverage
package system // import "github.com/docker/docker/api/server/router/system"

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"

    "github.com/containerd/log"
    "github.com/docker/docker/api/server/httputils"
    "github.com/docker/docker/api/server/router/build"
    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/events"
    "github.com/docker/docker/api/types/filters"
    "github.com/docker/docker/api/types/registry"
    "github.com/docker/docker/api/types/swarm"
    "github.com/docker/docker/api/types/system"
    timetypes "github.com/docker/docker/api/types/time"
    "github.com/docker/docker/api/types/versions"
    "github.com/docker/docker/pkg/ioutils"
    "github.com/pkg/errors"
    "golang.org/x/sync/errgroup"
)

func optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    w.WriteHeader(http.StatusOK)
    return nil
}

func (s *systemRouter) pingHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
    w.Header().Add("Pragma", "no-cache")

    builderVersion := build.BuilderVersion(s.features())
    if bv := builderVersion; bv != "" {
        w.Header().Set("Builder-Version", string(bv))
    }

    w.Header().Set("Swarm", s.swarmStatus())

    if r.Method == http.MethodHead {
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        w.Header().Set("Content-Length", "0")
        return nil
    }
    _, err := w.Write([]byte{'O', 'K'})
    return err
}

func (s *systemRouter) swarmStatus() string {
    if s.cluster != nil {
        if p, ok := s.cluster.(StatusProvider); ok {
            return p.Status()
        }
    }
    return string(swarm.LocalNodeStateInactive)
}

func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    version := httputils.VersionFromContext(ctx)
    info, _, _ := s.collectSystemInfo.Do(ctx, version, func(ctx context.Context) (*system.Info, error) {
        info, err := s.backend.SystemInfo(ctx)
        if err != nil {
            return nil, err
        }

        if s.cluster != nil {
            info.Swarm = s.cluster.Info(ctx)
            info.Warnings = append(info.Warnings, info.Swarm.Warnings...)
        }

        if versions.LessThan(version, "1.25") {
            // TODO: handle this conversion in engine-api
            kvSecOpts, err := system.DecodeSecurityOptions(info.SecurityOptions)
            if err != nil {
                info.Warnings = append(info.Warnings, err.Error())
            }
            var nameOnly []string
            for _, so := range kvSecOpts {
                nameOnly = append(nameOnly, so.Name)
            }
            info.SecurityOptions = nameOnly
        }
        if versions.LessThan(version, "1.39") {
            if info.KernelVersion == "" {
                info.KernelVersion = "<unknown>"
            }
            if info.OperatingSystem == "" {
                info.OperatingSystem = "<unknown>"
            }
        }
        if versions.LessThan(version, "1.44") {
            for k, rt := range info.Runtimes {
                // Status field introduced in API v1.44.
                info.Runtimes[k] = system.RuntimeWithStatus{Runtime: rt.Runtime}
            }
        }
        if versions.LessThan(version, "1.46") {
            // Containerd field introduced in API v1.46.
            info.Containerd = nil
        }

        // TODO(thaJeztah): Expected commits are deprecated, and should no longer be set in API 1.49.
        info.ContainerdCommit.Expected = info.ContainerdCommit.ID //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.49.
        info.RuncCommit.Expected = info.RuncCommit.ID             //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.49.
        info.InitCommit.Expected = info.InitCommit.ID             //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.49.

        if versions.GreaterThanOrEqualTo(version, "1.42") {
            info.KernelMemory = false
        }
        return info, nil
    })
    return httputils.WriteJSON(w, http.StatusOK, info)
}

func (s *systemRouter) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    info, err := s.backend.SystemVersion(ctx)
    if err != nil {
        return err
    }

    return httputils.WriteJSON(w, http.StatusOK, info)
}

func (s *systemRouter) getDiskUsage(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    if err := httputils.ParseForm(r); err != nil {
        return err
    }

    version := httputils.VersionFromContext(ctx)

    var getContainers, getImages, getVolumes, getBuildCache bool
    typeStrs, ok := r.Form["type"]
    if versions.LessThan(version, "1.42") || !ok {
        getContainers, getImages, getVolumes, getBuildCache = true, true, true, s.builder != nil
    } 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)}
            }
        }
    }

    eg, ctx := errgroup.WithContext(ctx)

    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
        })
    }

    var buildCache []*types.BuildCache
    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
        })
    }

    if err := eg.Wait(); err != nil {
        return err
    }

    var builderSize int64
    if versions.LessThan(version, "1.42") {
        for _, b := range buildCache {
            builderSize += b.Size
            // Parents field was added in API 1.42 to replace the Parent field.
            b.Parents = nil
        }
    }
    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)
        }
    }
    if versions.LessThan(version, "1.44") {
        for _, b := range systemDiskUsage.Images {
            b.VirtualSize = b.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
        }
    }

    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
    }
    return httputils.WriteJSON(w, http.StatusOK, du)
}

type invalidRequestError struct {
    Err error
}

func (e invalidRequestError) Error() string {
    return e.Err.Error()
}

func (e invalidRequestError) InvalidParameter() {}

func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    if err := httputils.ParseForm(r); err != nil {
        return err
    }

    since, err := eventTime(r.Form.Get("since"))
    if err != nil {
        return err
    }
    until, err := eventTime(r.Form.Get("until"))
    if err != nil {
        return err
    }

    var (
        timeout        <-chan time.Time
        onlyPastEvents bool
    )
    if !until.IsZero() {
        if until.Before(since) {
            return invalidRequestError{fmt.Errorf("`since` time (%s) cannot be after `until` time (%s)", r.Form.Get("since"), r.Form.Get("until"))}
        }

        now := time.Now()

        onlyPastEvents = until.Before(now)

        if !onlyPastEvents {
            dur := until.Sub(now)
            timer := time.NewTimer(dur)
            defer timer.Stop()
            timeout = timer.C
        }
    }

    ef, err := filters.FromJSON(r.Form.Get("filters"))
    if err != nil {
        return err
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    output := ioutils.NewWriteFlusher(w)
    defer output.Close()
    output.Flush()

    enc := json.NewEncoder(output)

    buffered, l := s.backend.SubscribeToEvents(since, until, ef)
    defer s.backend.UnsubscribeFromEvents(l)

    shouldSkip := func(ev events.Message) bool { return false }
    if versions.LessThan(httputils.VersionFromContext(ctx), "1.46") {
        // Image create events were added in API 1.46
        shouldSkip = func(ev events.Message) bool {
            return ev.Type == "image" && ev.Action == "create"
        }
    }

    for _, ev := range buffered {
        if shouldSkip(ev) {
            continue
        }
        if err := enc.Encode(ev); err != nil {
            return err
        }
    }

    if onlyPastEvents {
        return nil
    }

    for {
        select {
        case ev := <-l:
            jev, ok := ev.(events.Message)
            if !ok {
                log.G(ctx).Warnf("unexpected event message: %q", ev)
                continue
            }
            if shouldSkip(jev) {
                continue
            }
            if err := enc.Encode(jev); err != nil {
                return err
            }
        case <-timeout:
            return nil
        case <-ctx.Done():
            log.G(ctx).Debug("Client context cancelled, stop sending events")
            return nil
        }
    }
}

func (s *systemRouter) postAuth(ctx context.Context, 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
    }
    status, token, err := s.backend.AuthenticateToRegistry(ctx, config)
    if err != nil {
        return err
    }
    return httputils.WriteJSON(w, http.StatusOK, &registry.AuthenticateOKBody{
        Status:        status,
        IdentityToken: token,
    })
}

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
}