mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 02:09:56 +00:00
Merge pull request #7410 from yifan-gu/rkt_godep
Godep: Add godep for rkt.
This commit is contained in:
commit
17e612324e
116
Godeps/Godeps.json
generated
116
Godeps/Godeps.json
generated
@ -48,30 +48,130 @@
|
||||
"ImportPath": "github.com/abbot/go-http-auth",
|
||||
"Rev": "c0ef4539dfab4d21c8ef20ba2924f9fc6f186d35"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/appc/spec/aci",
|
||||
"Comment": "v0.5.1-55-g87808a3",
|
||||
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/appc/spec/pkg/acirenderer",
|
||||
"Comment": "v0.5.1-55-g87808a3",
|
||||
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/appc/spec/pkg/tarheader",
|
||||
"Comment": "v0.5.1-55-g87808a3",
|
||||
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/appc/spec/schema",
|
||||
"Comment": "v0.5.1-55-g87808a3",
|
||||
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/beorn7/perks/quantile",
|
||||
"Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/camlistore/lock",
|
||||
"Rev": "ae27720f340952636b826119b58130b9c1a847a0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/negroni",
|
||||
"Comment": "v0.1-62-g8d75e11",
|
||||
"Rev": "8d75e11374a1928608c906fe745b538483e7aeb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes",
|
||||
"Comment": "v2.0.4-288-g866a9d4",
|
||||
"Rev": "866a9d4e41401657ea44bf539b2c5561d6fdcd67"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/types",
|
||||
"Comment": "v2.0.4-288-g866a9d4",
|
||||
"Rev": "866a9d4e41401657ea44bf539b2c5561d6fdcd67"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-etcd/etcd",
|
||||
"Comment": "v2.0.0-3-g0424b5f",
|
||||
"Rev": "0424b5f86ef0ca57a5309c599f74bbb3e97ecd9d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-semver/semver",
|
||||
"Rev": "6fe83ccda8fb9b7549c9ab4ba47f47858bc950aa"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-systemd/dbus",
|
||||
"Comment": "v2-27-g97e243d",
|
||||
"Rev": "97e243d21a8e232e9d8af38ba2366dfcfceebeba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-systemd/unit",
|
||||
"Comment": "v2-27-g97e243d",
|
||||
"Rev": "97e243d21a8e232e9d8af38ba2366dfcfceebeba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/rkt/pkg/aci",
|
||||
"Comment": "v0.5.4",
|
||||
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/rkt/pkg/lock",
|
||||
"Comment": "v0.5.4",
|
||||
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/rkt/pkg/sys",
|
||||
"Comment": "v0.5.4",
|
||||
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/rkt/pkg/tar",
|
||||
"Comment": "v0.5.4",
|
||||
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/rkt/store",
|
||||
"Comment": "v0.5.4",
|
||||
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cpuguy83/go-md2man/mangen",
|
||||
"Comment": "v1.0.2-5-g2831f11",
|
||||
"Rev": "2831f11f66ff4008f10e2cd7ed9a85e3d3fc2bed"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/bufs",
|
||||
"Rev": "3dcccbd7064a1689f9c093a988ea11ac00e21f51"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/exp/lldb",
|
||||
"Rev": "9b0e4be12fbdb7b843e0a658a04c35d160371789"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/fileutil",
|
||||
"Rev": "21ae57c9dce724a15e88bd9cd46d5668f3e880a5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/mathutil",
|
||||
"Rev": "250d0b9d3304c5ea0c4cfc7d9efc7ee528b81f3b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/ql",
|
||||
"Rev": "fc1b91b82089d3f132fbed8a7c9f349c3133eb96"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/sortutil",
|
||||
"Rev": "d4401851b4c370f979b842fa1e45e0b3b718b391"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/strutil",
|
||||
"Rev": "97bc31f80ac4c9fa9c5dc5fea74c383858988ea2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cznic/zappy",
|
||||
"Rev": "47331054e4f96186e3ff772877c0443909368a45"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Rev": "3e6e67c4dcea3ac2f25fd4731abc0e1deaf36216"
|
||||
@ -331,6 +431,14 @@
|
||||
"Comment": "v1.0-28-g8adf9e1730c5",
|
||||
"Rev": "8adf9e1730c55cdc590de7d49766cb2acc88d8f2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/petar/GoLLRB/llrb",
|
||||
"Rev": "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/peterbourgon/diskv",
|
||||
"Rev": "508f5671a72eeaef05cf8c24abe7fbc1c07faf69"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/prometheus/client_golang/model",
|
||||
"Comment": "0.4.0-1-g692492e",
|
||||
@ -411,6 +519,14 @@
|
||||
"Comment": "v1.0-13-g5292687",
|
||||
"Rev": "5292687f5379e01054407da44d7c4590a61fd3de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/cast5",
|
||||
"Rev": "a7ead6ddf06233883deca151dffaef2effbf498f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/openpgp",
|
||||
"Rev": "a7ead6ddf06233883deca151dffaef2effbf498f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "cbcac7bb8415db9b6cb4d1ebab1dc9afbd688b97"
|
||||
|
81
Godeps/_workspace/src/github.com/appc/spec/aci/build.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/appc/spec/aci/build.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/appc/spec/pkg/tarheader"
|
||||
)
|
||||
|
||||
// BuildWalker creates a filepath.WalkFunc that walks over the given root
|
||||
// (which should represent an ACI layout on disk) and adds the files in the
|
||||
// rootfs/ subdirectory to the given ArchiveWriter
|
||||
func BuildWalker(root string, aw ArchiveWriter) filepath.WalkFunc {
|
||||
// cache of inode -> filepath, used to leverage hard links in the archive
|
||||
inos := map[uint64]string{}
|
||||
return func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relpath, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relpath == "." {
|
||||
return nil
|
||||
}
|
||||
if relpath == ManifestFile {
|
||||
// ignore; this will be written by the archive writer
|
||||
// TODO(jonboulle): does this make sense? maybe just remove from archivewriter?
|
||||
return nil
|
||||
}
|
||||
|
||||
link := ""
|
||||
var r io.Reader
|
||||
switch info.Mode() & os.ModeType {
|
||||
case os.ModeSocket:
|
||||
return nil
|
||||
case os.ModeNamedPipe:
|
||||
case os.ModeCharDevice:
|
||||
case os.ModeDevice:
|
||||
case os.ModeDir:
|
||||
case os.ModeSymlink:
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link = target
|
||||
default:
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
r = file
|
||||
}
|
||||
|
||||
hdr, err := tar.FileInfoHeader(info, link)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Because os.FileInfo's Name method returns only the base
|
||||
// name of the file it describes, it may be necessary to
|
||||
// modify the Name field of the returned header to provide the
|
||||
// full path name of the file.
|
||||
hdr.Name = relpath
|
||||
tarheader.Populate(hdr, info, inos)
|
||||
// If the file is a hard link to a file we've already seen, we
|
||||
// don't need the contents
|
||||
if hdr.Typeflag == tar.TypeLink {
|
||||
hdr.Size = 0
|
||||
r = nil
|
||||
}
|
||||
if err := aw.AddFile(hdr, r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
2
Godeps/_workspace/src/github.com/appc/spec/aci/doc.go
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/appc/spec/aci/doc.go
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// Package aci contains various functions for working with App Container Images.
|
||||
package aci
|
194
Godeps/_workspace/src/github.com/appc/spec/aci/file.go
generated
vendored
Normal file
194
Godeps/_workspace/src/github.com/appc/spec/aci/file.go
generated
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/appc/spec/schema"
|
||||
)
|
||||
|
||||
type FileType string
|
||||
|
||||
const (
|
||||
TypeGzip = FileType("gz")
|
||||
TypeBzip2 = FileType("bz2")
|
||||
TypeXz = FileType("xz")
|
||||
TypeTar = FileType("tar")
|
||||
TypeText = FileType("text")
|
||||
TypeUnknown = FileType("unknown")
|
||||
|
||||
readLen = 512 // max bytes to sniff
|
||||
|
||||
hexHdrGzip = "1f8b"
|
||||
hexHdrBzip2 = "425a68"
|
||||
hexHdrXz = "fd377a585a00"
|
||||
hexSigTar = "7573746172"
|
||||
|
||||
tarOffset = 257
|
||||
|
||||
textMime = "text/plain; charset=utf-8"
|
||||
)
|
||||
|
||||
var (
|
||||
hdrGzip []byte
|
||||
hdrBzip2 []byte
|
||||
hdrXz []byte
|
||||
sigTar []byte
|
||||
tarEnd int
|
||||
)
|
||||
|
||||
func mustDecodeHex(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func init() {
|
||||
hdrGzip = mustDecodeHex(hexHdrGzip)
|
||||
hdrBzip2 = mustDecodeHex(hexHdrBzip2)
|
||||
hdrXz = mustDecodeHex(hexHdrXz)
|
||||
sigTar = mustDecodeHex(hexSigTar)
|
||||
tarEnd = tarOffset + len(sigTar)
|
||||
}
|
||||
|
||||
// DetectFileType attempts to detect the type of file that the given reader
|
||||
// represents by comparing it against known file signatures (magic numbers)
|
||||
func DetectFileType(r io.Reader) (FileType, error) {
|
||||
var b bytes.Buffer
|
||||
n, err := io.CopyN(&b, r, readLen)
|
||||
if err != nil && err != io.EOF {
|
||||
return TypeUnknown, err
|
||||
}
|
||||
bs := b.Bytes()
|
||||
switch {
|
||||
case bytes.HasPrefix(bs, hdrGzip):
|
||||
return TypeGzip, nil
|
||||
case bytes.HasPrefix(bs, hdrBzip2):
|
||||
return TypeBzip2, nil
|
||||
case bytes.HasPrefix(bs, hdrXz):
|
||||
return TypeXz, nil
|
||||
case n > int64(tarEnd) && bytes.Equal(bs[tarOffset:tarEnd], sigTar):
|
||||
return TypeTar, nil
|
||||
case http.DetectContentType(bs) == textMime:
|
||||
return TypeText, nil
|
||||
default:
|
||||
return TypeUnknown, nil
|
||||
}
|
||||
}
|
||||
|
||||
// XzReader shells out to a command line xz executable (if
|
||||
// available) to decompress the given io.Reader using the xz
|
||||
// compression format
|
||||
func XzReader(r io.Reader) io.ReadCloser {
|
||||
rpipe, wpipe := io.Pipe()
|
||||
ex, err := exec.LookPath("xz")
|
||||
if err != nil {
|
||||
log.Fatalf("couldn't find xz executable: %v", err)
|
||||
}
|
||||
cmd := exec.Command(ex, "--decompress", "--stdout")
|
||||
cmd.Stdin = r
|
||||
cmd.Stdout = wpipe
|
||||
|
||||
go func() {
|
||||
err := cmd.Run()
|
||||
wpipe.CloseWithError(err)
|
||||
}()
|
||||
|
||||
return rpipe
|
||||
}
|
||||
|
||||
// ManifestFromImage extracts a new schema.ImageManifest from the given ACI image.
|
||||
func ManifestFromImage(rs io.ReadSeeker) (*schema.ImageManifest, error) {
|
||||
var im schema.ImageManifest
|
||||
|
||||
tr, err := NewCompressedTarReader(rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
switch err {
|
||||
case io.EOF:
|
||||
return nil, errors.New("missing manifest")
|
||||
case nil:
|
||||
if filepath.Clean(hdr.Name) == ManifestFile {
|
||||
data, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := im.UnmarshalJSON(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &im, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("error extracting tarball: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewCompressedTarReader creates a new tar.Reader reading from the given ACI image.
|
||||
func NewCompressedTarReader(rs io.ReadSeeker) (*tar.Reader, error) {
|
||||
cr, err := NewCompressedReader(rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tar.NewReader(cr), nil
|
||||
}
|
||||
|
||||
// NewCompressedReader creates a new io.Reader from the given ACI image.
|
||||
func NewCompressedReader(rs io.ReadSeeker) (io.Reader, error) {
|
||||
|
||||
var (
|
||||
dr io.Reader
|
||||
err error
|
||||
)
|
||||
|
||||
_, err = rs.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ftype, err := DetectFileType(rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = rs.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch ftype {
|
||||
case TypeGzip:
|
||||
dr, err = gzip.NewReader(rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case TypeBzip2:
|
||||
dr = bzip2.NewReader(rs)
|
||||
case TypeXz:
|
||||
dr = XzReader(rs)
|
||||
case TypeTar:
|
||||
dr = rs
|
||||
case TypeUnknown:
|
||||
return nil, errors.New("error: unknown image filetype")
|
||||
default:
|
||||
return nil, errors.New("no type returned from DetectFileType?")
|
||||
}
|
||||
return dr, nil
|
||||
}
|
136
Godeps/_workspace/src/github.com/appc/spec/aci/file_test.go
generated
vendored
Normal file
136
Godeps/_workspace/src/github.com/appc/spec/aci/file_test.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newTestACI(usedotslash bool) (*os.File, error) {
|
||||
tf, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manifestBody := `{"acKind":"ImageManifest","acVersion":"0.5.1","name":"example.com/app"}`
|
||||
|
||||
gw := gzip.NewWriter(tf)
|
||||
tw := tar.NewWriter(gw)
|
||||
|
||||
manifestPath := "manifest"
|
||||
if usedotslash {
|
||||
manifestPath = "./" + manifestPath
|
||||
}
|
||||
hdr := &tar.Header{
|
||||
Name: manifestPath,
|
||||
Size: int64(len(manifestBody)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tw.Write([]byte(manifestBody)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := gw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tf, nil
|
||||
}
|
||||
|
||||
func newEmptyTestACI() (*os.File, error) {
|
||||
tf, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw := gzip.NewWriter(tf)
|
||||
tw := tar.NewWriter(gw)
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := gw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tf, nil
|
||||
}
|
||||
|
||||
func TestManifestFromImage(t *testing.T) {
|
||||
for _, usedotslash := range []bool{false, true} {
|
||||
img, err := newTestACI(usedotslash)
|
||||
if err != nil {
|
||||
t.Fatalf("newTestACI: unexpected error: %v", err)
|
||||
}
|
||||
defer img.Close()
|
||||
defer os.Remove(img.Name())
|
||||
|
||||
im, err := ManifestFromImage(img)
|
||||
if err != nil {
|
||||
t.Fatalf("ManifestFromImage: unexpected error: %v", err)
|
||||
}
|
||||
if im.Name.String() != "example.com/app" {
|
||||
t.Errorf("expected %s, got %s", "example.com/app", im.Name.String())
|
||||
}
|
||||
|
||||
emptyImg, err := newEmptyTestACI()
|
||||
if err != nil {
|
||||
t.Fatalf("newEmptyTestACI: unexpected error: %v", err)
|
||||
}
|
||||
defer emptyImg.Close()
|
||||
defer os.Remove(emptyImg.Name())
|
||||
|
||||
im, err = ManifestFromImage(emptyImg)
|
||||
if err == nil {
|
||||
t.Fatalf("ManifestFromImage: expected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCompressedTarReader(t *testing.T) {
|
||||
img, err := newTestACI(false)
|
||||
if err != nil {
|
||||
t.Fatalf("newTestACI: unexpected error: %v", err)
|
||||
}
|
||||
defer img.Close()
|
||||
defer os.Remove(img.Name())
|
||||
|
||||
cr, err := NewCompressedTarReader(img)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCompressedTarReader: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ftype, err := DetectFileType(cr)
|
||||
if err != nil {
|
||||
t.Fatalf("DetectFileType: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if ftype != TypeText {
|
||||
t.Errorf("expected %v, got %v", TypeText, ftype)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCompressedReader(t *testing.T) {
|
||||
img, err := newTestACI(false)
|
||||
if err != nil {
|
||||
t.Fatalf("newTestACI: unexpected error: %v", err)
|
||||
}
|
||||
defer img.Close()
|
||||
defer os.Remove(img.Name())
|
||||
|
||||
cr, err := NewCompressedReader(img)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCompressedReader: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ftype, err := DetectFileType(cr)
|
||||
if err != nil {
|
||||
t.Fatalf("DetectFileType: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if ftype != TypeTar {
|
||||
t.Errorf("expected %v, got %v", TypeTar, ftype)
|
||||
}
|
||||
}
|
159
Godeps/_workspace/src/github.com/appc/spec/aci/layout.go
generated
vendored
Normal file
159
Godeps/_workspace/src/github.com/appc/spec/aci/layout.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
package aci
|
||||
|
||||
/*
|
||||
|
||||
Image Layout
|
||||
|
||||
The on-disk layout of an app container is straightforward.
|
||||
It includes a rootfs with all of the files that will exist in the root of the app and a manifest describing the image.
|
||||
The layout MUST contain an image manifest.
|
||||
|
||||
/manifest
|
||||
/rootfs/
|
||||
/rootfs/usr/bin/mysql
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/appc/spec/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
// Path to manifest file inside the layout
|
||||
ManifestFile = "manifest"
|
||||
// Path to rootfs directory inside the layout
|
||||
RootfsDir = "rootfs"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoRootFS = errors.New("no rootfs found in layout")
|
||||
ErrNoManifest = errors.New("no image manifest found in layout")
|
||||
)
|
||||
|
||||
// ValidateLayout takes a directory and validates that the layout of the directory
|
||||
// matches that expected by the Application Container Image format.
|
||||
// If any errors are encountered during the validation, it will abort and
|
||||
// return the first one.
|
||||
func ValidateLayout(dir string) error {
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error accessing layout: %v", err)
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return fmt.Errorf("given path %q is not a directory", dir)
|
||||
}
|
||||
var flist []string
|
||||
var imOK, rfsOK bool
|
||||
var im io.Reader
|
||||
walkLayout := func(fpath string, fi os.FileInfo, err error) error {
|
||||
rpath, err := filepath.Rel(dir, fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch rpath {
|
||||
case ".":
|
||||
case ManifestFile:
|
||||
im, err = os.Open(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imOK = true
|
||||
case RootfsDir:
|
||||
if !fi.IsDir() {
|
||||
return errors.New("rootfs is not a directory")
|
||||
}
|
||||
rfsOK = true
|
||||
default:
|
||||
flist = append(flist, rpath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := filepath.Walk(dir, walkLayout); err != nil {
|
||||
return err
|
||||
}
|
||||
return validate(imOK, im, rfsOK, flist)
|
||||
}
|
||||
|
||||
// ValidateArchive takes a *tar.Reader and validates that the layout of the
|
||||
// filesystem the reader encapsulates matches that expected by the
|
||||
// Application Container Image format. If any errors are encountered during
|
||||
// the validation, it will abort and return the first one.
|
||||
func ValidateArchive(tr *tar.Reader) error {
|
||||
var fseen map[string]bool = make(map[string]bool)
|
||||
var imOK, rfsOK bool
|
||||
var im bytes.Buffer
|
||||
Tar:
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
switch {
|
||||
case err == nil:
|
||||
case err == io.EOF:
|
||||
break Tar
|
||||
default:
|
||||
return err
|
||||
}
|
||||
name := filepath.Clean(hdr.Name)
|
||||
switch name {
|
||||
case ".":
|
||||
case ManifestFile:
|
||||
_, err := io.Copy(&im, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imOK = true
|
||||
case RootfsDir:
|
||||
if !hdr.FileInfo().IsDir() {
|
||||
return fmt.Errorf("rootfs is not a directory")
|
||||
}
|
||||
rfsOK = true
|
||||
default:
|
||||
if _, seen := fseen[name]; seen {
|
||||
return fmt.Errorf("duplicate file entry in archive: %s", name)
|
||||
}
|
||||
fseen[name] = true
|
||||
}
|
||||
}
|
||||
var flist []string
|
||||
for key := range fseen {
|
||||
flist = append(flist, key)
|
||||
}
|
||||
return validate(imOK, &im, rfsOK, flist)
|
||||
}
|
||||
|
||||
func validate(imOK bool, im io.Reader, rfsOK bool, files []string) error {
|
||||
defer func() {
|
||||
if rc, ok := im.(io.Closer); ok {
|
||||
rc.Close()
|
||||
}
|
||||
}()
|
||||
if !imOK {
|
||||
return ErrNoManifest
|
||||
}
|
||||
if !rfsOK {
|
||||
return ErrNoRootFS
|
||||
}
|
||||
b, err := ioutil.ReadAll(im)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading image manifest: %v", err)
|
||||
}
|
||||
var a schema.ImageManifest
|
||||
if err := a.UnmarshalJSON(b); err != nil {
|
||||
return fmt.Errorf("image manifest validation failed: %v", err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if !strings.HasPrefix(f, "rootfs") {
|
||||
return fmt.Errorf("unrecognized file path in layout: %q", f)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
62
Godeps/_workspace/src/github.com/appc/spec/aci/layout_test.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/appc/spec/aci/layout_test.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newValidateLayoutTest() (string, error) {
|
||||
td, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(td, "rootfs"), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(td, "rootfs", "dir", "rootfs"), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
evilManifestBody := "malformedManifest"
|
||||
manifestBody := `{"acKind":"ImageManifest","acVersion":"0.3.0","name":"example.com/app"}`
|
||||
|
||||
evilManifestPath := "rootfs/manifest"
|
||||
evilManifestPath = path.Join(td, evilManifestPath)
|
||||
|
||||
em, err := os.Create(evilManifestPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
em.WriteString(evilManifestBody)
|
||||
em.Close()
|
||||
|
||||
manifestPath := path.Join(td, "manifest")
|
||||
|
||||
m, err := os.Create(manifestPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
m.WriteString(manifestBody)
|
||||
m.Close()
|
||||
|
||||
return td, nil
|
||||
}
|
||||
|
||||
func TestValidateLayout(t *testing.T) {
|
||||
layoutPath, err := newValidateLayoutTest()
|
||||
if err != nil {
|
||||
t.Fatalf("newValidateLayoutTest: unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(layoutPath)
|
||||
|
||||
err = ValidateLayout(layoutPath)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateLayout: unexpected error: %v", err)
|
||||
}
|
||||
}
|
84
Godeps/_workspace/src/github.com/appc/spec/aci/writer.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/appc/spec/aci/writer.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/appc/spec/schema"
|
||||
)
|
||||
|
||||
// ArchiveWriter writes App Container Images. Users wanting to create an ACI or
|
||||
// should create an ArchiveWriter and add files to it; the ACI will be written
|
||||
// to the underlying tar.Writer
|
||||
type ArchiveWriter interface {
|
||||
AddFile(hdr *tar.Header, r io.Reader) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type imageArchiveWriter struct {
|
||||
*tar.Writer
|
||||
am *schema.ImageManifest
|
||||
}
|
||||
|
||||
// NewImageWriter creates a new ArchiveWriter which will generate an App
|
||||
// Container Image based on the given manifest and write it to the given
|
||||
// tar.Writer
|
||||
func NewImageWriter(am schema.ImageManifest, w *tar.Writer) ArchiveWriter {
|
||||
aw := &imageArchiveWriter{
|
||||
w,
|
||||
&am,
|
||||
}
|
||||
return aw
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) AddFile(hdr *tar.Header, r io.Reader) error {
|
||||
err := aw.Writer.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
_, err := io.Copy(aw.Writer, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) addFileNow(path string, contents []byte) error {
|
||||
buf := bytes.NewBuffer(contents)
|
||||
now := time.Now()
|
||||
hdr := tar.Header{
|
||||
Name: path,
|
||||
Mode: 0644,
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
Size: int64(buf.Len()),
|
||||
ModTime: now,
|
||||
Typeflag: tar.TypeReg,
|
||||
Uname: "root",
|
||||
Gname: "root",
|
||||
ChangeTime: now,
|
||||
}
|
||||
return aw.AddFile(&hdr, buf)
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) addManifest(name string, m json.Marshaler) error {
|
||||
out, err := m.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.addFileNow(name, out)
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) Close() error {
|
||||
if err := aw.addManifest(ManifestFile, aw.am); err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.Writer.Close()
|
||||
}
|
223
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/acirenderer.go
generated
vendored
Normal file
223
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/acirenderer.go
generated
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
package acirenderer
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/appc/spec/schema"
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
// An ACIRegistry provides all functions of an ACIProvider plus functions to
|
||||
// search for an aci and get its contents
|
||||
type ACIRegistry interface {
|
||||
ACIProvider
|
||||
GetImageManifest(key string) (*schema.ImageManifest, error)
|
||||
GetACI(name types.ACName, labels types.Labels) (string, error)
|
||||
}
|
||||
|
||||
// An ACIProvider provides functions to get an ACI contents, to convert an
|
||||
// ACI hash to the key under which the ACI is known to the provider and to resolve an
|
||||
// ImageID to the key under which it's known to the provider.
|
||||
type ACIProvider interface {
|
||||
// Read the ACI contents stream given the key. Use ResolveKey to
|
||||
// convert an ImageID to the relative provider's key.
|
||||
ReadStream(key string) (io.ReadCloser, error)
|
||||
// Converts an ImageID to the, if existent, key under which the
|
||||
// ACI is known to the provider
|
||||
ResolveKey(key string) (string, error)
|
||||
// Converts a Hash to the provider's key
|
||||
HashToKey(h hash.Hash) string
|
||||
}
|
||||
|
||||
// An Image contains the ImageManifest, the ACIProvider's key and its Level in
|
||||
// the dependency tree.
|
||||
type Image struct {
|
||||
Im *schema.ImageManifest
|
||||
Key string
|
||||
Level uint16
|
||||
}
|
||||
|
||||
// Images encapsulates an ordered slice of Image structs. It represents a flat
|
||||
// dependency tree.
|
||||
// The upper Image should be the first in the slice with a level of 0.
|
||||
// For example if A is the upper image and has two deps (in order B and C). And C has one dep (D),
|
||||
// the slice (reporting the app name and excluding im and Hash) should be:
|
||||
// [{A, Level: 0}, {C, Level:1}, {D, Level: 2}, {B, Level: 1}]
|
||||
type Images []Image
|
||||
|
||||
// ACIFiles represents which files to extract for every ACI
|
||||
type ACIFiles struct {
|
||||
Key string
|
||||
FileMap map[string]struct{}
|
||||
}
|
||||
|
||||
// RenderedACI is an (ordered) slice of ACIFiles
|
||||
type RenderedACI []*ACIFiles
|
||||
|
||||
// GetRenderedACIWithImageID, given an imageID, starts with the matching image
|
||||
// available in the store, creates the dependencies list and returns the
|
||||
// RenderedACI list.
|
||||
func GetRenderedACIWithImageID(imageID types.Hash, ap ACIRegistry) (RenderedACI, error) {
|
||||
imgs, err := CreateDepListFromImageID(imageID, ap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetRenderedACIFromList(imgs, ap)
|
||||
}
|
||||
|
||||
// GetRenderedACI, given an image app name and optional labels, starts with the
|
||||
// best matching image available in the store, creates the dependencies list
|
||||
// and returns the RenderedACI list.
|
||||
func GetRenderedACI(name types.ACName, labels types.Labels, ap ACIRegistry) (RenderedACI, error) {
|
||||
imgs, err := CreateDepListFromNameLabels(name, labels, ap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetRenderedACIFromList(imgs, ap)
|
||||
}
|
||||
|
||||
// GetRenderedACIFromList returns the RenderedACI list. All file outside rootfs
|
||||
// are excluded (at the moment only "manifest").
|
||||
func GetRenderedACIFromList(imgs Images, ap ACIProvider) (RenderedACI, error) {
|
||||
if len(imgs) == 0 {
|
||||
return nil, fmt.Errorf("image list empty")
|
||||
}
|
||||
|
||||
allFiles := make(map[string]struct{})
|
||||
renderedACI := RenderedACI{}
|
||||
|
||||
first := true
|
||||
for i, img := range imgs {
|
||||
pwlm := getUpperPWLM(imgs, i)
|
||||
ra, err := getACIFiles(img, ap, allFiles, pwlm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use the manifest from the upper ACI
|
||||
if first {
|
||||
ra.FileMap["manifest"] = struct{}{}
|
||||
first = false
|
||||
}
|
||||
renderedACI = append(renderedACI, ra)
|
||||
}
|
||||
|
||||
return renderedACI, nil
|
||||
}
|
||||
|
||||
// getUpperPWLM returns the pwl at the lower level for the branch where
|
||||
// img[pos] lives.
|
||||
func getUpperPWLM(imgs Images, pos int) map[string]struct{} {
|
||||
var pwlm map[string]struct{}
|
||||
curlevel := imgs[pos].Level
|
||||
// Start from our position and go back ignoring the other leafs.
|
||||
for i := pos; i >= 0; i-- {
|
||||
img := imgs[i]
|
||||
if img.Level < curlevel && len(img.Im.PathWhitelist) > 0 {
|
||||
pwlm = pwlToMap(img.Im.PathWhitelist)
|
||||
}
|
||||
curlevel = img.Level
|
||||
}
|
||||
return pwlm
|
||||
}
|
||||
|
||||
// getACIFiles returns the ACIFiles struct for the given image. All files
|
||||
// outside rootfs are excluded (at the moment only "manifest").
|
||||
func getACIFiles(img Image, ap ACIProvider, allFiles map[string]struct{}, pwlm map[string]struct{}) (*ACIFiles, error) {
|
||||
rs, err := ap.ReadStream(img.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rs.Close()
|
||||
|
||||
hash := sha512.New()
|
||||
r := io.TeeReader(rs, hash)
|
||||
|
||||
thispwlm := pwlToMap(img.Im.PathWhitelist)
|
||||
ra := &ACIFiles{FileMap: make(map[string]struct{})}
|
||||
if err = Walk(tar.NewReader(r), func(hdr *tar.Header) error {
|
||||
name := hdr.Name
|
||||
cleanName := filepath.Clean(name)
|
||||
|
||||
// Ignore files outside /rootfs/ (at the moment only "manifest")
|
||||
if !strings.HasPrefix(cleanName, "rootfs/") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Is the file in our PathWhiteList?
|
||||
// If the file is a directory continue also if not in PathWhiteList
|
||||
if hdr.Typeflag != tar.TypeDir {
|
||||
if len(img.Im.PathWhitelist) > 0 {
|
||||
if _, ok := thispwlm[cleanName]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// Is the file in the lower level PathWhiteList of this img branch?
|
||||
if pwlm != nil {
|
||||
if _, ok := pwlm[cleanName]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Is the file already provided by a previous image?
|
||||
if _, ok := allFiles[cleanName]; ok {
|
||||
return nil
|
||||
}
|
||||
ra.FileMap[cleanName] = struct{}{}
|
||||
allFiles[cleanName] = struct{}{}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Tar does not necessarily read the complete file, so ensure we read the entirety into the hash
|
||||
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
||||
return nil, fmt.Errorf("error reading ACI: %v", err)
|
||||
}
|
||||
|
||||
if g := ap.HashToKey(hash); g != img.Key {
|
||||
return nil, fmt.Errorf("image hash does not match expected (%s != %s)", g, img.Key)
|
||||
}
|
||||
|
||||
ra.Key = img.Key
|
||||
return ra, nil
|
||||
}
|
||||
|
||||
// pwlToMap converts a pathWhiteList slice to a map for faster search
|
||||
// It will also prepend "rootfs/" to the provided paths and they will be
|
||||
// relative to "/" so they can be easily compared with the tar.Header.Name
|
||||
// If pwl length is 0, a nil map is returned
|
||||
func pwlToMap(pwl []string) map[string]struct{} {
|
||||
if len(pwl) == 0 {
|
||||
return nil
|
||||
}
|
||||
m := make(map[string]struct{}, len(pwl))
|
||||
for _, name := range pwl {
|
||||
relpath := filepath.Join("rootfs", name)
|
||||
m[relpath] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func Walk(tarReader *tar.Reader, walkFunc func(hdr *tar.Header) error) error {
|
||||
for {
|
||||
hdr, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading tar entry: %v", err)
|
||||
}
|
||||
if err := walkFunc(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
1998
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/acirenderer_test.go
generated
vendored
Normal file
1998
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/acirenderer_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
74
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/resolve.go
generated
vendored
Normal file
74
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/resolve.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
package acirenderer
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
// CreateDepListFromImageID returns the flat dependency tree of the image with
|
||||
// the provided imageID
|
||||
func CreateDepListFromImageID(imageID types.Hash, ap ACIRegistry) (Images, error) {
|
||||
key, err := ap.ResolveKey(imageID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createDepList(key, ap)
|
||||
}
|
||||
|
||||
// CreateDepListFromNameLabels returns the flat dependency tree of the image
|
||||
// with the provided app name and optional labels.
|
||||
func CreateDepListFromNameLabels(name types.ACName, labels types.Labels, ap ACIRegistry) (Images, error) {
|
||||
key, err := ap.GetACI(name, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createDepList(key, ap)
|
||||
}
|
||||
|
||||
// createDepList returns the flat dependency tree as a list of Image type
|
||||
func createDepList(key string, ap ACIRegistry) (Images, error) {
|
||||
imgsl := list.New()
|
||||
im, err := ap.GetImageManifest(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
img := Image{Im: im, Key: key, Level: 0}
|
||||
imgsl.PushFront(img)
|
||||
|
||||
// Create a flat dependency tree. Use a LinkedList to be able to
|
||||
// insert elements in the list while working on it.
|
||||
for el := imgsl.Front(); el != nil; el = el.Next() {
|
||||
img := el.Value.(Image)
|
||||
dependencies := img.Im.Dependencies
|
||||
for _, d := range dependencies {
|
||||
var depimg Image
|
||||
var depKey string
|
||||
if d.ImageID != nil && !d.ImageID.Empty() {
|
||||
depKey, err = ap.ResolveKey(d.ImageID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
depKey, err = ap.GetACI(d.App, d.Labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
im, err := ap.GetImageManifest(depKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depimg = Image{Im: im, Key: depKey, Level: img.Level + 1}
|
||||
imgsl.InsertAfter(depimg, el)
|
||||
}
|
||||
}
|
||||
|
||||
imgs := Images{}
|
||||
for el := imgsl.Front(); el != nil; el = el.Next() {
|
||||
imgs = append(imgs, el.Value.(Image))
|
||||
}
|
||||
return imgs, nil
|
||||
}
|
91
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/store_test.go
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/appc/spec/pkg/acirenderer/store_test.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
package acirenderer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/appc/spec/aci"
|
||||
"github.com/appc/spec/schema"
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
const (
|
||||
hashPrefix = "sha512-"
|
||||
)
|
||||
|
||||
type TestStoreAci struct {
|
||||
data []byte
|
||||
key string
|
||||
ImageManifest *schema.ImageManifest
|
||||
}
|
||||
|
||||
type TestStore struct {
|
||||
acis map[string]*TestStoreAci
|
||||
}
|
||||
|
||||
func NewTestStore() *TestStore {
|
||||
return &TestStore{acis: make(map[string]*TestStoreAci)}
|
||||
}
|
||||
|
||||
func (ts *TestStore) WriteACI(path string) (string, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imageID := types.NewHashSHA512(data)
|
||||
|
||||
rs, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rs.Close()
|
||||
im, err := aci.ManifestFromImage(rs)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error retrieving ImageManifest: %v", err)
|
||||
}
|
||||
|
||||
key := imageID.String()
|
||||
ts.acis[key] = &TestStoreAci{data: data, key: key, ImageManifest: im}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (ts *TestStore) GetImageManifest(key string) (*schema.ImageManifest, error) {
|
||||
aci, ok := ts.acis[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("aci with key: %s not found", key)
|
||||
}
|
||||
return aci.ImageManifest, nil
|
||||
|
||||
}
|
||||
func (ts *TestStore) GetACI(name types.ACName, labels types.Labels) (string, error) {
|
||||
for _, aci := range ts.acis {
|
||||
if aci.ImageManifest.Name.String() == name.String() {
|
||||
return aci.key, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("aci not found")
|
||||
}
|
||||
|
||||
func (ts *TestStore) ReadStream(key string) (io.ReadCloser, error) {
|
||||
aci, ok := ts.acis[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("stream for key: %s not found", key)
|
||||
}
|
||||
return ioutil.NopCloser(bytes.NewReader(aci.data)), nil
|
||||
}
|
||||
|
||||
func (ts *TestStore) ResolveKey(key string) (string, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// HashToKey takes a hash.Hash (which currently _MUST_ represent a full SHA512),
|
||||
// calculates its sum, and returns a string which should be used as the key to
|
||||
// store the data matching the hash.
|
||||
func (ts *TestStore) HashToKey(h hash.Hash) string {
|
||||
s := h.Sum(nil)
|
||||
return fmt.Sprintf("%s%x", hashPrefix, s)
|
||||
}
|
3
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Package tarheader contains a simple abstraction to accurately create
|
||||
// tar.Headers on different operating systems.
|
||||
package tarheader
|
25
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_darwin.go
generated
vendored
Normal file
25
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_darwin.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
//+build darwin
|
||||
|
||||
package tarheader
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
populateHeaderStat = append(populateHeaderStat, populateHeaderCtime)
|
||||
}
|
||||
|
||||
func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
sec, nsec := st.Ctimespec.Unix()
|
||||
ctime := time.Unix(sec, nsec)
|
||||
h.ChangeTime = ctime
|
||||
}
|
23
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_linux.go
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_linux.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package tarheader
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
populateHeaderStat = append(populateHeaderStat, populateHeaderCtime)
|
||||
}
|
||||
|
||||
func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
sec, nsec := st.Ctim.Unix()
|
||||
ctime := time.Unix(sec, nsec)
|
||||
h.ChangeTime = ctime
|
||||
}
|
51
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix.go
generated
vendored
Normal file
51
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package tarheader
|
||||
|
||||
/*
|
||||
#define _BSD_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#include <sys/types.h>
|
||||
|
||||
unsigned int
|
||||
my_major(dev_t dev)
|
||||
{
|
||||
return major(dev);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
my_minor(dev_t dev)
|
||||
{
|
||||
return minor(dev);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"archive/tar"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
populateHeaderStat = append(populateHeaderStat, populateHeaderUnix)
|
||||
}
|
||||
|
||||
func populateHeaderUnix(h *tar.Header, fi os.FileInfo, seen map[uint64]string) {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
h.Uid = int(st.Uid)
|
||||
h.Gid = int(st.Gid)
|
||||
if st.Mode&syscall.S_IFMT == syscall.S_IFBLK || st.Mode&syscall.S_IFMT == syscall.S_IFCHR {
|
||||
h.Devminor = int64(C.my_minor(C.dev_t(st.Rdev)))
|
||||
h.Devmajor = int64(C.my_major(C.dev_t(st.Rdev)))
|
||||
}
|
||||
// If we have already seen this inode, generate a hardlink
|
||||
p, ok := seen[uint64(st.Ino)]
|
||||
if ok {
|
||||
h.Linkname = p
|
||||
h.Typeflag = tar.TypeLink
|
||||
} else {
|
||||
seen[uint64(st.Ino)] = h.Name
|
||||
}
|
||||
}
|
61
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix_test.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix_test.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
package tarheader
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mknod requires privilege ...
|
||||
func TestHeaderUnixDev(t *testing.T) {
|
||||
hExpect := tar.Header{
|
||||
Name: "./dev/test0",
|
||||
Size: 0,
|
||||
Typeflag: tar.TypeBlock,
|
||||
Devminor: 5,
|
||||
Devmajor: 233,
|
||||
}
|
||||
// make our test block device
|
||||
var path string
|
||||
{
|
||||
var err error
|
||||
path, err = ioutil.TempDir("", "tarheader-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
if err := os.Mkdir(filepath.Join(path, "dev"), os.FileMode(0755)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mode := uint32(hExpect.Mode&07777) | syscall.S_IFBLK
|
||||
dev := uint32(((hExpect.Devminor & 0xfff00) << 12) | ((hExpect.Devmajor & 0xfff) << 8) | (hExpect.Devminor & 0xff))
|
||||
if err := syscall.Mknod(filepath.Join(path, hExpect.Name), mode, int(dev)); err != nil {
|
||||
if err == syscall.EPERM {
|
||||
t.Skip("no permission to CAP_MKNOD")
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
fi, err := os.Stat(filepath.Join(path, hExpect.Name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hGot := tar.Header{
|
||||
Name: "./dev/test0",
|
||||
Size: 0,
|
||||
Typeflag: tar.TypeBlock,
|
||||
}
|
||||
|
||||
seen := map[uint64]string{}
|
||||
populateHeaderUnix(&hGot, fi, seen)
|
||||
if hGot.Devminor != hExpect.Devminor {
|
||||
t.Errorf("dev minor: got %d, expected %d", hGot.Devminor, hExpect.Devminor)
|
||||
}
|
||||
if hGot.Devmajor != hExpect.Devmajor {
|
||||
t.Errorf("dev major: got %d, expected %d", hGot.Devmajor, hExpect.Devmajor)
|
||||
}
|
||||
}
|
14
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/tarheader.go
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/tarheader.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package tarheader
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"os"
|
||||
)
|
||||
|
||||
var populateHeaderStat []func(h *tar.Header, fi os.FileInfo, seen map[uint64]string)
|
||||
|
||||
func Populate(h *tar.Header, fi os.FileInfo, seen map[uint64]string) {
|
||||
for _, pop := range populateHeaderStat {
|
||||
pop(h, fi, seen)
|
||||
}
|
||||
}
|
11
Godeps/_workspace/src/github.com/appc/spec/schema/doc.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/appc/spec/schema/doc.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Package schema provides definitions for the JSON schema of the different
|
||||
// manifests in the App Container Specification. The manifests are canonically
|
||||
// represented in their respective structs:
|
||||
// - `ImageManifest`
|
||||
// - `PodManifest`
|
||||
//
|
||||
// Validation is performed through serialization: if a blob of JSON data will
|
||||
// unmarshal to one of the *Manifests, it is considered a valid implementation
|
||||
// of the standard. Similarly, if a constructed *Manifest struct marshals
|
||||
// successfully to JSON, it must be valid.
|
||||
package schema
|
81
Godeps/_workspace/src/github.com/appc/spec/schema/image.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/appc/spec/schema/image.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ACIExtension = ".aci"
|
||||
ImageManifestKind = types.ACKind("ImageManifest")
|
||||
)
|
||||
|
||||
type ImageManifest struct {
|
||||
ACKind types.ACKind `json:"acKind"`
|
||||
ACVersion types.SemVer `json:"acVersion"`
|
||||
Name types.ACName `json:"name"`
|
||||
Labels types.Labels `json:"labels,omitempty"`
|
||||
App *types.App `json:"app,omitempty"`
|
||||
Annotations types.Annotations `json:"annotations,omitempty"`
|
||||
Dependencies types.Dependencies `json:"dependencies,omitempty"`
|
||||
PathWhitelist []string `json:"pathWhitelist,omitempty"`
|
||||
}
|
||||
|
||||
// imageManifest is a model to facilitate extra validation during the
|
||||
// unmarshalling of the ImageManifest
|
||||
type imageManifest ImageManifest
|
||||
|
||||
func BlankImageManifest() *ImageManifest {
|
||||
return &ImageManifest{ACKind: ImageManifestKind, ACVersion: AppContainerVersion}
|
||||
}
|
||||
|
||||
func (im *ImageManifest) UnmarshalJSON(data []byte) error {
|
||||
a := imageManifest(*im)
|
||||
err := json.Unmarshal(data, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nim := ImageManifest(a)
|
||||
if err := nim.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*im = nim
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im ImageManifest) MarshalJSON() ([]byte, error) {
|
||||
if err := im.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(imageManifest(im))
|
||||
}
|
||||
|
||||
var imKindError = types.InvalidACKindError(ImageManifestKind)
|
||||
|
||||
// assertValid performs extra assertions on an ImageManifest to ensure that
|
||||
// fields are set appropriately, etc. It is used exclusively when marshalling
|
||||
// and unmarshalling an ImageManifest. Most field-specific validation is
|
||||
// performed through the individual types being marshalled; assertValid()
|
||||
// should only deal with higher-level validation.
|
||||
func (im *ImageManifest) assertValid() error {
|
||||
if im.ACKind != ImageManifestKind {
|
||||
return imKindError
|
||||
}
|
||||
if im.ACVersion.Empty() {
|
||||
return errors.New(`acVersion must be set`)
|
||||
}
|
||||
if im.Name.Empty() {
|
||||
return errors.New(`name must be set`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *ImageManifest) GetLabel(name string) (val string, ok bool) {
|
||||
return im.Labels.Get(name)
|
||||
}
|
||||
|
||||
func (im *ImageManifest) GetAnnotation(name string) (val string, ok bool) {
|
||||
return im.Annotations.Get(name)
|
||||
}
|
48
Godeps/_workspace/src/github.com/appc/spec/schema/image_test.go
generated
vendored
Normal file
48
Godeps/_workspace/src/github.com/appc/spec/schema/image_test.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package schema
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmptyApp(t *testing.T) {
|
||||
imj := `
|
||||
{
|
||||
"acKind": "ImageManifest",
|
||||
"acVersion": "0.5.1",
|
||||
"name": "example.com/test"
|
||||
}
|
||||
`
|
||||
|
||||
var im ImageManifest
|
||||
|
||||
err := im.UnmarshalJSON([]byte(imj))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Marshal and Unmarshal to verify that no "app": {} is generated on
|
||||
// Marshal and converted to empty struct on Unmarshal
|
||||
buf, err := im.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = im.UnmarshalJSON(buf)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageManifestMerge(t *testing.T) {
|
||||
imj := `{"name": "example.com/test"}`
|
||||
im := &ImageManifest{}
|
||||
|
||||
if im.UnmarshalJSON([]byte(imj)) == nil {
|
||||
t.Fatal("Manifest JSON without acKind and acVersion unmarshalled successfully")
|
||||
}
|
||||
|
||||
im = BlankImageManifest()
|
||||
|
||||
err := im.UnmarshalJSON([]byte(imj))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
28
Godeps/_workspace/src/github.com/appc/spec/schema/kind.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/appc/spec/schema/kind.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
type Kind struct {
|
||||
ACVersion types.SemVer `json:"acVersion"`
|
||||
ACKind types.ACKind `json:"acKind"`
|
||||
}
|
||||
|
||||
type kind Kind
|
||||
|
||||
func (k *Kind) UnmarshalJSON(data []byte) error {
|
||||
nk := kind{}
|
||||
err := json.Unmarshal(data, &nk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*k = Kind(nk)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Kind) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(kind(k))
|
||||
}
|
146
Godeps/_workspace/src/github.com/appc/spec/schema/pod.go
generated
vendored
Normal file
146
Godeps/_workspace/src/github.com/appc/spec/schema/pod.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
const PodManifestKind = types.ACKind("PodManifest")
|
||||
|
||||
type PodManifest struct {
|
||||
ACVersion types.SemVer `json:"acVersion"`
|
||||
ACKind types.ACKind `json:"acKind"`
|
||||
Apps AppList `json:"apps"`
|
||||
Volumes []types.Volume `json:"volumes"`
|
||||
Isolators []types.Isolator `json:"isolators"`
|
||||
Annotations types.Annotations `json:"annotations"`
|
||||
Ports []types.ExposedPort `json:"ports"`
|
||||
}
|
||||
|
||||
// podManifest is a model to facilitate extra validation during the
|
||||
// unmarshalling of the PodManifest
|
||||
type podManifest PodManifest
|
||||
|
||||
func BlankPodManifest() *PodManifest {
|
||||
return &PodManifest{ACKind: PodManifestKind, ACVersion: AppContainerVersion}
|
||||
}
|
||||
|
||||
func (pm *PodManifest) UnmarshalJSON(data []byte) error {
|
||||
p := podManifest(*pm)
|
||||
err := json.Unmarshal(data, &p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
npm := PodManifest(p)
|
||||
if err := npm.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*pm = npm
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm PodManifest) MarshalJSON() ([]byte, error) {
|
||||
if err := pm.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(podManifest(pm))
|
||||
}
|
||||
|
||||
var pmKindError = types.InvalidACKindError(PodManifestKind)
|
||||
|
||||
// assertValid performs extra assertions on an PodManifest to
|
||||
// ensure that fields are set appropriately, etc. It is used exclusively when
|
||||
// marshalling and unmarshalling an PodManifest. Most
|
||||
// field-specific validation is performed through the individual types being
|
||||
// marshalled; assertValid() should only deal with higher-level validation.
|
||||
func (pm *PodManifest) assertValid() error {
|
||||
if pm.ACKind != PodManifestKind {
|
||||
return pmKindError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppList []RuntimeApp
|
||||
|
||||
type appList AppList
|
||||
|
||||
func (al *AppList) UnmarshalJSON(data []byte) error {
|
||||
a := appList{}
|
||||
err := json.Unmarshal(data, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nal := AppList(a)
|
||||
if err := nal.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*al = nal
|
||||
return nil
|
||||
}
|
||||
|
||||
func (al AppList) MarshalJSON() ([]byte, error) {
|
||||
if err := al.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(appList(al))
|
||||
}
|
||||
|
||||
func (al AppList) assertValid() error {
|
||||
seen := map[types.ACName]bool{}
|
||||
for _, a := range al {
|
||||
if _, ok := seen[a.Name]; ok {
|
||||
return fmt.Errorf(`duplicate apps of name %q`, a.Name)
|
||||
}
|
||||
seen[a.Name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves an app by the specified name from the AppList; if there is
|
||||
// no such app, nil is returned. The returned *RuntimeApp MUST be considered
|
||||
// read-only.
|
||||
func (al AppList) Get(name types.ACName) *RuntimeApp {
|
||||
for _, a := range al {
|
||||
if name.Equals(a.Name) {
|
||||
aa := a
|
||||
return &aa
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mount describes the mapping between a volume and an apps
|
||||
// MountPoint that will be fulfilled at runtime.
|
||||
type Mount struct {
|
||||
Volume types.ACName `json:"volume"`
|
||||
MountPoint types.ACName `json:"mountPoint"`
|
||||
}
|
||||
|
||||
func (r Mount) assertValid() error {
|
||||
if r.Volume.Empty() {
|
||||
return errors.New("volume must be set")
|
||||
}
|
||||
if r.MountPoint.Empty() {
|
||||
return errors.New("mountPoint must be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RuntimeApp describes an application referenced in a PodManifest
|
||||
type RuntimeApp struct {
|
||||
Name types.ACName `json:"name"`
|
||||
Image RuntimeImage `json:"image"`
|
||||
App *types.App `json:"app,omitempty"`
|
||||
Mounts []Mount `json:"mounts,omitempty"`
|
||||
Annotations types.Annotations `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// RuntimeImage describes an image referenced in a RuntimeApp
|
||||
type RuntimeImage struct {
|
||||
Name *types.ACName `json:"name,omitempty"`
|
||||
ID types.Hash `json:"id"`
|
||||
Labels types.Labels `json:"labels,omitempty"`
|
||||
}
|
59
Godeps/_workspace/src/github.com/appc/spec/schema/pod_test.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/appc/spec/schema/pod_test.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
func TestPodManifestMerge(t *testing.T) {
|
||||
pmj := `{}`
|
||||
pm := &PodManifest{}
|
||||
|
||||
if pm.UnmarshalJSON([]byte(pmj)) == nil {
|
||||
t.Fatal("Manifest JSON without acKind and acVersion unmarshalled successfully")
|
||||
}
|
||||
|
||||
pm = BlankPodManifest()
|
||||
|
||||
err := pm.UnmarshalJSON([]byte(pmj))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppList(t *testing.T) {
|
||||
ri := RuntimeImage{
|
||||
ID: *types.NewHashSHA512([]byte{}),
|
||||
}
|
||||
al := AppList{
|
||||
RuntimeApp{
|
||||
Name: "foo",
|
||||
Image: ri,
|
||||
},
|
||||
RuntimeApp{
|
||||
Name: "bar",
|
||||
Image: ri,
|
||||
},
|
||||
}
|
||||
if _, err := al.MarshalJSON(); err != nil {
|
||||
t.Errorf("want err=nil, got %v", err)
|
||||
}
|
||||
dal := AppList{
|
||||
RuntimeApp{
|
||||
Name: "foo",
|
||||
Image: ri,
|
||||
},
|
||||
RuntimeApp{
|
||||
Name: "bar",
|
||||
Image: ri,
|
||||
},
|
||||
RuntimeApp{
|
||||
Name: "foo",
|
||||
Image: ri,
|
||||
},
|
||||
}
|
||||
if _, err := dal.MarshalJSON(); err == nil {
|
||||
t.Errorf("want err, got nil")
|
||||
}
|
||||
}
|
53
Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoACKind = ACKindError("ACKind must be set")
|
||||
)
|
||||
|
||||
// ACKind wraps a string to define a field which must be set with one of
|
||||
// several ACKind values. If it is unset, or has an invalid value, the field
|
||||
// will refuse to marshal/unmarshal.
|
||||
type ACKind string
|
||||
|
||||
func (a ACKind) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
func (a ACKind) assertValid() error {
|
||||
s := a.String()
|
||||
switch s {
|
||||
case "ImageManifest", "PodManifest":
|
||||
return nil
|
||||
case "":
|
||||
return ErrNoACKind
|
||||
default:
|
||||
msg := fmt.Sprintf("bad ACKind: %s", s)
|
||||
return ACKindError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (a ACKind) MarshalJSON() ([]byte, error) {
|
||||
if err := a.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(a.String())
|
||||
}
|
||||
|
||||
func (a *ACKind) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
na := ACKind(s)
|
||||
if err := na.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*a = na
|
||||
return nil
|
||||
}
|
79
Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind_test.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind_test.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestACKindMarshalBad(t *testing.T) {
|
||||
tests := map[string]error{
|
||||
"Foo": ACKindError("bad ACKind: Foo"),
|
||||
"ApplicationManifest": ACKindError("bad ACKind: ApplicationManifest"),
|
||||
"": ErrNoACKind,
|
||||
}
|
||||
for in, werr := range tests {
|
||||
a := ACKind(in)
|
||||
b, gerr := json.Marshal(a)
|
||||
if b != nil {
|
||||
t.Errorf("ACKind(%q): want b=nil, got %v", in, b)
|
||||
}
|
||||
if jerr, ok := gerr.(*json.MarshalerError); !ok {
|
||||
t.Errorf("expected JSONMarshalerError")
|
||||
} else {
|
||||
if e := jerr.Err; e != werr {
|
||||
t.Errorf("err=%#v, want %#v", e, werr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACKindMarshalGood(t *testing.T) {
|
||||
for i, in := range []string{
|
||||
"ImageManifest",
|
||||
"PodManifest",
|
||||
} {
|
||||
a := ACKind(in)
|
||||
b, err := json.Marshal(a)
|
||||
if !reflect.DeepEqual(b, []byte(`"`+in+`"`)) {
|
||||
t.Errorf("#%d: marshalled=%v, want %v", i, b, []byte(in))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err=%v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACKindUnmarshalBad(t *testing.T) {
|
||||
tests := []string{
|
||||
"ImageManifest", // Not a valid JSON-encoded string
|
||||
`"garbage"`,
|
||||
`"AppManifest"`,
|
||||
`""`,
|
||||
}
|
||||
for i, in := range tests {
|
||||
var a, b ACKind
|
||||
err := a.UnmarshalJSON([]byte(in))
|
||||
if err == nil {
|
||||
t.Errorf("#%d: err=nil, want non-nil", i)
|
||||
} else if !reflect.DeepEqual(a, b) {
|
||||
t.Errorf("#%d: a=%v, want empty", i, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACKindUnmarshalGood(t *testing.T) {
|
||||
tests := map[string]ACKind{
|
||||
`"PodManifest"`: ACKind("PodManifest"),
|
||||
`"ImageManifest"`: ACKind("ImageManifest"),
|
||||
}
|
||||
for in, w := range tests {
|
||||
var a ACKind
|
||||
err := json.Unmarshal([]byte(in), &a)
|
||||
if err != nil {
|
||||
t.Errorf("%v: err=%v, want nil", in, err)
|
||||
} else if !reflect.DeepEqual(a, w) {
|
||||
t.Errorf("%v: a=%v, want %v", in, a, w)
|
||||
}
|
||||
}
|
||||
}
|
131
Godeps/_workspace/src/github.com/appc/spec/schema/types/acname.go
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/appc/spec/schema/types/acname.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ValidACName is a regular expression that defines a valid ACName
|
||||
ValidACName = regexp.MustCompile("^[a-z0-9]+([-./][a-z0-9]+)*$")
|
||||
|
||||
invalidChars = regexp.MustCompile("[^a-z0-9./-]")
|
||||
invalidEdges = regexp.MustCompile("(^[./-]+)|([./-]+$)")
|
||||
|
||||
ErrEmptyACName = ACNameError("ACName cannot be empty")
|
||||
ErrInvalidEdge = ACNameError("ACName must start and end with only lower case " +
|
||||
"alphanumeric characters")
|
||||
ErrInvalidChar = ACNameError("ACName must contain only lower case " +
|
||||
`alphanumeric characters plus ".", "-", "/"`)
|
||||
)
|
||||
|
||||
// ACName (an App-Container Name) is a format used by keys in different formats
|
||||
// of the App Container Standard. An ACName is restricted to characters
|
||||
// accepted by the DNS RFC[1] and "/"; all alphabetical characters must be
|
||||
// lowercase only. Furthermore, the first and last character ("edges") must be
|
||||
// alphanumeric, and an ACName cannot be empty. Programmatically, an ACName
|
||||
// must conform to the regular expression ValidACName.
|
||||
//
|
||||
// [1] http://tools.ietf.org/html/rfc1123#page-13
|
||||
type ACName string
|
||||
|
||||
func (n ACName) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
// Set sets the ACName to the given value, if it is valid; if not,
|
||||
// an error is returned.
|
||||
func (n *ACName) Set(s string) error {
|
||||
nn, err := NewACName(s)
|
||||
if err == nil {
|
||||
*n = *nn
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Equals checks whether a given ACName is equal to this one.
|
||||
func (n ACName) Equals(o ACName) bool {
|
||||
return strings.ToLower(string(n)) == strings.ToLower(string(o))
|
||||
}
|
||||
|
||||
// Empty returns a boolean indicating whether this ACName is empty.
|
||||
func (n ACName) Empty() bool {
|
||||
return n.String() == ""
|
||||
}
|
||||
|
||||
// NewACName generates a new ACName from a string. If the given string is
|
||||
// not a valid ACName, nil and an error are returned.
|
||||
func NewACName(s string) (*ACName, error) {
|
||||
n := ACName(s)
|
||||
if err := n.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
// MustACName generates a new ACName from a string, If the given string is
|
||||
// not a valid ACName, it panics.
|
||||
func MustACName(s string) *ACName {
|
||||
n, err := NewACName(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n ACName) assertValid() error {
|
||||
s := string(n)
|
||||
if len(s) == 0 {
|
||||
return ErrEmptyACName
|
||||
}
|
||||
if invalidChars.MatchString(s) {
|
||||
return ErrInvalidChar
|
||||
}
|
||||
if invalidEdges.MatchString(s) {
|
||||
return ErrInvalidEdge
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (n *ACName) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nn, err := NewACName(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*n = *nn
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface
|
||||
func (n ACName) MarshalJSON() ([]byte, error) {
|
||||
if err := n.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(n.String())
|
||||
}
|
||||
|
||||
// SanitizeACName replaces every invalid ACName character in s with a dash
|
||||
// making it a legal ACName string. If the character is an upper case letter it
|
||||
// replaces it with its lower case. It also removes illegal edge characters
|
||||
// (hyphens, periods and slashes).
|
||||
//
|
||||
// This is a helper function and its algorithm is not part of the spec. It
|
||||
// should not be called without the user explicitly asking for a suggestion.
|
||||
func SanitizeACName(s string) (string, error) {
|
||||
s = strings.ToLower(s)
|
||||
s = invalidChars.ReplaceAllString(s, "-")
|
||||
s = invalidEdges.ReplaceAllString(s, "")
|
||||
|
||||
if s == "" {
|
||||
return "", errors.New("must contain at least one valid character")
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
252
Godeps/_workspace/src/github.com/appc/spec/schema/types/acname_test.go
generated
vendored
Normal file
252
Godeps/_workspace/src/github.com/appc/spec/schema/types/acname_test.go
generated
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
goodNames = []string{
|
||||
"asdf",
|
||||
"foo-bar-baz",
|
||||
"database",
|
||||
"example.com/database",
|
||||
"example.com/ourapp-1.0.0",
|
||||
"sub-domain.example.com/org/product/release-1.0.0",
|
||||
}
|
||||
badNames = []string{
|
||||
"",
|
||||
"foo#",
|
||||
"EXAMPLE.com",
|
||||
"foo.com/BAR",
|
||||
"example.com/app_1",
|
||||
"/app",
|
||||
"app/",
|
||||
"-app",
|
||||
"app-",
|
||||
".app",
|
||||
"app.",
|
||||
}
|
||||
)
|
||||
|
||||
func TestNewACName(t *testing.T) {
|
||||
for i, in := range goodNames {
|
||||
l, err := NewACName(in)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: got err=%v, want nil", i, err)
|
||||
}
|
||||
if l == nil {
|
||||
t.Errorf("#%d: got l=nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewACNameBad(t *testing.T) {
|
||||
for i, in := range badNames {
|
||||
l, err := NewACName(in)
|
||||
if l != nil {
|
||||
t.Errorf("#%d: got l=%v, want nil", i, l)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("#%d: got err=nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustACName(t *testing.T) {
|
||||
for i, in := range goodNames {
|
||||
l := MustACName(in)
|
||||
if l == nil {
|
||||
t.Errorf("#%d: got l=nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expectPanicMustACName(i int, in string, t *testing.T) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
_ = MustACName(in)
|
||||
t.Errorf("#%d: panic expected", i)
|
||||
}
|
||||
|
||||
func TestMustACNameBad(t *testing.T) {
|
||||
for i, in := range badNames {
|
||||
expectPanicMustACName(i, in, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeACName(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"foo#": "foo",
|
||||
"EXAMPLE.com": "example.com",
|
||||
"foo.com/BAR": "foo.com/bar",
|
||||
"example.com/app_1": "example.com/app-1",
|
||||
"/app": "app",
|
||||
"app/": "app",
|
||||
"-app": "app",
|
||||
"app-": "app",
|
||||
".app": "app",
|
||||
"app.": "app",
|
||||
"app///": "app",
|
||||
"-/.app..": "app",
|
||||
"-/app.name-test/-/": "app.name-test",
|
||||
"sub-domain.example.com/org/product/release-1.0.0": "sub-domain.example.com/org/product/release-1.0.0",
|
||||
}
|
||||
for in, ex := range tests {
|
||||
o, err := SanitizeACName(in)
|
||||
if err != nil {
|
||||
t.Errorf("got err=%v, want nil", err)
|
||||
}
|
||||
if o != ex {
|
||||
t.Errorf("got l=%s, want %s", o, ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACNameSetGood(t *testing.T) {
|
||||
tests := map[string]ACName{
|
||||
"blargh": ACName("blargh"),
|
||||
"example.com/ourapp-1.0.0": ACName("example.com/ourapp-1.0.0"),
|
||||
}
|
||||
for in, w := range tests {
|
||||
// Ensure an empty name is set appropriately
|
||||
var a ACName
|
||||
err := a.Set(in)
|
||||
if err != nil {
|
||||
t.Errorf("%v: got err=%v, want nil", in, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(a, w) {
|
||||
t.Errorf("%v: a=%v, want %v", in, a, w)
|
||||
}
|
||||
|
||||
// Ensure an existing name is overwritten
|
||||
var b ACName = ACName("orig")
|
||||
err = b.Set(in)
|
||||
if err != nil {
|
||||
t.Errorf("%v: got err=%v, want nil", in, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(b, w) {
|
||||
t.Errorf("%v: b=%v, want %v", in, b, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACNameSetBad(t *testing.T) {
|
||||
for i, in := range badNames {
|
||||
// Ensure an empty name stays empty
|
||||
var a ACName
|
||||
err := a.Set(in)
|
||||
if err == nil {
|
||||
t.Errorf("#%d: err=%v, want nil", i, err)
|
||||
continue
|
||||
}
|
||||
if w := ACName(""); !reflect.DeepEqual(a, w) {
|
||||
t.Errorf("%d: a=%v, want %v", i, a, w)
|
||||
}
|
||||
|
||||
// Ensure an existing name is not overwritten
|
||||
var b ACName = ACName("orig")
|
||||
err = b.Set(in)
|
||||
if err == nil {
|
||||
t.Errorf("#%d: err=%v, want nil", i, err)
|
||||
continue
|
||||
}
|
||||
if w := ACName("orig"); !reflect.DeepEqual(b, w) {
|
||||
t.Errorf("%d: b=%v, want %v", i, b, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeACNameBad(t *testing.T) {
|
||||
tests := []string{
|
||||
"__",
|
||||
"..",
|
||||
"//",
|
||||
"",
|
||||
".//-"}
|
||||
for i, in := range tests {
|
||||
l, err := SanitizeACName(in)
|
||||
if l != "" {
|
||||
t.Errorf("#%d: got l=%v, want nil", i, l)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("#%d: got err=nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACNameUnmarshalBad(t *testing.T) {
|
||||
tests := []string{
|
||||
"",
|
||||
"garbage",
|
||||
`""`,
|
||||
`"EXAMPLE"`,
|
||||
`"example.com/app_1"`,
|
||||
}
|
||||
for i, in := range tests {
|
||||
var a, b ACName
|
||||
err := a.UnmarshalJSON([]byte(in))
|
||||
if err == nil {
|
||||
t.Errorf("#%d: err=nil, want non-nil", i)
|
||||
} else if !reflect.DeepEqual(a, b) {
|
||||
t.Errorf("#%d: a=%v, want empty", i, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACNameUnmarshalGood(t *testing.T) {
|
||||
tests := map[string]ACName{
|
||||
`"example"`: ACName("example"),
|
||||
`"foo.com/bar"`: ACName("foo.com/bar"),
|
||||
}
|
||||
for in, w := range tests {
|
||||
var a ACName
|
||||
err := json.Unmarshal([]byte(in), &a)
|
||||
if err != nil {
|
||||
t.Errorf("%v: err=%v, want nil", in, err)
|
||||
} else if !reflect.DeepEqual(a, w) {
|
||||
t.Errorf("%v: a=%v, want %v", in, a, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACNameMarshalBad(t *testing.T) {
|
||||
tests := map[string]error{
|
||||
"Foo": ErrInvalidChar,
|
||||
"foo#": ErrInvalidChar,
|
||||
"/foo": ErrInvalidEdge,
|
||||
"example.com/": ErrInvalidEdge,
|
||||
"": ErrEmptyACName,
|
||||
}
|
||||
for in, werr := range tests {
|
||||
a := ACName(in)
|
||||
b, gerr := json.Marshal(a)
|
||||
if b != nil {
|
||||
t.Errorf("ACName(%q): want b=nil, got %v", in, b)
|
||||
}
|
||||
if jerr, ok := gerr.(*json.MarshalerError); !ok {
|
||||
t.Errorf("expected JSONMarshalerError")
|
||||
} else {
|
||||
if e := jerr.Err; e != werr {
|
||||
t.Errorf("err=%#v, want %#v", e, werr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACNameMarshalGood(t *testing.T) {
|
||||
for i, in := range goodNames {
|
||||
a := ACName(in)
|
||||
b, err := json.Marshal(a)
|
||||
if !reflect.DeepEqual(b, []byte(`"`+in+`"`)) {
|
||||
t.Errorf("#%d: marshalled=%v, want %v", i, b, []byte(in))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err=%v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
}
|
92
Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations.go
generated
vendored
Normal file
92
Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Annotations []Annotation
|
||||
|
||||
type annotations Annotations
|
||||
|
||||
type Annotation struct {
|
||||
Name ACName `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (a Annotations) assertValid() error {
|
||||
seen := map[ACName]string{}
|
||||
for _, anno := range a {
|
||||
_, ok := seen[anno.Name]
|
||||
if ok {
|
||||
return fmt.Errorf(`duplicate annotations of name %q`, anno.Name)
|
||||
}
|
||||
seen[anno.Name] = anno.Value
|
||||
}
|
||||
if c, ok := seen["created"]; ok {
|
||||
if _, err := NewDate(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if h, ok := seen["homepage"]; ok {
|
||||
if _, err := NewURL(h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if d, ok := seen["documentation"]; ok {
|
||||
if _, err := NewURL(d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a Annotations) MarshalJSON() ([]byte, error) {
|
||||
if err := a.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(annotations(a))
|
||||
}
|
||||
|
||||
func (a *Annotations) UnmarshalJSON(data []byte) error {
|
||||
var ja annotations
|
||||
if err := json.Unmarshal(data, &ja); err != nil {
|
||||
return err
|
||||
}
|
||||
na := Annotations(ja)
|
||||
if err := na.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*a = na
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve the value of an annotation by the given name from Annotations, if
|
||||
// it exists.
|
||||
func (a Annotations) Get(name string) (val string, ok bool) {
|
||||
for _, anno := range a {
|
||||
if anno.Name.String() == name {
|
||||
return anno.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Set sets the value of an annotation by the given name, overwriting if one already exists.
|
||||
func (a *Annotations) Set(name ACName, value string) {
|
||||
for i, anno := range *a {
|
||||
if anno.Name.Equals(name) {
|
||||
(*a)[i] = Annotation{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
anno := Annotation{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
*a = append(*a, anno)
|
||||
}
|
217
Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations_test.go
generated
vendored
Normal file
217
Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations_test.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func makeAnno(n, v string) Annotation {
|
||||
name, err := NewACName(n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Annotation{
|
||||
Name: *name,
|
||||
Value: v,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotationsAssertValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
in []Annotation
|
||||
werr bool
|
||||
}{
|
||||
// duplicate names should fail
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("foo", "bar"),
|
||||
makeAnno("foo", "baz"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
// bad created should fail
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("created", "garbage"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
// bad homepage should fail
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("homepage", "not-A$@#URL"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
// bad documentation should fail
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("documentation", "ftp://isnotallowed.com"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
// good cases
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("created", "2004-05-14T23:11:14+00:00"),
|
||||
makeAnno("documentation", "http://example.com/docs"),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("foo", "bar"),
|
||||
makeAnno("homepage", "https://homepage.com"),
|
||||
},
|
||||
false,
|
||||
},
|
||||
// empty is OK
|
||||
{
|
||||
[]Annotation{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
a := Annotations(tt.in)
|
||||
err := a.assertValid()
|
||||
if gerr := (err != nil); gerr != tt.werr {
|
||||
t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotationsMarshal(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
in []Annotation
|
||||
wb []byte
|
||||
werr bool
|
||||
}{
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("foo", "bar"),
|
||||
makeAnno("foo", "baz"),
|
||||
makeAnno("website", "http://example.com/anno"),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("a", "b"),
|
||||
},
|
||||
[]byte(`[{"name":"a","value":"b"}]`),
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]Annotation{
|
||||
makeAnno("foo", "bar"),
|
||||
makeAnno("website", "http://example.com/anno"),
|
||||
},
|
||||
[]byte(`[{"name":"foo","value":"bar"},{"name":"website","value":"http://example.com/anno"}]`),
|
||||
false,
|
||||
},
|
||||
} {
|
||||
a := Annotations(tt.in)
|
||||
b, err := a.MarshalJSON()
|
||||
if !reflect.DeepEqual(b, tt.wb) {
|
||||
t.Errorf("#%d: b=%s, want %s", i, b, tt.wb)
|
||||
}
|
||||
gerr := err != nil
|
||||
if gerr != tt.werr {
|
||||
t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotationsUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
wann *Annotations
|
||||
werr bool
|
||||
}{
|
||||
{
|
||||
`garbage`,
|
||||
&Annotations{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`[{"name":"a","value":"b"},{"name":"a","value":"b"}]`,
|
||||
&Annotations{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`[{"name":"a","value":"b"}]`,
|
||||
&Annotations{
|
||||
makeAnno("a", "b"),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
a := &Annotations{}
|
||||
err := a.UnmarshalJSON([]byte(tt.in))
|
||||
gerr := err != nil
|
||||
if gerr != tt.werr {
|
||||
t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err)
|
||||
}
|
||||
if !reflect.DeepEqual(a, tt.wann) {
|
||||
t.Errorf("#%d: ann=%#v, want %#v", i, a, tt.wann)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAnnotationsGet(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
in string
|
||||
wval string
|
||||
wok bool
|
||||
}{
|
||||
{"foo", "bar", true},
|
||||
{"website", "http://example.com/anno", true},
|
||||
{"baz", "", false},
|
||||
{"wuuf", "", false},
|
||||
} {
|
||||
a := Annotations{
|
||||
makeAnno("foo", "bar"),
|
||||
makeAnno("website", "http://example.com/anno"),
|
||||
}
|
||||
gval, gok := a.Get(tt.in)
|
||||
if gval != tt.wval {
|
||||
t.Errorf("#%d: val=%v, want %v", i, gval, tt.wval)
|
||||
}
|
||||
if gok != tt.wok {
|
||||
t.Errorf("#%d: ok=%t, want %t", i, gok, tt.wok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotationsSet(t *testing.T) {
|
||||
a := Annotations{}
|
||||
|
||||
a.Set("foo", "bar")
|
||||
w := Annotations{
|
||||
Annotation{ACName("foo"), "bar"},
|
||||
}
|
||||
if !reflect.DeepEqual(w, a) {
|
||||
t.Fatalf("want %v, got %v", w, a)
|
||||
}
|
||||
|
||||
a.Set("dog", "woof")
|
||||
w = Annotations{
|
||||
Annotation{ACName("foo"), "bar"},
|
||||
Annotation{ACName("dog"), "woof"},
|
||||
}
|
||||
if !reflect.DeepEqual(w, a) {
|
||||
t.Fatalf("want %v, got %v", w, a)
|
||||
}
|
||||
|
||||
a.Set("foo", "baz")
|
||||
w = Annotations{
|
||||
Annotation{ACName("foo"), "baz"},
|
||||
Annotation{ACName("dog"), "woof"},
|
||||
}
|
||||
if !reflect.DeepEqual(w, a) {
|
||||
t.Fatalf("want %v, got %v", w, a)
|
||||
}
|
||||
}
|
75
Godeps/_workspace/src/github.com/appc/spec/schema/types/app.go
generated
vendored
Normal file
75
Godeps/_workspace/src/github.com/appc/spec/schema/types/app.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Exec Exec `json:"exec"`
|
||||
EventHandlers []EventHandler `json:"eventHandlers,omitempty"`
|
||||
User string `json:"user"`
|
||||
Group string `json:"group"`
|
||||
WorkingDirectory string `json:"workingDirectory,omitempty"`
|
||||
Environment Environment `json:"environment,omitempty"`
|
||||
MountPoints []MountPoint `json:"mountPoints,omitempty"`
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Isolators Isolators `json:"isolators,omitempty"`
|
||||
}
|
||||
|
||||
// app is a model to facilitate extra validation during the
|
||||
// unmarshalling of the App
|
||||
type app App
|
||||
|
||||
func (a *App) UnmarshalJSON(data []byte) error {
|
||||
ja := app(*a)
|
||||
err := json.Unmarshal(data, &ja)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
na := App(ja)
|
||||
if err := na.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if na.Environment == nil {
|
||||
na.Environment = make(Environment, 0)
|
||||
}
|
||||
*a = na
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a App) MarshalJSON() ([]byte, error) {
|
||||
if err := a.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(app(a))
|
||||
}
|
||||
|
||||
func (a *App) assertValid() error {
|
||||
if err := a.Exec.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if a.User == "" {
|
||||
return errors.New(`User is required`)
|
||||
}
|
||||
if a.Group == "" {
|
||||
return errors.New(`Group is required`)
|
||||
}
|
||||
if !path.IsAbs(a.WorkingDirectory) && a.WorkingDirectory != "" {
|
||||
return errors.New("WorkingDirectory must be an absolute path")
|
||||
}
|
||||
eh := make(map[string]bool)
|
||||
for _, e := range a.EventHandlers {
|
||||
name := e.Name
|
||||
if eh[name] {
|
||||
return fmt.Errorf("Only one eventHandler of name %q allowed", name)
|
||||
}
|
||||
eh[name] = true
|
||||
}
|
||||
if err := a.Environment.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
216
Godeps/_workspace/src/github.com/appc/spec/schema/types/app_test.go
generated
vendored
Normal file
216
Godeps/_workspace/src/github.com/appc/spec/schema/types/app_test.go
generated
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppValid(t *testing.T) {
|
||||
tests := []App{
|
||||
App{
|
||||
Exec: []string{"/bin/httpd"},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
WorkingDirectory: "/tmp",
|
||||
},
|
||||
App{
|
||||
Exec: []string{"/app"},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
EventHandlers: []EventHandler{
|
||||
{Name: "pre-start"},
|
||||
{Name: "post-stop"},
|
||||
},
|
||||
Environment: []EnvironmentVariable{
|
||||
{Name: "DEBUG", Value: "true"},
|
||||
},
|
||||
WorkingDirectory: "/tmp",
|
||||
},
|
||||
App{
|
||||
Exec: []string{"/app", "arg1", "arg2"},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
WorkingDirectory: "/tmp",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err != nil {
|
||||
t.Errorf("#%d: err == %v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppExecInvalid(t *testing.T) {
|
||||
tests := []App{
|
||||
App{
|
||||
Exec: nil,
|
||||
},
|
||||
App{
|
||||
Exec: []string{},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
},
|
||||
App{
|
||||
Exec: []string{"app"},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
},
|
||||
App{
|
||||
Exec: []string{"bin/app", "arg1"},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err == nil {
|
||||
t.Errorf("#%d: err == nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppEventHandlersInvalid(t *testing.T) {
|
||||
tests := []App{
|
||||
App{
|
||||
Exec: []string{"/bin/httpd"},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
EventHandlers: []EventHandler{
|
||||
EventHandler{
|
||||
Name: "pre-start",
|
||||
},
|
||||
EventHandler{
|
||||
Name: "pre-start",
|
||||
},
|
||||
},
|
||||
},
|
||||
App{
|
||||
Exec: []string{"/bin/httpd"},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
EventHandlers: []EventHandler{
|
||||
EventHandler{
|
||||
Name: "post-stop",
|
||||
},
|
||||
EventHandler{
|
||||
Name: "pre-start",
|
||||
},
|
||||
EventHandler{
|
||||
Name: "post-stop",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err == nil {
|
||||
t.Errorf("#%d: err == nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGroupInvalid(t *testing.T) {
|
||||
tests := []App{
|
||||
App{
|
||||
Exec: []string{"/app"},
|
||||
},
|
||||
App{
|
||||
Exec: []string{"/app"},
|
||||
User: "0",
|
||||
},
|
||||
App{
|
||||
Exec: []string{"app"},
|
||||
Group: "0",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err == nil {
|
||||
t.Errorf("#%d: err == nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppWorkingDirectoryInvalid(t *testing.T) {
|
||||
tests := []App{
|
||||
App{
|
||||
Exec: []string{"/app"},
|
||||
User: "foo",
|
||||
Group: "bar",
|
||||
WorkingDirectory: "stuff",
|
||||
},
|
||||
App{
|
||||
Exec: []string{"/app"},
|
||||
User: "foo",
|
||||
Group: "bar",
|
||||
WorkingDirectory: "../home/fred",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err == nil {
|
||||
t.Errorf("#%d: err == nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppEnvironmentInvalid(t *testing.T) {
|
||||
tests := []App{
|
||||
App{
|
||||
Exec: []string{"/app"},
|
||||
User: "foo",
|
||||
Group: "bar",
|
||||
Environment: Environment{
|
||||
EnvironmentVariable{"0DEBUG", "true"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err == nil {
|
||||
t.Errorf("#%d: err == nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
wann *App
|
||||
werr bool
|
||||
}{
|
||||
{
|
||||
`garbage`,
|
||||
&App{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{"Exec":"not a list"}`,
|
||||
&App{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{"Exec":["notfullyqualified"]}`,
|
||||
&App{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{"Exec":["/a"],"User":"0","Group":"0"}`,
|
||||
&App{
|
||||
Exec: Exec{
|
||||
"/a",
|
||||
},
|
||||
User: "0",
|
||||
Group: "0",
|
||||
Environment: make(Environment, 0),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
a := &App{}
|
||||
err := a.UnmarshalJSON([]byte(tt.in))
|
||||
gerr := err != nil
|
||||
if gerr != tt.werr {
|
||||
t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err)
|
||||
}
|
||||
if !reflect.DeepEqual(a, tt.wann) {
|
||||
t.Errorf("#%d: ann=%#v, want %#v", i, a, tt.wann)
|
||||
}
|
||||
}
|
||||
}
|
46
Godeps/_workspace/src/github.com/appc/spec/schema/types/date.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/appc/spec/schema/types/date.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Date wraps time.Time to marshal/unmarshal to/from JSON strings in strict
|
||||
// accordance with RFC3339
|
||||
// TODO(jonboulle): golang's implementation seems slightly buggy here;
|
||||
// according to http://tools.ietf.org/html/rfc3339#section-5.6 , applications
|
||||
// may choose to separate the date and time with a space instead of a T
|
||||
// character (for example, `date --rfc-3339` on GNU coreutils) - but this is
|
||||
// considered an error by go's parser. File a bug?
|
||||
type Date time.Time
|
||||
|
||||
func NewDate(s string) (*Date, error) {
|
||||
t, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad Date: %v", err)
|
||||
}
|
||||
d := Date(t)
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (d Date) String() string {
|
||||
return time.Time(d).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (d *Date) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nd, err := NewDate(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = *nd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Date) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
66
Godeps/_workspace/src/github.com/appc/spec/schema/types/date_test.go
generated
vendored
Normal file
66
Godeps/_workspace/src/github.com/appc/spec/schema/types/date_test.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
pst = time.FixedZone("Pacific", -8*60*60)
|
||||
)
|
||||
|
||||
func TestUnmarshalDate(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
|
||||
wt time.Time
|
||||
}{
|
||||
{
|
||||
`"2004-05-14T23:11:14+00:00"`,
|
||||
|
||||
time.Date(2004, 05, 14, 23, 11, 14, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
`"2001-02-03T04:05:06Z"`,
|
||||
|
||||
time.Date(2001, 02, 03, 04, 05, 06, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
`"2014-11-14T17:36:54-08:00"`,
|
||||
|
||||
time.Date(2014, 11, 14, 17, 36, 54, 0, pst),
|
||||
},
|
||||
{
|
||||
`"2004-05-14T23:11:14+00:00"`,
|
||||
|
||||
time.Date(2004, 05, 14, 23, 11, 14, 0, time.UTC),
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
var d Date
|
||||
if err := json.Unmarshal([]byte(tt.in), &d); err != nil {
|
||||
t.Errorf("#%d: got err=%v, want nil", i, err)
|
||||
}
|
||||
if gt := time.Time(d); !gt.Equal(tt.wt) {
|
||||
t.Errorf("#%d: got time=%v, want %v", i, gt, tt.wt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalDateBad(t *testing.T) {
|
||||
tests := []string{
|
||||
`not a json string`,
|
||||
`2014-11-14T17:36:54-08:00`,
|
||||
`"garbage"`,
|
||||
`"1416015188"`,
|
||||
`"Fri Nov 14 17:53:02 PST 2014"`,
|
||||
`"2014-11-1417:36:54"`,
|
||||
}
|
||||
for i, tt := range tests {
|
||||
var d Date
|
||||
if err := json.Unmarshal([]byte(tt), &d); err == nil {
|
||||
t.Errorf("#%d: unexpected nil err", i)
|
||||
}
|
||||
}
|
||||
}
|
43
Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Dependencies []Dependency
|
||||
|
||||
type Dependency struct {
|
||||
App ACName `json:"app"`
|
||||
ImageID *Hash `json:"imageID,omitempty"`
|
||||
Labels Labels `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
type dependency Dependency
|
||||
|
||||
func (d Dependency) assertValid() error {
|
||||
if len(d.App) < 1 {
|
||||
return errors.New(`App cannot be empty`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Dependency) MarshalJSON() ([]byte, error) {
|
||||
if err := d.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(dependency(d))
|
||||
}
|
||||
|
||||
func (d *Dependency) UnmarshalJSON(data []byte) error {
|
||||
var jd dependency
|
||||
if err := json.Unmarshal(data, &jd); err != nil {
|
||||
return err
|
||||
}
|
||||
nd := Dependency(jd)
|
||||
if err := nd.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*d = nd
|
||||
return nil
|
||||
}
|
26
Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies_test.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmptyHash(t *testing.T) {
|
||||
dj := `{"app": "example.com/reduce-worker-base"}`
|
||||
|
||||
var d Dependency
|
||||
|
||||
err := d.UnmarshalJSON([]byte(dj))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Marshal to verify that marshalling works without validation errors
|
||||
buf, err := d.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal to verify that the generated json will not create wrong empty hash
|
||||
err = d.UnmarshalJSON(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
4
Godeps/_workspace/src/github.com/appc/spec/schema/types/doc.go
generated
vendored
Normal file
4
Godeps/_workspace/src/github.com/appc/spec/schema/types/doc.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// Package types contains structs representing the various types in the app
|
||||
// container specification. It is used by the [schema manifest types](../)
|
||||
// to enforce validation.
|
||||
package types
|
96
Godeps/_workspace/src/github.com/appc/spec/schema/types/environment.go
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/appc/spec/schema/types/environment.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
envPattern = regexp.MustCompile("^[A-Za-z_][A-Za-z_0-9]*$")
|
||||
)
|
||||
|
||||
type Environment []EnvironmentVariable
|
||||
|
||||
type environment Environment
|
||||
|
||||
type EnvironmentVariable struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (ev EnvironmentVariable) assertValid() error {
|
||||
if len(ev.Name) == 0 {
|
||||
return fmt.Errorf(`environment variable name must not be empty`)
|
||||
}
|
||||
if !envPattern.MatchString(ev.Name) {
|
||||
return fmt.Errorf(`environment variable does not have valid identifier %q`, ev.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Environment) assertValid() error {
|
||||
seen := map[string]bool{}
|
||||
for _, env := range e {
|
||||
if err := env.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := seen[env.Name]
|
||||
if ok {
|
||||
return fmt.Errorf(`duplicate environment variable of name %q`, env.Name)
|
||||
}
|
||||
seen[env.Name] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Environment) MarshalJSON() ([]byte, error) {
|
||||
if err := e.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(environment(e))
|
||||
}
|
||||
|
||||
func (e *Environment) UnmarshalJSON(data []byte) error {
|
||||
var je environment
|
||||
if err := json.Unmarshal(data, &je); err != nil {
|
||||
return err
|
||||
}
|
||||
ne := Environment(je)
|
||||
if err := ne.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = ne
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve the value of an environment variable by the given name from
|
||||
// Environment, if it exists.
|
||||
func (e Environment) Get(name string) (value string, ok bool) {
|
||||
for _, env := range e {
|
||||
if env.Name == name {
|
||||
return env.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Set sets the value of an environment variable by the given name,
|
||||
// overwriting if one already exists.
|
||||
func (e *Environment) Set(name string, value string) {
|
||||
for i, env := range *e {
|
||||
if env.Name == name {
|
||||
(*e)[i] = EnvironmentVariable{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
env := EnvironmentVariable{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
*e = append(*e, env)
|
||||
}
|
62
Godeps/_workspace/src/github.com/appc/spec/schema/types/environment_test.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/appc/spec/schema/types/environment_test.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnvironmentAssertValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
env Environment
|
||||
werr bool
|
||||
}{
|
||||
// duplicate names should fail
|
||||
{
|
||||
Environment{
|
||||
EnvironmentVariable{"DEBUG", "true"},
|
||||
EnvironmentVariable{"DEBUG", "true"},
|
||||
},
|
||||
true,
|
||||
},
|
||||
// empty name should fail
|
||||
{
|
||||
Environment{
|
||||
EnvironmentVariable{"", "value"},
|
||||
},
|
||||
true,
|
||||
},
|
||||
// name beginning with digit should fail
|
||||
{
|
||||
Environment{
|
||||
EnvironmentVariable{"0DEBUG", "true"},
|
||||
},
|
||||
true,
|
||||
},
|
||||
// name with non [A-Za-z0-9_] should fail
|
||||
{
|
||||
Environment{
|
||||
EnvironmentVariable{"VERBOSE-DEBUG", "true"},
|
||||
},
|
||||
true,
|
||||
},
|
||||
// accepted environment variable forms
|
||||
{
|
||||
Environment{
|
||||
EnvironmentVariable{"DEBUG", "true"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
Environment{
|
||||
EnvironmentVariable{"_0_DEBUG_0_", "true"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
env := Environment(test.env)
|
||||
err := env.assertValid()
|
||||
if gerr := (err != nil); gerr != test.werr {
|
||||
t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, test.werr, err)
|
||||
}
|
||||
}
|
||||
}
|
28
Godeps/_workspace/src/github.com/appc/spec/schema/types/errors.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/appc/spec/schema/types/errors.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An ACKindError is returned when the wrong ACKind is set in a manifest
|
||||
type ACKindError string
|
||||
|
||||
func (e ACKindError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func InvalidACKindError(kind ACKind) ACKindError {
|
||||
return ACKindError(fmt.Sprintf("missing or bad ACKind (must be %#v)", kind))
|
||||
}
|
||||
|
||||
// An ACVersionError is returned when a bad ACVersion is set in a manifest
|
||||
type ACVersionError string
|
||||
|
||||
func (e ACVersionError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// An ACNameError is returned when a bad value is used for an ACName
|
||||
type ACNameError string
|
||||
|
||||
func (e ACNameError) Error() string {
|
||||
return string(e)
|
||||
}
|
47
Godeps/_workspace/src/github.com/appc/spec/schema/types/event_handler.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/appc/spec/schema/types/event_handler.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type EventHandler struct {
|
||||
Name string `json:"name"`
|
||||
Exec Exec `json:"exec"`
|
||||
}
|
||||
|
||||
type eventHandler EventHandler
|
||||
|
||||
func (e EventHandler) assertValid() error {
|
||||
s := e.Name
|
||||
switch s {
|
||||
case "pre-start", "post-stop":
|
||||
return nil
|
||||
case "":
|
||||
return errors.New(`eventHandler "name" cannot be empty`)
|
||||
default:
|
||||
return fmt.Errorf(`bad eventHandler "name": %q`, s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e EventHandler) MarshalJSON() ([]byte, error) {
|
||||
if err := e.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(eventHandler(e))
|
||||
}
|
||||
|
||||
func (e *EventHandler) UnmarshalJSON(data []byte) error {
|
||||
var je eventHandler
|
||||
err := json.Unmarshal(data, &je)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ne := EventHandler(je)
|
||||
if err := ne.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = ne
|
||||
return nil
|
||||
}
|
42
Godeps/_workspace/src/github.com/appc/spec/schema/types/exec.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/appc/spec/schema/types/exec.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Exec []string
|
||||
|
||||
type exec Exec
|
||||
|
||||
func (e Exec) assertValid() error {
|
||||
if len(e) < 1 {
|
||||
return errors.New(`Exec cannot be empty`)
|
||||
}
|
||||
if !filepath.IsAbs(e[0]) {
|
||||
return errors.New(`Exec[0] must be absolute path`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Exec) MarshalJSON() ([]byte, error) {
|
||||
if err := e.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(exec(e))
|
||||
}
|
||||
|
||||
func (e *Exec) UnmarshalJSON(data []byte) error {
|
||||
var je exec
|
||||
err := json.Unmarshal(data, &je)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ne := Exec(je)
|
||||
if err := ne.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = ne
|
||||
return nil
|
||||
}
|
28
Godeps/_workspace/src/github.com/appc/spec/schema/types/exec_test.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/appc/spec/schema/types/exec_test.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExecValid(t *testing.T) {
|
||||
tests := []Exec{
|
||||
Exec{"/bin/httpd"},
|
||||
Exec{"/app"},
|
||||
Exec{"/app", "arg1", "arg2"},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err != nil {
|
||||
t.Errorf("#%d: err == %v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecInvalid(t *testing.T) {
|
||||
tests := []Exec{
|
||||
Exec{},
|
||||
Exec{"app"},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if err := tt.assertValid(); err == nil {
|
||||
t.Errorf("#%d: err == nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
104
Godeps/_workspace/src/github.com/appc/spec/schema/types/hash.go
generated
vendored
Normal file
104
Godeps/_workspace/src/github.com/appc/spec/schema/types/hash.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHashSize = (sha512.Size / 2) + len("sha512-")
|
||||
)
|
||||
|
||||
// Hash encodes a hash specified in a string of the form:
|
||||
// "<type>-<value>"
|
||||
// for example
|
||||
// "sha512-06c733b1838136838e6d2d3e8fa5aea4c7905e92[...]"
|
||||
// Valid types are currently:
|
||||
// * sha512
|
||||
type Hash struct {
|
||||
typ string
|
||||
Val string
|
||||
}
|
||||
|
||||
func NewHash(s string) (*Hash, error) {
|
||||
elems := strings.Split(s, "-")
|
||||
if len(elems) != 2 {
|
||||
return nil, errors.New("badly formatted hash string")
|
||||
}
|
||||
nh := Hash{
|
||||
typ: elems[0],
|
||||
Val: elems[1],
|
||||
}
|
||||
if err := nh.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nh, nil
|
||||
}
|
||||
|
||||
func (h Hash) String() string {
|
||||
return fmt.Sprintf("%s-%s", h.typ, h.Val)
|
||||
}
|
||||
|
||||
func (h *Hash) Set(s string) error {
|
||||
nh, err := NewHash(s)
|
||||
if err == nil {
|
||||
*h = *nh
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (h Hash) Empty() bool {
|
||||
return reflect.DeepEqual(h, Hash{})
|
||||
}
|
||||
|
||||
func (h Hash) assertValid() error {
|
||||
switch h.typ {
|
||||
case "sha512":
|
||||
case "":
|
||||
return fmt.Errorf("unexpected empty hash type")
|
||||
default:
|
||||
return fmt.Errorf("unrecognized hash type: %v", h.typ)
|
||||
}
|
||||
if h.Val == "" {
|
||||
return fmt.Errorf("unexpected empty hash value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hash) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nh, err := NewHash(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*h = *nh
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Hash) MarshalJSON() ([]byte, error) {
|
||||
if err := h.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(h.String())
|
||||
}
|
||||
|
||||
func NewHashSHA512(b []byte) *Hash {
|
||||
h := sha512.New()
|
||||
h.Write(b)
|
||||
nh, _ := NewHash(fmt.Sprintf("sha512-%x", h.Sum(nil)))
|
||||
return nh
|
||||
}
|
||||
|
||||
func ShortHash(hash string) string {
|
||||
if len(hash) > maxHashSize {
|
||||
return hash[:maxHashSize]
|
||||
}
|
||||
return hash
|
||||
}
|
82
Godeps/_workspace/src/github.com/appc/spec/schema/types/hash_test.go
generated
vendored
Normal file
82
Godeps/_workspace/src/github.com/appc/spec/schema/types/hash_test.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarshalHash(t *testing.T) {
|
||||
tests := []struct {
|
||||
typ string
|
||||
val string
|
||||
|
||||
wout string
|
||||
}{
|
||||
{
|
||||
"sha512",
|
||||
"abcdefghi",
|
||||
|
||||
`"sha512-abcdefghi"`,
|
||||
},
|
||||
{
|
||||
"sha512",
|
||||
"06c733b1838136838e6d2d3e8fa5aea4c7905e92",
|
||||
|
||||
`"sha512-06c733b1838136838e6d2d3e8fa5aea4c7905e92"`,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
h := Hash{
|
||||
typ: tt.typ,
|
||||
Val: tt.val,
|
||||
}
|
||||
b, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: unexpected err=%v", i, err)
|
||||
}
|
||||
if g := string(b); g != tt.wout {
|
||||
t.Errorf("#%d: got string=%v, want %v", i, g, tt.wout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalHashBad(t *testing.T) {
|
||||
tests := []struct {
|
||||
typ string
|
||||
val string
|
||||
}{
|
||||
{
|
||||
// empty value
|
||||
"sha512",
|
||||
"",
|
||||
},
|
||||
{
|
||||
// bad type
|
||||
"sha1",
|
||||
"abcdef",
|
||||
},
|
||||
{
|
||||
// empty type
|
||||
"",
|
||||
"abcdef",
|
||||
},
|
||||
{
|
||||
// empty empty
|
||||
"",
|
||||
"",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
h := Hash{
|
||||
typ: tt.typ,
|
||||
Val: tt.val,
|
||||
}
|
||||
g, err := json.Marshal(h)
|
||||
if err == nil {
|
||||
t.Errorf("#%d: unexpected nil err", i)
|
||||
}
|
||||
if g != nil {
|
||||
t.Errorf("#%d: unexpected non-nil bytes: %v", i, g)
|
||||
}
|
||||
}
|
||||
}
|
89
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator.go
generated
vendored
Normal file
89
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
var (
|
||||
isolatorMap map[ACName]IsolatorValueConstructor
|
||||
)
|
||||
|
||||
func init() {
|
||||
isolatorMap = make(map[ACName]IsolatorValueConstructor)
|
||||
}
|
||||
|
||||
type IsolatorValueConstructor func() IsolatorValue
|
||||
|
||||
func AddIsolatorValueConstructor(n ACName, i IsolatorValueConstructor) {
|
||||
isolatorMap[n] = i
|
||||
}
|
||||
|
||||
type Isolators []Isolator
|
||||
|
||||
// GetByName returns the last isolator in the list by the given name.
|
||||
func (is *Isolators) GetByName(name ACName) *Isolator {
|
||||
var i Isolator
|
||||
for j := len(*is) - 1; j >= 0; j-- {
|
||||
i = []Isolator(*is)[j]
|
||||
if i.Name == name {
|
||||
return &i
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unrecognized returns a set of isolators that are not recognized.
|
||||
// An isolator is not recognized if it has not had an associated
|
||||
// constructor registered with AddIsolatorValueConstructor.
|
||||
func (is *Isolators) Unrecognized() Isolators {
|
||||
u := Isolators{}
|
||||
for _, i := range *is {
|
||||
if i.value == nil {
|
||||
u = append(u, i)
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
type IsolatorValue interface {
|
||||
UnmarshalJSON(b []byte) error
|
||||
AssertValid() error
|
||||
}
|
||||
type Isolator struct {
|
||||
Name ACName `json:"name"`
|
||||
ValueRaw *json.RawMessage `json:"value"`
|
||||
value IsolatorValue
|
||||
}
|
||||
type isolator Isolator
|
||||
|
||||
func (i *Isolator) Value() IsolatorValue {
|
||||
return i.value
|
||||
}
|
||||
|
||||
func (i *Isolator) UnmarshalJSON(b []byte) error {
|
||||
var ii isolator
|
||||
err := json.Unmarshal(b, &ii)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dst IsolatorValue
|
||||
con, ok := isolatorMap[ii.Name]
|
||||
if ok {
|
||||
dst = con()
|
||||
err = dst.UnmarshalJSON(*ii.ValueRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dst.AssertValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
i.value = dst
|
||||
i.ValueRaw = ii.ValueRaw
|
||||
i.Name = ii.Name
|
||||
|
||||
return nil
|
||||
}
|
69
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_linux_specific.go
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_linux_specific.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
LinuxCapabilitiesRetainSetName = "os/linux/capabilities-retain-set"
|
||||
LinuxCapabilitiesRevokeSetName = "os/linux/capabilities-revoke-set"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddIsolatorValueConstructor(LinuxCapabilitiesRetainSetName, NewLinuxCapabilitiesRetainSet)
|
||||
AddIsolatorValueConstructor(LinuxCapabilitiesRevokeSetName, NewLinuxCapabilitiesRevokeSet)
|
||||
}
|
||||
|
||||
type LinuxCapabilitiesSet interface {
|
||||
Set() []LinuxCapability
|
||||
AssertValid() error
|
||||
}
|
||||
|
||||
type LinuxCapability string
|
||||
type linuxCapabilitiesSetValue struct {
|
||||
Set []LinuxCapability `json:"set"`
|
||||
}
|
||||
|
||||
type linuxCapabilitiesSetBase struct {
|
||||
val linuxCapabilitiesSetValue
|
||||
}
|
||||
|
||||
func (l linuxCapabilitiesSetBase) AssertValid() error {
|
||||
if len(l.val.Set) == 0 {
|
||||
return errors.New("set must be non-empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *linuxCapabilitiesSetBase) UnmarshalJSON(b []byte) error {
|
||||
var v linuxCapabilitiesSetValue
|
||||
err := json.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.val = v
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (l linuxCapabilitiesSetBase) Set() []LinuxCapability {
|
||||
return l.val.Set
|
||||
}
|
||||
|
||||
func NewLinuxCapabilitiesRetainSet() IsolatorValue {
|
||||
return &LinuxCapabilitiesRetainSet{}
|
||||
}
|
||||
|
||||
type LinuxCapabilitiesRetainSet struct {
|
||||
linuxCapabilitiesSetBase
|
||||
}
|
||||
|
||||
func NewLinuxCapabilitiesRevokeSet() IsolatorValue {
|
||||
return &LinuxCapabilitiesRevokeSet{}
|
||||
}
|
||||
|
||||
type LinuxCapabilitiesRevokeSet struct {
|
||||
linuxCapabilitiesSetBase
|
||||
}
|
144
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_resources.go
generated
vendored
Normal file
144
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_resources.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDefaultTrue = errors.New("default must be false")
|
||||
ErrDefaultRequired = errors.New("default must be true")
|
||||
ErrRequestNonEmpty = errors.New("request not supported by this resource, must be empty")
|
||||
)
|
||||
|
||||
const (
|
||||
ResourceBlockBandwidthName = "resource/block-bandwidth"
|
||||
ResourceBlockIOPSName = "resource/block-iops"
|
||||
ResourceCPUName = "resource/cpu"
|
||||
ResourceMemoryName = "resource/memory"
|
||||
ResourceNetworkBandwidthName = "resource/network-bandwidth"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddIsolatorValueConstructor(ResourceBlockBandwidthName, NewResourceBlockBandwidth)
|
||||
AddIsolatorValueConstructor(ResourceBlockIOPSName, NewResourceBlockIOPS)
|
||||
AddIsolatorValueConstructor(ResourceCPUName, NewResourceCPU)
|
||||
AddIsolatorValueConstructor(ResourceMemoryName, NewResourceMemory)
|
||||
AddIsolatorValueConstructor(ResourceNetworkBandwidthName, NewResourceNetworkBandwidth)
|
||||
}
|
||||
|
||||
func NewResourceBlockBandwidth() IsolatorValue {
|
||||
return &ResourceBlockBandwidth{}
|
||||
}
|
||||
func NewResourceBlockIOPS() IsolatorValue {
|
||||
return &ResourceBlockIOPS{}
|
||||
}
|
||||
func NewResourceCPU() IsolatorValue {
|
||||
return &ResourceCPU{}
|
||||
}
|
||||
func NewResourceNetworkBandwidth() IsolatorValue {
|
||||
return &ResourceNetworkBandwidth{}
|
||||
}
|
||||
func NewResourceMemory() IsolatorValue {
|
||||
return &ResourceMemory{}
|
||||
}
|
||||
|
||||
type Resource interface {
|
||||
Limit() *resource.Quantity
|
||||
Request() *resource.Quantity
|
||||
Default() bool
|
||||
}
|
||||
|
||||
type ResourceBase struct {
|
||||
val resourceValue
|
||||
}
|
||||
|
||||
type resourceValue struct {
|
||||
Default bool `json:"default"`
|
||||
Request *resource.Quantity `json:"request"`
|
||||
Limit *resource.Quantity `json:"limit"`
|
||||
}
|
||||
|
||||
func (r ResourceBase) Limit() *resource.Quantity {
|
||||
return r.val.Limit
|
||||
}
|
||||
func (r ResourceBase) Request() *resource.Quantity {
|
||||
return r.val.Request
|
||||
}
|
||||
func (r ResourceBase) Default() bool {
|
||||
return r.val.Default
|
||||
}
|
||||
|
||||
func (r *ResourceBase) UnmarshalJSON(b []byte) error {
|
||||
return json.Unmarshal(b, &r.val)
|
||||
}
|
||||
|
||||
func (r ResourceBase) AssertValid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResourceBlockBandwidth struct {
|
||||
ResourceBase
|
||||
}
|
||||
|
||||
func (r ResourceBlockBandwidth) AssertValid() error {
|
||||
if r.Default() != true {
|
||||
return ErrDefaultRequired
|
||||
}
|
||||
if r.Request() != nil {
|
||||
return ErrRequestNonEmpty
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResourceBlockIOPS struct {
|
||||
ResourceBase
|
||||
}
|
||||
|
||||
func (r ResourceBlockIOPS) AssertValid() error {
|
||||
if r.Default() != true {
|
||||
return ErrDefaultRequired
|
||||
}
|
||||
if r.Request() != nil {
|
||||
return ErrRequestNonEmpty
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResourceCPU struct {
|
||||
ResourceBase
|
||||
}
|
||||
|
||||
func (r ResourceCPU) AssertValid() error {
|
||||
if r.Default() != false {
|
||||
return ErrDefaultTrue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResourceMemory struct {
|
||||
ResourceBase
|
||||
}
|
||||
|
||||
func (r ResourceMemory) AssertValid() error {
|
||||
if r.Default() != false {
|
||||
return ErrDefaultTrue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResourceNetworkBandwidth struct {
|
||||
ResourceBase
|
||||
}
|
||||
|
||||
func (r ResourceNetworkBandwidth) AssertValid() error {
|
||||
if r.Default() != true {
|
||||
return ErrDefaultRequired
|
||||
}
|
||||
if r.Request() != nil {
|
||||
return ErrRequestNonEmpty
|
||||
}
|
||||
return nil
|
||||
}
|
236
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_test.go
generated
vendored
Normal file
236
Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_test.go
generated
vendored
Normal file
@ -0,0 +1,236 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsolatorUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
msg string
|
||||
werr bool
|
||||
}{
|
||||
{
|
||||
`{
|
||||
"name": "os/linux/capabilities-retain-set",
|
||||
"value": {"set": ["CAP_KILL"]}
|
||||
}`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "os/linux/capabilities-retain-set",
|
||||
"value": {"set": ["CAP_PONIES"]}
|
||||
}`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "os/linux/capabilities-retain-set",
|
||||
"value": {"set": []}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "os/linux/capabilities-retain-set",
|
||||
"value": {"set": "CAP_PONIES"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/block-bandwidth",
|
||||
"value": {"default": true, "limit": "1G"}
|
||||
}`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/block-bandwidth",
|
||||
"value": {"default": false, "limit": "1G"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/block-bandwidth",
|
||||
"value": {"request": "30G", "limit": "1G"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/block-iops",
|
||||
"value": {"default": true, "limit": "1G"}
|
||||
}`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/block-iops",
|
||||
"value": {"default": false, "limit": "1G"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/block-iops",
|
||||
"value": {"request": "30G", "limit": "1G"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/cpu",
|
||||
"value": {"request": "30", "limit": "1"}
|
||||
}`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/memory",
|
||||
"value": {"request": "1G", "limit": "2Gi"}
|
||||
}`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/memory",
|
||||
"value": {"default": true, "request": "1G", "limit": "2G"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/network-bandwidth",
|
||||
"value": {"default": true, "limit": "1G"}
|
||||
}`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/network-bandwidth",
|
||||
"value": {"default": false, "limit": "1G"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"name": "resource/network-bandwidth",
|
||||
"value": {"request": "30G", "limit": "1G"}
|
||||
}`,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
var ii Isolator
|
||||
err := ii.UnmarshalJSON([]byte(tt.msg))
|
||||
if gerr := (err != nil); gerr != tt.werr {
|
||||
t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsolatorsGetByName(t *testing.T) {
|
||||
ex := `
|
||||
[
|
||||
{
|
||||
"name": "resource/cpu",
|
||||
"value": {"request": "30", "limit": "1"}
|
||||
},
|
||||
{
|
||||
"name": "resource/memory",
|
||||
"value": {"request": "1G", "limit": "2Gi"}
|
||||
},
|
||||
{
|
||||
"name": "os/linux/capabilities-retain-set",
|
||||
"value": {"set": ["CAP_KILL"]}
|
||||
},
|
||||
{
|
||||
"name": "os/linux/capabilities-revoke-set",
|
||||
"value": {"set": ["CAP_KILL"]}
|
||||
}
|
||||
]
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
name ACName
|
||||
wlimit int64
|
||||
wrequest int64
|
||||
wset []LinuxCapability
|
||||
}{
|
||||
{"resource/cpu", 1, 30, nil},
|
||||
{"resource/memory", 2147483648, 1000000000, nil},
|
||||
{"os/linux/capabilities-retain-set", 0, 0, []LinuxCapability{"CAP_KILL"}},
|
||||
{"os/linux/capabilities-revoke-set", 0, 0, []LinuxCapability{"CAP_KILL"}},
|
||||
}
|
||||
|
||||
var is Isolators
|
||||
err := json.Unmarshal([]byte(ex), &is)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(is) < 2 {
|
||||
t.Fatalf("too few items %v", len(is))
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
c := is.GetByName(tt.name)
|
||||
if c == nil {
|
||||
t.Fatalf("can't find item %v in %v items", tt.name, len(is))
|
||||
}
|
||||
switch v := c.Value().(type) {
|
||||
case Resource:
|
||||
var r Resource = v
|
||||
glimit := r.Limit()
|
||||
grequest := r.Request()
|
||||
if glimit.Value() != tt.wlimit || grequest.Value() != tt.wrequest {
|
||||
t.Errorf("#%d: glimit=%v, want %v, grequest=%v, want %v", i, glimit.Value(), tt.wlimit, grequest.Value(), tt.wrequest)
|
||||
}
|
||||
case LinuxCapabilitiesSet:
|
||||
var s LinuxCapabilitiesSet = v
|
||||
if !reflect.DeepEqual(s.Set(), tt.wset) {
|
||||
t.Errorf("#%d: gset=%v, want %v", i, s.Set(), tt.wset)
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unexecpected type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsolatorUnrecognized(t *testing.T) {
|
||||
msg := `
|
||||
[{
|
||||
"name": "resource/network-bandwidth",
|
||||
"value": {"default": true, "limit": "1G"}
|
||||
},
|
||||
{
|
||||
"name": "resource/network-ponies",
|
||||
"value": 0
|
||||
}]`
|
||||
|
||||
ex := Isolators{
|
||||
{Name: "resource/network-ponies"},
|
||||
}
|
||||
|
||||
is := Isolators{}
|
||||
if err := json.Unmarshal([]byte(msg), &is); err != nil {
|
||||
t.Fatalf("failed to unmarshal isolators: %v", err)
|
||||
}
|
||||
|
||||
u := is.Unrecognized()
|
||||
if len(u) != len(ex) {
|
||||
t.Errorf("unrecognized isolator list is wrong len: want %v, got %v", len(ex), len(u))
|
||||
}
|
||||
|
||||
for i, e := range ex {
|
||||
if e.Name != u[i].Name {
|
||||
t.Errorf("unrecognized isolator list mismatch: want %v, got %v", e.Name, u[i].Name)
|
||||
}
|
||||
}
|
||||
}
|
114
Godeps/_workspace/src/github.com/appc/spec/schema/types/labels.go
generated
vendored
Normal file
114
Godeps/_workspace/src/github.com/appc/spec/schema/types/labels.go
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var ValidOSArch = map[string][]string{
|
||||
"linux": {"amd64", "i386", "aarch64", "armv7l", "armv7b"},
|
||||
"freebsd": {"amd64", "i386", "arm"},
|
||||
"darwin": {"x86_64", "i386"},
|
||||
}
|
||||
|
||||
type Labels []Label
|
||||
|
||||
type labels Labels
|
||||
|
||||
type Label struct {
|
||||
Name ACName `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (l Labels) assertValid() error {
|
||||
seen := map[ACName]string{}
|
||||
for _, lbl := range l {
|
||||
if lbl.Name == "name" {
|
||||
return fmt.Errorf(`invalid label name: "name"`)
|
||||
}
|
||||
_, ok := seen[lbl.Name]
|
||||
if ok {
|
||||
return fmt.Errorf(`duplicate labels of name %q`, lbl.Name)
|
||||
}
|
||||
seen[lbl.Name] = lbl.Value
|
||||
}
|
||||
if os, ok := seen["os"]; ok {
|
||||
if validArchs, ok := ValidOSArch[os]; !ok {
|
||||
// Not a whitelisted OS. TODO: how to warn rather than fail?
|
||||
validOses := make([]string, 0, len(ValidOSArch))
|
||||
for validOs := range ValidOSArch {
|
||||
validOses = append(validOses, validOs)
|
||||
}
|
||||
sort.Strings(validOses)
|
||||
return fmt.Errorf(`bad os %#v (must be one of: %v)`, os, validOses)
|
||||
} else {
|
||||
// Whitelisted OS. We check arch here, as arch makes sense only
|
||||
// when os is defined.
|
||||
if arch, ok := seen["arch"]; ok {
|
||||
found := false
|
||||
for _, validArch := range validArchs {
|
||||
if arch == validArch {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf(`bad arch %#v for %v (must be one of: %v)`, arch, os, validArchs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Labels) MarshalJSON() ([]byte, error) {
|
||||
if err := l.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(labels(l))
|
||||
}
|
||||
|
||||
func (l *Labels) UnmarshalJSON(data []byte) error {
|
||||
var jl labels
|
||||
if err := json.Unmarshal(data, &jl); err != nil {
|
||||
return err
|
||||
}
|
||||
nl := Labels(jl)
|
||||
if err := nl.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*l = nl
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves the value of the label by the given name from Labels, if it exists
|
||||
func (l Labels) Get(name string) (val string, ok bool) {
|
||||
for _, lbl := range l {
|
||||
if lbl.Name.String() == name {
|
||||
return lbl.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// ToMap creates a map[ACName]string.
|
||||
func (l Labels) ToMap() map[ACName]string {
|
||||
labelsMap := make(map[ACName]string)
|
||||
for _, lbl := range l {
|
||||
labelsMap[lbl.Name] = lbl.Value
|
||||
}
|
||||
return labelsMap
|
||||
}
|
||||
|
||||
// LabelsFromMap creates Labels from a map[ACName]string
|
||||
func LabelsFromMap(labelsMap map[ACName]string) (Labels, error) {
|
||||
labels := Labels{}
|
||||
for n, v := range labelsMap {
|
||||
labels = append(labels, Label{Name: n, Value: v})
|
||||
}
|
||||
if err := labels.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return labels, nil
|
||||
}
|
78
Godeps/_workspace/src/github.com/appc/spec/schema/types/labels_test.go
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/appc/spec/schema/types/labels_test.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
errPrefix string
|
||||
}{
|
||||
{
|
||||
`[{"name": "os", "value": "linux"}, {"name": "arch", "value": "amd64"}]`,
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "linux"}, {"name": "arch", "value": "aarch64"}]`,
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "linux"}, {"name": "arch", "value": "armv7l"}]`,
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "linux"}, {"name": "arch", "value": "armv7b"}]`,
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "freebsd"}, {"name": "arch", "value": "amd64"}]`,
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "OS/360"}, {"name": "arch", "value": "S/360"}]`,
|
||||
`bad os "OS/360"`,
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "freebsd"}, {"name": "arch", "value": "armv7b"}]`,
|
||||
`bad arch "armv7b" for freebsd`,
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "linux"}, {"name": "arch", "value": "arm"}]`,
|
||||
`bad arch "arm" for linux`,
|
||||
},
|
||||
{
|
||||
`[{"name": "name"}]`,
|
||||
`invalid label name: "name"`,
|
||||
},
|
||||
{
|
||||
`[{"name": "os", "value": "linux"}, {"name": "os", "value": "freebsd"}]`,
|
||||
`duplicate labels of name "os"`,
|
||||
},
|
||||
{
|
||||
`[{"name": "arch", "value": "amd64"}, {"name": "os", "value": "freebsd"}, {"name": "arch", "value": "x86_64"}]`,
|
||||
`duplicate labels of name "arch"`,
|
||||
},
|
||||
{
|
||||
`[]`,
|
||||
"",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
var l Labels
|
||||
if err := json.Unmarshal([]byte(tt.in), &l); err != nil {
|
||||
if tt.errPrefix == "" {
|
||||
t.Errorf("#%d: got err=%v, expected no error", i, err)
|
||||
} else if !strings.HasPrefix(err.Error(), tt.errPrefix) {
|
||||
t.Errorf("#%d: got err=%v, expected prefix %#v", i, err, tt.errPrefix)
|
||||
}
|
||||
} else {
|
||||
t.Log(l)
|
||||
if tt.errPrefix != "" {
|
||||
t.Errorf("#%d: got no err, expected prefix %#v", i, tt.errPrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint.go
generated
vendored
Normal file
72
Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MountPoint struct {
|
||||
Name ACName `json:"name"`
|
||||
Path string `json:"path"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
}
|
||||
|
||||
func (mount MountPoint) assertValid() error {
|
||||
if mount.Name.Empty() {
|
||||
return errors.New("name must be set")
|
||||
}
|
||||
if len(mount.Path) == 0 {
|
||||
return errors.New("path must be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountPointFromString takes a command line mountpoint parameter and returns a mountpoint
|
||||
//
|
||||
// It is useful for actool patch-manifest --mounts
|
||||
//
|
||||
// Example mountpoint parameters:
|
||||
// database,path=/tmp,readOnly=true
|
||||
func MountPointFromString(mp string) (*MountPoint, error) {
|
||||
var mount MountPoint
|
||||
|
||||
mp = "name=" + mp
|
||||
v, err := url.ParseQuery(strings.Replace(mp, ",", "&", -1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, val := range v {
|
||||
if len(val) > 1 {
|
||||
return nil, fmt.Errorf("label %s with multiple values %q", key, val)
|
||||
}
|
||||
|
||||
// TOOD(philips): make this less hardcoded
|
||||
switch key {
|
||||
case "name":
|
||||
acn, err := NewACName(val[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mount.Name = *acn
|
||||
case "path":
|
||||
mount.Path = val[0]
|
||||
case "readOnly":
|
||||
ro, err := strconv.ParseBool(val[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mount.ReadOnly = ro
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown mountpoint parameter %q", key)
|
||||
}
|
||||
}
|
||||
err = mount.assertValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mount, nil
|
||||
}
|
69
Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint_test.go
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint_test.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMountPointFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
s string
|
||||
mount MountPoint
|
||||
}{
|
||||
{
|
||||
"foobar,path=/tmp",
|
||||
MountPoint{
|
||||
Name: "foobar",
|
||||
Path: "/tmp",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"foobar,path=/tmp,readOnly=false",
|
||||
MountPoint{
|
||||
Name: "foobar",
|
||||
Path: "/tmp",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"foobar,path=/tmp,readOnly=true",
|
||||
MountPoint{
|
||||
Name: "foobar",
|
||||
Path: "/tmp",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
mount, err := MountPointFromString(tt.s)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: got err=%v, want nil", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(*mount, tt.mount) {
|
||||
t.Errorf("#%d: mount=%v, want %v", i, *mount, tt.mount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountPointFromStringBad(t *testing.T) {
|
||||
tests := []string{
|
||||
"#foobar,path=/tmp",
|
||||
"foobar,path=/tmp,readOnly=true,asdf=asdf",
|
||||
"foobar,path=/tmp,readOnly=maybe",
|
||||
"foobar,path=/tmp,readOnly=",
|
||||
"foobar,path=",
|
||||
"foobar",
|
||||
"",
|
||||
",path=/",
|
||||
}
|
||||
for i, in := range tests {
|
||||
l, err := MountPointFromString(in)
|
||||
if l != nil {
|
||||
t.Errorf("#%d: got l=%v, want nil", i, l)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("#%d: got err=nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
13
Godeps/_workspace/src/github.com/appc/spec/schema/types/port.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/appc/spec/schema/types/port.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package types
|
||||
|
||||
type Port struct {
|
||||
Name ACName `json:"name"`
|
||||
Protocol string `json:"protocol"`
|
||||
Port uint `json:"port"`
|
||||
SocketActivated bool `json:"socketActivated"`
|
||||
}
|
||||
|
||||
type ExposedPort struct {
|
||||
Name ACName `json:"name"`
|
||||
HostPort uint `json:"hostPort"`
|
||||
}
|
62
Godeps/_workspace/src/github.com/appc/spec/schema/types/semver.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/appc/spec/schema/types/semver.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoZeroSemVer = ACVersionError("SemVer cannot be zero")
|
||||
ErrBadSemVer = ACVersionError("SemVer is bad")
|
||||
)
|
||||
|
||||
// SemVer implements the Unmarshaler interface to define a field that must be
|
||||
// a semantic version string
|
||||
// TODO(jonboulle): extend upstream instead of wrapping?
|
||||
type SemVer semver.Version
|
||||
|
||||
// NewSemVer generates a new SemVer from a string. If the given string does
|
||||
// not represent a valid SemVer, nil and an error are returned.
|
||||
func NewSemVer(s string) (*SemVer, error) {
|
||||
nsv, err := semver.NewVersion(s)
|
||||
if err != nil {
|
||||
return nil, ErrBadSemVer
|
||||
}
|
||||
v := SemVer(*nsv)
|
||||
if v.Empty() {
|
||||
return nil, ErrNoZeroSemVer
|
||||
}
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func (sv SemVer) String() string {
|
||||
s := semver.Version(sv)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (sv SemVer) Empty() bool {
|
||||
return semver.Version(sv) == semver.Version{}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (sv *SemVer) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := NewSemVer(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*sv = *v
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface
|
||||
func (sv SemVer) MarshalJSON() ([]byte, error) {
|
||||
if sv.Empty() {
|
||||
return nil, ErrNoZeroSemVer
|
||||
}
|
||||
return json.Marshal(sv.String())
|
||||
}
|
115
Godeps/_workspace/src/github.com/appc/spec/schema/types/semver_test.go
generated
vendored
Normal file
115
Godeps/_workspace/src/github.com/appc/spec/schema/types/semver_test.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
func TestMarshalSemver(t *testing.T) {
|
||||
tests := []struct {
|
||||
sv SemVer
|
||||
|
||||
wd []byte
|
||||
}{
|
||||
{
|
||||
SemVer(semver.Version{Major: 1}),
|
||||
|
||||
[]byte(`"1.0.0"`),
|
||||
},
|
||||
{
|
||||
SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1}),
|
||||
|
||||
[]byte(`"3.2.1"`),
|
||||
},
|
||||
{
|
||||
SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1, PreRelease: "foo"}),
|
||||
|
||||
[]byte(`"3.2.1-foo"`),
|
||||
},
|
||||
{
|
||||
SemVer(semver.Version{Major: 1, Minor: 2, Patch: 3, PreRelease: "alpha", Metadata: "git"}),
|
||||
|
||||
[]byte(`"1.2.3-alpha+git"`),
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
d, err := json.Marshal(tt.sv)
|
||||
if !reflect.DeepEqual(d, tt.wd) {
|
||||
t.Errorf("#%d: d=%v, want %v", i, string(d), string(tt.wd))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err=%v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalSemver(t *testing.T) {
|
||||
tests := []struct {
|
||||
d []byte
|
||||
|
||||
wsv SemVer
|
||||
werr bool
|
||||
}{
|
||||
{
|
||||
[]byte(`"1.0.0"`),
|
||||
|
||||
SemVer(semver.Version{Major: 1}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]byte(`"3.2.1"`),
|
||||
SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1}),
|
||||
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]byte(`"3.2.1-foo"`),
|
||||
|
||||
SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1, PreRelease: "foo"}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]byte(`"1.2.3-alpha+git"`),
|
||||
|
||||
SemVer(semver.Version{Major: 1, Minor: 2, Patch: 3, PreRelease: "alpha", Metadata: "git"}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]byte(`"1"`),
|
||||
|
||||
SemVer{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]byte(`"1.2.3.4"`),
|
||||
|
||||
SemVer{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]byte(`1.2.3`),
|
||||
|
||||
SemVer{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]byte(`"v1.2.3"`),
|
||||
|
||||
SemVer{},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
var sv SemVer
|
||||
err := json.Unmarshal(tt.d, &sv)
|
||||
if !reflect.DeepEqual(sv, tt.wsv) {
|
||||
t.Errorf("#%d: semver=%#v, want %#v", i, sv, tt.wsv)
|
||||
}
|
||||
if gerr := (err != nil); gerr != tt.werr {
|
||||
t.Errorf("#%d: err==%v, want errstate %t", i, err, tt.werr)
|
||||
}
|
||||
}
|
||||
}
|
57
Godeps/_workspace/src/github.com/appc/spec/schema/types/url.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/appc/spec/schema/types/url.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// URL wraps url.URL to marshal/unmarshal to/from JSON strings and enforce
|
||||
// that the scheme is HTTP/HTTPS only
|
||||
type URL url.URL
|
||||
|
||||
func NewURL(s string) (*URL, error) {
|
||||
uu, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad URL: %v", err)
|
||||
}
|
||||
nu := URL(*uu)
|
||||
if err := nu.assertValidScheme(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nu, nil
|
||||
}
|
||||
|
||||
func (u URL) String() string {
|
||||
uu := url.URL(u)
|
||||
return uu.String()
|
||||
}
|
||||
|
||||
func (u URL) assertValidScheme() error {
|
||||
switch u.Scheme {
|
||||
case "http", "https":
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("bad URL scheme, must be http/https")
|
||||
}
|
||||
}
|
||||
|
||||
func (u *URL) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
nu, err := NewURL(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*u = *nu
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u URL) MarshalJSON() ([]byte, error) {
|
||||
if err := u.assertValidScheme(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(u.String())
|
||||
}
|
123
Godeps/_workspace/src/github.com/appc/spec/schema/types/url_test.go
generated
vendored
Normal file
123
Godeps/_workspace/src/github.com/appc/spec/schema/types/url_test.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mustParseURL(t *testing.T, s string) url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing URL: %v", err)
|
||||
}
|
||||
return *u
|
||||
}
|
||||
|
||||
func TestMarshalURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
u url.URL
|
||||
|
||||
w string
|
||||
}{
|
||||
{
|
||||
mustParseURL(t, "http://foo.com"),
|
||||
|
||||
`"http://foo.com"`,
|
||||
},
|
||||
{
|
||||
mustParseURL(t, "http://foo.com/huh/what?is=this"),
|
||||
|
||||
`"http://foo.com/huh/what?is=this"`,
|
||||
},
|
||||
{
|
||||
mustParseURL(t, "https://example.com/bar"),
|
||||
|
||||
`"https://example.com/bar"`,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
u := URL(tt.u)
|
||||
b, err := json.Marshal(u)
|
||||
if g := string(b); g != tt.w {
|
||||
t.Errorf("#%d: got %q, want %q", i, g, tt.w)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err=%v, want nil", i, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalURLBad(t *testing.T) {
|
||||
tests := []url.URL{
|
||||
mustParseURL(t, "ftp://foo.com"),
|
||||
mustParseURL(t, "unix:///hello"),
|
||||
}
|
||||
for i, tt := range tests {
|
||||
u := URL(tt)
|
||||
b, err := json.Marshal(u)
|
||||
if b != nil {
|
||||
t.Errorf("#%d: got %v, want nil", i, b)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("#%d: got unexpected err=nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
|
||||
w URL
|
||||
}{
|
||||
{
|
||||
`"http://foo.com"`,
|
||||
|
||||
URL(mustParseURL(t, "http://foo.com")),
|
||||
},
|
||||
{
|
||||
`"http://yis.com/hello?goodbye=yes"`,
|
||||
|
||||
URL(mustParseURL(t, "http://yis.com/hello?goodbye=yes")),
|
||||
},
|
||||
{
|
||||
`"https://ohai.net"`,
|
||||
|
||||
URL(mustParseURL(t, "https://ohai.net")),
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
var g URL
|
||||
err := json.Unmarshal([]byte(tt.in), &g)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: want err=nil, got %v", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(g, tt.w) {
|
||||
t.Errorf("#%d: got url=%v, want %v", i, g, tt.w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalURLBad(t *testing.T) {
|
||||
var empty = URL{}
|
||||
tests := []string{
|
||||
"badjson",
|
||||
"http://google.com",
|
||||
`"ftp://example.com"`,
|
||||
`"unix://file.net"`,
|
||||
`"not a url"`,
|
||||
}
|
||||
for i, tt := range tests {
|
||||
var g URL
|
||||
err := json.Unmarshal([]byte(tt), &g)
|
||||
if err == nil {
|
||||
t.Errorf("#%d: want err, got nil", i)
|
||||
}
|
||||
if !reflect.DeepEqual(g, empty) {
|
||||
t.Errorf("#%d: got %v, want %v", i, g, empty)
|
||||
}
|
||||
}
|
||||
}
|
78
Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid.go
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoEmptyUUID = errors.New("UUID cannot be empty")
|
||||
)
|
||||
|
||||
// UUID encodes an RFC4122-compliant UUID, marshaled to/from a string
|
||||
// TODO(jonboulle): vendor a package for this?
|
||||
// TODO(jonboulle): consider more flexibility in input string formats.
|
||||
// Right now, we only accept:
|
||||
// "6733C088-A507-4694-AABF-EDBE4FC5266F"
|
||||
// "6733C088A5074694AABFEDBE4FC5266F"
|
||||
type UUID [16]byte
|
||||
|
||||
func (u UUID) String() string {
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:16])
|
||||
}
|
||||
|
||||
func (u *UUID) Set(s string) error {
|
||||
nu, err := NewUUID(s)
|
||||
if err == nil {
|
||||
*u = *nu
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewUUID generates a new UUID from the given string. If the string does not
|
||||
// represent a valid UUID, nil and an error are returned.
|
||||
func NewUUID(s string) (*UUID, error) {
|
||||
s = strings.Replace(s, "-", "", -1)
|
||||
if len(s) != 32 {
|
||||
return nil, errors.New("bad UUID length != 32")
|
||||
}
|
||||
dec, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var u UUID
|
||||
for i, b := range dec {
|
||||
u[i] = b
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (u UUID) Empty() bool {
|
||||
return reflect.DeepEqual(u, UUID{})
|
||||
}
|
||||
|
||||
func (u *UUID) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
uu, err := NewUUID(s)
|
||||
if uu.Empty() {
|
||||
return ErrNoEmptyUUID
|
||||
}
|
||||
if err == nil {
|
||||
*u = *uu
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (u UUID) MarshalJSON() ([]byte, error) {
|
||||
if u.Empty() {
|
||||
return nil, ErrNoEmptyUUID
|
||||
}
|
||||
return json.Marshal(u.String())
|
||||
}
|
59
Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid_test.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid_test.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewUUID(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
ws string
|
||||
}{
|
||||
{
|
||||
"6733C088-A507-4694-AABF-EDBE4FC5266F",
|
||||
|
||||
"6733c088-a507-4694-aabf-edbe4fc5266f",
|
||||
},
|
||||
{
|
||||
"6733C088A5074694AABFEDBE4FC5266F",
|
||||
|
||||
"6733c088-a507-4694-aabf-edbe4fc5266f",
|
||||
},
|
||||
{
|
||||
"0aaf0a79-1a39-4d59-abbf-1bebca8209d2",
|
||||
|
||||
"0aaf0a79-1a39-4d59-abbf-1bebca8209d2",
|
||||
},
|
||||
{
|
||||
"0aaf0a791a394d59abbf1bebca8209d2",
|
||||
|
||||
"0aaf0a79-1a39-4d59-abbf-1bebca8209d2",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
gu, err := NewUUID(tt.in)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err=%v, want %v", i, err, nil)
|
||||
}
|
||||
if gs := gu.String(); gs != tt.ws {
|
||||
t.Errorf("#%d: String()=%v, want %v", i, gs, tt.ws)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUUIDBad(t *testing.T) {
|
||||
tests := []string{
|
||||
"asdf",
|
||||
"0AAF0A79-1A39-4D59-ABBF-1BEBCA8209D2ABC",
|
||||
"",
|
||||
}
|
||||
for i, tt := range tests {
|
||||
g, err := NewUUID(tt)
|
||||
if err == nil {
|
||||
t.Errorf("#%d: err=nil, want non-nil", i)
|
||||
}
|
||||
if g != nil {
|
||||
t.Errorf("#%d: err=%v, want %v", i, g, nil)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
124
Godeps/_workspace/src/github.com/appc/spec/schema/types/volume.go
generated
vendored
Normal file
124
Godeps/_workspace/src/github.com/appc/spec/schema/types/volume.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Volume encapsulates a volume which should be mounted into the filesystem
|
||||
// of all apps in a PodManifest
|
||||
type Volume struct {
|
||||
Name ACName `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// currently used only by "host"
|
||||
// TODO(jonboulle): factor out?
|
||||
Source string `json:"source,omitempty"`
|
||||
ReadOnly *bool `json:"readOnly,omitempty"`
|
||||
}
|
||||
|
||||
type volume Volume
|
||||
|
||||
func (v Volume) assertValid() error {
|
||||
if v.Name.Empty() {
|
||||
return errors.New("name must be set")
|
||||
}
|
||||
|
||||
switch v.Kind {
|
||||
case "empty":
|
||||
if v.Source != "" {
|
||||
return errors.New("source for empty volume must be empty")
|
||||
}
|
||||
return nil
|
||||
case "host":
|
||||
if v.Source == "" {
|
||||
return errors.New("source for host volume cannot be empty")
|
||||
}
|
||||
if !filepath.IsAbs(v.Source) {
|
||||
return errors.New("source for host volume must be absolute path")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New(`unrecognized volume kind: should be one of "empty", "host"`)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Volume) UnmarshalJSON(data []byte) error {
|
||||
var vv volume
|
||||
if err := json.Unmarshal(data, &vv); err != nil {
|
||||
return err
|
||||
}
|
||||
nv := Volume(vv)
|
||||
if err := nv.assertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
*v = nv
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Volume) MarshalJSON() ([]byte, error) {
|
||||
if err := v.assertValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(volume(v))
|
||||
}
|
||||
|
||||
func (v Volume) String() string {
|
||||
s := fmt.Sprintf("%s,kind=%s,readOnly=%t", v.Name, v.Kind, *v.ReadOnly)
|
||||
if v.Source != "" {
|
||||
s = s + fmt.Sprintf("source=%s", v.Source)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// VolumeFromString takes a command line volume parameter and returns a volume
|
||||
//
|
||||
// Example volume parameters:
|
||||
// database,kind=host,source=/tmp,readOnly=true
|
||||
func VolumeFromString(vp string) (*Volume, error) {
|
||||
var vol Volume
|
||||
|
||||
vp = "name=" + vp
|
||||
v, err := url.ParseQuery(strings.Replace(vp, ",", "&", -1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, val := range v {
|
||||
if len(val) > 1 {
|
||||
return nil, fmt.Errorf("label %s with multiple values %q", key, val)
|
||||
}
|
||||
|
||||
// TOOD(philips): make this less hardcoded
|
||||
switch key {
|
||||
case "name":
|
||||
acn, err := NewACName(val[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vol.Name = *acn
|
||||
case "kind":
|
||||
vol.Kind = val[0]
|
||||
case "source":
|
||||
vol.Source = val[0]
|
||||
case "readOnly":
|
||||
ro, err := strconv.ParseBool(val[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vol.ReadOnly = &ro
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown volume parameter %q", key)
|
||||
}
|
||||
}
|
||||
err = vol.assertValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vol, nil
|
||||
}
|
85
Godeps/_workspace/src/github.com/appc/spec/schema/types/volume_test.go
generated
vendored
Normal file
85
Godeps/_workspace/src/github.com/appc/spec/schema/types/volume_test.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVolumeFromString(t *testing.T) {
|
||||
trueVar := true
|
||||
falseVar := false
|
||||
tests := []struct {
|
||||
s string
|
||||
v Volume
|
||||
}{
|
||||
{
|
||||
"foobar,kind=host,source=/tmp",
|
||||
Volume{
|
||||
Name: "foobar",
|
||||
Kind: "host",
|
||||
Source: "/tmp",
|
||||
ReadOnly: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"foobar,kind=host,source=/tmp,readOnly=false",
|
||||
Volume{
|
||||
Name: "foobar",
|
||||
Kind: "host",
|
||||
Source: "/tmp",
|
||||
ReadOnly: &falseVar,
|
||||
},
|
||||
},
|
||||
{
|
||||
"foobar,kind=host,source=/tmp,readOnly=true",
|
||||
Volume{
|
||||
Name: "foobar",
|
||||
Kind: "host",
|
||||
Source: "/tmp",
|
||||
ReadOnly: &trueVar,
|
||||
},
|
||||
},
|
||||
{
|
||||
"foobar,kind=empty",
|
||||
Volume{
|
||||
Name: "foobar",
|
||||
Kind: "empty",
|
||||
ReadOnly: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"foobar,kind=empty,readOnly=true",
|
||||
Volume{
|
||||
Name: "foobar",
|
||||
Kind: "empty",
|
||||
ReadOnly: &trueVar,
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
v, err := VolumeFromString(tt.s)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: got err=%v, want nil", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(*v, tt.v) {
|
||||
t.Errorf("#%d: v=%v, want %v", i, *v, tt.v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeFromStringBad(t *testing.T) {
|
||||
tests := []string{
|
||||
"#foobar,kind=host,source=/tmp",
|
||||
"foobar,kind=host,source=/tmp,readOnly=true,asdf=asdf",
|
||||
"foobar,kind=empty,source=/tmp",
|
||||
}
|
||||
for i, in := range tests {
|
||||
l, err := VolumeFromString(in)
|
||||
if l != nil {
|
||||
t.Errorf("#%d: got l=%v, want nil", i, l)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("#%d: got err=nil, want non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
25
Godeps/_workspace/src/github.com/appc/spec/schema/version.go
generated
vendored
Normal file
25
Godeps/_workspace/src/github.com/appc/spec/schema/version.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"github.com/appc/spec/schema/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// version represents the canonical version of the appc spec and tooling.
|
||||
// For now, the schema and tooling is coupled with the spec itself, so
|
||||
// this must be kept in sync with the VERSION file in the root of the repo.
|
||||
version string = "0.5.1+git"
|
||||
)
|
||||
|
||||
var (
|
||||
// AppContainerVersion is the SemVer representation of version
|
||||
AppContainerVersion types.SemVer
|
||||
)
|
||||
|
||||
func init() {
|
||||
v, err := types.NewSemVer(version)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
AppContainerVersion = *v
|
||||
}
|
1
Godeps/_workspace/src/github.com/camlistore/lock/.gitignore
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/camlistore/lock/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
*~
|
202
Godeps/_workspace/src/github.com/camlistore/lock/COPYING
generated
vendored
Normal file
202
Godeps/_workspace/src/github.com/camlistore/lock/COPYING
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
3
Godeps/_workspace/src/github.com/camlistore/lock/README.txt
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/camlistore/lock/README.txt
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
File locking library.
|
||||
|
||||
See http://godoc.org/github.com/camlistore/lock
|
158
Godeps/_workspace/src/github.com/camlistore/lock/lock.go
generated
vendored
Normal file
158
Godeps/_workspace/src/github.com/camlistore/lock/lock.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Lock locks the given file, creating the file if necessary. If the
|
||||
// file already exists, it must have zero size or an error is returned.
|
||||
// The lock is an exclusive lock (a write lock), but locked files
|
||||
// should neither be read from nor written to. Such files should have
|
||||
// zero size and only exist to co-ordinate ownership across processes.
|
||||
//
|
||||
// A nil Closer is returned if an error occurred. Otherwise, close that
|
||||
// Closer to release the lock.
|
||||
//
|
||||
// On Linux, FreeBSD and OSX, a lock has the same semantics as fcntl(2)'s
|
||||
// advisory locks. In particular, closing any other file descriptor for the
|
||||
// same file will release the lock prematurely.
|
||||
//
|
||||
// Attempting to lock a file that is already locked by the current process
|
||||
// has undefined behavior.
|
||||
//
|
||||
// On other operating systems, lock will fallback to using the presence and
|
||||
// content of a file named name + '.lock' to implement locking behavior.
|
||||
func Lock(name string) (io.Closer, error) {
|
||||
return lockFn(name)
|
||||
}
|
||||
|
||||
var lockFn = lockPortable
|
||||
|
||||
// Portable version not using fcntl. Doesn't handle crashes as gracefully,
|
||||
// since it can leave stale lock files.
|
||||
// TODO: write pid of owner to lock file and on race see if pid is
|
||||
// still alive?
|
||||
func lockPortable(name string) (io.Closer, error) {
|
||||
absName, err := filepath.Abs(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't Lock file %q: can't find abs path: %v", name, err)
|
||||
}
|
||||
fi, err := os.Stat(absName)
|
||||
if err == nil && fi.Size() > 0 {
|
||||
if isStaleLock(absName) {
|
||||
os.Remove(absName)
|
||||
} else {
|
||||
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
|
||||
}
|
||||
}
|
||||
f, err := os.OpenFile(absName, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lock file %s %v", absName, err)
|
||||
}
|
||||
if err := json.NewEncoder(f).Encode(&pidLockMeta{OwnerPID: os.Getpid()}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lockCloser{f: f, abs: absName}, nil
|
||||
}
|
||||
|
||||
type pidLockMeta struct {
|
||||
OwnerPID int
|
||||
}
|
||||
|
||||
func isStaleLock(path string) bool {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
var meta pidLockMeta
|
||||
if json.NewDecoder(f).Decode(&meta) != nil {
|
||||
return false
|
||||
}
|
||||
if meta.OwnerPID == 0 {
|
||||
return false
|
||||
}
|
||||
p, err := os.FindProcess(meta.OwnerPID)
|
||||
if err != nil {
|
||||
// e.g. on Windows
|
||||
return true
|
||||
}
|
||||
// On unix, os.FindProcess always is true, so we have to send
|
||||
// it a signal to see if it's alive.
|
||||
if signalZero != nil {
|
||||
if p.Signal(signalZero) != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var signalZero os.Signal // nil or set by lock_sigzero.go
|
||||
|
||||
type lockCloser struct {
|
||||
f *os.File
|
||||
abs string
|
||||
once sync.Once
|
||||
err error
|
||||
}
|
||||
|
||||
func (lc *lockCloser) Close() error {
|
||||
lc.once.Do(lc.close)
|
||||
return lc.err
|
||||
}
|
||||
|
||||
func (lc *lockCloser) close() {
|
||||
if err := lc.f.Close(); err != nil {
|
||||
lc.err = err
|
||||
}
|
||||
if err := os.Remove(lc.abs); err != nil {
|
||||
lc.err = err
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
lockmu sync.Mutex
|
||||
locked = map[string]bool{} // abs path -> true
|
||||
)
|
||||
|
||||
// unlocker is used by the darwin and linux implementations with fcntl
|
||||
// advisory locks.
|
||||
type unlocker struct {
|
||||
f *os.File
|
||||
abs string
|
||||
}
|
||||
|
||||
func (u *unlocker) Close() error {
|
||||
lockmu.Lock()
|
||||
// Remove is not necessary but it's nice for us to clean up.
|
||||
// If we do do this, though, it needs to be before the
|
||||
// u.f.Close below.
|
||||
os.Remove(u.abs)
|
||||
if err := u.f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(locked, u.abs)
|
||||
lockmu.Unlock()
|
||||
return nil
|
||||
}
|
32
Godeps/_workspace/src/github.com/camlistore/lock/lock_appengine.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/camlistore/lock/lock_appengine.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// +build appengine
|
||||
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lockFn = lockAppEngine
|
||||
}
|
||||
|
||||
func lockAppEngine(name string) (io.Closer, error) {
|
||||
return nil, errors.New("Lock not available on App Engine")
|
||||
}
|
80
Godeps/_workspace/src/github.com/camlistore/lock/lock_darwin_amd64.go
generated
vendored
Normal file
80
Godeps/_workspace/src/github.com/camlistore/lock/lock_darwin_amd64.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
// +build darwin,amd64
|
||||
// +build !appengine
|
||||
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lockFn = lockFcntl
|
||||
}
|
||||
|
||||
func lockFcntl(name string) (io.Closer, error) {
|
||||
abs, err := filepath.Abs(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lockmu.Lock()
|
||||
if locked[abs] {
|
||||
lockmu.Unlock()
|
||||
return nil, fmt.Errorf("file %q already locked", abs)
|
||||
}
|
||||
locked[abs] = true
|
||||
lockmu.Unlock()
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err == nil && fi.Size() > 0 {
|
||||
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
|
||||
}
|
||||
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Lock Create of %s (abs: %s) failed: %v", name, abs, err)
|
||||
}
|
||||
|
||||
// This type matches C's "struct flock" defined in /usr/include/sys/fcntl.h.
|
||||
// TODO: move this into the standard syscall package.
|
||||
k := struct {
|
||||
Start uint64 // sizeof(off_t): 8
|
||||
Len uint64 // sizeof(off_t): 8
|
||||
Pid uint32 // sizeof(pid_t): 4
|
||||
Type uint16 // sizeof(short): 2
|
||||
Whence uint16 // sizeof(short): 2
|
||||
}{
|
||||
Type: syscall.F_WRLCK,
|
||||
Whence: uint16(os.SEEK_SET),
|
||||
Start: 0,
|
||||
Len: 0, // 0 means to lock the entire file.
|
||||
Pid: uint32(os.Getpid()),
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k)))
|
||||
if errno != 0 {
|
||||
f.Close()
|
||||
return nil, errno
|
||||
}
|
||||
return &unlocker{f, abs}, nil
|
||||
}
|
79
Godeps/_workspace/src/github.com/camlistore/lock/lock_freebsd.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/camlistore/lock/lock_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lockFn = lockFcntl
|
||||
}
|
||||
|
||||
func lockFcntl(name string) (io.Closer, error) {
|
||||
abs, err := filepath.Abs(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lockmu.Lock()
|
||||
if locked[abs] {
|
||||
lockmu.Unlock()
|
||||
return nil, fmt.Errorf("file %q already locked", abs)
|
||||
}
|
||||
locked[abs] = true
|
||||
lockmu.Unlock()
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err == nil && fi.Size() > 0 {
|
||||
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
|
||||
}
|
||||
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This type matches C's "struct flock" defined in /usr/include/fcntl.h.
|
||||
// TODO: move this into the standard syscall package.
|
||||
k := struct {
|
||||
Start int64 /* off_t starting offset */
|
||||
Len int64 /* off_t len = 0 means until end of file */
|
||||
Pid int32 /* pid_t lock owner */
|
||||
Type int16 /* short lock type: read/write, etc. */
|
||||
Whence int16 /* short type of l_start */
|
||||
Sysid int32 /* int remote system id or zero for local */
|
||||
}{
|
||||
Start: 0,
|
||||
Len: 0, // 0 means to lock the entire file.
|
||||
Pid: int32(os.Getpid()),
|
||||
Type: syscall.F_WRLCK,
|
||||
Whence: int16(os.SEEK_SET),
|
||||
Sysid: 0,
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k)))
|
||||
if errno != 0 {
|
||||
f.Close()
|
||||
return nil, errno
|
||||
}
|
||||
return &unlocker{f, abs}, nil
|
||||
}
|
80
Godeps/_workspace/src/github.com/camlistore/lock/lock_linux_amd64.go
generated
vendored
Normal file
80
Godeps/_workspace/src/github.com/camlistore/lock/lock_linux_amd64.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
// +build linux,amd64
|
||||
// +build !appengine
|
||||
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lockFn = lockFcntl
|
||||
}
|
||||
|
||||
func lockFcntl(name string) (io.Closer, error) {
|
||||
abs, err := filepath.Abs(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lockmu.Lock()
|
||||
if locked[abs] {
|
||||
lockmu.Unlock()
|
||||
return nil, fmt.Errorf("file %q already locked", abs)
|
||||
}
|
||||
locked[abs] = true
|
||||
lockmu.Unlock()
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err == nil && fi.Size() > 0 {
|
||||
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
|
||||
}
|
||||
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This type matches C's "struct flock" defined in /usr/include/bits/fcntl.h.
|
||||
// TODO: move this into the standard syscall package.
|
||||
k := struct {
|
||||
Type uint32
|
||||
Whence uint32
|
||||
Start uint64
|
||||
Len uint64
|
||||
Pid uint32
|
||||
}{
|
||||
Type: syscall.F_WRLCK,
|
||||
Whence: uint32(os.SEEK_SET),
|
||||
Start: 0,
|
||||
Len: 0, // 0 means to lock the entire file.
|
||||
Pid: uint32(os.Getpid()),
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k)))
|
||||
if errno != 0 {
|
||||
f.Close()
|
||||
return nil, errno
|
||||
}
|
||||
return &unlocker{f, abs}, nil
|
||||
}
|
81
Godeps/_workspace/src/github.com/camlistore/lock/lock_linux_arm.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/camlistore/lock/lock_linux_arm.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
// +build linux,arm
|
||||
// +build !appengine
|
||||
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lockFn = lockFcntl
|
||||
}
|
||||
|
||||
func lockFcntl(name string) (io.Closer, error) {
|
||||
abs, err := filepath.Abs(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lockmu.Lock()
|
||||
if locked[abs] {
|
||||
lockmu.Unlock()
|
||||
return nil, fmt.Errorf("file %q already locked", abs)
|
||||
}
|
||||
locked[abs] = true
|
||||
lockmu.Unlock()
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err == nil && fi.Size() > 0 {
|
||||
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
|
||||
}
|
||||
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This type matches C's "struct flock" defined in /usr/include/bits/fcntl.h.
|
||||
// TODO: move this into the standard syscall package.
|
||||
k := struct {
|
||||
Type uint16
|
||||
Whence uint16
|
||||
Start uint32
|
||||
Len uint32
|
||||
Pid uint32
|
||||
}{
|
||||
Type: syscall.F_WRLCK,
|
||||
Whence: uint16(os.SEEK_SET),
|
||||
Start: 0,
|
||||
Len: 0, // 0 means to lock the entire file.
|
||||
Pid: uint32(os.Getpid()),
|
||||
}
|
||||
|
||||
const F_SETLK = 6 // actual value. syscall package is wrong: golang.org/issue/7059
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(F_SETLK), uintptr(unsafe.Pointer(&k)))
|
||||
if errno != 0 {
|
||||
f.Close()
|
||||
return nil, errno
|
||||
}
|
||||
return &unlocker{f, abs}, nil
|
||||
}
|
55
Godeps/_workspace/src/github.com/camlistore/lock/lock_plan9.go
generated
vendored
Normal file
55
Godeps/_workspace/src/github.com/camlistore/lock/lock_plan9.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lockFn = lockPlan9
|
||||
}
|
||||
|
||||
func lockPlan9(name string) (io.Closer, error) {
|
||||
var f *os.File
|
||||
abs, err := filepath.Abs(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lockmu.Lock()
|
||||
if locked[abs] {
|
||||
lockmu.Unlock()
|
||||
return nil, fmt.Errorf("file %q already locked", abs)
|
||||
}
|
||||
locked[abs] = true
|
||||
lockmu.Unlock()
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err == nil && fi.Size() > 0 {
|
||||
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
|
||||
}
|
||||
|
||||
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Lock Create of %s (abs: %s) failed: %v", name, abs, err)
|
||||
}
|
||||
|
||||
return &unlocker{f, abs}, nil
|
||||
}
|
26
Godeps/_workspace/src/github.com/camlistore/lock/lock_sigzero.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/camlistore/lock/lock_sigzero.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
// +build !appengine
|
||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
||||
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import "syscall"
|
||||
|
||||
func init() {
|
||||
signalZero = syscall.Signal(0)
|
||||
}
|
131
Godeps/_workspace/src/github.com/camlistore/lock/lock_test.go
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/camlistore/lock/lock_test.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
Copyright 2013 The Go Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLock(t *testing.T) {
|
||||
testLock(t, false)
|
||||
}
|
||||
|
||||
func TestLockPortable(t *testing.T) {
|
||||
testLock(t, true)
|
||||
}
|
||||
|
||||
func TestLockInChild(t *testing.T) {
|
||||
f := os.Getenv("TEST_LOCK_FILE")
|
||||
if f == "" {
|
||||
// not child
|
||||
return
|
||||
}
|
||||
lock := Lock
|
||||
if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v {
|
||||
lock = lockPortable
|
||||
}
|
||||
|
||||
lk, err := lock(f)
|
||||
if err != nil {
|
||||
log.Fatalf("Lock failed: %v", err)
|
||||
}
|
||||
|
||||
if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_CRASH")); v {
|
||||
// Simulate a crash, or at least not unlocking the
|
||||
// lock. We still exit 0 just to simplify the parent
|
||||
// process exec code.
|
||||
os.Exit(0)
|
||||
}
|
||||
lk.Close()
|
||||
}
|
||||
|
||||
func testLock(t *testing.T, portable bool) {
|
||||
lock := Lock
|
||||
if portable {
|
||||
lock = lockPortable
|
||||
}
|
||||
|
||||
td, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
path := filepath.Join(td, "foo.lock")
|
||||
|
||||
childLock := func(crash bool) error {
|
||||
cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
|
||||
cmd.Env = []string{"TEST_LOCK_FILE=" + path}
|
||||
if portable {
|
||||
cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
|
||||
}
|
||||
if crash {
|
||||
cmd.Env = append(cmd.Env, "TEST_LOCK_CRASH=1")
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
t.Logf("Child output: %q (err %v)", out, err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Child Process lock of %s failed: %v %s", path, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
t.Logf("Locking in crashing child...")
|
||||
if err := childLock(true); err != nil {
|
||||
t.Fatalf("first lock in child process: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Locking+unlocking in child...")
|
||||
if err := childLock(false); err != nil {
|
||||
t.Fatalf("lock in child process after crashing child: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Locking in parent...")
|
||||
lk1, err := lock(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("Again in parent...")
|
||||
_, err = lock(path)
|
||||
if err == nil {
|
||||
t.Fatal("expected second lock to fail")
|
||||
}
|
||||
|
||||
t.Logf("Locking in child...")
|
||||
if childLock(false) == nil {
|
||||
t.Fatalf("expected lock in child process to fail")
|
||||
}
|
||||
|
||||
t.Logf("Unlocking lock in parent")
|
||||
if err := lk1.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lk3, err := lock(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lk3.Close()
|
||||
}
|
19
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/doc.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package httptypes defines how etcd's HTTP API entities are serialized to and deserialized from JSON.
|
||||
*/
|
||||
|
||||
package httptypes
|
49
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type HTTPError struct {
|
||||
Message string `json:"message"`
|
||||
// HTTP return code
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// TODO(xiangli): handle http write errors
|
||||
func (e HTTPError) WriteTo(w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(e.Code)
|
||||
b, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
log.Panicf("marshal HTTPError should never fail: %v", err)
|
||||
}
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func NewHTTPError(code int, m string) *HTTPError {
|
||||
return &HTTPError{
|
||||
Message: m,
|
||||
Code: code,
|
||||
}
|
||||
}
|
47
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors_test.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptypes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPErrorWriteTo(t *testing.T) {
|
||||
err := NewHTTPError(http.StatusBadRequest, "what a bad request you made!")
|
||||
rr := httptest.NewRecorder()
|
||||
err.WriteTo(rr)
|
||||
|
||||
wcode := http.StatusBadRequest
|
||||
wheader := http.Header(map[string][]string{
|
||||
"Content-Type": []string{"application/json"},
|
||||
})
|
||||
wbody := `{"message":"what a bad request you made!"}`
|
||||
|
||||
if wcode != rr.Code {
|
||||
t.Errorf("HTTP status code %d, want %d", rr.Code, wcode)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(wheader, rr.HeaderMap) {
|
||||
t.Errorf("HTTP headers %v, want %v", rr.HeaderMap, wheader)
|
||||
}
|
||||
|
||||
gbody := rr.Body.String()
|
||||
if wbody != gbody {
|
||||
t.Errorf("HTTP body %q, want %q", gbody, wbody)
|
||||
}
|
||||
}
|
67
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
ClientURLs []string `json:"clientURLs"`
|
||||
}
|
||||
|
||||
type MemberCreateRequest struct {
|
||||
PeerURLs types.URLs
|
||||
}
|
||||
|
||||
type MemberUpdateRequest struct {
|
||||
MemberCreateRequest
|
||||
}
|
||||
|
||||
func (m *MemberCreateRequest) UnmarshalJSON(data []byte) error {
|
||||
s := struct {
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
urls, err := types.NewURLs(s.PeerURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.PeerURLs = urls
|
||||
return nil
|
||||
}
|
||||
|
||||
type MemberCollection []Member
|
||||
|
||||
func (c *MemberCollection) MarshalJSON() ([]byte, error) {
|
||||
d := struct {
|
||||
Members []Member `json:"members"`
|
||||
}{
|
||||
Members: []Member(*c),
|
||||
}
|
||||
|
||||
return json.Marshal(d)
|
||||
}
|
135
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member_test.go
generated
vendored
Normal file
135
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member_test.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
func TestMemberUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
body []byte
|
||||
wantMember Member
|
||||
wantError bool
|
||||
}{
|
||||
// no URLs, just check ID & Name
|
||||
{
|
||||
body: []byte(`{"id": "c", "name": "dungarees"}`),
|
||||
wantMember: Member{ID: "c", Name: "dungarees", PeerURLs: nil, ClientURLs: nil},
|
||||
},
|
||||
|
||||
// both client and peer URLs
|
||||
{
|
||||
body: []byte(`{"peerURLs": ["http://127.0.0.1:4001"], "clientURLs": ["http://127.0.0.1:4001"]}`),
|
||||
wantMember: Member{
|
||||
PeerURLs: []string{
|
||||
"http://127.0.0.1:4001",
|
||||
},
|
||||
ClientURLs: []string{
|
||||
"http://127.0.0.1:4001",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// multiple peer URLs
|
||||
{
|
||||
body: []byte(`{"peerURLs": ["http://127.0.0.1:4001", "https://example.com"]}`),
|
||||
wantMember: Member{
|
||||
PeerURLs: []string{
|
||||
"http://127.0.0.1:4001",
|
||||
"https://example.com",
|
||||
},
|
||||
ClientURLs: nil,
|
||||
},
|
||||
},
|
||||
|
||||
// multiple client URLs
|
||||
{
|
||||
body: []byte(`{"clientURLs": ["http://127.0.0.1:4001", "https://example.com"]}`),
|
||||
wantMember: Member{
|
||||
PeerURLs: nil,
|
||||
ClientURLs: []string{
|
||||
"http://127.0.0.1:4001",
|
||||
"https://example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// invalid JSON
|
||||
{
|
||||
body: []byte(`{"peerU`),
|
||||
wantError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
got := Member{}
|
||||
err := json.Unmarshal(tt.body, &got)
|
||||
if tt.wantError != (err != nil) {
|
||||
t.Errorf("#%d: want error %t, got %v", i, tt.wantError, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantMember, got) {
|
||||
t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.wantMember, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCreateRequestUnmarshal(t *testing.T) {
|
||||
body := []byte(`{"peerURLs": ["http://127.0.0.1:8081", "https://127.0.0.1:8080"]}`)
|
||||
want := MemberCreateRequest{
|
||||
PeerURLs: types.URLs([]url.URL{
|
||||
url.URL{Scheme: "http", Host: "127.0.0.1:8081"},
|
||||
url.URL{Scheme: "https", Host: "127.0.0.1:8080"},
|
||||
}),
|
||||
}
|
||||
|
||||
var req MemberCreateRequest
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
t.Fatalf("Unmarshal returned unexpected err=%v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(want, req) {
|
||||
t.Fatalf("Failed to unmarshal MemberCreateRequest: want=%#v, got=%#v", want, req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCreateRequestUnmarshalFail(t *testing.T) {
|
||||
tests := [][]byte{
|
||||
// invalid JSON
|
||||
[]byte(``),
|
||||
[]byte(`{`),
|
||||
|
||||
// spot-check validation done in types.NewURLs
|
||||
[]byte(`{"peerURLs": "foo"}`),
|
||||
[]byte(`{"peerURLs": ["."]}`),
|
||||
[]byte(`{"peerURLs": []}`),
|
||||
[]byte(`{"peerURLs": ["http://127.0.0.1:4001/foo"]}`),
|
||||
[]byte(`{"peerURLs": ["http://127.0.0.1"]}`),
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
var req MemberCreateRequest
|
||||
if err := json.Unmarshal(tt, &req); err == nil {
|
||||
t.Errorf("#%d: expected err, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
41
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id.go
generated
vendored
Normal file
41
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ID represents a generic identifier which is canonically
|
||||
// stored as a uint64 but is typically represented as a
|
||||
// base-16 string for input/output
|
||||
type ID uint64
|
||||
|
||||
func (i ID) String() string {
|
||||
return strconv.FormatUint(uint64(i), 16)
|
||||
}
|
||||
|
||||
// IDFromString attempts to create an ID from a base-16 string.
|
||||
func IDFromString(s string) (ID, error) {
|
||||
i, err := strconv.ParseUint(s, 16, 64)
|
||||
return ID(i), err
|
||||
}
|
||||
|
||||
// IDSlice implements the sort interface
|
||||
type IDSlice []ID
|
||||
|
||||
func (p IDSlice) Len() int { return len(p) }
|
||||
func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) }
|
||||
func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
95
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id_test.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id_test.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIDString(t *testing.T) {
|
||||
tests := []struct {
|
||||
input ID
|
||||
want string
|
||||
}{
|
||||
{
|
||||
input: 12,
|
||||
want: "c",
|
||||
},
|
||||
{
|
||||
input: 4918257920282737594,
|
||||
want: "444129853c343bba",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
got := tt.input.String()
|
||||
if tt.want != got {
|
||||
t.Errorf("#%d: ID.String failure: want=%v, got=%v", i, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want ID
|
||||
}{
|
||||
{
|
||||
input: "17",
|
||||
want: 23,
|
||||
},
|
||||
{
|
||||
input: "612840dae127353",
|
||||
want: 437557308098245459,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
got, err := IDFromString(tt.input)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: IDFromString failure: err=%v", i, err)
|
||||
continue
|
||||
}
|
||||
if tt.want != got {
|
||||
t.Errorf("#%d: IDFromString failure: want=%v, got=%v", i, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDFromStringFail(t *testing.T) {
|
||||
tests := []string{
|
||||
"",
|
||||
"XXX",
|
||||
"612840dae127353612840dae127353",
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
_, err := IDFromString(tt)
|
||||
if err == nil {
|
||||
t.Fatalf("#%d: IDFromString expected error, but err=nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDSlice(t *testing.T) {
|
||||
g := []ID{10, 500, 5, 1, 100, 25}
|
||||
w := []ID{1, 5, 10, 25, 100, 500}
|
||||
sort.Sort(IDSlice(g))
|
||||
if !reflect.DeepEqual(g, w) {
|
||||
t.Errorf("slice after sort = %#v, want %#v", g, w)
|
||||
}
|
||||
}
|
178
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go
generated
vendored
Normal file
178
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go
generated
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Set interface {
|
||||
Add(string)
|
||||
Remove(string)
|
||||
Contains(string) bool
|
||||
Equals(Set) bool
|
||||
Length() int
|
||||
Values() []string
|
||||
Copy() Set
|
||||
Sub(Set) Set
|
||||
}
|
||||
|
||||
func NewUnsafeSet(values ...string) *unsafeSet {
|
||||
set := &unsafeSet{make(map[string]struct{})}
|
||||
for _, v := range values {
|
||||
set.Add(v)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func NewThreadsafeSet(values ...string) *tsafeSet {
|
||||
us := NewUnsafeSet(values...)
|
||||
return &tsafeSet{us, sync.RWMutex{}}
|
||||
}
|
||||
|
||||
type unsafeSet struct {
|
||||
d map[string]struct{}
|
||||
}
|
||||
|
||||
// Add adds a new value to the set (no-op if the value is already present)
|
||||
func (us *unsafeSet) Add(value string) {
|
||||
us.d[value] = struct{}{}
|
||||
}
|
||||
|
||||
// Remove removes the given value from the set
|
||||
func (us *unsafeSet) Remove(value string) {
|
||||
delete(us.d, value)
|
||||
}
|
||||
|
||||
// Contains returns whether the set contains the given value
|
||||
func (us *unsafeSet) Contains(value string) (exists bool) {
|
||||
_, exists = us.d[value]
|
||||
return
|
||||
}
|
||||
|
||||
// ContainsAll returns whether the set contains all given values
|
||||
func (us *unsafeSet) ContainsAll(values []string) bool {
|
||||
for _, s := range values {
|
||||
if !us.Contains(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Equals returns whether the contents of two sets are identical
|
||||
func (us *unsafeSet) Equals(other Set) bool {
|
||||
v1 := sort.StringSlice(us.Values())
|
||||
v2 := sort.StringSlice(other.Values())
|
||||
v1.Sort()
|
||||
v2.Sort()
|
||||
return reflect.DeepEqual(v1, v2)
|
||||
}
|
||||
|
||||
// Length returns the number of elements in the set
|
||||
func (us *unsafeSet) Length() int {
|
||||
return len(us.d)
|
||||
}
|
||||
|
||||
// Values returns the values of the Set in an unspecified order.
|
||||
func (us *unsafeSet) Values() (values []string) {
|
||||
values = make([]string, 0)
|
||||
for val, _ := range us.d {
|
||||
values = append(values, val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Copy creates a new Set containing the values of the first
|
||||
func (us *unsafeSet) Copy() Set {
|
||||
cp := NewUnsafeSet()
|
||||
for val, _ := range us.d {
|
||||
cp.Add(val)
|
||||
}
|
||||
|
||||
return cp
|
||||
}
|
||||
|
||||
// Sub removes all elements in other from the set
|
||||
func (us *unsafeSet) Sub(other Set) Set {
|
||||
oValues := other.Values()
|
||||
result := us.Copy().(*unsafeSet)
|
||||
|
||||
for _, val := range oValues {
|
||||
if _, ok := result.d[val]; !ok {
|
||||
continue
|
||||
}
|
||||
delete(result.d, val)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type tsafeSet struct {
|
||||
us *unsafeSet
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Add(value string) {
|
||||
ts.m.Lock()
|
||||
defer ts.m.Unlock()
|
||||
ts.us.Add(value)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Remove(value string) {
|
||||
ts.m.Lock()
|
||||
defer ts.m.Unlock()
|
||||
ts.us.Remove(value)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Contains(value string) (exists bool) {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Contains(value)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Equals(other Set) bool {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Equals(other)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Length() int {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Length()
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Values() (values []string) {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Values()
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Copy() Set {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
usResult := ts.us.Copy().(*unsafeSet)
|
||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Sub(other Set) Set {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
usResult := ts.us.Sub(other).(*unsafeSet)
|
||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
||||
}
|
186
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set_test.go
generated
vendored
Normal file
186
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set_test.go
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnsafeSet(t *testing.T) {
|
||||
driveSetTests(t, NewUnsafeSet())
|
||||
}
|
||||
|
||||
func TestThreadsafeSet(t *testing.T) {
|
||||
driveSetTests(t, NewThreadsafeSet())
|
||||
}
|
||||
|
||||
// Check that two slices contents are equal; order is irrelevant
|
||||
func equal(a, b []string) bool {
|
||||
as := sort.StringSlice(a)
|
||||
bs := sort.StringSlice(b)
|
||||
as.Sort()
|
||||
bs.Sort()
|
||||
return reflect.DeepEqual(as, bs)
|
||||
}
|
||||
|
||||
func driveSetTests(t *testing.T, s Set) {
|
||||
// Verify operations on an empty set
|
||||
eValues := []string{}
|
||||
values := s.Values()
|
||||
if !reflect.DeepEqual(values, eValues) {
|
||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
||||
}
|
||||
if l := s.Length(); l != 0 {
|
||||
t.Fatalf("Expected length=0, got %d", l)
|
||||
}
|
||||
for _, v := range []string{"foo", "bar", "baz"} {
|
||||
if s.Contains(v) {
|
||||
t.Fatalf("Expect s.Contains(%q) to be fale, got true", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Add three items, ensure they show up
|
||||
s.Add("foo")
|
||||
s.Add("bar")
|
||||
s.Add("baz")
|
||||
|
||||
eValues = []string{"foo", "bar", "baz"}
|
||||
values = s.Values()
|
||||
if !equal(values, eValues) {
|
||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
||||
}
|
||||
|
||||
for _, v := range eValues {
|
||||
if !s.Contains(v) {
|
||||
t.Fatalf("Expect s.Contains(%q) to be true, got false", v)
|
||||
}
|
||||
}
|
||||
|
||||
if l := s.Length(); l != 3 {
|
||||
t.Fatalf("Expected length=3, got %d", l)
|
||||
}
|
||||
|
||||
// Add the same item a second time, ensuring it is not duplicated
|
||||
s.Add("foo")
|
||||
|
||||
values = s.Values()
|
||||
if !equal(values, eValues) {
|
||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
||||
}
|
||||
if l := s.Length(); l != 3 {
|
||||
t.Fatalf("Expected length=3, got %d", l)
|
||||
}
|
||||
|
||||
// Remove all items, ensure they are gone
|
||||
s.Remove("foo")
|
||||
s.Remove("bar")
|
||||
s.Remove("baz")
|
||||
|
||||
eValues = []string{}
|
||||
values = s.Values()
|
||||
if !equal(values, eValues) {
|
||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
||||
}
|
||||
|
||||
if l := s.Length(); l != 0 {
|
||||
t.Fatalf("Expected length=0, got %d", l)
|
||||
}
|
||||
|
||||
// Create new copies of the set, and ensure they are unlinked to the
|
||||
// original Set by making modifications
|
||||
s.Add("foo")
|
||||
s.Add("bar")
|
||||
cp1 := s.Copy()
|
||||
cp2 := s.Copy()
|
||||
s.Remove("foo")
|
||||
cp3 := s.Copy()
|
||||
cp1.Add("baz")
|
||||
|
||||
for i, tt := range []struct {
|
||||
want []string
|
||||
got []string
|
||||
}{
|
||||
{[]string{"bar"}, s.Values()},
|
||||
{[]string{"foo", "bar", "baz"}, cp1.Values()},
|
||||
{[]string{"foo", "bar"}, cp2.Values()},
|
||||
{[]string{"bar"}, cp3.Values()},
|
||||
} {
|
||||
if !equal(tt.want, tt.got) {
|
||||
t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got)
|
||||
}
|
||||
}
|
||||
|
||||
for i, tt := range []struct {
|
||||
want bool
|
||||
got bool
|
||||
}{
|
||||
{true, s.Equals(cp3)},
|
||||
{true, cp3.Equals(s)},
|
||||
{false, s.Equals(cp2)},
|
||||
{false, s.Equals(cp1)},
|
||||
{false, cp1.Equals(s)},
|
||||
{false, cp2.Equals(s)},
|
||||
{false, cp2.Equals(cp1)},
|
||||
} {
|
||||
if tt.got != tt.want {
|
||||
t.Fatalf("case %d: want %t, got %t", i, tt.want, tt.got)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Subtract values from a Set, ensuring a new Set is created and
|
||||
// the original Sets are unmodified
|
||||
sub1 := cp1.Sub(s)
|
||||
sub2 := cp2.Sub(cp1)
|
||||
|
||||
for i, tt := range []struct {
|
||||
want []string
|
||||
got []string
|
||||
}{
|
||||
{[]string{"foo", "bar", "baz"}, cp1.Values()},
|
||||
{[]string{"foo", "bar"}, cp2.Values()},
|
||||
{[]string{"bar"}, s.Values()},
|
||||
{[]string{"foo", "baz"}, sub1.Values()},
|
||||
{[]string{}, sub2.Values()},
|
||||
} {
|
||||
if !equal(tt.want, tt.got) {
|
||||
t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsafeSetContainsAll(t *testing.T) {
|
||||
vals := []string{"foo", "bar", "baz"}
|
||||
s := NewUnsafeSet(vals...)
|
||||
|
||||
tests := []struct {
|
||||
strs []string
|
||||
wcontain bool
|
||||
}{
|
||||
{[]string{}, true},
|
||||
{vals[:1], true},
|
||||
{vals[:2], true},
|
||||
{vals, true},
|
||||
{[]string{"cuz"}, false},
|
||||
{[]string{vals[0], "cuz"}, false},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if g := s.ContainsAll(tt.strs); g != tt.wcontain {
|
||||
t.Errorf("#%d: ok = %v, want %v", i, g, tt.wcontain)
|
||||
}
|
||||
}
|
||||
}
|
22
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
// Uint64Slice implements sort interface
|
||||
type Uint64Slice []uint64
|
||||
|
||||
func (p Uint64Slice) Len() int { return len(p) }
|
||||
func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
||||
func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
30
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice_test.go
generated
vendored
Normal file
30
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice_test.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUint64Slice(t *testing.T) {
|
||||
g := Uint64Slice{10, 500, 5, 1, 100, 25}
|
||||
w := Uint64Slice{1, 5, 10, 25, 100, 500}
|
||||
sort.Sort(g)
|
||||
if !reflect.DeepEqual(g, w) {
|
||||
t.Errorf("slice after sort = %#v, want %#v", g, w)
|
||||
}
|
||||
}
|
74
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls.go
generated
vendored
Normal file
74
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type URLs []url.URL
|
||||
|
||||
func NewURLs(strs []string) (URLs, error) {
|
||||
all := make([]url.URL, len(strs))
|
||||
if len(all) == 0 {
|
||||
return nil, errors.New("no valid URLs given")
|
||||
}
|
||||
for i, in := range strs {
|
||||
in = strings.TrimSpace(in)
|
||||
u, err := url.Parse(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return nil, fmt.Errorf("URL scheme must be http or https: %s", in)
|
||||
}
|
||||
if _, _, err := net.SplitHostPort(u.Host); err != nil {
|
||||
return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in)
|
||||
}
|
||||
if u.Path != "" {
|
||||
return nil, fmt.Errorf("URL must not contain a path: %s", in)
|
||||
}
|
||||
all[i] = *u
|
||||
}
|
||||
us := URLs(all)
|
||||
us.Sort()
|
||||
|
||||
return us, nil
|
||||
}
|
||||
|
||||
func (us URLs) String() string {
|
||||
return strings.Join(us.StringSlice(), ",")
|
||||
}
|
||||
|
||||
func (us *URLs) Sort() {
|
||||
sort.Sort(us)
|
||||
}
|
||||
func (us URLs) Len() int { return len(us) }
|
||||
func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() }
|
||||
func (us URLs) Swap(i, j int) { us[i], us[j] = us[j], us[i] }
|
||||
|
||||
func (us URLs) StringSlice() []string {
|
||||
out := make([]string, len(us))
|
||||
for i := range us {
|
||||
out[i] = us[i].String()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
169
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls_test.go
generated
vendored
Normal file
169
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls_test.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/pkg/testutil"
|
||||
)
|
||||
|
||||
func TestNewURLs(t *testing.T) {
|
||||
tests := []struct {
|
||||
strs []string
|
||||
wurls URLs
|
||||
}{
|
||||
{
|
||||
[]string{"http://127.0.0.1:4001"},
|
||||
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
|
||||
},
|
||||
// it can trim space
|
||||
{
|
||||
[]string{" http://127.0.0.1:4001 "},
|
||||
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
|
||||
},
|
||||
// it does sort
|
||||
{
|
||||
[]string{
|
||||
"http://127.0.0.2:4001",
|
||||
"http://127.0.0.1:4001",
|
||||
},
|
||||
testutil.MustNewURLs(t, []string{
|
||||
"http://127.0.0.1:4001",
|
||||
"http://127.0.0.2:4001",
|
||||
}),
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
urls, _ := NewURLs(tt.strs)
|
||||
if !reflect.DeepEqual(urls, tt.wurls) {
|
||||
t.Errorf("#%d: urls = %+v, want %+v", i, urls, tt.wurls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLsString(t *testing.T) {
|
||||
tests := []struct {
|
||||
us URLs
|
||||
wstr string
|
||||
}{
|
||||
{
|
||||
URLs{},
|
||||
"",
|
||||
},
|
||||
{
|
||||
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
|
||||
"http://127.0.0.1:4001",
|
||||
},
|
||||
{
|
||||
testutil.MustNewURLs(t, []string{
|
||||
"http://127.0.0.1:4001",
|
||||
"http://127.0.0.2:4001",
|
||||
}),
|
||||
"http://127.0.0.1:4001,http://127.0.0.2:4001",
|
||||
},
|
||||
{
|
||||
testutil.MustNewURLs(t, []string{
|
||||
"http://127.0.0.2:4001",
|
||||
"http://127.0.0.1:4001",
|
||||
}),
|
||||
"http://127.0.0.2:4001,http://127.0.0.1:4001",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
g := tt.us.String()
|
||||
if g != tt.wstr {
|
||||
t.Errorf("#%d: string = %s, want %s", i, g, tt.wstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLsSort(t *testing.T) {
|
||||
g := testutil.MustNewURLs(t, []string{
|
||||
"http://127.0.0.4:4001",
|
||||
"http://127.0.0.2:4001",
|
||||
"http://127.0.0.1:4001",
|
||||
"http://127.0.0.3:4001",
|
||||
})
|
||||
w := testutil.MustNewURLs(t, []string{
|
||||
"http://127.0.0.1:4001",
|
||||
"http://127.0.0.2:4001",
|
||||
"http://127.0.0.3:4001",
|
||||
"http://127.0.0.4:4001",
|
||||
})
|
||||
gurls := URLs(g)
|
||||
gurls.Sort()
|
||||
if !reflect.DeepEqual(g, w) {
|
||||
t.Errorf("URLs after sort = %#v, want %#v", g, w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLsStringSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
us URLs
|
||||
wstr []string
|
||||
}{
|
||||
{
|
||||
URLs{},
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
|
||||
[]string{"http://127.0.0.1:4001"},
|
||||
},
|
||||
{
|
||||
testutil.MustNewURLs(t, []string{
|
||||
"http://127.0.0.1:4001",
|
||||
"http://127.0.0.2:4001",
|
||||
}),
|
||||
[]string{"http://127.0.0.1:4001", "http://127.0.0.2:4001"},
|
||||
},
|
||||
{
|
||||
testutil.MustNewURLs(t, []string{
|
||||
"http://127.0.0.2:4001",
|
||||
"http://127.0.0.1:4001",
|
||||
}),
|
||||
[]string{"http://127.0.0.2:4001", "http://127.0.0.1:4001"},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
g := tt.us.StringSlice()
|
||||
if !reflect.DeepEqual(g, tt.wstr) {
|
||||
t.Errorf("#%d: string slice = %+v, want %+v", i, g, tt.wstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewURLsFail(t *testing.T) {
|
||||
tests := [][]string{
|
||||
// no urls given
|
||||
{},
|
||||
// missing protocol scheme
|
||||
{"://127.0.0.1:4001"},
|
||||
// unsupported scheme
|
||||
{"mailto://127.0.0.1:4001"},
|
||||
// not conform to host:port
|
||||
{"http://127.0.0.1"},
|
||||
// contain a path
|
||||
{"http://127.0.0.1:4001/path"},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
_, err := NewURLs(tt)
|
||||
if err == nil {
|
||||
t.Errorf("#%d: err = nil, but error", i)
|
||||
}
|
||||
}
|
||||
}
|
202
Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver.go
generated
vendored
Normal file
202
Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Major int64
|
||||
Minor int64
|
||||
Patch int64
|
||||
PreRelease PreRelease
|
||||
Metadata string
|
||||
}
|
||||
|
||||
type PreRelease string
|
||||
|
||||
func splitOff(input *string, delim string) (val string) {
|
||||
parts := strings.SplitN(*input, delim, 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
*input = parts[0]
|
||||
val = parts[1]
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func NewVersion(version string) (*Version, error) {
|
||||
v := Version{}
|
||||
|
||||
dotParts := strings.SplitN(version, ".", 3)
|
||||
|
||||
if len(dotParts) != 3 {
|
||||
return nil, errors.New(fmt.Sprintf("%s is not in dotted-tri format", version))
|
||||
}
|
||||
|
||||
v.Metadata = splitOff(&dotParts[2], "+")
|
||||
v.PreRelease = PreRelease(splitOff(&dotParts[2], "-"))
|
||||
|
||||
parsed := make([]int64, 3, 3)
|
||||
|
||||
for i, v := range dotParts[:3] {
|
||||
val, err := strconv.ParseInt(v, 10, 64)
|
||||
parsed[i] = val
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
v.Major = parsed[0]
|
||||
v.Minor = parsed[1]
|
||||
v.Patch = parsed[2]
|
||||
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func (v *Version) String() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
base := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
buffer.WriteString(base)
|
||||
|
||||
if v.PreRelease != "" {
|
||||
buffer.WriteString(fmt.Sprintf("-%s", v.PreRelease))
|
||||
}
|
||||
|
||||
if v.Metadata != "" {
|
||||
buffer.WriteString(fmt.Sprintf("+%s", v.Metadata))
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (v *Version) LessThan(versionB Version) bool {
|
||||
versionA := *v
|
||||
cmp := recursiveCompare(versionA.Slice(), versionB.Slice())
|
||||
|
||||
if cmp == 0 {
|
||||
cmp = preReleaseCompare(versionA, versionB)
|
||||
}
|
||||
|
||||
if cmp == -1 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/* Slice converts the comparable parts of the semver into a slice of strings */
|
||||
func (v *Version) Slice() []int64 {
|
||||
return []int64{v.Major, v.Minor, v.Patch}
|
||||
}
|
||||
|
||||
func (p *PreRelease) Slice() []string {
|
||||
preRelease := string(*p)
|
||||
return strings.Split(preRelease, ".")
|
||||
}
|
||||
|
||||
func preReleaseCompare(versionA Version, versionB Version) int {
|
||||
a := versionA.PreRelease
|
||||
b := versionB.PreRelease
|
||||
|
||||
/* Handle the case where if two versions are otherwise equal it is the
|
||||
* one without a PreRelease that is greater */
|
||||
if len(a) == 0 && (len(b) > 0) {
|
||||
return 1
|
||||
} else if len(b) == 0 && (len(a) > 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// If there is a prelease, check and compare each part.
|
||||
return recursivePreReleaseCompare(a.Slice(), b.Slice())
|
||||
}
|
||||
|
||||
func recursiveCompare(versionA []int64, versionB []int64) int {
|
||||
if len(versionA) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursiveCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
|
||||
// Handle slice length disparity.
|
||||
if len(versionA) == 0 {
|
||||
// Nothing to compare too, so we return 0
|
||||
return 0
|
||||
} else if len(versionB) == 0 {
|
||||
// We're longer than versionB so return 1.
|
||||
return 1
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
aInt := false; bInt := false
|
||||
|
||||
aI, err := strconv.Atoi(versionA[0])
|
||||
if err == nil {
|
||||
aInt = true
|
||||
}
|
||||
|
||||
bI, err := strconv.Atoi(versionB[0])
|
||||
if err == nil {
|
||||
bInt = true
|
||||
}
|
||||
|
||||
// Handle Integer Comparison
|
||||
if aInt && bInt {
|
||||
if aI > bI {
|
||||
return 1
|
||||
} else if aI < bI {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Handle String Comparison
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
// BumpMajor increments the Major field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpMajor() {
|
||||
v.Major += 1
|
||||
v.Minor = 0
|
||||
v.Patch = 0
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
||||
|
||||
// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpMinor() {
|
||||
v.Minor += 1
|
||||
v.Patch = 0
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
||||
|
||||
// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpPatch() {
|
||||
v.Patch += 1
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
187
Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver_test.go
generated
vendored
Normal file
187
Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver_test.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
greaterVersion string
|
||||
lesserVersion string
|
||||
}
|
||||
|
||||
var fixtures = []fixture{
|
||||
fixture{"0.0.0", "0.0.0-foo"},
|
||||
fixture{"0.0.1", "0.0.0"},
|
||||
fixture{"1.0.0", "0.9.9"},
|
||||
fixture{"0.10.0", "0.9.0"},
|
||||
fixture{"0.99.0", "0.10.0"},
|
||||
fixture{"2.0.0", "1.2.3"},
|
||||
fixture{"0.0.0", "0.0.0-foo"},
|
||||
fixture{"0.0.1", "0.0.0"},
|
||||
fixture{"1.0.0", "0.9.9"},
|
||||
fixture{"0.10.0", "0.9.0"},
|
||||
fixture{"0.99.0", "0.10.0"},
|
||||
fixture{"2.0.0", "1.2.3"},
|
||||
fixture{"0.0.0", "0.0.0-foo"},
|
||||
fixture{"0.0.1", "0.0.0"},
|
||||
fixture{"1.0.0", "0.9.9"},
|
||||
fixture{"0.10.0", "0.9.0"},
|
||||
fixture{"0.99.0", "0.10.0"},
|
||||
fixture{"2.0.0", "1.2.3"},
|
||||
fixture{"1.2.3", "1.2.3-asdf"},
|
||||
fixture{"1.2.3", "1.2.3-4"},
|
||||
fixture{"1.2.3", "1.2.3-4-foo"},
|
||||
fixture{"1.2.3-5-foo", "1.2.3-5"},
|
||||
fixture{"1.2.3-5", "1.2.3-4"},
|
||||
fixture{"1.2.3-5-foo", "1.2.3-5-Foo"},
|
||||
fixture{"3.0.0", "2.7.2+asdf"},
|
||||
fixture{"3.0.0+foobar", "2.7.2"},
|
||||
fixture{"1.2.3-a.10", "1.2.3-a.5"},
|
||||
fixture{"1.2.3-a.b", "1.2.3-a.5"},
|
||||
fixture{"1.2.3-a.b", "1.2.3-a"},
|
||||
fixture{"1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"},
|
||||
fixture{"1.0.0", "1.0.0-rc.1"},
|
||||
fixture{"1.0.0-rc.2", "1.0.0-rc.1"},
|
||||
fixture{"1.0.0-rc.1", "1.0.0-beta.11"},
|
||||
fixture{"1.0.0-beta.11", "1.0.0-beta.2"},
|
||||
fixture{"1.0.0-beta.2", "1.0.0-beta"},
|
||||
fixture{"1.0.0-beta", "1.0.0-alpha.beta"},
|
||||
fixture{"1.0.0-alpha.beta", "1.0.0-alpha.1"},
|
||||
fixture{"1.0.0-alpha.1", "1.0.0-alpha"},
|
||||
}
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
for _, v := range fixtures {
|
||||
gt, err := NewVersion(v.greaterVersion)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
lt, err := NewVersion(v.lesserVersion)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if gt.LessThan(*lt) == true {
|
||||
t.Errorf("%s should not be less than %s", gt, lt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testString(t *testing.T, orig string, version *Version) {
|
||||
if orig != version.String() {
|
||||
t.Errorf("%s != %s", orig, version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
for _, v := range fixtures {
|
||||
gt, err := NewVersion(v.greaterVersion)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
testString(t, v.greaterVersion, gt)
|
||||
|
||||
lt, err := NewVersion(v.lesserVersion)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
testString(t, v.lesserVersion, lt)
|
||||
}
|
||||
}
|
||||
|
||||
func shuffleStringSlice(src []string) []string {
|
||||
dest := make([]string, len(src))
|
||||
rand.Seed(time.Now().Unix())
|
||||
perm := rand.Perm(len(src))
|
||||
for i, v := range perm {
|
||||
dest[v] = src[i]
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
sortedVersions := []string{"1.0.0", "1.0.2", "1.2.0", "3.1.1"}
|
||||
unsortedVersions := shuffleStringSlice(sortedVersions)
|
||||
|
||||
semvers := []*Version{}
|
||||
for _, v := range unsortedVersions {
|
||||
sv, err := NewVersion(v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
semvers = append(semvers, sv)
|
||||
}
|
||||
|
||||
Sort(semvers)
|
||||
|
||||
for idx, sv := range semvers {
|
||||
if sv.String() != sortedVersions[idx] {
|
||||
t.Fatalf("incorrect sort at index %v", idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpMajor(t *testing.T) {
|
||||
version, _ := NewVersion("1.0.0")
|
||||
version.BumpMajor()
|
||||
if version.Major != 2 {
|
||||
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
|
||||
}
|
||||
|
||||
version, _ = NewVersion("1.5.2")
|
||||
version.BumpMajor()
|
||||
if version.Minor != 0 && version.Patch != 0 {
|
||||
t.Fatalf("bumping major on 1.5.2 resulted in %v", version)
|
||||
}
|
||||
|
||||
version, _ = NewVersion("1.0.0+build.1-alpha.1")
|
||||
version.BumpMajor()
|
||||
if version.PreRelease != "" && version.PreRelease != "" {
|
||||
t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpMinor(t *testing.T) {
|
||||
version, _ := NewVersion("1.0.0")
|
||||
version.BumpMinor()
|
||||
|
||||
if version.Major != 1 {
|
||||
t.Fatalf("bumping minor on 1.0.0 resulted in %v", version)
|
||||
}
|
||||
|
||||
if version.Minor != 1 {
|
||||
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
|
||||
}
|
||||
|
||||
version, _ = NewVersion("1.0.0+build.1-alpha.1")
|
||||
version.BumpMinor()
|
||||
if version.PreRelease != "" && version.PreRelease != "" {
|
||||
t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpPatch(t *testing.T) {
|
||||
version, _ := NewVersion("1.0.0")
|
||||
version.BumpPatch()
|
||||
|
||||
if version.Major != 1 {
|
||||
t.Fatalf("bumping minor on 1.0.0 resulted in %v", version)
|
||||
}
|
||||
|
||||
if version.Minor != 0 {
|
||||
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
|
||||
}
|
||||
|
||||
if version.Patch != 1 {
|
||||
t.Fatalf("bumping major on 1.0.0 resulted in %v", version)
|
||||
}
|
||||
|
||||
version, _ = NewVersion("1.0.0+build.1-alpha.1")
|
||||
version.BumpPatch()
|
||||
if version.PreRelease != "" && version.PreRelease != "" {
|
||||
t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version)
|
||||
}
|
||||
}
|
24
Godeps/_workspace/src/github.com/coreos/go-semver/semver/sort.go
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/coreos/go-semver/semver/sort.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Versions []*Version
|
||||
|
||||
func (s Versions) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s Versions) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s Versions) Less(i, j int) bool {
|
||||
return s[i].LessThan(*s[j])
|
||||
}
|
||||
|
||||
// Sort sorts the given slice of Version
|
||||
func Sort(versions []*Version) {
|
||||
sort.Sort(Versions(versions))
|
||||
}
|
213
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/deserialize.go
generated
vendored
Normal file
213
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/deserialize.go
generated
vendored
Normal file
@ -0,0 +1,213 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Deserialize parses a systemd unit file into a list of UnitOption objects.
|
||||
func Deserialize(f io.Reader) (opts []*UnitOption, err error) {
|
||||
lexer, optchan, errchan := newLexer(f)
|
||||
go lexer.lex()
|
||||
|
||||
for opt := range optchan {
|
||||
opts = append(opts, &(*opt))
|
||||
}
|
||||
|
||||
err = <-errchan
|
||||
return opts, err
|
||||
}
|
||||
|
||||
func newLexer(f io.Reader) (*lexer, <-chan *UnitOption, <-chan error) {
|
||||
optchan := make(chan *UnitOption)
|
||||
errchan := make(chan error, 1)
|
||||
buf := bufio.NewReader(f)
|
||||
|
||||
return &lexer{buf, optchan, errchan, ""}, optchan, errchan
|
||||
}
|
||||
|
||||
type lexer struct {
|
||||
buf *bufio.Reader
|
||||
optchan chan *UnitOption
|
||||
errchan chan error
|
||||
section string
|
||||
}
|
||||
|
||||
func (l *lexer) lex() {
|
||||
var err error
|
||||
next := l.lexNextSection
|
||||
for next != nil {
|
||||
next, err = next()
|
||||
if err != nil {
|
||||
l.errchan <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
close(l.optchan)
|
||||
close(l.errchan)
|
||||
}
|
||||
|
||||
type lexStep func() (lexStep, error)
|
||||
|
||||
func (l *lexer) lexSectionName() (lexStep, error) {
|
||||
sec, err := l.buf.ReadBytes(']')
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to find end of section")
|
||||
}
|
||||
|
||||
return l.lexSectionSuffixFunc(string(sec[:len(sec)-1])), nil
|
||||
}
|
||||
|
||||
func (l *lexer) lexSectionSuffixFunc(section string) lexStep {
|
||||
return func() (lexStep, error) {
|
||||
garbage, err := l.toEOL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
garbage = bytes.TrimSpace(garbage)
|
||||
if len(garbage) > 0 {
|
||||
return nil, fmt.Errorf("found garbage after section name %s: %v", l.section, garbage)
|
||||
}
|
||||
|
||||
return l.lexNextSectionOrOptionFunc(section), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) ignoreLineFunc(next lexStep) lexStep {
|
||||
return func() (lexStep, error) {
|
||||
for {
|
||||
line, err := l.toEOL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
line = bytes.TrimSuffix(line, []byte{' '})
|
||||
|
||||
// lack of continuation means this line has been exhausted
|
||||
if !bytes.HasSuffix(line, []byte{'\\'}) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// reached end of buffer, safe to exit
|
||||
return next, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) lexNextSection() (lexStep, error) {
|
||||
r, _, err := l.buf.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r == '[' {
|
||||
return l.lexSectionName, nil
|
||||
} else if isComment(r) {
|
||||
return l.ignoreLineFunc(l.lexNextSection), nil
|
||||
}
|
||||
|
||||
return l.lexNextSection, nil
|
||||
}
|
||||
|
||||
func (l *lexer) lexNextSectionOrOptionFunc(section string) lexStep {
|
||||
return func() (lexStep, error) {
|
||||
r, _, err := l.buf.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if unicode.IsSpace(r) {
|
||||
return l.lexNextSectionOrOptionFunc(section), nil
|
||||
} else if r == '[' {
|
||||
return l.lexSectionName, nil
|
||||
} else if isComment(r) {
|
||||
return l.ignoreLineFunc(l.lexNextSectionOrOptionFunc(section)), nil
|
||||
}
|
||||
|
||||
l.buf.UnreadRune()
|
||||
return l.lexOptionNameFunc(section), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) lexOptionNameFunc(section string) lexStep {
|
||||
return func() (lexStep, error) {
|
||||
var partial bytes.Buffer
|
||||
for {
|
||||
r, _, err := l.buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r == '\n' || r == '\r' {
|
||||
return nil, errors.New("unexpected newline encountered while parsing option name")
|
||||
}
|
||||
|
||||
if r == '=' {
|
||||
break
|
||||
}
|
||||
|
||||
partial.WriteRune(r)
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(partial.String())
|
||||
return l.lexOptionValueFunc(section, name), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) lexOptionValueFunc(section, name string) lexStep {
|
||||
return func() (lexStep, error) {
|
||||
var partial bytes.Buffer
|
||||
|
||||
for {
|
||||
line, err := l.toEOL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// lack of continuation means this value has been exhausted
|
||||
idx := bytes.LastIndex(line, []byte{'\\'})
|
||||
if idx == -1 || idx != (len(line)-1) {
|
||||
partial.Write(line)
|
||||
break
|
||||
}
|
||||
|
||||
partial.Write(line[0:idx])
|
||||
partial.WriteRune(' ')
|
||||
}
|
||||
|
||||
val := strings.TrimSpace(partial.String())
|
||||
l.optchan <- &UnitOption{Section: section, Name: name, Value: val}
|
||||
|
||||
return l.lexNextSectionOrOptionFunc(section), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) toEOL() ([]byte, error) {
|
||||
line, err := l.buf.ReadBytes('\n')
|
||||
// ignore EOF here since it's roughly equivalent to EOL
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
line = bytes.TrimSuffix(line, []byte{'\r'})
|
||||
line = bytes.TrimSuffix(line, []byte{'\n'})
|
||||
|
||||
return line, nil
|
||||
}
|
||||
|
||||
func isComment(r rune) bool {
|
||||
return r == '#' || r == ';'
|
||||
}
|
301
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/deserialize_test.go
generated
vendored
Normal file
301
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/deserialize_test.go
generated
vendored
Normal file
@ -0,0 +1,301 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeserialize(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []byte
|
||||
output []*UnitOption
|
||||
}{
|
||||
// multiple options underneath a section
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description=Foo
|
||||
Description=Bar
|
||||
Requires=baz.service
|
||||
After=baz.service
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Foo"},
|
||||
&UnitOption{"Unit", "Description", "Bar"},
|
||||
&UnitOption{"Unit", "Requires", "baz.service"},
|
||||
&UnitOption{"Unit", "After", "baz.service"},
|
||||
},
|
||||
},
|
||||
|
||||
// multiple sections
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description=Foo
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/sleep infinity
|
||||
|
||||
[X-Third-Party]
|
||||
Pants=on
|
||||
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Foo"},
|
||||
&UnitOption{"Service", "ExecStart", "/usr/bin/sleep infinity"},
|
||||
&UnitOption{"X-Third-Party", "Pants", "on"},
|
||||
},
|
||||
},
|
||||
|
||||
// multiple sections with no options
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
[Service]
|
||||
[X-Third-Party]
|
||||
`),
|
||||
[]*UnitOption{},
|
||||
},
|
||||
|
||||
// multiple values not special-cased
|
||||
{
|
||||
[]byte(`[Service]
|
||||
Environment= "FOO=BAR" "BAZ=QUX"
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Service", "Environment", "\"FOO=BAR\" \"BAZ=QUX\""},
|
||||
},
|
||||
},
|
||||
|
||||
// line continuations respected
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description= Unnecessarily wrapped \
|
||||
words here
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Unnecessarily wrapped words here"},
|
||||
},
|
||||
},
|
||||
|
||||
// comments ignored
|
||||
{
|
||||
[]byte(`; comment alpha
|
||||
# comment bravo
|
||||
[Unit]
|
||||
; comment charlie
|
||||
# comment delta
|
||||
#Description=Foo
|
||||
Description=Bar
|
||||
; comment echo
|
||||
# comment foxtrot
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// apparent comment lines inside of line continuations not ignored
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description=Bar\
|
||||
# comment alpha
|
||||
|
||||
Description=Bar\
|
||||
# comment bravo \
|
||||
Baz
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Bar # comment alpha"},
|
||||
&UnitOption{"Unit", "Description", "Bar # comment bravo Baz"},
|
||||
},
|
||||
},
|
||||
|
||||
// options outside of sections are ignored
|
||||
{
|
||||
[]byte(`Description=Foo
|
||||
[Unit]
|
||||
Description=Bar
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// garbage outside of sections are ignored
|
||||
{
|
||||
[]byte(`<<<<<<<<
|
||||
[Unit]
|
||||
Description=Bar
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// garbage used as unit option
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
<<<<<<<<=Bar
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "<<<<<<<<", "Bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// option name with spaces are valid
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Some Thing = Bar
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Some Thing", "Bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// lack of trailing newline doesn't cause problem for non-continued file
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description=Bar`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// unit file with continuation but no following line is ok, too
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description=Bar \`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// Assert utf8 characters are preserved
|
||||
{
|
||||
[]byte(`[©]
|
||||
µ☃=ÇôrèÕ$`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"©", "µ☃", "ÇôrèÕ$"},
|
||||
},
|
||||
},
|
||||
|
||||
// whitespace removed around option name
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description =words here
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "words here"},
|
||||
},
|
||||
},
|
||||
|
||||
// whitespace around option value stripped
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description= words here `),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "words here"},
|
||||
},
|
||||
},
|
||||
|
||||
// whitespace around option value stripped, regardless of continuation
|
||||
{
|
||||
[]byte(`[Unit]
|
||||
Description= words here \
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "words here"},
|
||||
},
|
||||
},
|
||||
|
||||
// backslash not considered continuation if followed by text
|
||||
{
|
||||
[]byte(`[Service]
|
||||
ExecStart=/bin/bash -c "while true; do echo \"ping\"; sleep 1; done"
|
||||
`),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Service", "ExecStart", `/bin/bash -c "while true; do echo \"ping\"; sleep 1; done"`},
|
||||
},
|
||||
},
|
||||
|
||||
// backslash not considered continuation if followed by whitespace, but still trimmed
|
||||
{
|
||||
[]byte(`[Service]
|
||||
ExecStart=/bin/bash echo poof \ `),
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Service", "ExecStart", `/bin/bash echo poof \`},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert := func(expect, output []*UnitOption) error {
|
||||
if len(expect) != len(output) {
|
||||
return fmt.Errorf("expected %d items, got %d", len(expect), len(output))
|
||||
}
|
||||
|
||||
for i, _ := range expect {
|
||||
if !reflect.DeepEqual(expect[i], output[i]) {
|
||||
return fmt.Errorf("item %d: expected %v, got %v", i, expect[i], output[i])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
output, err := Deserialize(bytes.NewReader(tt.input))
|
||||
if err != nil {
|
||||
t.Errorf("case %d: unexpected error parsing unit: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = assert(tt.output, output)
|
||||
if err != nil {
|
||||
t.Errorf("case %d: %v", i, err)
|
||||
t.Log("Expected options:")
|
||||
logUnitOptionSlice(t, tt.output)
|
||||
t.Log("Actual options:")
|
||||
logUnitOptionSlice(t, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeserializeFail(t *testing.T) {
|
||||
tests := [][]byte{
|
||||
// malformed section header
|
||||
[]byte(`[Unit
|
||||
Description=Foo
|
||||
`),
|
||||
|
||||
// garbage following section header
|
||||
[]byte(`[Unit] pants
|
||||
Description=Foo
|
||||
`),
|
||||
|
||||
// option without value
|
||||
[]byte(`[Unit]
|
||||
Description
|
||||
`),
|
||||
|
||||
// garbage inside of section
|
||||
[]byte(`[Unit]
|
||||
<<<<<<
|
||||
Description=Foo
|
||||
`),
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
output, err := Deserialize(bytes.NewReader(tt))
|
||||
if err == nil {
|
||||
t.Errorf("case %d: unexpected non-nil error, received nil", i)
|
||||
t.Log("Output:")
|
||||
logUnitOptionSlice(t, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logUnitOptionSlice(t *testing.T, opts []*UnitOption) {
|
||||
for idx, opt := range opts {
|
||||
t.Logf("%d: %v", idx, opt)
|
||||
}
|
||||
}
|
36
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/option.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/option.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type UnitOption struct {
|
||||
Section string
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (uo *UnitOption) String() string {
|
||||
return fmt.Sprintf("{Section: %q, Name: %q, Value: %q}", uo.Section, uo.Name, uo.Value)
|
||||
}
|
||||
|
||||
func (uo *UnitOption) Match(other *UnitOption) bool {
|
||||
return uo.Section == other.Section &&
|
||||
uo.Name == other.Name &&
|
||||
uo.Value == other.Value
|
||||
}
|
||||
|
||||
func AllMatch(u1 []*UnitOption, u2 []*UnitOption) bool {
|
||||
length := len(u1)
|
||||
if length != len(u2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
if !u1[i].Match(u2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
200
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/option_test.go
generated
vendored
Normal file
200
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/option_test.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAllMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
u1 []*UnitOption
|
||||
u2 []*UnitOption
|
||||
match bool
|
||||
}{
|
||||
// empty lists match
|
||||
{
|
||||
u1: []*UnitOption{},
|
||||
u2: []*UnitOption{},
|
||||
match: true,
|
||||
},
|
||||
|
||||
// simple match of a single option
|
||||
{
|
||||
u1: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
},
|
||||
u2: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
|
||||
// single option mismatched
|
||||
{
|
||||
u1: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
},
|
||||
u2: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "BAR"},
|
||||
},
|
||||
match: false,
|
||||
},
|
||||
|
||||
// multiple options match
|
||||
{
|
||||
u1: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
|
||||
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
|
||||
},
|
||||
u2: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
|
||||
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
|
||||
// mismatch length
|
||||
{
|
||||
u1: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
|
||||
},
|
||||
u2: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
|
||||
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
|
||||
},
|
||||
match: false,
|
||||
},
|
||||
|
||||
// multiple options misordered
|
||||
{
|
||||
u1: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
|
||||
},
|
||||
u2: []*UnitOption{
|
||||
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
},
|
||||
match: false,
|
||||
},
|
||||
|
||||
// interleaved sections mismatch
|
||||
{
|
||||
u1: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
|
||||
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
|
||||
{Section: "Service", Name: "ExecStop", Value: "/bin/true"},
|
||||
},
|
||||
u2: []*UnitOption{
|
||||
{Section: "Unit", Name: "Description", Value: "FOO"},
|
||||
{Section: "Service", Name: "ExecStart", Value: "/bin/true"},
|
||||
{Section: "Unit", Name: "BindsTo", Value: "bar.service"},
|
||||
{Section: "Service", Name: "ExecStop", Value: "/bin/true"},
|
||||
},
|
||||
match: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
match := AllMatch(tt.u1, tt.u2)
|
||||
if match != tt.match {
|
||||
t.Errorf("case %d: failed comparing u1 to u2 - expected match=%t, got %t", i, tt.match, match)
|
||||
}
|
||||
|
||||
match = AllMatch(tt.u2, tt.u1)
|
||||
if match != tt.match {
|
||||
t.Errorf("case %d: failed comparing u2 to u1 - expected match=%t, got %t", i, tt.match, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
o1 *UnitOption
|
||||
o2 *UnitOption
|
||||
match bool
|
||||
}{
|
||||
// empty options match
|
||||
{
|
||||
o1: &UnitOption{},
|
||||
o2: &UnitOption{},
|
||||
match: true,
|
||||
},
|
||||
|
||||
// all fields match
|
||||
{
|
||||
o1: &UnitOption{
|
||||
Section: "Unit",
|
||||
Name: "Description",
|
||||
Value: "FOO",
|
||||
},
|
||||
o2: &UnitOption{
|
||||
Section: "Unit",
|
||||
Name: "Description",
|
||||
Value: "FOO",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
|
||||
// Section mismatch
|
||||
{
|
||||
o1: &UnitOption{
|
||||
Section: "Unit",
|
||||
Name: "Description",
|
||||
Value: "FOO",
|
||||
},
|
||||
o2: &UnitOption{
|
||||
Section: "X-Other",
|
||||
Name: "Description",
|
||||
Value: "FOO",
|
||||
},
|
||||
match: false,
|
||||
},
|
||||
|
||||
// Name mismatch
|
||||
{
|
||||
o1: &UnitOption{
|
||||
Section: "Unit",
|
||||
Name: "Description",
|
||||
Value: "FOO",
|
||||
},
|
||||
o2: &UnitOption{
|
||||
Section: "Unit",
|
||||
Name: "BindsTo",
|
||||
Value: "FOO",
|
||||
},
|
||||
match: false,
|
||||
},
|
||||
|
||||
// Value mismatch
|
||||
{
|
||||
o1: &UnitOption{
|
||||
Section: "Unit",
|
||||
Name: "Description",
|
||||
Value: "FOO",
|
||||
},
|
||||
o2: &UnitOption{
|
||||
Section: "Unit",
|
||||
Name: "Description",
|
||||
Value: "BAR",
|
||||
},
|
||||
match: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
match := tt.o1.Match(tt.o2)
|
||||
if match != tt.match {
|
||||
t.Errorf("case %d: failed comparing o1 to o2 - expected match=%t, got %t", i, tt.match, match)
|
||||
}
|
||||
|
||||
match = tt.o2.Match(tt.o1)
|
||||
if match != tt.match {
|
||||
t.Errorf("case %d: failed comparing o2 to o1 - expected match=%t, got %t", i, tt.match, match)
|
||||
}
|
||||
}
|
||||
}
|
51
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/serialize.go
generated
vendored
Normal file
51
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/serialize.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Serialize encodes all of the given UnitOption objects into a unit file
|
||||
func Serialize(opts []*UnitOption) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if len(opts) == 0 {
|
||||
return &buf
|
||||
}
|
||||
|
||||
curSection := opts[0].Section
|
||||
|
||||
writeSectionHeader(&buf, curSection)
|
||||
writeNewline(&buf)
|
||||
|
||||
for _, opt := range opts {
|
||||
if opt.Section != curSection {
|
||||
curSection = opt.Section
|
||||
|
||||
writeNewline(&buf)
|
||||
writeSectionHeader(&buf, curSection)
|
||||
writeNewline(&buf)
|
||||
}
|
||||
|
||||
writeOption(&buf, opt)
|
||||
writeNewline(&buf)
|
||||
}
|
||||
|
||||
return &buf
|
||||
}
|
||||
|
||||
func writeNewline(buf *bytes.Buffer) {
|
||||
buf.WriteRune('\n')
|
||||
}
|
||||
|
||||
func writeSectionHeader(buf *bytes.Buffer, section string) {
|
||||
buf.WriteRune('[')
|
||||
buf.WriteString(section)
|
||||
buf.WriteRune(']')
|
||||
}
|
||||
|
||||
func writeOption(buf *bytes.Buffer, opt *UnitOption) {
|
||||
buf.WriteString(opt.Name)
|
||||
buf.WriteRune('=')
|
||||
buf.WriteString(opt.Value)
|
||||
}
|
134
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/serialize_test.go
generated
vendored
Normal file
134
Godeps/_workspace/src/github.com/coreos/go-systemd/unit/serialize_test.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSerialize(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []*UnitOption
|
||||
output string
|
||||
}{
|
||||
// no options results in empty file
|
||||
{
|
||||
[]*UnitOption{},
|
||||
``,
|
||||
},
|
||||
|
||||
// options with same section share the header
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Foo"},
|
||||
&UnitOption{"Unit", "BindsTo", "bar.service"},
|
||||
},
|
||||
`[Unit]
|
||||
Description=Foo
|
||||
BindsTo=bar.service
|
||||
`,
|
||||
},
|
||||
|
||||
// options with same name are not combined
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Foo"},
|
||||
&UnitOption{"Unit", "Description", "Bar"},
|
||||
},
|
||||
`[Unit]
|
||||
Description=Foo
|
||||
Description=Bar
|
||||
`,
|
||||
},
|
||||
|
||||
// multiple options printed under different section headers
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Foo"},
|
||||
&UnitOption{"Service", "ExecStart", "/usr/bin/sleep infinity"},
|
||||
},
|
||||
`[Unit]
|
||||
Description=Foo
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/sleep infinity
|
||||
`,
|
||||
},
|
||||
|
||||
// no optimization for unsorted options
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Foo"},
|
||||
&UnitOption{"Service", "ExecStart", "/usr/bin/sleep infinity"},
|
||||
&UnitOption{"Unit", "BindsTo", "bar.service"},
|
||||
},
|
||||
`[Unit]
|
||||
Description=Foo
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/sleep infinity
|
||||
|
||||
[Unit]
|
||||
BindsTo=bar.service
|
||||
`,
|
||||
},
|
||||
|
||||
// utf8 characters are not a problem
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"©", "µ☃", "ÇôrèÕ$"},
|
||||
},
|
||||
`[©]
|
||||
µ☃=ÇôrèÕ$
|
||||
`,
|
||||
},
|
||||
|
||||
// no verification is done on section names
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Un\nit", "Description", "Foo"},
|
||||
},
|
||||
`[Un
|
||||
it]
|
||||
Description=Foo
|
||||
`,
|
||||
},
|
||||
|
||||
// no verification is done on option names
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Desc\nription", "Foo"},
|
||||
},
|
||||
`[Unit]
|
||||
Desc
|
||||
ription=Foo
|
||||
`,
|
||||
},
|
||||
|
||||
// no verification is done on option values
|
||||
{
|
||||
[]*UnitOption{
|
||||
&UnitOption{"Unit", "Description", "Fo\no"},
|
||||
},
|
||||
`[Unit]
|
||||
Description=Fo
|
||||
o
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
outReader := Serialize(tt.input)
|
||||
outBytes, err := ioutil.ReadAll(outReader)
|
||||
if err != nil {
|
||||
t.Errorf("case %d: encountered error while reading output: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
output := string(outBytes)
|
||||
if tt.output != output {
|
||||
t.Errorf("case %d: incorrect output")
|
||||
t.Logf("Expected:\n%s", tt.output)
|
||||
t.Logf("Actual:\n%s", output)
|
||||
}
|
||||
}
|
||||
}
|
170
Godeps/_workspace/src/github.com/coreos/rkt/pkg/aci/aci.go
generated
vendored
Normal file
170
Godeps/_workspace/src/github.com/coreos/rkt/pkg/aci/aci.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright 2014 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package aci implements helper functions for working with ACIs
|
||||
package aci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/appc/spec/aci"
|
||||
"github.com/appc/spec/schema"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
)
|
||||
|
||||
type ACIEntry struct {
|
||||
Header *tar.Header
|
||||
Contents string
|
||||
}
|
||||
|
||||
type imageArchiveWriter struct {
|
||||
*tar.Writer
|
||||
am *schema.ImageManifest
|
||||
}
|
||||
|
||||
// NewImageWriter creates a new ArchiveWriter which will generate an App
|
||||
// Container Image based on the given manifest and write it to the given
|
||||
// tar.Writer
|
||||
// TODO(sgotti) this is a copy of appc/spec/aci.imageArchiveWriter with
|
||||
// addFileNow changed to create the file with the current user. needed for
|
||||
// testing as non root user.
|
||||
func NewImageWriter(am schema.ImageManifest, w *tar.Writer) aci.ArchiveWriter {
|
||||
aw := &imageArchiveWriter{
|
||||
w,
|
||||
&am,
|
||||
}
|
||||
return aw
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) AddFile(hdr *tar.Header, r io.Reader) error {
|
||||
err := aw.Writer.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
_, err := io.Copy(aw.Writer, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) addFileNow(path string, contents []byte) error {
|
||||
buf := bytes.NewBuffer(contents)
|
||||
now := time.Now()
|
||||
hdr := tar.Header{
|
||||
Name: path,
|
||||
Mode: 0644,
|
||||
Uid: os.Getuid(),
|
||||
Gid: os.Getgid(),
|
||||
Size: int64(buf.Len()),
|
||||
ModTime: now,
|
||||
Typeflag: tar.TypeReg,
|
||||
ChangeTime: now,
|
||||
}
|
||||
return aw.AddFile(&hdr, buf)
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) addManifest(name string, m json.Marshaler) error {
|
||||
out, err := m.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.addFileNow(name, out)
|
||||
}
|
||||
|
||||
func (aw *imageArchiveWriter) Close() error {
|
||||
if err := aw.addManifest(aci.ManifestFile, aw.am); err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.Writer.Close()
|
||||
}
|
||||
|
||||
// NewBasicACI creates a new ACI in the given directory with the given name.
|
||||
// Used for testing.
|
||||
func NewBasicACI(dir string, name string) (*os.File, error) {
|
||||
manifest := fmt.Sprintf(`{"acKind":"ImageManifest","acVersion":"0.5.4","name":"%s"}`, name)
|
||||
return NewACI(dir, manifest, nil)
|
||||
}
|
||||
|
||||
// NewACI creates a new ACI in the given directory with the given image
|
||||
// manifest and entries.
|
||||
// Used for testing.
|
||||
func NewACI(dir string, manifest string, entries []*ACIEntry) (*os.File, error) {
|
||||
var im schema.ImageManifest
|
||||
if err := im.UnmarshalJSON([]byte(manifest)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile(dir, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
tw := tar.NewWriter(tf)
|
||||
aw := NewImageWriter(im, tw)
|
||||
|
||||
for _, entry := range entries {
|
||||
// Add default mode
|
||||
if entry.Header.Mode == 0 {
|
||||
if entry.Header.Typeflag == tar.TypeDir {
|
||||
entry.Header.Mode = 0755
|
||||
} else {
|
||||
entry.Header.Mode = 0644
|
||||
}
|
||||
}
|
||||
// Add calling user uid and gid or tests will fail
|
||||
entry.Header.Uid = os.Getuid()
|
||||
entry.Header.Gid = os.Getgid()
|
||||
sr := strings.NewReader(entry.Contents)
|
||||
if err := aw.AddFile(entry.Header, sr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := aw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tf, nil
|
||||
}
|
||||
|
||||
// NewDetachedSignature creates a new openpgp armored detached signature for the given ACI
|
||||
// signed with armoredPrivateKey.
|
||||
func NewDetachedSignature(armoredPrivateKey string, aci io.Reader) (io.Reader, error) {
|
||||
entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(entityList) < 1 {
|
||||
return nil, errors.New("empty entity list")
|
||||
}
|
||||
signature := &bytes.Buffer{}
|
||||
if err := openpgp.ArmoredDetachSign(signature, entityList[0], aci, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signature, nil
|
||||
}
|
61
Godeps/_workspace/src/github.com/coreos/rkt/pkg/aci/render.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/coreos/rkt/pkg/aci/render.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
|
||||
"github.com/appc/spec/pkg/acirenderer"
|
||||
"github.com/appc/spec/schema/types"
|
||||
ptar "github.com/coreos/rkt/pkg/tar"
|
||||
)
|
||||
|
||||
// Given an imageID, start with the matching image available in the store,
|
||||
// build its dependency list and render it inside dir
|
||||
func RenderACIWithImageID(imageID types.Hash, dir string, ap acirenderer.ACIRegistry) error {
|
||||
renderedACI, err := acirenderer.GetRenderedACIWithImageID(imageID, ap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return renderImage(renderedACI, dir, ap)
|
||||
}
|
||||
|
||||
// Given an image app name and optional labels, get the best matching image
|
||||
// available in the store, build its dependency list and render it inside dir
|
||||
func RenderACI(name types.ACName, labels types.Labels, dir string, ap acirenderer.ACIRegistry) error {
|
||||
renderedACI, err := acirenderer.GetRenderedACI(name, labels, ap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return renderImage(renderedACI, dir, ap)
|
||||
}
|
||||
|
||||
// Given an already populated dependency list, it will extract, under the provided
|
||||
// directory, the rendered ACI
|
||||
func RenderACIFromList(imgs acirenderer.Images, dir string, ap acirenderer.ACIProvider) error {
|
||||
renderedACI, err := acirenderer.GetRenderedACIFromList(imgs, ap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return renderImage(renderedACI, dir, ap)
|
||||
}
|
||||
|
||||
// Given a RenderedACI, it will extract, under the provided directory, the
|
||||
// needed files from the right source ACI.
|
||||
// The manifest will be extracted from the upper ACI.
|
||||
// No file overwriting is done as it should usually be called
|
||||
// providing an empty directory.
|
||||
func renderImage(renderedACI acirenderer.RenderedACI, dir string, ap acirenderer.ACIProvider) error {
|
||||
for _, ra := range renderedACI {
|
||||
rs, err := ap.ReadStream(ra.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rs.Close()
|
||||
// Overwrite is not needed. If a file needs to be overwritten then the renderedACI builder has a bug
|
||||
if err := ptar.ExtractTar(tar.NewReader(rs), dir, false, ra.FileMap); err != nil {
|
||||
return fmt.Errorf("error extracting ACI: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
190
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir.go
generated
vendored
Normal file
190
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir.go
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2014 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package lock implements simple locking primitives on a
|
||||
// regular file or directory using flock
|
||||
package lock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
ErrNotExist = errors.New("file does not exist")
|
||||
ErrPermission = errors.New("permission denied")
|
||||
ErrNotRegular = errors.New("not a regular file")
|
||||
)
|
||||
|
||||
// FileLock represents a lock on a regular file or a directory
|
||||
type FileLock struct {
|
||||
path string
|
||||
fd int
|
||||
}
|
||||
|
||||
type LockType int
|
||||
|
||||
const (
|
||||
Dir LockType = iota
|
||||
RegFile
|
||||
)
|
||||
|
||||
// TryExclusiveLock takes an exclusive lock without blocking.
|
||||
// This is idempotent when the Lock already represents an exclusive lock,
|
||||
// and tries promote a shared lock to exclusive atomically.
|
||||
// It will return ErrLocked if any lock is already held.
|
||||
func (l *FileLock) TryExclusiveLock() error {
|
||||
err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err == syscall.EWOULDBLOCK {
|
||||
err = ErrLocked
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TryExclusiveLock takes an exclusive lock on a file/directory without blocking.
|
||||
// It will return ErrLocked if any lock is already held on the file/directory.
|
||||
func TryExclusiveLock(path string, lockType LockType) (*FileLock, error) {
|
||||
l, err := NewLock(path, lockType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = l.TryExclusiveLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, err
|
||||
}
|
||||
|
||||
// ExclusiveLock takes an exclusive lock.
|
||||
// This is idempotent when the Lock already represents an exclusive lock,
|
||||
// and promotes a shared lock to exclusive atomically.
|
||||
// It will block if an exclusive lock is already held.
|
||||
func (l *FileLock) ExclusiveLock() error {
|
||||
return syscall.Flock(l.fd, syscall.LOCK_EX)
|
||||
}
|
||||
|
||||
// ExclusiveLock takes an exclusive lock on a file/directory.
|
||||
// It will block if an exclusive lock is already held on the file/directory.
|
||||
func ExclusiveLock(path string, lockType LockType) (*FileLock, error) {
|
||||
l, err := NewLock(path, lockType)
|
||||
if err == nil {
|
||||
err = l.ExclusiveLock()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// TrySharedLock takes a co-operative (shared) lock without blocking.
|
||||
// This is idempotent when the Lock already represents a shared lock,
|
||||
// and tries demote an exclusive lock to shared atomically.
|
||||
// It will return ErrLocked if an exclusive lock already exists.
|
||||
func (l *FileLock) TrySharedLock() error {
|
||||
err := syscall.Flock(l.fd, syscall.LOCK_SH|syscall.LOCK_NB)
|
||||
if err == syscall.EWOULDBLOCK {
|
||||
err = ErrLocked
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TrySharedLock takes a co-operative (shared) lock on a file/directory without blocking.
|
||||
// It will return ErrLocked if an exclusive lock already exists on the file/directory.
|
||||
func TrySharedLock(path string, lockType LockType) (*FileLock, error) {
|
||||
l, err := NewLock(path, lockType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = l.TrySharedLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// SharedLock takes a co-operative (shared) lock on.
|
||||
// This is idempotent when the Lock already represents a shared lock,
|
||||
// and demotes an exclusive lock to shared atomically.
|
||||
// It will block if an exclusive lock is already held.
|
||||
func (l *FileLock) SharedLock() error {
|
||||
return syscall.Flock(l.fd, syscall.LOCK_SH)
|
||||
}
|
||||
|
||||
// SharedLock takes a co-operative (shared) lock on a file/directory.
|
||||
// It will block if an exclusive lock is already held on the file/directory.
|
||||
func SharedLock(path string, lockType LockType) (*FileLock, error) {
|
||||
l, err := NewLock(path, lockType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = l.SharedLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Unlock unlocks the lock
|
||||
func (l *FileLock) Unlock() error {
|
||||
return syscall.Flock(l.fd, syscall.LOCK_UN)
|
||||
}
|
||||
|
||||
// Fd returns the lock's file descriptor, or an error if the lock is closed
|
||||
func (l *FileLock) Fd() (int, error) {
|
||||
var err error
|
||||
if l.fd == -1 {
|
||||
err = errors.New("lock closed")
|
||||
}
|
||||
return l.fd, err
|
||||
}
|
||||
|
||||
// Close closes the lock which implicitly unlocks it as well
|
||||
func (l *FileLock) Close() error {
|
||||
fd := l.fd
|
||||
l.fd = -1
|
||||
return syscall.Close(fd)
|
||||
}
|
||||
|
||||
// NewLock opens a new lock on a file without acquisition
|
||||
func NewLock(path string, lockType LockType) (*FileLock, error) {
|
||||
l := &FileLock{path: path, fd: -1}
|
||||
|
||||
mode := syscall.O_RDONLY | syscall.O_CLOEXEC
|
||||
if lockType == Dir {
|
||||
mode |= syscall.O_DIRECTORY
|
||||
}
|
||||
lfd, err := syscall.Open(l.path, mode, 0)
|
||||
if err != nil {
|
||||
if err == syscall.ENOENT {
|
||||
err = ErrNotExist
|
||||
} else if err == syscall.EACCES {
|
||||
err = ErrPermission
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
l.fd = lfd
|
||||
|
||||
var stat syscall.Stat_t
|
||||
err = syscall.Fstat(lfd, &stat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check if the file is a regular file
|
||||
if lockType == RegFile && !(stat.Mode&syscall.S_IFMT == syscall.S_IFREG) {
|
||||
return nil, ErrNotRegular
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user