dotcloud/docker

View on GitHub
pkg/tarsum/versioning.go

Summary

Maintainability
A
0 mins
Test Coverage
package tarsum // import "github.com/docker/docker/pkg/tarsum"

import (
    "archive/tar"
    "errors"
    "io"
    "sort"
    "strconv"
    "strings"
)

// Version is used for versioning of the TarSum algorithm
// based on the prefix of the hash used
// i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"
type Version int

// Prefix of "tarsum"
const (
    Version0 Version = iota
    Version1
    // VersionDev this constant will be either the latest or an unsettled next-version of the TarSum calculation
    VersionDev
)

// WriteV1Header writes a tar header to a writer in V1 tarsum format.
func WriteV1Header(h *tar.Header, w io.Writer) {
    for _, elem := range v1TarHeaderSelect(h) {
        w.Write([]byte(elem[0] + elem[1]))
    }
}

// VersionLabelForChecksum returns the label for the given tarsum
// checksum, i.e., everything before the first `+` character in
// the string or an empty string if no label separator is found.
func VersionLabelForChecksum(checksum string) string {
    // Checksums are in the form: {versionLabel}+{hashID}:{hex}
    sepIndex := strings.Index(checksum, "+")
    if sepIndex < 0 {
        return ""
    }
    return checksum[:sepIndex]
}

// GetVersions gets a list of all known tarsum versions.
func GetVersions() []Version {
    v := []Version{}
    for k := range tarSumVersions {
        v = append(v, k)
    }
    return v
}

var (
    tarSumVersions = map[Version]string{
        Version0:   "tarsum",
        Version1:   "tarsum.v1",
        VersionDev: "tarsum.dev",
    }
    tarSumVersionsByName = map[string]Version{
        "tarsum":     Version0,
        "tarsum.v1":  Version1,
        "tarsum.dev": VersionDev,
    }
)

func (tsv Version) String() string {
    return tarSumVersions[tsv]
}

// GetVersionFromTarsum returns the Version from the provided string.
func GetVersionFromTarsum(tarsum string) (Version, error) {
    versionName, _, _ := strings.Cut(tarsum, "+")
    version, ok := tarSumVersionsByName[versionName]
    if !ok {
        return -1, ErrNotVersion
    }
    return version, nil
}

// Errors that may be returned by functions in this package
var (
    ErrNotVersion            = errors.New("string does not include a TarSum Version")
    ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
)

// tarHeaderSelector is the interface which different versions
// of tarsum should use for selecting and ordering tar headers
// for each item in the archive.
type tarHeaderSelector interface {
    selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
}

type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)

func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
    return f(h)
}

func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
    return [][2]string{
        {"name", h.Name},
        {"mode", strconv.FormatInt(h.Mode, 10)},
        {"uid", strconv.Itoa(h.Uid)},
        {"gid", strconv.Itoa(h.Gid)},
        {"size", strconv.FormatInt(h.Size, 10)},
        {"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)},
        {"typeflag", string([]byte{h.Typeflag})},
        {"linkname", h.Linkname},
        {"uname", h.Uname},
        {"gname", h.Gname},
        {"devmajor", strconv.FormatInt(h.Devmajor, 10)},
        {"devminor", strconv.FormatInt(h.Devminor, 10)},
    }
}

func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
    // Get extended attributes.
    const paxSchilyXattr = "SCHILY.xattr."
    var xattrs [][2]string
    for k, v := range h.PAXRecords {
        if xattr, ok := strings.CutPrefix(k, paxSchilyXattr); ok {
            // h.Xattrs keys take precedence over h.PAXRecords keys, like
            // archive/tar does when writing.
            if vv, ok := h.Xattrs[xattr]; ok { //nolint:staticcheck // field deprecated in stdlib
                v = vv
            }
            xattrs = append(xattrs, [2]string{xattr, v})
        }
    }
    // Get extended attributes which are not in PAXRecords.
    for k, v := range h.Xattrs { //nolint:staticcheck // field deprecated in stdlib
        if _, ok := h.PAXRecords[paxSchilyXattr+k]; !ok {
            xattrs = append(xattrs, [2]string{k, v})
        }
    }
    sort.Slice(xattrs, func(i, j int) bool { return xattrs[i][0] < xattrs[j][0] })

    // Make the slice with enough capacity to hold the 11 basic headers
    // we want from the v0 selector plus however many xattrs we have.
    orderedHeaders = make([][2]string, 0, 11+len(xattrs))

    // Copy all headers from v0 excluding the 'mtime' header (the 5th element).
    v0headers := v0TarHeaderSelect(h)
    orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
    orderedHeaders = append(orderedHeaders, v0headers[6:]...)

    // Finally, append the sorted xattrs.
    orderedHeaders = append(orderedHeaders, xattrs...)

    return
}

var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
    Version0:   v0TarHeaderSelect,
    Version1:   v1TarHeaderSelect,
    VersionDev: v1TarHeaderSelect,
}

func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
    headerSelector, ok := registeredHeaderSelectors[v]
    if !ok {
        return nil, ErrVersionNotImplemented
    }

    return headerSelector, nil
}