dotcloud/docker

View on GitHub
image/v1/imagev1.go

Summary

Maintainability
A
35 mins
Test Coverage
package v1 // import "github.com/docker/docker/image/v1"

import (
    "context"
    "encoding/json"
    "errors"
    "strings"

    "github.com/containerd/log"
    "github.com/docker/docker/api/types/versions"
    "github.com/docker/docker/image"
    "github.com/docker/docker/layer"
    "github.com/opencontainers/go-digest"
)

const (
    // noFallbackMinVersion is the minimum version for which v1compatibility
    // information will not be marshaled through the Image struct to remove
    // blank fields.
    noFallbackMinVersion = "1.8.3"

    fullLen = 64
)

// HistoryFromConfig creates a History struct from v1 configuration JSON
func HistoryFromConfig(imageJSON []byte, emptyLayer bool) (image.History, error) {
    h := image.History{}
    var v1Image image.V1Image
    if err := json.Unmarshal(imageJSON, &v1Image); err != nil {
        return h, err
    }

    return image.History{
        Author:     v1Image.Author,
        Created:    v1Image.Created,
        CreatedBy:  strings.Join(v1Image.ContainerConfig.Cmd, " "),
        Comment:    v1Image.Comment,
        EmptyLayer: emptyLayer,
    }, nil
}

// CreateID creates an ID from v1 image, layerID and parent ID.
// Used for backwards compatibility with old clients.
func CreateID(v1Image image.V1Image, layerID layer.ChainID, parent digest.Digest) (digest.Digest, error) {
    v1Image.ID = ""
    v1JSON, err := json.Marshal(v1Image)
    if err != nil {
        return "", err
    }

    var config map[string]*json.RawMessage
    if err := json.Unmarshal(v1JSON, &config); err != nil {
        return "", err
    }

    // FIXME: note that this is slightly incompatible with RootFS logic
    config["layer_id"] = rawJSON(layerID)
    if parent != "" {
        config["parent"] = rawJSON(parent)
    }

    configJSON, err := json.Marshal(config)
    if err != nil {
        return "", err
    }
    log.G(context.TODO()).Debugf("CreateV1ID %s", configJSON)

    return digest.FromBytes(configJSON), nil
}

// MakeConfigFromV1Config creates an image config from the legacy V1 config format.
func MakeConfigFromV1Config(imageJSON []byte, rootfs *image.RootFS, history []image.History) ([]byte, error) {
    var dver struct {
        DockerVersion string `json:"docker_version"`
    }

    if err := json.Unmarshal(imageJSON, &dver); err != nil {
        return nil, err
    }

    useFallback := versions.LessThan(dver.DockerVersion, noFallbackMinVersion)

    if useFallback {
        var v1Image image.V1Image
        err := json.Unmarshal(imageJSON, &v1Image)
        if err != nil {
            return nil, err
        }
        imageJSON, err = json.Marshal(v1Image)
        if err != nil {
            return nil, err
        }
    }

    var c map[string]*json.RawMessage
    if err := json.Unmarshal(imageJSON, &c); err != nil {
        return nil, err
    }

    delete(c, "id")
    delete(c, "parent")
    delete(c, "Size") // Size is calculated from data on disk and is inconsistent
    delete(c, "parent_id")
    delete(c, "layer_id")
    delete(c, "throwaway")

    c["rootfs"] = rawJSON(rootfs)
    c["history"] = rawJSON(history)

    return json.Marshal(c)
}

func rawJSON(value interface{}) *json.RawMessage {
    jsonval, err := json.Marshal(value)
    if err != nil {
        return nil
    }
    return (*json.RawMessage)(&jsonval)
}

// ValidateID checks whether an ID string is a valid image ID.
func ValidateID(id string) error {
    if len(id) != fullLen {
        return errors.New("image ID '" + id + "' is invalid")
    }
    for _, c := range id {
        if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
            return errors.New("image ID '" + id + "' is invalid")
        }
    }
    return nil
}