Remove VIC provider code.

The VIC provider is stale and there are no developers working on this
anymore.  Removing the provider from the repo.
This commit is contained in:
sflxn
2019-05-06 15:57:24 -07:00
parent cfa37871ab
commit 4feab78b76
1113 changed files with 3 additions and 246759 deletions

View File

@@ -51,10 +51,6 @@ Onur Filiz (onfiliz@amazon.com)
Fei Xu (xufei40@huawei.com)
**VIC**
Loc Nguyen (nloc@vmware.com)
**Azure Batch**
Lawrence Gripper (lawrence.gripper@microsoft.com)

465
Gopkg.lock generated
View File

@@ -30,17 +30,6 @@
revision = "6d20bdbae88c06c36d72eb512295417693bfdf4e"
version = "v21.1.0"
[[projects]]
branch = "master"
digest = "1:81f8c061c3d18ed1710957910542bc17d2b789c6cd19e0f654c30b35fd255ca5"
name = "github.com/Azure/go-ansiterm"
packages = [
".",
"winterm",
]
pruneopts = "NUT"
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]]
digest = "1:f5ebea42286dc1adc789f62e7718426505370cf64b9c8a17775952037b1e2c87"
name = "github.com/Azure/go-autorest"
@@ -67,30 +56,6 @@
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
version = "v0.3.0"
[[projects]]
digest = "1:d4b3bb952a228f05d52a58f8ffc39d5edf5b8f1bd291ac82f0249ffa31c509f5"
name = "github.com/Microsoft/go-winio"
packages = ["."]
pruneopts = "NUT"
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
version = "v0.4.7"
[[projects]]
digest = "1:d8ebbd207f3d3266d4423ce4860c9f3794956306ded6c7ba312ecc69cdfbf04c"
name = "github.com/PuerkitoBio/purell"
packages = ["."]
pruneopts = "NUT"
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:8098cd40cd09879efbf12e33bcd51ead4a66006ac802cd563a66c4f3373b9727"
name = "github.com/PuerkitoBio/urlesc"
packages = ["."]
pruneopts = "NUT"
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
[[projects]]
digest = "1:6989062eb7ccf25cf38bf4fe3dba097ee209f896cda42cefdca3927047bef7b6"
name = "github.com/Sirupsen/logrus"
@@ -125,14 +90,6 @@
pruneopts = "NUT"
revision = "f0300d1749da6fa982027e449ec0c7a145510c3c"
[[projects]]
digest = "1:5a23cd3a5496a0b2da7e3b348d14e77b11a210738c398200d7d6f04ea8cf3bd8"
name = "github.com/asaskevich/govalidator"
packages = ["."]
pruneopts = "NUT"
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
version = "v9"
[[projects]]
digest = "1:2d9c38febaba966bfedd651a9b2f60a2d80423a731430eea2c4331e86ae7d9a7"
name = "github.com/aws/aws-sdk-go"
@@ -232,102 +189,6 @@
pruneopts = "NUT"
revision = "6c6132ff69f0f6c088739067407b5d32c52e1d0f"
[[projects]]
digest = "1:182a67996c78d0ee343dbba889378c35513d428ac31432cd0cf3c9e902abc3c4"
name = "github.com/docker/distribution"
packages = [
".",
"context",
"digest",
"reference",
"uuid",
]
pruneopts = "NUT"
revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89"
version = "v2.6.2"
[[projects]]
digest = "1:9bbbe90fa9b5ebe3ee2e4d369c471dd60e54542a9fc4877d09e1b0876cbb986d"
name = "github.com/docker/docker"
packages = [
"api/errors",
"api/types",
"api/types/backend",
"api/types/blkiodev",
"api/types/container",
"api/types/filters",
"api/types/mount",
"api/types/network",
"api/types/registry",
"api/types/strslice",
"api/types/swarm",
"api/types/versions",
"daemon/graphdriver",
"image",
"image/v1",
"layer",
"oci",
"pkg/archive",
"pkg/chrootarchive",
"pkg/fileutils",
"pkg/idtools",
"pkg/ioutils",
"pkg/jsonlog",
"pkg/jsonmessage",
"pkg/longpath",
"pkg/mount",
"pkg/plugingetter",
"pkg/plugins",
"pkg/plugins/transport",
"pkg/pools",
"pkg/progress",
"pkg/promise",
"pkg/pubsub",
"pkg/random",
"pkg/reexec",
"pkg/streamformatter",
"pkg/stringid",
"pkg/system",
"pkg/term",
"pkg/term/windows",
"pkg/truncindex",
"plugin/v2",
"reference",
]
pruneopts = "NUT"
revision = "49bf474f9ed7ce7143a59d1964ff7b7fd9b52178"
[[projects]]
digest = "1:f133477f38c590bdcd6fc534617df17983f7a21e5b686d4a3495abeb21c631ec"
name = "github.com/docker/go-connections"
packages = [
"nat",
"sockets",
"tlsconfig",
]
pruneopts = "NUT"
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
version = "v0.3.0"
[[projects]]
digest = "1:4340101f42556a9cb2f7a360a0e95a019bfef6247d92e6c4c46f2433cf86a482"
name = "github.com/docker/go-units"
packages = ["."]
pruneopts = "NUT"
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
version = "v0.3.3"
[[projects]]
digest = "1:99fde7697aae0c6aaf4863353ebbbf2ec52f4e5fb7cb9baa723fa91a8996634f"
name = "github.com/docker/libnetwork"
packages = [
"iptables",
"portallocator",
]
pruneopts = "NUT"
revision = "1ee720e18fe98dceda6039bdd005ffbcb359d343"
version = "v0.5.6"
[[projects]]
branch = "master"
digest = "1:da25cf063072a10461c19320e82117d85f9d60be4c95a62bc8d5a49acf7d0ca5"
@@ -355,94 +216,6 @@
revision = "6529cf7c58879c08d927016dde4477f18a0634cb"
version = "v1.36.0"
[[projects]]
digest = "1:a5ed113e47732685c9c0cf527b29b51364f42064e3f602b270e0c06c182b403a"
name = "github.com/go-openapi/analysis"
packages = ["."]
pruneopts = "NUT"
revision = "d5a75b7d751ca3f11ad5d93cfe97405f2c3f6a47"
[[projects]]
digest = "1:9839ea727da3bb175c788768f6236e189502af3dc49facd1a4af0eb12b6a3f74"
name = "github.com/go-openapi/errors"
packages = ["."]
pruneopts = "NUT"
revision = "fc3f73a224499b047eda7191e5d22e1e9631e86f"
[[projects]]
branch = "master"
digest = "1:260f7ebefc63024c8dfe2c9f1a2935a89fa4213637a1f522f592f80c001cc441"
name = "github.com/go-openapi/jsonpointer"
packages = ["."]
pruneopts = "NUT"
revision = "779f45308c19820f1a69e9a4cd965f496e0da10f"
[[projects]]
branch = "master"
digest = "1:98abd61947ff5c7c6fcfec5473d02a4821ed3a2dd99a4fbfdb7925b0dd745546"
name = "github.com/go-openapi/jsonreference"
packages = ["."]
pruneopts = "NUT"
revision = "36d33bfe519efae5632669801b180bf1a245da3b"
[[projects]]
digest = "1:9950e28fe5089a160efe645f3e465db93cf93624863b6be8b6d9fff5fff42c50"
name = "github.com/go-openapi/loads"
packages = [
".",
"fmts",
]
pruneopts = "NUT"
revision = "6bb6486231e079ea125c0f39994ed3d0c53399ed"
[[projects]]
digest = "1:d7e44f8be1ed14889ccbaf60d94c1584fc4d5d875d1e6aeca5655d48b9ac936f"
name = "github.com/go-openapi/runtime"
packages = [
".",
"client",
]
pruneopts = "NUT"
revision = "3b13ebb46790d871d74a6c2450fa4b1280f90854"
[[projects]]
branch = "master"
digest = "1:178729f9dda60fa63034db9f8b015fe296dce604011629346dd2f43f7559c6fe"
name = "github.com/go-openapi/spec"
packages = ["."]
pruneopts = "NUT"
revision = "bfb48d37839bcacb52be3f539ffac5abcda7e195"
[[projects]]
digest = "1:92d0a87fc62ffd2c7da0e4619923dd28b563ccf2c2247cef8e271a27966f5999"
name = "github.com/go-openapi/strfmt"
packages = ["."]
pruneopts = "NUT"
revision = "0cb3db44c13bad3b3f567b762a66751972a310cc"
[[projects]]
branch = "master"
digest = "1:774bcdb2dd9f44ca7e31a1e7416f157f3d3a7fabd13dbb4b0d428976d3132872"
name = "github.com/go-openapi/swag"
packages = ["."]
pruneopts = "NUT"
revision = "cf0bdb963811675a4d7e74901cefc7411a1df939"
[[projects]]
digest = "1:3fb29099436d9eb621797ec58e9e54573ecbb9223927a56bac16d9f06a2b2e10"
name = "github.com/go-openapi/validate"
packages = ["."]
pruneopts = "NUT"
revision = "035dcd74f1f61e83debe1c22950dc53556e7e4b2"
[[projects]]
digest = "1:ca3369c0fc8d471d8698f85a37a4f8c98a847402681a31431fb87a84fa2e5373"
name = "github.com/godbus/dbus"
packages = ["."]
pruneopts = "NUT"
revision = "a389bdde4dd695d414e47b755e95e72b7826432c"
version = "v4.1.0"
[[projects]]
digest = "1:acba7f3d06725b78c6999c2ba6c40b80539c10346fe37b4681072f1666c87d0c"
name = "github.com/gogo/protobuf"
@@ -798,22 +571,6 @@
revision = "1624edc4454b8682399def8740d46db5e4362ba4"
version = "v1.1.5"
[[projects]]
branch = "master"
digest = "1:7b21c7fc5551b46d1308b4ffa9e9e49b66c7a8b0ba88c0130474b0e7a20d859f"
name = "github.com/kr/pretty"
packages = ["."]
pruneopts = "NUT"
revision = "cfb55aafdaf3ec08f0db22699ab822c50091b1c4"
[[projects]]
branch = "master"
digest = "1:c3a7836b5904db0f8b609595b619916a6831cb35b8b714aec39f96d00c6155d8"
name = "github.com/kr/text"
packages = ["."]
pruneopts = "NUT"
revision = "7cafcd837844e784b526369c9bce262804aebc60"
[[projects]]
digest = "1:648ad8414e400ae89650dea8db781f129d14cb4cc70a271fdcbb6fb0ede91883"
name = "github.com/lawrencegripper/pod2docker"
@@ -822,18 +579,6 @@
revision = "9a9182c0c682798fe214fa11df7bfca83048d433"
version = "v0.5.1"
[[projects]]
branch = "master"
digest = "1:ada518b8c338e10e0afa443d84671476d3bd1d926e13713938088e8ddbee1a3e"
name = "github.com/mailru/easyjson"
packages = [
"buffer",
"jlexer",
"jwriter",
]
pruneopts = "NUT"
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
[[projects]]
digest = "1:1e5a8bf6fb005f1808852ad1a4bedb7b4b3291af90dec12c4c9e4a955a42c9b2"
name = "github.com/mitchellh/copystructure"
@@ -898,26 +643,6 @@
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
version = "1.0.1"
[[projects]]
digest = "1:5c29878bf9db5a7f960bdf06f01e92d4f0836e2fb0164f254759d19596d81c22"
name = "github.com/opencontainers/runc"
packages = [
"libcontainer/configs",
"libcontainer/devices",
"libcontainer/system",
"libcontainer/user",
]
pruneopts = "NUT"
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
version = "v0.1.1"
[[projects]]
digest = "1:74baf4ab7cce59e4b716a3820a05cb43bbd7640e3e085ee9c84d0f268fca97b3"
name = "github.com/opencontainers/runtime-spec"
packages = ["specs-go"]
pruneopts = "NUT"
revision = "1c7c27d043c2a5e513a44084d2b10d77d1402b8c"
[[projects]]
digest = "1:cce3a18fb0b96b5015cd8ca03a57d20a662679de03c4dc4b6ff5f17ea2050fa6"
name = "github.com/pborman/uuid"
@@ -961,14 +686,6 @@
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "NUT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:cb24eec7a9478395847671abfbea162885f0be9c7ff6ef20b699dc20804ae1a4"
name = "github.com/ryanuber/go-glob"
@@ -1001,33 +718,6 @@
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:10301358a67805684f6b525cba6ad7ec014dbd56cccc2926fadc9189faa7889a"
name = "github.com/stretchr/objx"
packages = ["."]
pruneopts = "NUT"
revision = "facf9a85c22f48d2f52f2380e4efce1768749a89"
version = "v0.1"
[[projects]]
digest = "1:6a02f7a43a434d20225f8fef98694f3ceda5a3dad2376ffb07e3b2f201fab9a3"
name = "github.com/stretchr/testify"
packages = [
"assert",
"mock",
]
pruneopts = "NUT"
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
digest = "1:d9eb1d964a449cf51455fdac2ec3451c664af8e8b93c23d9c31d22b84ea82fde"
name = "github.com/tchap/go-patricia"
packages = ["patricia"]
pruneopts = "NUT"
revision = "666120de432aea38ab06bd5c818f04f4129882c9"
version = "v2.2.6"
[[projects]]
digest = "1:c20cebd4c498afb08178e3cb7e550762ee6d27663a81eb69436c4198c1d07cb4"
name = "github.com/ugorji/go"
@@ -1035,18 +725,6 @@
pruneopts = "NUT"
revision = "0053ebfd9d0ee06ccefbfe17072021e1d4acebee"
[[projects]]
digest = "1:c50d03312be5e5914ccb213c500c92d0231c6364bd5f968bc6b756ba7d99f1b0"
name = "github.com/vbatts/tar-split"
packages = [
"archive/tar",
"tar/asm",
"tar/storage",
]
pruneopts = "NUT"
revision = "38ec4ddb06dedbea0a895c4848b248eb38af221b"
version = "v0.10.2"
[[projects]]
digest = "1:868e54a0d7eefb6a5fc88b7379c56ea70d50fb2c7905690f83bb9b7474a321e1"
name = "github.com/virtual-kubelet/azure-aci"
@@ -1060,114 +738,6 @@
revision = "a846478f02b6b4219bf5bd8ea5d608513ef623f7"
version = "v0.2.0"
[[projects]]
digest = "1:a707ec1c12a88afc978307bca7f40bde7bd6b6434ceee41f8d7c6985e245dbdb"
name = "github.com/vishvananda/netlink"
packages = [
".",
"nl",
]
pruneopts = "NUT"
revision = "482f7a52b758233521878cb6c5904b6bd63f3457"
[[projects]]
branch = "master"
digest = "1:cfa9b2092e9b5f46feaa6c0d7a957618cd74d26cb2a72e3638f3ead773b23854"
name = "github.com/vishvananda/netns"
packages = ["."]
pruneopts = "NUT"
revision = "be1fbeda19366dea804f00efff2dd73a1642fdcc"
[[projects]]
digest = "1:f19aa8b1f1aafc3780fb3d51ee193ef71e5395daf2402dc8fa3d0d3ef2908432"
name = "github.com/vmware/govmomi"
packages = [
".",
"find",
"list",
"nfc",
"object",
"performance",
"property",
"session",
"task",
"vim25",
"vim25/debug",
"vim25/methods",
"vim25/mo",
"vim25/progress",
"vim25/soap",
"vim25/types",
"vim25/xml",
]
pruneopts = "NUT"
revision = "123ed177021588bac57b5c87c1a84270ddf2eca8"
version = "v0.17.1"
[[projects]]
branch = "feature/wolfpack"
digest = "1:79b933ce82b0a5da2678624d52d714deff91854904e8649889dc83be36907dc5"
name = "github.com/vmware/vic"
packages = [
"lib/apiservers/engine/backends/cache",
"lib/apiservers/engine/backends/container",
"lib/apiservers/engine/backends/convert",
"lib/apiservers/engine/backends/endpoint",
"lib/apiservers/engine/backends/filter",
"lib/apiservers/engine/backends/kv",
"lib/apiservers/engine/backends/portmap",
"lib/apiservers/engine/constants",
"lib/apiservers/engine/errors",
"lib/apiservers/engine/network",
"lib/apiservers/engine/proxy",
"lib/apiservers/portlayer/client",
"lib/apiservers/portlayer/client/containers",
"lib/apiservers/portlayer/client/events",
"lib/apiservers/portlayer/client/interaction",
"lib/apiservers/portlayer/client/kv",
"lib/apiservers/portlayer/client/logging",
"lib/apiservers/portlayer/client/misc",
"lib/apiservers/portlayer/client/scopes",
"lib/apiservers/portlayer/client/storage",
"lib/apiservers/portlayer/client/tasks",
"lib/apiservers/portlayer/models",
"lib/archive",
"lib/config",
"lib/config/executor",
"lib/constants",
"lib/metadata",
"lib/migration/feature",
"pkg/certificate",
"pkg/dio",
"pkg/errors",
"pkg/ip",
"pkg/log",
"pkg/log/syslog",
"pkg/retry",
"pkg/trace",
"pkg/version",
"pkg/vsphere/extraconfig",
"pkg/vsphere/performance",
"pkg/vsphere/session",
"pkg/vsphere/sys",
]
pruneopts = "NUT"
revision = "c7d40ac878b09c577c307bc5331c7dd39619ed7c"
[[projects]]
branch = "master"
digest = "1:3f8f243b8b4ebd09c52535a2ed7c96953bad2702c4c4b3356d9ccf1a81c0a944"
name = "github.com/vmware/vmw-guestinfo"
packages = [
"bdoor",
"message",
"rpcout",
"rpcvmx",
"vmcheck",
]
pruneopts = "NUT"
revision = "25eff159a728be87e103a0b8045e08273f4dbec4"
[[projects]]
digest = "1:a3cb0432171318ddfe4c7cc40c6edf94cb6a61859ffa5cec27f4d5162dfc4db7"
name = "go.opencensus.io"
@@ -1197,16 +767,12 @@
[[projects]]
branch = "master"
digest = "1:4906a472dae724cd9a033dd55eba6e3ad0deb45e76e054697b2739786459a62f"
digest = "1:79847878995c2d6116f60bfda3346b9ed1e2b214208d991bdfcce2bb25b1cf2f"
name = "golang.org/x/crypto"
packages = [
"blake2b",
"internal/subtle",
"nacl/secretbox",
"pkcs12",
"pkcs12/internal/rc2",
"poly1305",
"salsa20/salsa",
"ssh/terminal",
]
pruneopts = "NUT"
@@ -1214,7 +780,7 @@
[[projects]]
branch = "master"
digest = "1:cae7a492e42f53d7b8805f65a3da384a423129259718f6cab454f76ae233efb6"
digest = "1:0b615b6917f52a3f2a93eb930bdc05b7c61c9ac9d1a630cf2128b5c505264e71"
name = "golang.org/x/net"
packages = [
"context",
@@ -1223,9 +789,7 @@
"http2",
"http2/hpack",
"idna",
"internal/socks",
"internal/timeseries",
"proxy",
"trace",
"websocket",
]
@@ -1267,7 +831,7 @@
revision = "7dfd1290c7917b7ba22824b9d24954ab3002fe24"
[[projects]]
digest = "1:e33513a825fcd765e97b5de639a2f7547542d1a8245df0cef18e1fd390b778a9"
digest = "1:e7071ed636b5422cc51c0e3a6cebc229d6c9fffc528814b519a980641422d619"
name = "golang.org/x/text"
packages = [
"collate",
@@ -1284,7 +848,6 @@
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
"width",
]
pruneopts = "NUT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
@@ -1764,7 +1327,6 @@
"github.com/cpuguy83/strongerrors",
"github.com/cpuguy83/strongerrors/status",
"github.com/cpuguy83/strongerrors/status/ocstatus",
"github.com/docker/docker/api/types/strslice",
"github.com/google/go-cmp/cmp",
"github.com/google/uuid",
"github.com/gophercloud/gophercloud",
@@ -1775,35 +1337,15 @@
"github.com/gorilla/websocket",
"github.com/hashicorp/nomad/api",
"github.com/hashicorp/nomad/testutil",
"github.com/kr/pretty",
"github.com/lawrencegripper/pod2docker",
"github.com/mitchellh/go-homedir",
"github.com/pkg/errors",
"github.com/spf13/cobra",
"github.com/spf13/pflag",
"github.com/stretchr/testify/mock",
"github.com/virtual-kubelet/azure-aci/client",
"github.com/virtual-kubelet/azure-aci/client/aci",
"github.com/virtual-kubelet/azure-aci/client/api",
"github.com/virtual-kubelet/azure-aci/client/network",
"github.com/vmware/vic/lib/apiservers/engine/backends/cache",
"github.com/vmware/vic/lib/apiservers/engine/errors",
"github.com/vmware/vic/lib/apiservers/engine/proxy",
"github.com/vmware/vic/lib/apiservers/portlayer/client",
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers",
"github.com/vmware/vic/lib/apiservers/portlayer/client/interaction",
"github.com/vmware/vic/lib/apiservers/portlayer/client/logging",
"github.com/vmware/vic/lib/apiservers/portlayer/client/scopes",
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage",
"github.com/vmware/vic/lib/apiservers/portlayer/client/tasks",
"github.com/vmware/vic/lib/apiservers/portlayer/models",
"github.com/vmware/vic/lib/constants",
"github.com/vmware/vic/lib/metadata",
"github.com/vmware/vic/pkg/dio",
"github.com/vmware/vic/pkg/log",
"github.com/vmware/vic/pkg/retry",
"github.com/vmware/vic/pkg/trace",
"github.com/vmware/vic/pkg/vsphere/sys",
"go.opencensus.io/exporter/jaeger",
"go.opencensus.io/plugin/ochttp",
"go.opencensus.io/plugin/ochttp/propagation/b3",
@@ -1812,7 +1354,6 @@
"golang.org/x/net/context",
"golang.org/x/sync/errgroup",
"google.golang.org/grpc",
"gopkg.in/yaml.v2",
"gotest.tools/assert",
"gotest.tools/assert/cmp",
"k8s.io/api/coordination/v1beta1",

View File

@@ -74,10 +74,6 @@
name = "github.com/cpuguy83/strongerrors"
version = "0.2.1"
[[constraint]]
branch = "feature/wolfpack"
name = "github.com/vmware/vic"
[[override]]
name = "github.com/docker/docker"
revision = "49bf474f9ed7ce7143a59d1964ff7b7fd9b52178"

View File

@@ -1,16 +0,0 @@
// +build linux,vic_provider
package register
import (
"github.com/virtual-kubelet/virtual-kubelet/providers"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic"
)
func init() {
register("vic", initVic)
}
func initVic(cfg InitConfig) (providers.Provider, error) {
return vic.NewVicProvider(cfg.ConfigPath, cfg.ResourceManager, cfg.NodeName, cfg.OperatingSystem)
}

View File

@@ -1,80 +0,0 @@
# vSphere Integrated Containers virtual kubelet provider
This is a very early preview of the [VMware vSphere Integrated Containers](https://github.com/vmware/vic) virtual kubelet provider.
## Introduction to vSphere Integrated Containers
For those who are unfamiliar with vSphere Integrated Containers, it is a open source project that enables users to create a CaaS (container as a service) endpoint on a vSphere cluster. There are a few advantages of using vSphere Integrated Containers over using standard Docker in a VM:
1. Containers are wrapped in VMs and provides stronger isolation than a standard OS containers.
1. Use of vSphere Integrated Containers removes the need to deploy a host VM, OS, and docker software. It also removes the requirement to maintain that stack. An administrator can simply deploy a VCH (virtual container host) and get back a docker endpoint. With this endpoint, users can deploy containers. vSphere manages the endpoint.
1. The VCH can span a vCenter cluster, providing a much larger virtual host than any VM or physical machine on which users can deploy their containers. Users can deploy containers across the entire vCenter cluster using this single virtual host.
## Virtual Kubelet in vSphere Integrated Containers
Within a deployed VCH, there is a daemon that provides a Docker compatible endpoint. We refer to this daemon as a Docker personality server. With the vSphere integrated Container virtual kubelet provider, we are introducing a second personality server that provides a Kubelet compatible endpoint for the VCH. With this endpoint, the VCH can join a kubernetes cluster, providing a robust virtual node for pods. Just as the VCH provides a CaaS endpoint for Docker users, it will now provide a Pod-as-a-Service endpoint for kubernetes users. vSphere manages the availability of the pods and remove the need for administrators to maintain the uptime of a normal node, it's software, and the pods running on it. For the user, it provides a much larger virtual node than any single VM or physical machine.
![VIC with virtual kubelet](./vic_kubelet_design.png)
## Pod VM in vSphere Integrated Containers
With the vSphere integrated Container virtual kubelet provider, we are also introducing a new isolation concept called the pod vm. With the original vSphere Integrated Containers, we introduced stronger container isolation through our container vm concept. Interested parties may go to the vSphere Integrated Containers github project page to read all about it. A standard pod in a standard kubernetes node is just a collection of containers with a parent container. The containers are isolated by the OS, but the pod is not. With vSphere Integrated Containers, pods are strongly isolated within a pod vm. Each pod runs within it's own pod vm.
![Pod VM Basic Design](./podvm_basic_design.png)
## Project Status
This is an early preview. The project relies on a feature branch within the vSphere Integrated Containers project. Currently, pods can be created and removed. Some node statuses can be queried. This provider reuses vSphere Integrated Containers to provision pods. As such, there is a dependency between this provider and the vSphere Integrated Containers project. All changes to that project are currently being done on a feature branch, named wolfpack.
There are a lot of kubelet features still undefined in the virtual-kubelet project, such as networking, volumes, exec, logging, etc. As these are defined, we will add this support into our provider and into vSphere Integrated Containers.
## Building vSphere Integrated Containers with virtual kubelet support
This project and the associated vSphere Integrated Containers project are in active development. The simplest way to utilize this virtual kubelet provider, is to build the kubelet project and the wolfpack feature branch of vSphere Integrated Containers. The following is a step by step instructions on building both. There is an asciinema playback of this process below. The current instructions assumes your development environment is Linux.
First, ensure golang 1.8.x and git are installed on your machine. The vSphere Integrated Containers project checks for go 1.8. Next, perform the following steps
1. make sure you have go 1.8.x and prepare your GOPATH
* $> `go version`
* $> `mkdir -p go/src/github.com/virtual-kubelet`
1. get and build the virtual kubelet
* $> `cd go/src/github.com/virtual-kubelet`
* $> `git clone https://github.com/virtual-kubelet/virtual-kubelet.git`
* $> `cd virtual-kubelet`
* $> `go build .`
1. get wolfpack feature branch of vSphere Integrated Containers
* $> `cd`
* $> `mkdir -p go/src/github.com/vmware`
* $> `cd $GOPATH/src/github.com/vmware`
* $> `git clone https://github.com/vmware/vic`
* $> `cd vic`
* $> `git checkout feature/wolfpack`
1. build vSphere Integrated Containers **as root**
* $> `cd $GOPATH/src/github.com/vmware/vic`
* $> `sudo su`
* $> `export GOPATH=/home/[user]/go`
* $> `export PATH=$PATH:/usr/local/go/bin:/home/[user]/go/bin`
* $> `export VIRTUAL_KUBELET_PATH=$GOPATH/src/github.com/virtual-kubelet/virtual-kubelet/virtual-kubelet`
* $> `make most-vkubelet`
* $> `chown -R user:user *`
* $> `exit` (exit root)
You should now have a **bin** folder in the vic folder. This contains all the necessary assets to deploy a VCH using the vic-machine CLI.
[Asciinema recording of the build steps](https://asciinema.org/a/oeGbhPmKWqVgWeQxCOPLHCcYN)
## Usage
To get started with the vSphere Integrated Containers' virtual kubelet, users should first familiarize themselves with deploying vSphere Integrated Containers and using the VCH as a docker endpoint. We're leveraging the same deployment model with the virtual kubelet. Administrators deploy a VCH, using the vic-machine CLI or [VIC Appliance](https://github.com/vmware/vic-product), specifying the kubernetes cluster that the VCH should join.
Virtual Container Hosts should be deployed with the proper CLI flags to enable virtual kubelet, including both `--k8s-server-address` and `--k8s-config`.
Once the VCH is fully started, it automatically joins the cluster. Administrators can then apply taints or labels to the virtual node to create node affinity. Pods created with the right toleration or nodeSelector will then be deployed onto the VCH.
1. Get IP or FQDN of your kubernetes master node
2. Get location of your kube config you use for kubectl (usually in $HOME/.kube/config)
3. Deploy a virtual container host with vic-machine.
4. $> `kubectl get nodes`
5. Deploy a pod
[Asciinema recording of an example usage](https://asciinema.org/a/nArPOJSKWJwx09UsJVUFiI2y7)

View File

@@ -1,162 +0,0 @@
package cache
import (
"fmt"
"sync"
"github.com/vmware/vic/pkg/trace"
vicpod "github.com/virtual-kubelet/virtual-kubelet/providers/vic/pod"
)
type PodCache interface {
Rehydrate(op trace.Operation) error
Get(op trace.Operation, namespace, name string) (*vicpod.VicPod, error)
GetAll(op trace.Operation) []*vicpod.VicPod
Add(op trace.Operation, namespace, name string, pod *vicpod.VicPod) error
Delete(op trace.Operation, namespace, name string) error
}
type VicPodCache struct {
cache map[string]*vicpod.VicPod
lock sync.Mutex
}
type CacheError string
func (c CacheError) Error() string { return string(c) }
const (
PodCachePodNameError = CacheError("PodCache called with empty pod name")
PodCacheNilPodError = CacheError("PodCache called with nil pod")
)
func NewVicPodCache() PodCache {
v := &VicPodCache{}
v.cache = make(map[string]*vicpod.VicPod, 0)
return v
}
// Rehydrate replenishes the cache in the event of a virtual kubelet restart.
// NOT YET IMPLEMENTED
//
// arguments:
// op operation trace logger
// returns:
// error
func (v *VicPodCache) Rehydrate(op trace.Operation) error {
return nil
}
// Get returns the pod definition for a running pod
//
// arguments:
// op operation trace logger
// namespace namespace of the pod. Empty namespace assumes default.
// name name of the pod
// returns:
// error
func (v *VicPodCache) Get(op trace.Operation, namespace, name string) (*vicpod.VicPod, error) {
defer trace.End(trace.Begin(name, op))
if name == "" {
op.Errorf(PodCachePodNameError.Error())
return nil, PodCachePodNameError
}
//TODO: handle namespaces
pod, ok := v.cache[name]
if !ok {
err := fmt.Errorf("Pod %s not found in cache", name)
op.Info(err)
return nil, err
}
return pod, nil
}
// GetAll returns the pod definitions for all running pods
//
// arguments:
// op operation trace logger
// returns:
// error
func (v *VicPodCache) GetAll(op trace.Operation) []*vicpod.VicPod {
defer trace.End(trace.Begin("", op))
defer v.lock.Unlock()
v.lock.Lock()
list := make([]*vicpod.VicPod, 0)
for _, vp := range v.cache {
list = append(list, vp)
}
return list
}
// Add saves the pod definition of a running pod
//
// arguments:
// op operation trace logger
// namespace namespace of the pod. Empty namespace assumes default.
// name name of the pod
// pod pod definition
// returns:
// error
func (v *VicPodCache) Add(op trace.Operation, namespace, name string, pod *vicpod.VicPod) error {
defer trace.End(trace.Begin(name, op))
defer v.lock.Unlock()
v.lock.Lock()
if name == "" {
op.Errorf(PodCachePodNameError.Error())
return PodCachePodNameError
}
if pod == nil {
op.Errorf(PodCacheNilPodError.Error())
return PodCacheNilPodError
}
//TODO: handle namespaces
_, ok := v.cache[name]
if ok {
err := fmt.Errorf("Pod %s already cached. Duplicate pod.", name)
op.Error(err)
return err
}
v.cache[name] = pod
return nil
}
// Delete removes a pod definition from the cache. It does not stop/delete the
// actual pod.
//
// arguments:
// op operation trace logger
// namespace namespace of the pod. Empty namespace assumes default.
// name name of the pod
// returns:
// error
func (v *VicPodCache) Delete(op trace.Operation, namespace, name string) error {
defer trace.End(trace.Begin(name, op))
defer v.lock.Unlock()
v.lock.Lock()
if name == "" {
op.Errorf(PodCachePodNameError.Error())
return PodCachePodNameError
}
//TODO: handle namespaces
delete(v.cache, name)
return nil
}

View File

@@ -1,139 +0,0 @@
package cache
import (
"context"
"testing"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/pod"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
v1 "k8s.io/api/core/v1"
"github.com/vmware/vic/pkg/trace"
)
var (
testpod *v1.Pod
)
func init() {
testpod = &v1.Pod{}
}
func setup(t *testing.T, op trace.Operation) PodCache {
c := NewVicPodCache()
assert.Check(t, c != nil, "NewPod did not return a valid cache")
//populate with dummy data
vp := pod.VicPod{
ID: "123",
Pod: testpod,
}
c.Add(op, "namespace1", "testpod1a", &vp)
c.Add(op, "namespace1", "testpod1b", &vp)
c.Add(op, "namespace2", "testpod2a", &vp)
c.Add(op, "namespace2", "testpod2b", &vp)
return c
}
func TestRehydrate(t *testing.T) {
op := trace.NewOperation(context.Background(), "")
c := NewVicPodCache()
assert.Check(t, c != nil, "NewPod did not return a valid cache")
err := c.Rehydrate(op)
assert.Check(t, err, "PodCache.Rehydrate failed with error: %s", err)
}
func TestAdd(t *testing.T) {
var err error
op := trace.NewOperation(context.Background(), "")
c := NewVicPodCache()
assert.Check(t, c != nil, "NewPod did not return a valid cache")
//populate with dummy data
vp := pod.VicPod{
ID: "123",
Pod: testpod,
}
// Positive cases
err = c.Add(op, "namespace1", "testpod1a", &vp)
assert.Check(t, err, "PodCache.Add failed with error: %s", err)
// Negative cases
err = c.Add(op, "namespace1", "", &vp)
assert.Check(t, err != nil, "PodCache.Add expected error for empty name")
assert.Check(t, is.DeepEqual(err, PodCachePodNameError))
err = c.Add(op, "namespace1", "test2", nil)
assert.Check(t, err != nil, "PodCache.Add expected error for nil pod")
assert.Check(t, is.DeepEqual(err, PodCacheNilPodError))
}
func TestGet(t *testing.T) {
var err error
var vpod *pod.VicPod
op := trace.NewOperation(context.Background(), "")
c := setup(t, op)
// Positive cases
vpod, err = c.Get(op, "namespace1", "testpod1a")
assert.Check(t, err, "PodCache.Get failed with error: %s", err)
assert.Check(t, vpod != nil, "PodCache.Get expected to return non-nil pod but received nil")
vpod, err = c.Get(op, "namespace2", "testpod2a")
assert.Check(t, err, "PodCache.Get failed with error: %s", err)
assert.Check(t, vpod != nil, "PodCache.Get expected to return non-nil pod but received nil")
// Negative cases
vpod, err = c.Get(op, "namespace1", "")
assert.Check(t, is.DeepEqual(err, PodCachePodNameError))
assert.Check(t, is.Nil(vpod), "PodCache.Get expected to return nil pod but received non-nil")
//TODO: uncomment out once namespace support added to cache
//vpod, err = c.Get(op, "namespace1", "testpod2a")
//assert.NotNil(t, err, "PodCache.Get did not respect namespace: %s", err)
//vpod, err = c.Get(op, "", "testpod1a")
//assert.NotNil(t, err, "PodCache.Get did not respect namespace: %s", err)
}
func TestGetAll(t *testing.T) {
op := trace.NewOperation(context.Background(), "")
c := setup(t, op)
vps := c.GetAll(op)
assert.Check(t, vps != nil, "PodCache.GetAll returned nil slice")
assert.Check(t, is.Len(vps, 4), "PodCache.Get did not return all pod definitions. Returned %d pods.", len(vps))
}
func TestDelete(t *testing.T) {
var err error
op := trace.NewOperation(context.Background(), "")
c := setup(t, op)
// Positive cases
err = c.Delete(op, "namespace1", "testpod1a")
assert.Check(t, err, "PodCache.Delete failed with error: %s", err)
vps := c.GetAll(op)
assert.Check(t, is.Len(vps, 3), "PodCache.Delete did not delete pod.")
// Negative cases
err = c.Delete(op, "namespace2", "")
assert.Check(t, is.DeepEqual(err, PodCachePodNameError))
//TODO: uncomment the tests below once namespace support added to cache
//vps = c.GetAll(op)
//currCount := len(vps)
//err = c.Delete(op, "", "testpod1b")
//assert.NotNil(t, err, "PodCache.Delete expected to return error but received nil")
//vps = c.GetAll(op)
//assert.Len(t, vps, currCount, "PodCache.Delete ignored namespace")
}

View File

@@ -1,77 +0,0 @@
package vic
import (
"context"
"fmt"
"io/ioutil"
"os"
"gopkg.in/yaml.v2"
"strings"
"github.com/vmware/vic/pkg/trace"
)
type VicConfig struct {
PersonaAddr string `yaml:"persona-server"`
PortlayerAddr string `yaml:"portlayer-server"`
HostUUID string `yaml:"host-uuid"`
}
const (
personaAddrEnv = "PERSONA_ADDR"
portlayerAddrEnv = "PORTLAYER_ADDR"
hostUUIDEnv = "HOST_UUID"
localVirtualKubelet = "LOCAL_VIRTUAL_KUBELET"
)
func LocalInstance() bool {
value := strings.ToLower(os.Getenv(localVirtualKubelet))
if value == "1" || value == "t" || value == "true" {
return true
}
return false
}
func NewVicConfig(op trace.Operation, configFile string) VicConfig {
var config VicConfig
if configFile == "" {
config.loadConfigFromEnv()
} else {
config.loadConfigFile(configFile)
}
return config
}
func (v *VicConfig) loadConfigFile(configFile string) error {
op := trace.NewOperation(context.Background(), "LoadConfigFile - %s", configFile)
defer trace.End(trace.Begin("", op))
contents, err := ioutil.ReadFile(configFile)
if err != nil {
return err
}
var config VicConfig
err = yaml.Unmarshal(contents, &config)
if err != nil {
err = fmt.Errorf("Unable to unmarshal vic virtual kubelet configfile: %s", err.Error())
op.Error(err)
return err
}
*v = config
return nil
}
func (v *VicConfig) loadConfigFromEnv() {
v.PersonaAddr = os.Getenv(personaAddrEnv)
v.PortlayerAddr = os.Getenv(portlayerAddrEnv)
v.HostUUID = os.Getenv(hostUUIDEnv)
}

View File

@@ -1,13 +0,0 @@
package constants
const (
// DefaultCPUs - the default number of container VM CPUs
DefaultCPUs = 2
DefaultMemory = 512
DummyImage = "f6e427c148a766d2d6c117d67359a0aa7d133b5bc05830a7ff6e8b64ff6b1d1d" //busybox
DummyLayerID = "02d3847f0b0fb7acd4419040cc53febf91cb112db2451d9b27a245dee5b227c0" //busybox
DummyRepoName = "busybox"
HostName = "test-kubelet"
)

View File

@@ -1,113 +0,0 @@
package vic
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/vmware/vic/pkg/trace"
)
type CreateResponse struct {
Id string `json:"Id"`
Warnings string `json:"Warnings"`
}
// Super simplistic docker client for the virtual kubelet to perform some operations
type DockerClient interface {
Ping(op trace.Operation) error
CreateContainer(op trace.Operation, config string) error
PullImage(op trace.Operation, image string) error
}
type VicDockerClient struct {
serverAddr string
}
func NewVicDockerClient(personaAddr string) DockerClient {
return &VicDockerClient{
serverAddr: personaAddr,
}
}
func (v *VicDockerClient) Ping(op trace.Operation) error {
personaServer := fmt.Sprintf("http://%s/v1.35/info", v.serverAddr)
resp, err := http.Get(personaServer)
if err != nil {
op.Errorf("Ping failed: error = %s", err.Error())
return err
}
if resp.StatusCode >= 300 {
op.Errorf("Ping failed: status = %d", resp.StatusCode)
return fmt.Errorf("Server Error")
}
return nil
}
func (v *VicDockerClient) CreateContainer(op trace.Operation, config string) error {
personaServer := fmt.Sprintf("http://%s/v1.35/containers/create", v.serverAddr)
reader := bytes.NewBuffer([]byte(config))
resp, err := http.Post(personaServer, "application/json", reader)
if err != nil {
op.Errorf("Error from from docker create: error = %s", err.Error())
return err
}
if resp.StatusCode >= 300 {
op.Errorf("Error from from docker create: status = %d", resp.StatusCode)
return fmt.Errorf("Image not found")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
op.Infof("Response from docker create: status = %d", resp.StatusCode)
op.Infof("Response from docker create: body = %s", string(body))
var createResp CreateResponse
err = json.Unmarshal(body, &createResp)
if err != nil {
op.Errorf("Failed to unmarshal response from container create post")
return err
}
startContainerUrl := fmt.Sprintf("http://%s/v1.35/containers/%s/start", v.serverAddr, createResp.Id)
op.Infof("Starting container with request - %s", startContainerUrl)
_, err = http.Post(startContainerUrl, "", nil)
if err != nil {
op.Errorf("Failed to start container %s", createResp.Id)
return err
}
return nil
}
func (v *VicDockerClient) PullImage(op trace.Operation, image string) error {
pullClient := &http.Client{Timeout: 60 * time.Second}
personaServer := fmt.Sprintf("http://%s/v1.35/images/create?fromImage=%s", v.serverAddr, image)
op.Infof("POST %s", personaServer)
reader := bytes.NewBuffer([]byte(""))
resp, err := pullClient.Post(personaServer, "application/json", reader)
if err != nil {
op.Errorf("Error from docker pull: error = %s", err.Error())
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
msg := fmt.Sprintf("Error from docker pull: status = %d", resp.StatusCode)
op.Errorf(msg)
return fmt.Errorf(msg)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
msg := fmt.Sprintf("Error reading docker pull response: error = %s", err.Error())
op.Errorf(msg)
return fmt.Errorf(msg)
}
op.Infof("Response from docker pull: body = %s", string(body))
return nil
}

View File

@@ -1,268 +0,0 @@
package operations
import (
"context"
"testing"
vicpod "github.com/virtual-kubelet/virtual-kubelet/providers/vic/pod"
"github.com/vmware/vic/lib/metadata"
"github.com/vmware/vic/pkg/trace"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"fmt"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/cache"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
proxymocks "github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy/mocks"
)
var (
pod v1.Pod
imgConfig metadata.ImageConfig
busyboxIsoConfig proxy.IsolationContainerConfig
alpineIsoConfig proxy.IsolationContainerConfig
vicPod vicpod.VicPod
)
const (
podID = "123"
podName = "busybox-sleep"
podHandle = "fakehandle"
fakeEP = "fake-endpoint"
stateRunning = "Running"
stateStarting = "Starting"
stateStopping = "Stopping"
stateStopped = "Stopped"
stateRemoving = "Removing"
stateRemoved = "Removed"
)
func createMocks(t *testing.T) (*proxymocks.ImageStore, *proxymocks.IsolationProxy, cache.PodCache, trace.Operation) {
store := &proxymocks.ImageStore{}
ip := &proxymocks.IsolationProxy{}
cache := cache.NewVicPodCache()
op := trace.NewOperation(context.Background(), "tests")
return store, ip, cache, op
}
func fakeError(myErr string) error {
return fmt.Errorf("fake error: %s", myErr)
}
func initPod() {
pod = v1.Pod{
//TypeMeta: v1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: podName,
GenerateName: "",
Namespace: "default",
SelfLink: "/api/v1/namespaces/default/pods/busybox-sleep",
UID: "b1fc6e1b-499b-11e8-946c-000c29479092",
ResourceVersion: "10338145",
Generation: 0,
DeletionTimestamp: nil,
DeletionGracePeriodSeconds: nil,
Labels: map[string]string{},
Annotations: map[string]string{},
OwnerReferences: nil,
Initializers: nil,
Finalizers: nil,
ClusterName: "",
},
Spec: v1.PodSpec{
Volumes: []v1.Volume{
{
Name: "default-token-9q9lr",
VolumeSource: v1.VolumeSource{
HostPath: nil,
EmptyDir: nil,
GCEPersistentDisk: nil,
AWSElasticBlockStore: nil,
GitRepo: nil,
Secret: &v1.SecretVolumeSource{
SecretName: "default-token-9q9lr",
Items: nil,
Optional: nil,
},
NFS: nil,
ISCSI: nil,
Glusterfs: nil,
PersistentVolumeClaim: nil,
RBD: nil,
FlexVolume: nil,
Cinder: nil,
CephFS: nil,
Flocker: nil,
DownwardAPI: nil,
FC: nil,
AzureFile: nil,
ConfigMap: nil,
VsphereVolume: nil,
Quobyte: nil,
AzureDisk: nil,
PhotonPersistentDisk: nil,
Projected: nil,
PortworxVolume: nil,
ScaleIO: nil,
StorageOS: nil,
},
},
},
InitContainers: nil,
Containers: []v1.Container{
{
Name: "busybox-container",
Image: "busybox",
Command: []string{"/bin/sleep"},
Args: []string{"2m"},
WorkingDir: "",
Ports: nil,
EnvFrom: nil,
Env: nil,
Resources: v1.ResourceRequirements{},
VolumeMounts: []v1.VolumeMount{
{
Name: "default-token-9q9lr",
ReadOnly: true,
MountPath: "/var/run/secrets/kubernetes.io/serviceaccount",
SubPath: "",
MountPropagation: nil,
},
},
LivenessProbe: nil,
ReadinessProbe: nil,
Lifecycle: nil,
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: "File",
ImagePullPolicy: "IfNotPresent",
SecurityContext: nil,
Stdin: false,
StdinOnce: false,
TTY: false,
},
{
Name: "alpine-container",
Image: "alpine",
Command: nil,
Args: nil,
WorkingDir: "",
Ports: nil,
EnvFrom: nil,
Env: nil,
Resources: v1.ResourceRequirements{},
VolumeMounts: []v1.VolumeMount{
{
Name: "default-token-9q9lr",
ReadOnly: true,
MountPath: "/var/run/secrets/kubernetes.io/serviceaccount",
SubPath: "",
MountPropagation: nil,
},
},
LivenessProbe: nil,
ReadinessProbe: nil,
Lifecycle: nil,
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: "File",
ImagePullPolicy: "IfNotPresent",
SecurityContext: nil,
Stdin: false,
StdinOnce: false,
TTY: false,
},
},
RestartPolicy: "Always",
TerminationGracePeriodSeconds: new(int64),
ActiveDeadlineSeconds: nil,
DNSPolicy: "ClusterFirst",
NodeSelector: map[string]string{"affinity": "vmware"},
ServiceAccountName: "default",
DeprecatedServiceAccount: "default",
AutomountServiceAccountToken: nil,
NodeName: "vic-kubelet",
HostNetwork: false,
HostPID: false,
HostIPC: false,
SecurityContext: &v1.PodSecurityContext{},
ImagePullSecrets: nil,
Hostname: "",
Subdomain: "",
Affinity: nil,
SchedulerName: "default-scheduler",
Tolerations: []v1.Toleration{
{
Key: "node.kubernetes.io/not-ready",
Operator: "Exists",
Value: "",
Effect: "NoExecute",
TolerationSeconds: new(int64),
},
{
Key: "node.kubernetes.io/unreachable",
Operator: "Exists",
Value: "",
Effect: "NoExecute",
TolerationSeconds: new(int64),
},
},
HostAliases: nil,
PriorityClassName: "",
Priority: nil,
},
}
busyboxIsoConfig = proxy.IsolationContainerConfig{
ID: "",
ImageID: "",
LayerID: "",
ImageName: "busybox",
Name: "busybox-container",
Namespace: "",
Cmd: []string{"/bin/sleep", "2m"},
Path: "",
Entrypoint: nil,
Env: nil,
WorkingDir: "",
User: "",
StopSignal: "",
Attach: false,
StdinOnce: false,
OpenStdin: false,
Tty: false,
CPUCount: 2,
Memory: 2048,
PortMap: map[string]proxy.PortBinding{},
}
alpineIsoConfig = proxy.IsolationContainerConfig{
ID: "",
ImageID: "",
LayerID: "",
ImageName: "alpine",
Name: "alpine-container",
Namespace: "",
Cmd: nil,
Path: "",
Entrypoint: nil,
Env: nil,
WorkingDir: "",
User: "",
StopSignal: "",
Attach: false,
StdinOnce: false,
OpenStdin: false,
Tty: false,
CPUCount: 2,
Memory: 2048,
PortMap: map[string]proxy.PortBinding{},
}
vicPod = vicpod.VicPod{
ID: podID,
Pod: &pod,
}
}

View File

@@ -1,455 +0,0 @@
package operations
import (
"fmt"
"strings"
"sync"
"time"
"github.com/kr/pretty"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/cache"
vicpod "github.com/virtual-kubelet/virtual-kubelet/providers/vic/pod"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
vicerrors "github.com/vmware/vic/lib/apiservers/engine/errors"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/lib/metadata"
"github.com/vmware/vic/pkg/trace"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PodCreator interface {
CreatePod(op trace.Operation, pod *v1.Pod, start bool) error
}
type VicPodCreator struct {
client *client.PortLayer
imageStore proxy.ImageStore
isolationProxy proxy.IsolationProxy
podCache cache.PodCache
personaAddr string
portlayerAddr string
}
type VicPodCreatorError string
func (e VicPodCreatorError) Error() string { return string(e) }
func NewPodCreatorPullError(image, msg string) VicPodCreatorError {
return VicPodCreatorError(fmt.Sprintf("VicPodCreator failed to get image %s's config from the image store: %s", image, msg))
}
func NewPodCreatorNilImgConfigError(image string) VicPodCreatorError {
return VicPodCreatorError(fmt.Sprintf("VicPodCreator failed to get image %s's config from the image store", image))
}
const (
// MemoryAlignMB is the value to which container VM memory must align in order for hotadd to work
MemoryAlignMB = 128
// MemoryMinMB - the minimum allowable container memory size
MemoryMinMB = 512
// MemoryDefaultMB - the default container VM memory size
MemoryDefaultMB = 2048
// MinCPUs - the minimum number of allowable CPUs the container can use
MinCPUs = 1
// DefaultCPUs - the default number of container VM CPUs
DefaultCPUs = 2
DefaultMemory = 512
MiBytesUnit = 1024 * 1024
defaultEnvPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
// Errors
PodCreatorPortlayerClientError = VicPodCreatorError("PodCreator called with an invalid portlayer client")
PodCreatorImageStoreError = VicPodCreatorError("PodCreator called with an invalid image store")
PodCreatorIsolationProxyError = VicPodCreatorError("PodCreator called with an invalid isolation proxy")
PodCreatorPodCacheError = VicPodCreatorError("PodCreator called with an invalid pod cache")
PodCreatorPersonaAddrError = VicPodCreatorError("PodCreator called with an invalid VIC persona addr")
PodCreatorPortlayerAddrError = VicPodCreatorError("PodCreator called with an invalid VIC portlayer addr")
PodCreatorInvalidPodSpecError = VicPodCreatorError("CreatePod called with nil pod")
PodCreatorInvalidArgsError = VicPodCreatorError("Invalid arguments")
)
func NewPodCreator(client *client.PortLayer, imageStore proxy.ImageStore, isolationProxy proxy.IsolationProxy, podCache cache.PodCache, personaAddr string, portlayerAddr string) (PodCreator, error) {
if client == nil {
return nil, PodCreatorPortlayerClientError
} else if imageStore == nil {
return nil, PodCreatorImageStoreError
} else if isolationProxy == nil {
return nil, PodCreatorIsolationProxyError
} else if podCache == nil {
return nil, PodCreatorPodCacheError
}
return &VicPodCreator{
client: client,
imageStore: imageStore,
podCache: podCache,
personaAddr: personaAddr,
portlayerAddr: portlayerAddr,
isolationProxy: isolationProxy,
}, nil
}
// CreatePod creates the pod and potentially start it
//
// arguments:
// op operation trace logger
// pod pod spec
// start start the pod after creation
// returns:
// error
func (v *VicPodCreator) CreatePod(op trace.Operation, pod *v1.Pod, start bool) error {
defer trace.End(trace.Begin("", op))
if pod == nil {
op.Errorf(PodCreatorInvalidPodSpecError.Error())
return PodCreatorInvalidPodSpecError
}
defer trace.End(trace.Begin(pod.Name, op))
// Pull all containers simultaneously
err := v.pullPodContainers(op, pod)
if err != nil {
op.Errorf("PodCreator failed to pull containers: %s", err.Error())
return err
}
// Transform kube container config to docker create config
id, err := v.createPod(op, pod, start)
if err != nil {
op.Errorf("pod_creator failed to create pod: %s", err.Error())
return err
}
vp := &vicpod.VicPod{
ID: id,
Pod: pod.DeepCopy(),
}
err = v.podCache.Add(op, "", pod.Name, vp)
if err != nil {
//TODO: What should we do if pod already exist?
}
if start {
ps, err := NewPodStarter(v.client, v.isolationProxy)
if err != nil {
op.Errorf("Error creating pod starter: %s", err.Error())
return err
}
err = ps.Start(op, id, pod.Name)
if err != nil {
return err
}
now := metav1.NewTime(time.Now())
vp.Pod.Status.StartTime = &now
}
return nil
}
// pullPodContainers simultaneously pulls all containers in a pod
//
// arguments:
// op operation trace logger
// pod pod spec
// returns:
// error
func (v *VicPodCreator) pullPodContainers(op trace.Operation, pod *v1.Pod) error {
defer trace.End(trace.Begin("", op))
if pod == nil || pod.Spec.Containers == nil {
return PodCreatorInvalidPodSpecError
}
var pullGroup sync.WaitGroup
errChan := make(chan error, 2)
for _, c := range pod.Spec.Containers {
pullGroup.Add(1)
go func(img string, policy v1.PullPolicy) {
defer pullGroup.Done()
// Pull image config from VIC's image store if policy allows
var realize bool
if policy == v1.PullIfNotPresent {
realize = true
} else {
realize = false
}
_, err := v.imageStore.Get(op, img, "", realize)
if err != nil {
err = fmt.Errorf("VicPodCreator failed to get image %s's config from the image store: %s", img, err.Error())
op.Error(err)
errChan <- err
}
}(c.Image, c.ImagePullPolicy)
}
pullGroup.Wait()
close(errChan)
for err := range errChan {
if err != nil {
return err
}
}
return nil
}
// createPod creates a pod using the VIC portlayer. Images can be pulled serially if not already present.
//
// arguments:
// op operation trace logger
// pod pod spec
// start start the pod after creation
// returns:
// (pod id, error)
func (v *VicPodCreator) createPod(op trace.Operation, pod *v1.Pod, start bool) (string, error) {
defer trace.End(trace.Begin("", op))
if pod == nil || pod.Spec.Containers == nil {
op.Errorf(PodCreatorInvalidPodSpecError.Error())
return "", PodCreatorInvalidPodSpecError
}
id, h, err := v.isolationProxy.CreateHandle(op)
if err != nil {
return "", err
}
for idx, c := range pod.Spec.Containers {
// Pull image config from VIC's image store if policy allows
var realize bool
if c.ImagePullPolicy == v1.PullIfNotPresent {
realize = true
} else {
realize = false
}
imgConfig, err := v.imageStore.Get(op, c.Image, "", realize)
if err != nil {
err = NewPodCreatorPullError(c.Image, err.Error())
op.Error(err)
return "", err
}
if imgConfig == nil {
err = NewPodCreatorNilImgConfigError(c.Image)
op.Error(err)
return "", err
}
op.Debugf("Receive image config from imagestore = %# v", pretty.Formatter(imgConfig))
// Create the initial config
ic, err := IsolationContainerConfigFromKubeContainer(op, &c, imgConfig, pod)
if err != nil {
return "", err
}
op.Debugf("isolation config %# v", pretty.Formatter(ic))
h, err = v.isolationProxy.AddImageToHandle(op, h, c.Name, imgConfig.V1Image.ID, imgConfig.ImageID, imgConfig.Name)
if err != nil {
return "", err
}
//TODO: We need one task with the container ID as the portlayer uses this to track session. Longer term, we should figure out
// a way to fix this in the portlayer?
if idx == 0 {
h, err = v.isolationProxy.CreateHandleTask(op, h, id, imgConfig.V1Image.ID, ic)
} else {
h, err = v.isolationProxy.CreateHandleTask(op, h, fmt.Sprintf("Container-%d-task", idx), imgConfig.V1Image.ID, ic)
}
if err != nil {
return "", err
}
h, err = v.isolationProxy.AddHandleToScope(op, h, ic)
if err != nil {
return id, err
}
}
// Need both interaction and logging added or we will not be able to retrieve output.log or tether.debug
h, err = v.isolationProxy.AddInteractionToHandle(op, h)
if err != nil {
return "", err
}
h, err = v.isolationProxy.AddLoggingToHandle(op, h)
if err != nil {
return "", err
}
err = v.isolationProxy.CommitHandle(op, h, id, -1)
if err != nil {
return "", err
}
op.Debugf("Created Pod: %s, Handle: %s, ID: %s", pod.Name, h, id)
return id, nil
}
//------------------------------------
// Utility Functions
//------------------------------------
func IsolationContainerConfigFromKubeContainer(op trace.Operation, cSpec *v1.Container, imgConfig *metadata.ImageConfig, pod *v1.Pod) (proxy.IsolationContainerConfig, error) {
defer trace.End(trace.Begin("", op))
if cSpec == nil || imgConfig == nil || pod == nil {
op.Errorf("Invalid args to IsolationContainerConfigFromKubeContainer: cSpec(%#v), imgConfig(%#v), pod(%#v)", cSpec, imgConfig, pod)
return proxy.IsolationContainerConfig{}, PodCreatorInvalidArgsError
}
op.Debugf("** IsolationContainerConfig... imgConfig = %#v", imgConfig)
config := proxy.IsolationContainerConfig{
Name: cSpec.Name,
WorkingDir: cSpec.WorkingDir,
ImageName: cSpec.Image,
Tty: cSpec.TTY,
StdinOnce: cSpec.StdinOnce,
OpenStdin: cSpec.Stdin,
PortMap: make(map[string]proxy.PortBinding, 0),
}
setResourceFromKubeSpec(op, &config, cSpec)
// Overwrite or append the image's config from the CLI with the metadata from the image's
// layer metadata where appropriate
if len(cSpec.Command) > 0 {
config.Cmd = make([]string, len(cSpec.Command))
copy(config.Cmd, cSpec.Command)
config.Cmd = append(config.Cmd, cSpec.Args...)
} else if imgConfig.Config != nil {
config.Cmd = make([]string, len(imgConfig.Config.Cmd))
copy(config.Cmd, imgConfig.Config.Cmd)
}
config.User = ""
if imgConfig.Config != nil {
if imgConfig.Config.User != "" {
config.User = imgConfig.Config.User
}
// set up environment
config.Env = setEnvFromImageConfig(config.Tty, config.Env, imgConfig.Config.Env)
}
op.Debugf("config = %#v", config)
// TODO: Cache the container (so that they are shared with the persona)
return config, nil
}
func setEnvFromImageConfig(tty bool, env []string, imgEnv []string) []string {
// Set PATH in ENV if needed
env = setPathFromImageConfig(env, imgEnv)
containerEnv := make(map[string]string, len(env))
for _, e := range env {
kv := strings.SplitN(e, "=", 2)
var val string
if len(kv) == 2 {
val = kv[1]
}
containerEnv[kv[0]] = val
}
// Set TERM to xterm if tty is set, unless user supplied a different TERM
if tty {
if _, ok := containerEnv["TERM"]; !ok {
env = append(env, "TERM=xterm")
}
}
// add remaining environment variables from the image config to the container
// config, taking care not to overwrite anything
for _, imageEnv := range imgEnv {
key := strings.SplitN(imageEnv, "=", 2)[0]
// is environment variable already set in container config?
if _, ok := containerEnv[key]; !ok {
// no? let's copy it from the image config
env = append(env, imageEnv)
}
}
return env
}
func setPathFromImageConfig(env []string, imgEnv []string) []string {
// check if user supplied PATH environment variable at creation time
for _, v := range env {
if strings.HasPrefix(v, "PATH=") {
// a PATH is set, bail
return env
}
}
// check to see if the image this container is created from supplies a PATH
for _, v := range imgEnv {
if strings.HasPrefix(v, "PATH=") {
// a PATH was found, add it to the config
env = append(env, v)
return env
}
}
// no PATH set, use the default
env = append(env, fmt.Sprintf("PATH=%s", defaultEnvPath))
return env
}
func setResourceFromKubeSpec(op trace.Operation, config *proxy.IsolationContainerConfig, cSpec *v1.Container) error {
if config == nil {
return vicerrors.BadRequestError("invalid config")
}
// Get resource request. If not specified, use the limits. If that's not set, use default VIC values.
config.CPUCount = cSpec.Resources.Requests.Cpu().Value()
if config.CPUCount == 0 {
config.CPUCount = cSpec.Resources.Limits.Cpu().Value()
if config.CPUCount == 0 {
config.CPUCount = DefaultCPUs
}
}
config.Memory = cSpec.Resources.Requests.Memory().Value()
if config.Memory == 0 {
config.Memory = cSpec.Resources.Limits.Memory().Value()
if config.Memory == 0 {
config.Memory = DefaultMemory
}
}
// convert from bytes to MiB for vsphere
memoryMB := config.Memory / MiBytesUnit
if memoryMB == 0 {
memoryMB = MemoryDefaultMB
} else if memoryMB < MemoryMinMB {
memoryMB = MemoryMinMB
}
// check that memory is aligned
if remainder := memoryMB % MemoryAlignMB; remainder != 0 {
op.Warnf("Default container VM memory must be %d aligned for hotadd, rounding up.", MemoryAlignMB)
memoryMB += MemoryAlignMB - remainder
}
config.Memory = memoryMB
op.Debugf("Container memory: %d MB", config.Memory)
return nil
}

View File

@@ -1,467 +0,0 @@
package operations
import (
"fmt"
"testing"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/lib/metadata"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func init() {
initPod()
}
func TestNewPodCreator(t *testing.T) {
var c PodCreator
var err error
store, proxy, cache, _ := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Positive cases
c, err = NewPodCreator(client, store, proxy, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
// Negative cases
c, err = NewPodCreator(nil, store, proxy, cache, persona, portlayer)
assert.Check(t, is.Nil(c), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodCreatorPortlayerClientError))
c, err = NewPodCreator(client, nil, proxy, cache, persona, portlayer)
assert.Check(t, is.Nil(c), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodCreatorImageStoreError))
c, err = NewPodCreator(client, store, nil, cache, persona, portlayer)
assert.Check(t, is.Nil(c), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodCreatorIsolationProxyError))
c, err = NewPodCreator(client, store, proxy, nil, persona, portlayer)
assert.Check(t, is.Nil(c), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodCreatorPodCacheError))
}
func TestCreatePod_NilPod(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Create nil pod
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, nil, true)
assert.Check(t, err != nil, "Expected error from createPod but received '%s'", err)
}
func TestCreatePod_Success(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err, "Expected error from createPod but received '%s'", err)
}
func TestCreatePod_ImageStoreError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
fakeErr := fmt.Errorf("Error getting pod containers")
store.On("Get", op, "alpine", "", true).Return(nil, fakeErr)
store.On("Get", op, "busybox", "", true).Return(nil, fakeErr)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
}
func TestCreatePod_CreateHandleError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake create handle error")
ip.On("CreateHandle", op).Return(podID, podHandle, fakeErr)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_AddImageError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake add image error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, fakeErr)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_CreateHandleTaskError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake create handle task error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, fakeErr)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, fakeErr)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_AddHandleToScopeError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake add handle to scope error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, fakeErr)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, fakeErr)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_AddInteractionError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake add interaction error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, fakeErr)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_AddLoggingError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake add logging error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, fakeErr)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_CommitError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake commit error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(fakeErr)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_HandleError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake handle error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, fakeErr)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_BindScopeError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake bind scope error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, fakeErr)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, nil)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}
func TestCreatePod_SetStateError(t *testing.T) {
store, ip, cache, op := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Setup mocks
fakeErr := fmt.Errorf("fake set state error")
ip.On("CreateHandle", op).Return(podID, podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "busybox-container", "", "", "").Return(podHandle, nil)
ip.On("AddImageToHandle", op, podHandle, "alpine-container", "", "", "").Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, podID, "", busyboxIsoConfig).Return(podHandle, nil)
ip.On("CreateHandleTask", op, podHandle, "Container-1-task", "", alpineIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, busyboxIsoConfig).Return(podHandle, nil)
ip.On("AddHandleToScope", op, podHandle, alpineIsoConfig).Return(podHandle, nil)
ip.On("AddInteractionToHandle", op, podHandle).Return(podHandle, nil)
ip.On("AddLoggingToHandle", op, podHandle).Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("BindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "RUNNING").Return(podHandle, fakeErr)
store.On("Get", op, "busybox", "", true).Return(&metadata.ImageConfig{}, nil)
store.On("Get", op, "alpine", "", true).Return(&metadata.ImageConfig{}, nil)
// The test
c, err := NewPodCreator(client, store, ip, cache, persona, portlayer)
assert.Check(t, c != nil, "Expected not-nil creating a pod creator but received nil")
err = c.CreatePod(op, &pod, true)
assert.Check(t, err != nil, "Expected nil error from createPod")
assert.Check(t, is.Equal(err.Error(), fakeErr.Error()))
}

View File

@@ -1,174 +0,0 @@
package operations
import (
"fmt"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/cache"
vicpod "github.com/virtual-kubelet/virtual-kubelet/providers/vic/pod"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
vicerrors "github.com/vmware/vic/lib/apiservers/engine/errors"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/pkg/retry"
"github.com/vmware/vic/pkg/trace"
"k8s.io/api/core/v1"
)
type PodDeleter interface {
DeletePod(op trace.Operation, pod *v1.Pod) error
}
type VicPodDeleter struct {
client *client.PortLayer
imageStore proxy.ImageStore
isolationProxy proxy.IsolationProxy
podCache cache.PodCache
personaAddr string
portlayerAddr string
}
type VicPodDeleterError string
func (e VicPodDeleterError) Error() string { return string(e) }
const (
PodDeleterPortlayerClientError = VicPodDeleterError("PodDeleter called with an invalid portlayer client")
PodDeleterIsolationProxyError = VicPodDeleterError("PodDeleter called with an invalid isolation proxy")
PodDeleterPodCacheError = VicPodDeleterError("PodDeleter called with an invalid pod cache")
PodDeleterPersonaAddrError = VicPodDeleterError("PodDeleter called with an invalid VIC persona addr")
PodDeleterPortlayerAddrError = VicPodDeleterError("PodDeleter called with an invalid VIC portlayer addr")
PodDeleterInvalidPodSpecError = VicPodDeleterError("PodDeleter called with nil pod")
)
type DeleteResponse struct {
Id string `json:"Id"`
Warnings string `json:"Warnings"`
}
func NewPodDeleter(client *client.PortLayer, isolationProxy proxy.IsolationProxy, podCache cache.PodCache, personaAddr string, portlayerAddr string) (PodDeleter, error) {
if client == nil {
return nil, PodDeleterPortlayerClientError
} else if isolationProxy == nil {
return nil, PodDeleterIsolationProxyError
} else if podCache == nil {
return nil, PodDeleterPodCacheError
}
return &VicPodDeleter{
client: client,
podCache: podCache,
personaAddr: personaAddr,
portlayerAddr: portlayerAddr,
isolationProxy: isolationProxy,
}, nil
}
// DeletePod deletes a pod
//
// arguments:
// op operation trace logger
// pod pod spec
// returns:
// error
func (v *VicPodDeleter) DeletePod(op trace.Operation, pod *v1.Pod) error {
if pod == nil {
return PodDeleterInvalidPodSpecError
}
defer trace.End(trace.Begin(pod.Name, op))
// Get pod from cache
vp, err := v.podCache.Get(op, "", pod.Name)
if err != nil {
return err
}
// Stop pod if not already stopped
// Transform kube container config to docker create config
err = v.deletePod(op, vp, true)
if err != nil {
op.Errorf("PodDeleter failed to delete pod: %s", err.Error())
return err
}
op.Infof("PodDeleter deleting from cache, name: %s, ID: %s", pod.Name, vp.ID)
v.podCache.Delete(op, "", pod.Name)
return nil
}
// deletePod deletes a pod
//
// arguments:
// op operation trace logger
// vp VIC pod struct
// force if set to true, the pod will be deleted even if it's still running
// returns:
// error
func (v *VicPodDeleter) deletePod(op trace.Operation, vp *vicpod.VicPod, force bool) error {
defer trace.End(trace.Begin("", op))
if vp == nil {
return PodDeleterInvalidPodSpecError
}
id := vp.ID
name := vp.Pod.Name
running := false
stopper, err := NewPodStopper(v.client, v.isolationProxy)
if err != nil {
return err
}
// Use the force and stop the container first
if force {
if err := stopper.Stop(op, id, name); err != nil {
return err
}
} else {
state, err := v.isolationProxy.State(op, id, name)
if err != nil {
return err
}
switch state {
case "Error":
// force stop if container state is error to make sure container is deletable later
stopper.Stop(op, id, name)
case "Starting":
// if we are starting let the user know they must use the force
return fmt.Errorf("The container is starting. To remove use -f")
case "Running":
running = true
}
handle, err := v.isolationProxy.Handle(op, id, name)
if err != nil {
return err
}
// Unbind the container to the scope
_, ep, err := v.isolationProxy.UnbindScope(op, handle, name)
if err != nil {
return err
}
op.Infof("Scope Unbind returned endpoints %# +v", ep)
}
// Retry remove operation if container is not in running state. If in running state, we only try
// once to prevent retries from degrading performance.
if !running {
operation := func() error {
return v.isolationProxy.Remove(op, id, true)
}
op.Infof("Delete Pod, ID: %s, running: %v", vp.ID, running)
return retry.Do(operation, vicerrors.IsConflictError)
}
err = v.isolationProxy.Remove(op, id, true)
op.Infof("Delete Pod, ID: %s, running: %v err: %v", vp.ID, running, err)
return err
}

View File

@@ -1,212 +0,0 @@
package operations
import (
"testing"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func init() {
initPod()
}
func TestNewPodDeleter(t *testing.T) {
_, ip, cache, _ := createMocks(t)
client := client.Default
persona := "1.2.3.4"
portlayer := "1.2.3.4"
// Positive Cases
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
// Negative Cases
d, err = NewPodDeleter(nil, ip, cache, persona, portlayer)
assert.Check(t, is.Nil(d), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodDeleterPortlayerClientError))
d, err = NewPodDeleter(client, nil, cache, persona, portlayer)
assert.Check(t, is.Nil(d), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodDeleterIsolationProxyError))
d, err = NewPodDeleter(client, ip, nil, persona, portlayer)
assert.Check(t, is.Nil(d), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodDeleterPodCacheError))
}
func TestDeletePod(t *testing.T) {
client := client.Default
_, ip, cache, op := createMocks(t)
persona := "1.2.3.4"
portlayer := "1.2.3.4"
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Remove", op, podID, true).Return(nil)
// Add vicPod to the cache
cache.Add(op, "", pod.Name, &vicPod)
// Positive case
err = d.DeletePod(op, &pod)
assert.Check(t, err, "Expected nil")
}
func TestDeletePodErrorHandle(t *testing.T) {
client := client.Default
_, ip, cache, op := createMocks(t)
persona := "1.2.3.4"
portlayer := "1.2.3.4"
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Remove", op, podID, true).Return(nil)
// Add vicPod to the cache
cache.Add(op, "", pod.Name, &vicPod)
// Failed Handle
fakeErr := fakeError("invalid handle")
ip.On("Handle", op, podID, podName).Return("", fakeErr)
err = d.DeletePod(op, &pod)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected invalid handle error")
}
func TestDeletePodErrorUnbindScope(t *testing.T) {
client := client.Default
_, ip, cache, op := createMocks(t)
persona := "1.2.3.4"
portlayer := "1.2.3.4"
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Remove", op, podID, true).Return(nil)
// Add vicPod to the cache
cache.Add(op, "", pod.Name, &vicPod)
// Failed UnbindScope
fakeErr := fakeError("failed UnbindScope")
ip.On("UnbindScope", op, podHandle, podName).Return("", nil, fakeErr)
err = d.DeletePod(op, &pod)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected failed UnbindScope error")
}
func TestDeletePodErrorSetState(t *testing.T) {
client := client.Default
_, ip, cache, op := createMocks(t)
persona := "1.2.3.4"
portlayer := "1.2.3.4"
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
ip.On("Remove", op, podID, true).Return(nil)
// Add vicPod to the cache
cache.Add(op, "", pod.Name, &vicPod)
// Failed SetState
fakeErr := fakeError("failed SetState")
ip.On("SetState", op, podHandle, podName, "STOPPED").Return("", fakeErr)
err = d.DeletePod(op, &pod)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected failed SetState error")
}
func TestDeletePodErrorCommitHandle(t *testing.T) {
client := client.Default
_, ip, cache, op := createMocks(t)
persona := "1.2.3.4"
portlayer := "1.2.3.4"
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("Remove", op, podID, true).Return(nil)
// Add vicPod to the cache
cache.Add(op, "", pod.Name, &vicPod)
// Failed Commit
fakeErr := fakeError("failed Commit")
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(fakeErr)
err = d.DeletePod(op, &pod)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected failed Commit error")
}
func TestDeletePodErrorRemove(t *testing.T) {
client := client.Default
_, ip, cache, op := createMocks(t)
persona := "1.2.3.4"
portlayer := "1.2.3.4"
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
// Add vicPod to the cache
cache.Add(op, "", pod.Name, &vicPod)
// Failed Remove
fakeErr := fakeError("failed Remove")
ip.On("Remove", op, podID, true).Return(fakeErr)
err = d.DeletePod(op, &pod)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected failed Remove error")
}
func TestDeletePodErrorBadArgs(t *testing.T) {
client := client.Default
_, ip, cache, op := createMocks(t)
persona := "1.2.3.4"
portlayer := "1.2.3.4"
d, err := NewPodDeleter(client, ip, cache, persona, portlayer)
assert.Check(t, d != nil, "Expected non-nil creating a pod Deleter but received nil")
// Negative Cases
err = d.DeletePod(op, nil)
assert.Check(t, is.DeepEqual(err, PodDeleterInvalidPodSpecError))
}

View File

@@ -1,91 +0,0 @@
package operations
import (
"context"
"fmt"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/pkg/trace"
)
type PodStarter interface {
Start(op trace.Operation, id, name string) error
}
type VicPodStarter struct {
client *client.PortLayer
isolationProxy proxy.IsolationProxy
imageStore proxy.ImageStore
}
type VicPodStarterError string
func (e VicPodStarterError) Error() string { return string(e) }
const (
PodStarterPortlayerClientError = VicPodStarterError("PodStarter called with an invalid portlayer client")
PodStarterIsolationProxyError = VicPodStarterError("PodStarter called with an invalid isolation proxy")
PodStarterInvalidPodIDError = VicPodStarterError("PodStarter called with invalid Pod ID")
PodStarterInvalidPodNameError = VicPodStarterError("PodStarter called with invalid Pod name")
)
func NewPodStarter(client *client.PortLayer, isolationProxy proxy.IsolationProxy) (PodStarter, error) {
defer trace.End(trace.Begin("", context.Background()))
if client == nil {
return nil, PodStarterPortlayerClientError
}
if isolationProxy == nil {
return nil, PodStarterIsolationProxyError
}
return &VicPodStarter{
client: client,
isolationProxy: isolationProxy,
}, nil
}
// Start starts up the pod vm
//
// arguments:
// op operation trace logger
// id pod id
// name pod name
// returns:
// error
func (v *VicPodStarter) Start(op trace.Operation, id, name string) error {
defer trace.End(trace.Begin(fmt.Sprintf("id(%s), name(%s)", id, name), op))
h, err := v.isolationProxy.Handle(op, id, name)
if err != nil {
return err
}
// Bind the container to the scope
h, ep, err := v.isolationProxy.BindScope(op, h, name)
if err != nil {
return err
}
op.Debugf("*** Scope bind returned endpoints %#v", ep)
defer func() {
if err != nil {
op.Debugf("Unbinding %s due to error - %s", id, err.Error())
v.isolationProxy.UnbindScope(op, h, name)
}
}()
h, err = v.isolationProxy.SetState(op, h, name, "RUNNING")
if err != nil {
return err
}
// map ports
err = v.isolationProxy.CommitHandle(op, h, id, -1)
return nil
}

View File

@@ -1,33 +0,0 @@
package operations
import (
"testing"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy/mocks"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestNewPodStarter(t *testing.T) {
var s PodStarter
var err error
client := client.Default
ip := &mocks.IsolationProxy{}
// Positive Cases
s, err = NewPodStarter(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod starter but received nil")
// Negative Cases
s, err = NewPodStarter(nil, ip)
assert.Check(t, is.Nil(s), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodStarterPortlayerClientError))
s, err = NewPodStarter(client, nil)
assert.Check(t, is.Nil(s), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodStarterIsolationProxyError))
}
//NOTE: The rest of PodStarter tests were handled in PodCreator's tests so there's no need for further tests.

View File

@@ -1,151 +0,0 @@
package operations
import (
"fmt"
"net"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/pkg/trace"
"k8s.io/api/core/v1"
)
type PodStatus interface {
GetStatus(op trace.Operation, namespace string, name string, hostAddress string) (*v1.PodStatus, error)
}
type VicPodStatus struct {
client *client.PortLayer
isolationProxy proxy.IsolationProxy
}
type VicPodStatusError string
func (e VicPodStatusError) Error() string { return string(e) }
const (
PodStatusPortlayerClientError = VicPodStatusError("PodStatus called with an invalid portlayer client")
PodStatusIsolationProxyError = VicPodStatusError("PodStatus called with an invalid isolation proxy")
PodStatusInvalidPodIDError = VicPodStatusError("PodStatus called with invalid PodID")
PodStatusInvalidPodNameError = VicPodStatusError("PodStatus called with invalid PodName")
)
func NewPodStatus(client *client.PortLayer, isolationProxy proxy.IsolationProxy) (PodStatus, error) {
if client == nil {
return nil, PodStatusPortlayerClientError
} else if isolationProxy == nil {
return nil, PodStatusIsolationProxyError
}
return &VicPodStatus{
client: client,
isolationProxy: isolationProxy,
}, nil
}
// Gets pod status does not delete it
//
// arguments:
// op operation trace logger
// id pod id
// name pod name
// returns:
// error
func (v *VicPodStatus) GetStatus(op trace.Operation, id, name string, hostAddress string) (*v1.PodStatus, error) {
defer trace.End(trace.Begin(fmt.Sprintf("id(%s), name(%s)", id, name), op))
return v.getStatus(op, id, name, hostAddress)
}
func (v *VicPodStatus) getStatus(op trace.Operation, id, name string, hostAddress string) (*v1.PodStatus, error) {
defer trace.End(trace.Begin(fmt.Sprintf("id(%s), name(%s)", id, name), op))
// Start out with unknown
phase := v1.PodUnknown
podReady := v1.ConditionUnknown
podInitialized := v1.ConditionUnknown
podScheduled := v1.ConditionUnknown
// Get current state
state, err := v.isolationProxy.State(op, id, name)
if err == nil {
podScheduled = v1.ConditionTrue
switch state {
case "Starting":
// if we are starting let the user know they must use the force
phase = v1.PodPending
podInitialized = v1.ConditionFalse
podReady = v1.ConditionFalse
case "Running":
phase = v1.PodRunning
podInitialized = v1.ConditionTrue
podReady = v1.ConditionTrue
case "Stopping":
phase = v1.PodRunning
podReady = v1.ConditionFalse
podInitialized = v1.ConditionTrue
case "Stopped":
phase = v1.PodSucceeded
podReady = v1.ConditionFalse
podInitialized = v1.ConditionTrue
case "Removing":
phase = v1.PodSucceeded
podReady = v1.ConditionFalse
podInitialized = v1.ConditionTrue
case "Removed":
phase = v1.PodSucceeded
podReady = v1.ConditionFalse
podInitialized = v1.ConditionTrue
}
}
status := &v1.PodStatus{
Phase: phase,
Conditions: []v1.PodCondition{
{
Type: v1.PodInitialized,
Status: podInitialized,
},
{
Type: v1.PodReady,
Status: podReady,
},
{
Type: v1.PodScheduled,
Status: podScheduled,
},
},
}
addresses, err := v.getIPAddresses(op, id, name)
if err == nil && len(addresses) > 0 {
status.HostIP = hostAddress
status.PodIP = addresses[0]
} else {
status.HostIP = "0.0.0.0"
status.PodIP = "0.0.0.0"
}
return status, nil
}
func (v *VicPodStatus) getIPAddresses(op trace.Operation, id, name string) ([]string, error) {
defer trace.End(trace.Begin(id, op))
apAddresses, err := v.isolationProxy.EpAddresses(op, id, name)
if err != nil {
return nil, err
}
IPAddresses := make([]string, 0)
for _, epAddr := range apAddresses {
if epAddr != "" {
ip, _, err := net.ParseCIDR(epAddr)
if err == nil {
IPAddresses = append(IPAddresses, ip.String())
}
}
}
return IPAddresses, err
}

View File

@@ -1,225 +0,0 @@
package operations
import (
"testing"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
v1 "k8s.io/api/core/v1"
)
func TestNewPodStatus(t *testing.T) {
_, ip, _, _ := createMocks(t)
client := client.Default
// Positive Cases
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
// Negative Cases
s, err = NewPodStatus(nil, ip)
assert.Check(t, is.Nil(s), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodStatusPortlayerClientError))
s, err = NewPodStatus(client, nil)
assert.Check(t, is.Nil(s), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodStatusIsolationProxyError))
}
func TestStatusPodStarting(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
assert.Check(t, err, "Expected nil")
HostAddress := "1.2.3.4"
EndpointAddresses := []string{
"5.6.7.8/24",
}
// Set up the mocks for this test
ip.On("State", op, podID, podName).Return(stateStarting, nil)
ip.On("EpAddresses", op, podID, podName).Return(EndpointAddresses, nil)
// Positive case
status, err := s.GetStatus(op, podID, podName, HostAddress)
assert.Check(t, err, "Expected nil")
assert.Check(t, is.Equal(status.Phase, v1.PodPending), "Expected Phase Pending")
verifyConditions(t, status.Conditions, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse)
assert.Check(t, is.Equal(status.HostIP, "1.2.3.4"), "Expected Host IP Address")
assert.Check(t, is.Equal(status.PodIP, "5.6.7.8"), "Expected Pod IP Address")
}
func TestStatusPodRunning(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
assert.Check(t, err, "Expected nil")
HostAddress := "1.2.3.4"
EndpointAddresses := []string{
"5.6.7.8/24",
}
// Set up the mocks for this test
ip.On("State", op, podID, podName).Return(stateRunning, nil)
ip.On("EpAddresses", op, podID, podName).Return(EndpointAddresses, nil)
// Pod Running case
status, err := s.GetStatus(op, podID, podName, HostAddress)
assert.Check(t, err, "Expected nil")
assert.Check(t, is.Equal(status.Phase, v1.PodRunning), "Expected Phase PodRunning")
verifyConditions(t, status.Conditions, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue)
assert.Check(t, is.Equal(status.HostIP, "1.2.3.4"), "Expected Host IP Address")
assert.Check(t, is.Equal(status.PodIP, "5.6.7.8"), "Expected Pod IP Address")
}
func TestStatusPodStopping(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
assert.Check(t, err, "Expected nil")
HostAddress := "1.2.3.4"
EndpointAddresses := []string{
"5.6.7.8/24",
}
// Set up the mocks for this test
ip.On("State", op, podID, podName).Return(stateStopping, nil)
ip.On("EpAddresses", op, podID, podName).Return(EndpointAddresses, nil)
// Pod error case
status, err := s.GetStatus(op, podID, podName, HostAddress)
assert.Check(t, is.Equal(status.Phase, v1.PodRunning), "Expected Phase PodFailed")
verifyConditions(t, status.Conditions, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse)
assert.Check(t, is.Equal(status.HostIP, "1.2.3.4"), "Expected Host IP Address")
assert.Check(t, is.Equal(status.PodIP, "5.6.7.8"), "Expected Pod IP Address")
}
func TestStatusPodStopped(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
assert.Check(t, err, "Expected nil")
HostAddress := "1.2.3.4"
EndpointAddresses := []string{
"5.6.7.8/24",
}
// Set up the mocks for this test
ip.On("State", op, podID, podName).Return(stateStopped, nil)
ip.On("EpAddresses", op, podID, podName).Return(EndpointAddresses, nil)
// Pod error case
status, err := s.GetStatus(op, podID, podName, HostAddress)
assert.Check(t, is.Equal(status.Phase, v1.PodSucceeded), "Expected Phase PodFailed")
verifyConditions(t, status.Conditions, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse)
assert.Check(t, is.Equal(status.HostIP, "1.2.3.4"), "Expected Host IP Address")
assert.Check(t, is.Equal(status.PodIP, "5.6.7.8"), "Expected Pod IP Address")
}
func TestStatusPodRemoving(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
assert.Check(t, err, "Expected nil")
HostAddress := "1.2.3.4"
EndpointAddresses := []string{
"5.6.7.8/24",
}
// Set up the mocks for this test
ip.On("State", op, podID, podName).Return(stateRemoving, nil)
ip.On("EpAddresses", op, podID, podName).Return(EndpointAddresses, nil)
// Pod error case
status, err := s.GetStatus(op, podID, podName, HostAddress)
assert.Check(t, is.Equal(status.Phase, v1.PodSucceeded), "Expected Phase PodFailed")
verifyConditions(t, status.Conditions, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse)
assert.Check(t, is.Equal(status.HostIP, "1.2.3.4"), "Expected Host IP Address")
assert.Check(t, is.Equal(status.PodIP, "5.6.7.8"), "Expected Pod IP Address")
}
func TestStatusPodRemoved(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
assert.Check(t, err, "Expected nil")
HostAddress := "1.2.3.4"
EndpointAddresses := []string{
"5.6.7.8/24",
}
// Set up the mocks for this test
ip.On("State", op, podID, podName).Return(stateRemoved, nil)
ip.On("EpAddresses", op, podID, podName).Return(EndpointAddresses, nil)
// Pod error case
status, err := s.GetStatus(op, podID, podName, HostAddress)
assert.Check(t, is.Equal(status.Phase, v1.PodSucceeded), "Expected Phase PodFailed")
verifyConditions(t, status.Conditions, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse)
assert.Check(t, is.Equal(status.HostIP, "1.2.3.4"), "Expected Host IP Address")
assert.Check(t, is.Equal(status.PodIP, "5.6.7.8"), "Expected Pod IP Address")
}
func TestStatusError(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
// Start with arguments
s, err := NewPodStatus(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Status but received nil")
assert.Check(t, err, "Expected nil")
HostAddress := "0.0.0.0"
// Set up the mocks for this test
fakeErr := fakeError("invalid Pod")
ip.On("State", op, podID, podName).Return("", fakeErr)
ip.On("EpAddresses", op, podID, podName).Return(nil, fakeErr)
// Error case
status, err := s.GetStatus(op, podID, podName, HostAddress)
assert.Check(t, err, "Expected nil")
assert.Check(t, is.Equal(status.Phase, v1.PodUnknown), "Expected Phase PodUnknown")
verifyConditions(t, status.Conditions, v1.ConditionUnknown, v1.ConditionUnknown, v1.ConditionUnknown)
assert.Check(t, is.Equal(status.HostIP, "0.0.0.0"), "Expected Host IP Address")
assert.Check(t, is.Equal(status.PodIP, "0.0.0.0"), "Expected Pod IP Address")
}
func verifyConditions(t *testing.T, conditions []v1.PodCondition, scheduled v1.ConditionStatus, initialized v1.ConditionStatus, ready v1.ConditionStatus) {
for _, condition := range conditions {
switch condition.Type {
case v1.PodScheduled:
assert.Check(t, is.Equal(condition.Status, scheduled), "Condition Pod Scheduled")
break
case v1.PodInitialized:
assert.Check(t, is.Equal(condition.Status, initialized), "Condition Pod Initialized")
break
case v1.PodReady:
assert.Check(t, is.Equal(condition.Status, ready), "Condition Pod Ready")
break
}
}
}

View File

@@ -1,95 +0,0 @@
package operations
import (
"fmt"
"time"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
vicerrors "github.com/vmware/vic/lib/apiservers/engine/errors"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/pkg/retry"
"github.com/vmware/vic/pkg/trace"
)
type PodStopper interface {
Stop(op trace.Operation, id, name string) error
}
type VicPodStopper struct {
client *client.PortLayer
isolationProxy proxy.IsolationProxy
}
type VicPodStopperError string
func (e VicPodStopperError) Error() string { return string(e) }
const (
PodStopperPortlayerClientError = VicPodStopperError("PodStopper called with an invalid portlayer client")
PodStopperIsolationProxyError = VicPodStopperError("PodStopper called with an invalid isolation proxy")
PodStopperInvalidPodIDError = VicPodStopperError("PodStopper called with invalid PodID")
PodStopperInvalidPodNameError = VicPodStopperError("PodStopper called with invalid PodName")
)
func NewPodStopper(client *client.PortLayer, isolationProxy proxy.IsolationProxy) (PodStopper, error) {
if client == nil {
return nil, PodStopperPortlayerClientError
} else if isolationProxy == nil {
return nil, PodStopperIsolationProxyError
}
return &VicPodStopper{
client: client,
isolationProxy: isolationProxy,
}, nil
}
// Stop stops a pod but does not delete it
//
// arguments:
// op operation trace logger
// id pod id
// name pod name
// returns:
// error
func (v *VicPodStopper) Stop(op trace.Operation, id, name string) error {
defer trace.End(trace.Begin(fmt.Sprintf("id(%s), name(%s)", id, name), op))
operation := func() error {
return v.stop(op, id, name)
}
config := retry.NewBackoffConfig()
config.MaxElapsedTime = 10 * time.Minute
if err := retry.DoWithConfig(operation, vicerrors.IsConflictError, config); err != nil {
return err
}
return nil
}
func (v *VicPodStopper) stop(op trace.Operation, id, name string) error {
defer trace.End(trace.Begin(fmt.Sprintf("id(%s), name(%s)", id, name), op))
h, err := v.isolationProxy.Handle(op, id, name)
if err != nil {
return err
}
// Unbind the container to the scope
h, ep, err := v.isolationProxy.UnbindScope(op, h, name)
if err != nil {
return err
}
op.Infof("Scope Unbind returned endpoints %# +v", ep)
h, err = v.isolationProxy.SetState(op, h, name, "STOPPED")
if err != nil {
return err
}
err = v.isolationProxy.CommitHandle(op, h, id, -1)
op.Infof("Commit handler returned %v", err)
return err
}

View File

@@ -1,133 +0,0 @@
package operations
import (
"testing"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestNewPodStopper(t *testing.T) {
_, ip, _, _ := createMocks(t)
client := client.Default
// Positive Cases
s, err := NewPodStopper(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Stopper but received nil")
// Negative Cases
s, err = NewPodStopper(nil, ip)
assert.Check(t, is.Nil(s), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodStopperPortlayerClientError))
s, err = NewPodStopper(client, nil)
assert.Check(t, is.Nil(s), "Expected nil")
assert.Check(t, is.DeepEqual(err, PodStopperIsolationProxyError))
}
func TestStopPod(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
// Start with arguments
s, err := NewPodStopper(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Stopper but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
// Positive case
err = s.Stop(op, podID, podName)
assert.Check(t, err, "Expected nil")
}
func TestStopPodErrorHandle(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
// Start with arguments
s, err := NewPodStopper(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Stopper but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
// Failed Handle
fakeErr := fakeError("invalid handle")
ip.On("Handle", op, podID, podName).Return("", fakeErr)
err = s.Stop(op, podID, podName)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected invalid handle error")
}
func TestStopPodErrorUnbindScope(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
// Start with arguments
s, err := NewPodStopper(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Stopper but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
// Failed UnbindScope
fakeErr := fakeError("failed UnbindScope")
ip.On("UnbindScope", op, podHandle, podName).Return("", nil, fakeErr)
err = s.Stop(op, podID, podName)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected failed UnbindScope error")
}
func TestStopPodErrorSetState(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
// Start with arguments
s, err := NewPodStopper(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Stopper but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(nil)
// Failed SetState
fakeErr := fakeError("failed SetState")
ip.On("SetState", op, podHandle, podName, "STOPPED").Return("", fakeErr)
err = s.Stop(op, podID, podName)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected failed SetState error")
}
func TestStopPodErrorCommit(t *testing.T) {
client := client.Default
_, ip, _, op := createMocks(t)
// Start with arguments
s, err := NewPodStopper(client, ip)
assert.Check(t, s != nil, "Expected non-nil creating a pod Stopper but received nil")
assert.Check(t, err, "Expected nil")
// Set up the mocks for this test
ip.On("Handle", op, podID, podName).Return(podHandle, nil)
ip.On("UnbindScope", op, podHandle, podName).Return(podHandle, fakeEP, nil)
ip.On("SetState", op, podHandle, podName, "STOPPED").Return(podHandle, nil)
// Failed Commit
fakeErr := fakeError("failed Commit")
ip.On("CommitHandle", op, podHandle, podID, int32(-1)).Return(fakeErr)
err = s.Stop(op, podID, podName)
assert.Check(t, is.Error(err, fakeErr.Error()), "Expected failed Commit error")
}

View File

@@ -1,10 +0,0 @@
package pod
import (
"k8s.io/api/core/v1"
)
type VicPod struct {
ID string
Pod *v1.Pod
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -1,167 +0,0 @@
package proxy
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/lib/metadata"
"github.com/vmware/vic/pkg/trace"
)
//TODO: This image cache needs to hook into the VIC persona to receive an event when users pulled images
// via docker.
type ImageStore interface {
Get(op trace.Operation, idOrRef, tag string, actuate bool) (*metadata.ImageConfig, error)
GetImages(op trace.Operation) []*metadata.ImageConfig
PullImage(op trace.Operation, image, tag, username, password string) error
}
type VicImageStore struct {
client *client.PortLayer
personaAddr string
portlayerAddr string
}
type ImageStoreError string
func (e ImageStoreError) Error() string { return string(e) }
const (
ImageStorePortlayerClientError = ImageStoreError("ImageStore cannot be created without a valid portlayer client")
ImageStorePersonaAddrError = ImageStoreError("ImageStore cannot be created without a valid VIC persona addr")
ImageStorePortlayerAddrError = ImageStoreError("ImageStore cannot be created without a valid VIC portlayer addr")
ImageStoreContainerIDError = ImageStoreError("ImageStore called with empty container ID")
ImageStoreEmptyUserNameError = ImageStoreError("ImageStore called with empty username")
ImageStoreEmptyPasswordError = ImageStoreError("ImageStore called with empty password")
)
func NewImageStore(plClient *client.PortLayer, personaAddr, portlayerAddr string) (ImageStore, error) {
if plClient == nil {
return nil, ImageStorePortlayerClientError
}
if personaAddr == "" {
return nil, ImageStorePersonaAddrError
}
if portlayerAddr == "" {
return nil, ImageStorePortlayerAddrError
}
err := cache.InitializeImageCache(plClient)
if err != nil {
return nil, err
}
vs := &VicImageStore{
client: plClient,
personaAddr: personaAddr,
portlayerAddr: portlayerAddr,
}
return vs, nil
}
// Get retrieves the VIC ImageConfig data structure. If the config is not cached,
// VicImageStore can request imagec to pull the image if actuate is set to true.
//
// arguments:
// op operation trace logger
// idOrRef docker image id or reference
// tag docker image tag
// realize determines whether the image is pulled if not in the cache
// returns:
// error
func (v *VicImageStore) Get(op trace.Operation, idOrRef, tag string, realize bool) (*metadata.ImageConfig, error) {
defer trace.End(trace.Begin(fmt.Sprintf("Get - %s:%s", idOrRef, tag), op))
if idOrRef == "" {
op.Errorf(ImageStoreContainerIDError.Error())
return nil, ImageStoreContainerIDError
}
c, err := cache.ImageCache().Get(idOrRef)
if err != nil && realize {
err = v.PullImage(op, idOrRef, tag, "", "")
if err == nil {
//TODO: Find a better way to get update imageconfig instead of this hammer
err := cache.InitializeImageCache(v.client)
if err != nil {
return nil, err
}
c, err = cache.ImageCache().Get(idOrRef)
if err != nil {
return nil, err
}
}
}
return c, nil
}
// Get retrieves all the VIC ImageConfig data structure.
//
// arguments:
// op operation trace logger
// returns:
// array of ImageConfig
func (v *VicImageStore) GetImages(op trace.Operation) []*metadata.ImageConfig {
defer trace.End(trace.Begin("", op))
return cache.ImageCache().GetImages()
}
// PullImage makes a request to the VIC persona server (imageC component) to retrieve a container image.
//
// arguments:
// op operation trace logger
// idOrRef docker image id or reference
// tag docker image tag
// username user name for the registry server
// password password for the registry server
// returns:
// array of ImageConfig
func (v *VicImageStore) PullImage(op trace.Operation, image, tag, username, password string) error {
defer trace.End(trace.Begin(fmt.Sprintf("Get - %s:%s", image, tag), op))
if image == "" {
op.Errorf(ImageStoreContainerIDError.Error())
return ImageStoreContainerIDError
}
pullClient := &http.Client{Timeout: 60 * time.Second}
var personaServer string
if tag == "" {
personaServer = fmt.Sprintf("http://%s/v1.35/images/create?fromImage=%s", v.personaAddr, image)
} else {
personaServer = fmt.Sprintf("http://%s/v1.35/images/create?fromImage=%s&tag=%s", v.personaAddr, image, tag)
}
op.Infof("POST %s", personaServer)
reader := bytes.NewBuffer([]byte(""))
resp, err := pullClient.Post(personaServer, "application/json", reader)
if err != nil {
op.Errorf("Error from docker pull: error = %s", err.Error())
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
msg := fmt.Sprintf("Error from docker pull: status = %d", resp.StatusCode)
op.Errorf(msg)
return fmt.Errorf(msg)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
msg := fmt.Sprintf("Error reading docker pull response: error = %s", err.Error())
op.Errorf(msg)
return fmt.Errorf(msg)
}
op.Infof("Response from docker pull: body = %s", string(body))
return nil
}

View File

@@ -1,619 +0,0 @@
package proxy
import (
"fmt"
"time"
vicerrors "github.com/vmware/vic/lib/apiservers/engine/errors"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers"
"github.com/vmware/vic/lib/apiservers/portlayer/client/interaction"
"github.com/vmware/vic/lib/apiservers/portlayer/client/logging"
"github.com/vmware/vic/lib/apiservers/portlayer/client/scopes"
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
"github.com/vmware/vic/lib/apiservers/portlayer/client/tasks"
"github.com/vmware/vic/lib/apiservers/portlayer/models"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/sys"
"github.com/docker/docker/api/types/strslice"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/cache"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/constants"
)
type IsolationProxy interface {
CreateHandle(op trace.Operation) (string, string, error)
AddImageToHandle(op trace.Operation, handle, deltaID, layerID, imageID, imageName string) (string, error)
CreateHandleTask(op trace.Operation, handle, id, layerID string, config IsolationContainerConfig) (string, error)
AddHandleToScope(op trace.Operation, handle string, config IsolationContainerConfig) (string, error)
AddInteractionToHandle(op trace.Operation, handle string) (string, error)
AddLoggingToHandle(op trace.Operation, handle string) (string, error)
CommitHandle(op trace.Operation, handle, containerID string, waitTime int32) error
SetState(op trace.Operation, handle, name, state string) (string, error)
BindScope(op trace.Operation, handle string, name string) (string, interface{}, error)
UnbindScope(op trace.Operation, handle string, name string) (string, interface{}, error)
Handle(op trace.Operation, id, name string) (string, error)
Remove(op trace.Operation, id string, force bool) error
State(op trace.Operation, id, name string) (string, error)
EpAddresses(op trace.Operation, id, name string) ([]string, error)
}
type VicIsolationProxy struct {
client *client.PortLayer
imageStore ImageStore
podCache cache.PodCache
portlayerAddr string
hostUUID string
}
type PortBinding struct {
HostIP string
HostPort string
}
type IsolationContainerConfig struct {
ID string
ImageID string
LayerID string
ImageName string
Name string
Namespace string
Cmd []string
Path string
Entrypoint []string
//Args []string
Env []string
WorkingDir string
User string
StopSignal string
Attach bool
StdinOnce bool
OpenStdin bool
Tty bool
CPUCount int64
Memory int64
PortMap map[string]PortBinding
}
func NewIsolationProxy(plClient *client.PortLayer, portlayerAddr string, hostUUID string, imageStore ImageStore, podCache cache.PodCache) IsolationProxy {
if plClient == nil {
return nil
}
return &VicIsolationProxy{
client: plClient,
imageStore: imageStore,
podCache: podCache,
portlayerAddr: portlayerAddr,
hostUUID: hostUUID,
}
}
// CreateHandle creates a "manifest" that will be used by Commit() to create the actual
// isolation vm.
//
// returns:
// (container/pod id, handle, error)
func (v *VicIsolationProxy) CreateHandle(op trace.Operation) (string, string, error) {
defer trace.End(trace.Begin("CreateHandle", op))
if v.client == nil {
return "", "", vicerrors.NillPortlayerClientError("IsolationProxy")
}
// Call the Exec port layer to create the container
var err error
var hostUUID string
if v.hostUUID != "" {
hostUUID = v.hostUUID
} else {
hostUUID, err = sys.UUID()
}
if err != nil {
return "", "", vicerrors.InternalServerError("IsolationProxy.CreateContainerHandle got unexpected error getting VCH UUID")
}
plCreateParams := initIsolationConfig(op, "", constants.DummyRepoName, constants.DummyImage, constants.DummyLayerID, hostUUID)
createResults, err := v.client.Containers.Create(plCreateParams)
if err != nil {
if _, ok := err.(*containers.CreateNotFound); ok {
cerr := fmt.Errorf("No such image: %s", constants.DummyImage)
op.Errorf("%s (%s)", cerr, err)
return "", "", vicerrors.NotFoundError(cerr.Error())
}
// If we get here, most likely something went wrong with the port layer API server
return "", "", vicerrors.InternalServerError(err.Error())
}
id := createResults.Payload.ID
h := createResults.Payload.Handle
return id, h, nil
}
// Handle retrieves a handle to a VIC container. Handles should be treated as opaque strings.
//
// returns:
// (handle string, error)
func (v *VicIsolationProxy) Handle(op trace.Operation, id, name string) (string, error) {
if v.client == nil {
return "", vicerrors.NillPortlayerClientError("IsolationProxy")
}
resp, err := v.client.Containers.Get(containers.NewGetParamsWithContext(op).WithID(id))
if err != nil {
switch err := err.(type) {
case *containers.GetNotFound:
return "", vicerrors.NotFoundError(name)
case *containers.GetDefault:
return "", vicerrors.InternalServerError(err.Payload.Message)
default:
return "", vicerrors.InternalServerError(err.Error())
}
}
return resp.Payload, nil
}
func (v *VicIsolationProxy) AddImageToHandle(op trace.Operation, handle, deltaID, layerID, imageID, imageName string) (string, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", vicerrors.InternalServerError("IsolationProxy.AddImageToContainer failed to get the portlayer client")
}
var err error
var hostUUID string
if v.hostUUID != "" {
hostUUID = v.hostUUID
} else {
hostUUID, err = sys.UUID()
}
if err != nil {
return "", vicerrors.InternalServerError("IsolationProxy.AddImageToContainer got unexpected error getting VCH UUID")
}
response, err := v.client.Storage.ImageJoin(storage.NewImageJoinParamsWithContext(op).WithStoreName(hostUUID).WithID(layerID).
WithConfig(&models.ImageJoinConfig{
Handle: handle,
DeltaID: deltaID,
ImageID: imageID,
RepoName: imageName,
}))
if err != nil {
return "", vicerrors.InternalServerError(err.Error())
}
handle, ok := response.Payload.Handle.(string)
if !ok {
return "", vicerrors.InternalServerError(fmt.Sprintf("Type assertion failed for %#+v", handle))
}
return handle, nil
}
func (v *VicIsolationProxy) CreateHandleTask(op trace.Operation, handle, id, layerID string, config IsolationContainerConfig) (string, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", vicerrors.InternalServerError("IsolationProxy.CreateContainerTask failed to create a portlayer client")
}
op.Debugf("CreateHandleTask - %#v", config)
plTaskParams := IsolationContainerConfigToTask(op, id, layerID, config)
plTaskParams.Config.Handle = handle
op.Debugf("*** CreateContainerTask - params = %#v", *plTaskParams.Config)
responseJoin, err := v.client.Tasks.Join(plTaskParams)
if err != nil {
op.Errorf("Unable to join primary task to container: %+v", err)
return "", vicerrors.InternalServerError(err.Error())
}
handle, ok := responseJoin.Payload.Handle.(string)
if !ok {
return "", vicerrors.InternalServerError(fmt.Sprintf("Type assertion failed on handle from task join: %#+v", handle))
}
plBindParams := tasks.NewBindParamsWithContext(op).WithConfig(&models.TaskBindConfig{Handle: handle, ID: id})
responseBind, err := v.client.Tasks.Bind(plBindParams)
if err != nil {
op.Errorf("Unable to bind primary task to container: %+v", err)
return "", vicerrors.InternalServerError(err.Error())
}
handle, ok = responseBind.Payload.Handle.(string)
if !ok {
return "", vicerrors.InternalServerError(fmt.Sprintf("Type assertion failed on handle from task bind %#+v", handle))
}
return handle, nil
}
// AddHandleToScope adds a container, referenced by handle, to a scope.
// If an error is return, the returned handle should not be used.
//
// returns:
// modified handle
func (v *VicIsolationProxy) AddHandleToScope(op trace.Operation, handle string, config IsolationContainerConfig) (string, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", vicerrors.NillPortlayerClientError("IsolationProxy")
}
// configure network
netConf := networkConfigFromIsolationConfig(config)
if netConf != nil {
addContRes, err := v.client.Scopes.AddContainer(scopes.NewAddContainerParamsWithContext(op).
WithScope(netConf.NetworkName).
WithConfig(&models.ScopesAddContainerConfig{
Handle: handle,
NetworkConfig: netConf,
}))
if err != nil {
op.Errorf("IsolationProxy.AddContainerToScope: Scopes error: %s", err.Error())
return handle, vicerrors.InternalServerError(err.Error())
}
defer func() {
if err == nil {
return
}
// roll back the AddContainer call
if _, err2 := v.client.Scopes.RemoveContainer(scopes.NewRemoveContainerParamsWithContext(op).WithHandle(handle).WithScope(netConf.NetworkName)); err2 != nil {
op.Warnf("could not roll back container add: %s", err2)
}
}()
handle = addContRes.Payload
}
return handle, nil
}
// AddLoggingToHandle adds logging capability to the isolation vm, referenced by handle.
// If an error is return, the returned handle should not be used.
//
// returns:
// modified handle
func (v *VicIsolationProxy) AddLoggingToHandle(op trace.Operation, handle string) (string, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", vicerrors.NillPortlayerClientError("IsolationProxy")
}
response, err := v.client.Logging.LoggingJoin(logging.NewLoggingJoinParamsWithContext(op).
WithConfig(&models.LoggingJoinConfig{
Handle: handle,
}))
if err != nil {
return "", vicerrors.InternalServerError(err.Error())
}
handle, ok := response.Payload.Handle.(string)
if !ok {
return "", vicerrors.InternalServerError(fmt.Sprintf("Type assertion failed for %#+v", handle))
}
return handle, nil
}
// AddInteractionToContainer adds interaction capabilities to a container, referenced by handle.
// If an error is return, the returned handle should not be used.
//
// returns:
// modified handle
func (v *VicIsolationProxy) AddInteractionToHandle(op trace.Operation, handle string) (string, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", vicerrors.NillPortlayerClientError("IsolationProxy")
}
response, err := v.client.Interaction.InteractionJoin(interaction.NewInteractionJoinParamsWithContext(op).
WithConfig(&models.InteractionJoinConfig{
Handle: handle,
}))
if err != nil {
return "", vicerrors.InternalServerError(err.Error())
}
handle, ok := response.Payload.Handle.(string)
if !ok {
return "", vicerrors.InternalServerError(fmt.Sprintf("Type assertion failed for %#+v", handle))
}
return handle, nil
}
func (v *VicIsolationProxy) CommitHandle(op trace.Operation, handle, containerID string, waitTime int32) error {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return vicerrors.NillPortlayerClientError("IsolationProxy")
}
var commitParams *containers.CommitParams
if waitTime > 0 {
commitParams = containers.NewCommitParamsWithContext(op).WithHandle(handle).WithWaitTime(&waitTime)
} else {
commitParams = containers.NewCommitParamsWithContext(op).WithHandle(handle)
}
_, err := v.client.Containers.Commit(commitParams)
if err != nil {
switch err := err.(type) {
case *containers.CommitNotFound:
return vicerrors.NotFoundError(containerID)
case *containers.CommitConflict:
return vicerrors.ConflictError(err.Error())
case *containers.CommitDefault:
return vicerrors.InternalServerError(err.Payload.Message)
default:
return vicerrors.InternalServerError(err.Error())
}
}
return nil
}
//TODO: I don't think this function should be in here.
// BindNetwork binds the handle to the scope and returns endpoints. Caller of the function does not need
// to interpret the return value. In the event the caller want to unbind,
func (v *VicIsolationProxy) BindScope(op trace.Operation, handle string, name string) (string, interface{}, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", nil, vicerrors.NillPortlayerClientError("IsolationProxy")
}
bindParams := scopes.NewBindContainerParamsWithContext(op).WithHandle(handle)
bindRes, err := v.client.Scopes.BindContainer(bindParams)
if err != nil {
switch err := err.(type) {
case *scopes.BindContainerNotFound:
return "", nil, vicerrors.NotFoundError(name)
case *scopes.BindContainerInternalServerError:
return "", nil, vicerrors.InternalServerError(err.Payload.Message)
default:
return "", nil, vicerrors.InternalServerError(err.Error())
}
}
return bindRes.Payload.Handle, bindRes.Payload.Endpoints, nil
}
func (v *VicIsolationProxy) UnbindScope(op trace.Operation, handle string, name string) (string, interface{}, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", nil, vicerrors.NillPortlayerClientError("IsolationProxy")
}
unbindParams := scopes.NewUnbindContainerParamsWithContext(op).WithHandle(handle)
resp, err := v.client.Scopes.UnbindContainer(unbindParams)
if err != nil {
switch err := err.(type) {
case *scopes.UnbindContainerNotFound:
return "", nil, vicerrors.NotFoundError(name)
case *scopes.UnbindContainerInternalServerError:
return "", nil, vicerrors.InternalServerError(err.Payload.Message)
default:
return "", nil, vicerrors.InternalServerError(err.Error())
}
}
return resp.Payload.Handle, resp.Payload.Endpoints, nil
}
// SetState adds the desire state of the isolation unit once the handle is commited.
//
// returns handle string and error
func (v *VicIsolationProxy) SetState(op trace.Operation, handle, name, state string) (string, error) {
defer trace.End(trace.Begin(handle, op))
if v.client == nil {
return "", vicerrors.NillPortlayerClientError("IsolationProxy")
}
resp, err := v.client.Containers.StateChange(containers.NewStateChangeParamsWithContext(op).WithHandle(handle).WithState(state))
if err != nil {
switch err := err.(type) {
case *containers.StateChangeNotFound:
return "", vicerrors.NotFoundError(name)
case *containers.StateChangeDefault:
return "", vicerrors.InternalServerError(err.Payload.Message)
default:
return "", vicerrors.InternalServerError(err.Error())
}
}
return resp.Payload, nil
}
func (v *VicIsolationProxy) Remove(op trace.Operation, id string, force bool) error {
defer trace.End(trace.Begin(id, op))
if v.client == nil {
return vicerrors.NillPortlayerClientError("IsolationProxy")
}
pForce := force
params := containers.NewContainerRemoveParamsWithContext(op).
WithID(id).
WithForce(&pForce).
WithTimeout(120 * time.Second)
removeOK, err := v.client.Containers.ContainerRemove(params)
op.Debugf("ContainerRemove returned %# +v", removeOK)
return err
}
func (v *VicIsolationProxy) State(op trace.Operation, id, name string) (string, error) {
defer trace.End(trace.Begin(id, op))
payload, err := v.getInfo(op, id, name)
if err != nil {
return "", err
}
state := payload.ContainerConfig.State
return state, nil
}
func (v *VicIsolationProxy) EpAddresses(op trace.Operation, id, name string) ([]string, error) {
defer trace.End(trace.Begin(id, op))
payload, err := v.getInfo(op, id, name)
if err != nil {
return nil, err
}
addresses := make([]string, 0)
for _, ep := range payload.Endpoints {
addresses = append(addresses, ep.Address)
}
return addresses, nil
}
// Private methods
func (v *VicIsolationProxy) getInfo(op trace.Operation, id, name string) (*models.ContainerInfo, error) {
defer trace.End(trace.Begin(id, op))
if v.client == nil {
return nil, vicerrors.NillPortlayerClientError("IsolationProxy")
}
results, err := v.client.Containers.GetContainerInfo(containers.NewGetContainerInfoParamsWithContext(op).WithID(id))
if err != nil {
switch err := err.(type) {
case *containers.GetContainerInfoNotFound:
return nil, vicerrors.NotFoundError(name)
case *containers.GetContainerInfoInternalServerError:
return nil, vicerrors.InternalServerError(err.Payload.Message)
default:
return nil, vicerrors.InternalServerError(fmt.Sprintf("Unknown error from port layer: %s", err))
}
}
return results.Payload, nil
}
//------------------------------------
// Utility Functions
//------------------------------------
// Convert isolation container config to portlayer task param
func IsolationContainerConfigToTask(op trace.Operation, id, layerID string, ic IsolationContainerConfig) *tasks.JoinParams {
config := &models.TaskJoinConfig{}
var path string
var args []string
// we explicitly specify the ID for the primary task so that it's the same as the containerID
config.ID = id
// Set the filesystem namespace this task expects to run in
config.Namespace = layerID
// Expand cmd into entrypoint and args
cmd := strslice.StrSlice(ic.Cmd)
if len(ic.Entrypoint) != 0 {
path, args = ic.Entrypoint[0], append(ic.Entrypoint[1:], cmd...)
} else {
path, args = cmd[0], cmd[1:]
}
// copy the path
config.Path = path
// copy the args
config.Args = make([]string, len(args))
copy(config.Args, args)
// copy the env array
config.Env = make([]string, len(ic.Env))
copy(config.Env, ic.Env)
// working dir
config.WorkingDir = ic.WorkingDir
// user
config.User = ic.User
// attach. Always set to true otherwise we cannot attach later.
// this tells portlayer container is attachable.
config.Attach = true
// openstdin
config.OpenStdin = ic.OpenStdin
// tty
config.Tty = ic.Tty
// container stop signal
config.StopSignal = ic.StopSignal
op.Debugf("dockerContainerCreateParamsToTask = %+v", config)
return tasks.NewJoinParamsWithContext(op).WithConfig(config)
}
// initIsolationConfig returns a default config used to create the isolation unit handle
func initIsolationConfig(op trace.Operation, name, repoName, imageID, layerID, imageStore string) *containers.CreateParams {
config := &models.ContainerCreateConfig{}
config.NumCpus = constants.DefaultCPUs
config.MemoryMB = constants.DefaultMemory
// Layer/vmdk to use
config.Layer = layerID
// Image ID
config.Image = imageID
// Repo Requested
config.RepoName = repoName
//copy friendly name
config.Name = name
// image store
config.ImageStore = &models.ImageStore{Name: imageStore}
// network
config.NetworkDisabled = true
// hostname
config.Hostname = constants.HostName
//// domainname - https://github.com/moby/moby/issues/27067
//config.Domainname = cc.Config.Domainname
op.Debugf("dockerContainerCreateParamsToPortlayer = %+v", config)
return containers.NewCreateParamsWithContext(op).WithCreateConfig(config)
}
func networkConfigFromIsolationConfig(config IsolationContainerConfig) *models.NetworkConfig {
nc := &models.NetworkConfig{
NetworkName: "default",
}
for key, val := range config.PortMap {
nc.Ports = append(nc.Ports, fmt.Sprintf("%s:%s", val.HostPort, key))
}
return nc
}

View File

@@ -1,65 +0,0 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package mocks
import metadata "github.com/vmware/vic/lib/metadata"
import mock "github.com/stretchr/testify/mock"
import trace "github.com/vmware/vic/pkg/trace"
// ImageStore is an autogenerated mock type for the ImageStore type
type ImageStore struct {
mock.Mock
}
// Get provides a mock function with given fields: op, idOrRef, tag, actuate
func (_m *ImageStore) Get(op trace.Operation, idOrRef string, tag string, actuate bool) (*metadata.ImageConfig, error) {
ret := _m.Called(op, idOrRef, tag, actuate)
var r0 *metadata.ImageConfig
if rf, ok := ret.Get(0).(func(trace.Operation, string, string, bool) *metadata.ImageConfig); ok {
r0 = rf(op, idOrRef, tag, actuate)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*metadata.ImageConfig)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, string, bool) error); ok {
r1 = rf(op, idOrRef, tag, actuate)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetImages provides a mock function with given fields: op
func (_m *ImageStore) GetImages(op trace.Operation) []*metadata.ImageConfig {
ret := _m.Called(op)
var r0 []*metadata.ImageConfig
if rf, ok := ret.Get(0).(func(trace.Operation) []*metadata.ImageConfig); ok {
r0 = rf(op)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*metadata.ImageConfig)
}
}
return r0
}
// PullImage provides a mock function with given fields: op, image, tag, username, password
func (_m *ImageStore) PullImage(op trace.Operation, image string, tag string, username string, password string) error {
ret := _m.Called(op, image, tag, username, password)
var r0 error
if rf, ok := ret.Get(0).(func(trace.Operation, string, string, string, string) error); ok {
r0 = rf(op, image, tag, username, password)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -1,318 +0,0 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
import proxy "github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
import trace "github.com/vmware/vic/pkg/trace"
// IsolationProxy is an autogenerated mock type for the IsolationProxy type
type IsolationProxy struct {
mock.Mock
}
// AddHandleToScope provides a mock function with given fields: op, handle, config
func (_m *IsolationProxy) AddHandleToScope(op trace.Operation, handle string, config proxy.IsolationContainerConfig) (string, error) {
ret := _m.Called(op, handle, config)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, proxy.IsolationContainerConfig) string); ok {
r0 = rf(op, handle, config)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, proxy.IsolationContainerConfig) error); ok {
r1 = rf(op, handle, config)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AddImageToHandle provides a mock function with given fields: op, handle, deltaID, layerID, imageID, imageName
func (_m *IsolationProxy) AddImageToHandle(op trace.Operation, handle string, deltaID string, layerID string, imageID string, imageName string) (string, error) {
ret := _m.Called(op, handle, deltaID, layerID, imageID, imageName)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string, string, string, string) string); ok {
r0 = rf(op, handle, deltaID, layerID, imageID, imageName)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, string, string, string, string) error); ok {
r1 = rf(op, handle, deltaID, layerID, imageID, imageName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AddInteractionToHandle provides a mock function with given fields: op, handle
func (_m *IsolationProxy) AddInteractionToHandle(op trace.Operation, handle string) (string, error) {
ret := _m.Called(op, handle)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string) string); ok {
r0 = rf(op, handle)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string) error); ok {
r1 = rf(op, handle)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AddLoggingToHandle provides a mock function with given fields: op, handle
func (_m *IsolationProxy) AddLoggingToHandle(op trace.Operation, handle string) (string, error) {
ret := _m.Called(op, handle)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string) string); ok {
r0 = rf(op, handle)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string) error); ok {
r1 = rf(op, handle)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// BindScope provides a mock function with given fields: op, handle, name
func (_m *IsolationProxy) BindScope(op trace.Operation, handle string, name string) (string, interface{}, error) {
ret := _m.Called(op, handle, name)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string) string); ok {
r0 = rf(op, handle, name)
} else {
r0 = ret.Get(0).(string)
}
var r1 interface{}
if rf, ok := ret.Get(1).(func(trace.Operation, string, string) interface{}); ok {
r1 = rf(op, handle, name)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(interface{})
}
}
var r2 error
if rf, ok := ret.Get(2).(func(trace.Operation, string, string) error); ok {
r2 = rf(op, handle, name)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// CommitHandle provides a mock function with given fields: op, handle, containerID, waitTime
func (_m *IsolationProxy) CommitHandle(op trace.Operation, handle string, containerID string, waitTime int32) error {
ret := _m.Called(op, handle, containerID, waitTime)
var r0 error
if rf, ok := ret.Get(0).(func(trace.Operation, string, string, int32) error); ok {
r0 = rf(op, handle, containerID, waitTime)
} else {
r0 = ret.Error(0)
}
return r0
}
// CreateHandle provides a mock function with given fields: op
func (_m *IsolationProxy) CreateHandle(op trace.Operation) (string, string, error) {
ret := _m.Called(op)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation) string); ok {
r0 = rf(op)
} else {
r0 = ret.Get(0).(string)
}
var r1 string
if rf, ok := ret.Get(1).(func(trace.Operation) string); ok {
r1 = rf(op)
} else {
r1 = ret.Get(1).(string)
}
var r2 error
if rf, ok := ret.Get(2).(func(trace.Operation) error); ok {
r2 = rf(op)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// CreateHandleTask provides a mock function with given fields: op, handle, id, layerID, config
func (_m *IsolationProxy) CreateHandleTask(op trace.Operation, handle string, id string, layerID string, config proxy.IsolationContainerConfig) (string, error) {
ret := _m.Called(op, handle, id, layerID, config)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string, string, proxy.IsolationContainerConfig) string); ok {
r0 = rf(op, handle, id, layerID, config)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, string, string, proxy.IsolationContainerConfig) error); ok {
r1 = rf(op, handle, id, layerID, config)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// EpAddresses provides a mock function with given fields: op, id, name
func (_m *IsolationProxy) EpAddresses(op trace.Operation, id string, name string) ([]string, error) {
ret := _m.Called(op, id, name)
var r0 []string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string) []string); ok {
r0 = rf(op, id, name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, string) error); ok {
r1 = rf(op, id, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Handle provides a mock function with given fields: op, id, name
func (_m *IsolationProxy) Handle(op trace.Operation, id string, name string) (string, error) {
ret := _m.Called(op, id, name)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string) string); ok {
r0 = rf(op, id, name)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, string) error); ok {
r1 = rf(op, id, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Remove provides a mock function with given fields: op, id, force
func (_m *IsolationProxy) Remove(op trace.Operation, id string, force bool) error {
ret := _m.Called(op, id, force)
var r0 error
if rf, ok := ret.Get(0).(func(trace.Operation, string, bool) error); ok {
r0 = rf(op, id, force)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetState provides a mock function with given fields: op, handle, name, state
func (_m *IsolationProxy) SetState(op trace.Operation, handle string, name string, state string) (string, error) {
ret := _m.Called(op, handle, name, state)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string, string) string); ok {
r0 = rf(op, handle, name, state)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, string, string) error); ok {
r1 = rf(op, handle, name, state)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// State provides a mock function with given fields: op, id, name
func (_m *IsolationProxy) State(op trace.Operation, id string, name string) (string, error) {
ret := _m.Called(op, id, name)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string) string); ok {
r0 = rf(op, id, name)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(trace.Operation, string, string) error); ok {
r1 = rf(op, id, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UnbindScope provides a mock function with given fields: op, handle, name
func (_m *IsolationProxy) UnbindScope(op trace.Operation, handle string, name string) (string, interface{}, error) {
ret := _m.Called(op, handle, name)
var r0 string
if rf, ok := ret.Get(0).(func(trace.Operation, string, string) string); ok {
r0 = rf(op, handle, name)
} else {
r0 = ret.Get(0).(string)
}
var r1 interface{}
if rf, ok := ret.Get(1).(func(trace.Operation, string, string) interface{}); ok {
r1 = rf(op, handle, name)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(interface{})
}
}
var r2 error
if rf, ok := ret.Get(2).(func(trace.Operation, string, string) error); ok {
r2 = rf(op, handle, name)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}

View File

@@ -1,179 +0,0 @@
package utils
import (
"fmt"
"strings"
)
const (
BYTE = 1.0 << (10 * iota)
KILOBYTE
MEGABYTE
GIGABYTE
TERABYTE
PETABYTE
)
const (
KILOBYTESUFFIX = "Ki"
MEGABYTESUFFIX = "Mi"
GIGABYTESUFFIX = "Gi"
TERABYTESUFFIX = "Ti"
PETABYTESUFFIX = "Pi"
)
const (
ONE = 1.0
KILO = 1000.0
MEGA = KILO * KILO
GIGA = MEGA * KILO
TERA = GIGA * KILO
PETA = TERA * KILO
)
const (
KILOSUFFIX = "K"
MEGASUFFIX = "M"
GIGASUFFIX = "G"
TERASUFFIX = "T"
PETASUFFIX = "P"
)
const (
MEMORYCUTOVER = 100
FREQUENCYCUTOVER = 10
)
const (
MINPODSIZE = 2 * GIGABYTE
)
func MemsizeToBytesize(size int64, unit string) int64 {
u := strings.ToLower(unit)
var sizeInBytes int64
switch u {
case "b":
sizeInBytes = size
break
case "k":
case "kb":
sizeInBytes = size * KILOBYTE
break
case "m":
case "mb":
sizeInBytes = size * MEGABYTE
break
case "g":
case "gb":
sizeInBytes = size * GIGABYTE
break
case "t":
case "tb":
sizeInBytes = size * TERABYTE
break
case "p":
case "pb":
sizeInBytes = size * PETABYTE
break
default:
return 0
}
return sizeInBytes
}
func MemsizeToDecimalString(size int64, unit string) string {
sizeInBytes := MemsizeToBytesize(size, unit)
var res string
if sizeInBytes >= PETA*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/PETA, PETASUFFIX)
} else if sizeInBytes >= TERA*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/TERA, TERASUFFIX)
} else if sizeInBytes >= GIGA*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/GIGA, GIGASUFFIX)
} else if sizeInBytes >= MEGA*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/MEGA, MEGASUFFIX)
} else if sizeInBytes >= KILO*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/KILO, KILOSUFFIX)
} else {
res = fmt.Sprintf("%d", sizeInBytes)
}
return res
}
func MemsizeToBinaryString(size int64, unit string) string {
sizeInBytes := MemsizeToBytesize(size, unit)
var res string
if sizeInBytes >= PETABYTE*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/PETABYTE, PETABYTESUFFIX)
} else if sizeInBytes >= TERABYTE*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/TERABYTE, TERABYTESUFFIX)
} else if sizeInBytes >= GIGABYTE*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/GIGABYTE, GIGABYTESUFFIX)
} else if sizeInBytes >= MEGABYTE*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/MEGABYTE, MEGABYTESUFFIX)
} else if sizeInBytes >= KILOBYTE*MEMORYCUTOVER {
res = fmt.Sprintf("%d%s", sizeInBytes/KILOBYTE, KILOBYTESUFFIX)
} else {
res = fmt.Sprintf("%d", sizeInBytes)
}
return res
}
func MemsizeToMaxPodCount(size int64, unit string) int64 {
sizeInBytes := MemsizeToBytesize(size, unit)
// Divide by minimum pod size
return sizeInBytes / MINPODSIZE
}
func FrequencyToHertzFrequency(size int64, unit string) int64 {
u := strings.ToLower(unit)
var sizeInBytes int64
switch u {
case "k":
case "khz":
sizeInBytes = size * KILO
break
case "m":
case "mhz":
sizeInBytes = size * MEGA
break
case "g":
case "ghz":
sizeInBytes = size * GIGA
break
default:
return 0
}
return sizeInBytes
}
func CpuFrequencyToString(size int64, unit string) string {
var res string
hertz := FrequencyToHertzFrequency(size, unit)
if hertz >= GIGA*10 {
res = fmt.Sprintf("%d%s", hertz/GIGA, GIGASUFFIX)
} else if hertz >= MEGA*FREQUENCYCUTOVER {
res = fmt.Sprintf("%d%s", hertz/MEGA, MEGASUFFIX)
} else if hertz >= KILO*FREQUENCYCUTOVER {
res = fmt.Sprintf("%d%s", hertz/KILO, KILOSUFFIX)
} else {
res = fmt.Sprintf("%d", hertz)
}
return res
}
func CpuFrequencyToCores(size int64, unit string) int64 {
hertz := FrequencyToHertzFrequency(size, unit)
// Assume 1G per Core
cores := hertz / GIGA
return cores
}

View File

@@ -1,54 +0,0 @@
package utils
import (
"testing"
"strings"
"gotest.tools/assert"
)
func TestMemoryConversion(t *testing.T) {
memSize := MemsizeToBinaryString(2, "Gb")
assert.Assert(t, memSize == "2048Mi")
memSize = MemsizeToDecimalString(2, "Gb")
assert.Assert(t, memSize == "2147M")
memSize = MemsizeToBinaryString(2048, "Mb")
assert.Assert(t, memSize == "2048Mi")
memSize = MemsizeToDecimalString(2048, "Mb")
assert.Assert(t, memSize == "2147M")
memSize = MemsizeToBinaryString(2048*1024, "Kb")
assert.Assert(t, memSize == "2048Mi")
memSize = MemsizeToDecimalString(2048*1024, "Kb")
assert.Assert(t, memSize == "2147M")
memSize = MemsizeToBinaryString(MEMORYCUTOVER, "Gb")
assert.Assert(t, memSize == "100Gi")
memSize = MemsizeToBinaryString(MEMORYCUTOVER-1, "Gb")
strings.HasSuffix(memSize, "Mi")
assert.Assert(t, strings.HasSuffix(memSize, "Mi"))
memSize = MemsizeToBinaryString((MEMORYCUTOVER-1)*1024, "Mb")
assert.Assert(t, strings.HasSuffix(memSize, "Mi"))
memSize = MemsizeToDecimalString(MEMORYCUTOVER*1000000000, "b")
assert.Assert(t, memSize == "100G")
memSize = MemsizeToDecimalString((MEMORYCUTOVER-1)*1000000000, "b")
assert.Assert(t, strings.HasSuffix(memSize, "M"))
}
func TestFrequencyConversion(t *testing.T) {
feq := CpuFrequencyToString(FREQUENCYCUTOVER, "Ghz")
assert.Assert(t, feq == "10G")
feq = CpuFrequencyToString(FREQUENCYCUTOVER-1, "Ghz")
assert.Assert(t, feq == "9000M")
feq = CpuFrequencyToString((FREQUENCYCUTOVER-1)*1000, "Mhz")
assert.Assert(t, feq == "9000M")
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,507 +0,0 @@
package vic
import (
"fmt"
"os"
"path"
"syscall"
"time"
"golang.org/x/net/context"
log "github.com/Sirupsen/logrus"
"github.com/kr/pretty"
vicerrors "github.com/vmware/vic/lib/apiservers/engine/errors"
vicproxy "github.com/vmware/vic/lib/apiservers/engine/proxy"
"github.com/vmware/vic/lib/apiservers/portlayer/client"
"github.com/vmware/vic/lib/apiservers/portlayer/models"
vicconst "github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/pkg/dio"
viclog "github.com/vmware/vic/pkg/log"
"github.com/vmware/vic/pkg/retry"
"github.com/vmware/vic/pkg/trace"
"github.com/virtual-kubelet/virtual-kubelet/manager"
"github.com/virtual-kubelet/virtual-kubelet/providers"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/cache"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/operations"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/proxy"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic/utils"
"net"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type VicProvider struct {
resourceManager *manager.ResourceManager
nodeName string
os string
podCount int
config VicConfig
podCache cache.PodCache
client *client.PortLayer
imageStore proxy.ImageStore
isolationProxy proxy.IsolationProxy
systemProxy *vicproxy.VicSystemProxy
}
const (
// Name of filename used in the endpoint vm
LogFilename = "virtual-kubelet"
// PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ...
PanicLevel uint8 = iota
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel
// InfoLevel level. General operational entries about what's going on inside the
// application.
InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel
)
var (
portlayerUp chan struct{}
)
func NewVicProvider(configFile string, rm *manager.ResourceManager, nodeName, operatingSystem string) (*VicProvider, error) {
initLogger()
op := trace.NewOperation(context.Background(), "VicProvider creation: config - %s", configFile)
defer trace.End(trace.Begin("", op))
config := NewVicConfig(op, configFile)
op.Infof("Provider config = %#v", config)
plClient := vicproxy.NewPortLayerClient(config.PortlayerAddr)
op.Infof("** Wait for VCH servers to start")
if !waitForVCH(op, plClient, config.PersonaAddr) {
msg := "VicProvider timed out waiting for VCH's persona and portlayer servers"
op.Errorf(msg)
return nil, fmt.Errorf(msg)
}
i, err := proxy.NewImageStore(plClient, config.PersonaAddr, config.PortlayerAddr)
if err != nil {
msg := "Couldn't initialize the image store"
op.Error(msg)
return nil, fmt.Errorf(msg)
}
op.Infof("** creating proxy")
p := VicProvider{
config: config,
nodeName: nodeName,
os: operatingSystem,
podCache: cache.NewVicPodCache(),
client: plClient,
resourceManager: rm,
systemProxy: vicproxy.NewSystemProxy(plClient),
}
p.imageStore = i
p.isolationProxy = proxy.NewIsolationProxy(plClient, config.PortlayerAddr, config.HostUUID, i, p.podCache)
op.Infof("** ready to go")
return &p, nil
}
func waitForVCH(op trace.Operation, plClient *client.PortLayer, personaAddr string) bool {
backoffConf := retry.NewBackoffConfig()
backoffConf.MaxInterval = 2 * time.Second
backoffConf.InitialInterval = 500 * time.Millisecond
backoffConf.MaxElapsedTime = 10 * time.Minute
// Wait for portlayer to start up
systemProxy := vicproxy.NewSystemProxy(plClient)
opWaitForPortlayer := func() error {
op.Infof("** Checking portlayer server is running")
if !systemProxy.PingPortlayer(context.Background()) {
return vicerrors.ServerNotReadyError{Name: "Portlayer"}
}
return nil
}
if err := retry.DoWithConfig(opWaitForPortlayer, vicerrors.IsServerNotReady, backoffConf); err != nil {
op.Errorf("Wait for portlayer to be ready failed")
return false
}
// Wait for persona to start up
dockerClient := NewVicDockerClient(personaAddr)
opWaitForPersona := func() error {
op.Infof("** Checking persona server is running")
if err := dockerClient.Ping(op); err != nil {
return vicerrors.ServerNotReadyError{Name: "Persona"}
}
return nil
}
if err := retry.DoWithConfig(opWaitForPersona, vicerrors.IsServerNotReady, backoffConf); err != nil {
op.Errorf("Wait for VIC docker server to be ready failed")
return false
}
return true
}
func initLogger() {
var logPath string
if LocalInstance() {
logPath = path.Join("", ".", LogFilename+".log")
} else {
logPath = path.Join("", vicconst.DefaultLogDir, LogFilename+".log")
}
os.MkdirAll(vicconst.DefaultLogDir, 0755)
// #nosec: Expect file permissions to be 0600 or less
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_SYNC|syscall.O_NOCTTY, 0644)
if err != nil {
detail := fmt.Sprintf("failed to open file for VIC's virtual kubelet provider log: %s", err)
log.Error(detail)
}
// use multi-writer so it goes to both screen and session log
writer := dio.MultiWriter(f, os.Stdout)
logcfg := viclog.NewLoggingConfig()
logcfg.SetLogLevel(DebugLevel)
trace.SetLogLevel(DebugLevel)
trace.Logger.Out = writer
err = viclog.Init(logcfg)
if err != nil {
return
}
trace.InitLogger(logcfg)
}
// CreatePod takes a Kubernetes Pod and deploys it within the provider.
func (v *VicProvider) CreatePod(ctx context.Context, pod *v1.Pod) error {
op := trace.NewOperation(context.Background(), "CreatePod - %s", pod.Name)
defer trace.End(trace.Begin(pod.Name, op))
op.Debugf("Creating %s's pod = %# +v", pod.Name, pretty.Formatter(pod))
pc, err := operations.NewPodCreator(v.client, v.imageStore, v.isolationProxy, v.podCache, v.config.PersonaAddr, v.config.PortlayerAddr)
if err != nil {
return err
}
err = pc.CreatePod(op, pod, true)
if err != nil {
return err
}
//v.resourceManager.AddPod()
op.Debugf("** pod created ok")
return nil
}
// UpdatePod takes a Kubernetes Pod and updates it within the provider.
func (v *VicProvider) UpdatePod(ctx context.Context, pod *v1.Pod) error {
op := trace.NewOperation(context.Background(), "UpdatePod - %s", pod.Name)
defer trace.End(trace.Begin(pod.Name, op))
return nil
}
// DeletePod takes a Kubernetes Pod and deletes it from the provider.
func (v *VicProvider) DeletePod(ctx context.Context, pod *v1.Pod) error {
op := trace.NewOperation(context.Background(), "DeletePod - %s", pod.Name)
defer trace.End(trace.Begin(pod.Name, op))
op.Infof("Deleting %s's pod spec = %#v", pod.Name, pod.Spec)
pd, err := operations.NewPodDeleter(v.client, v.isolationProxy, v.podCache, v.config.PersonaAddr, v.config.PortlayerAddr)
if err != nil {
return err
}
err = pd.DeletePod(op, pod)
return err
}
// GetPod retrieves a pod by name from the provider (can be cached).
func (v *VicProvider) GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error) {
op := trace.NewOperation(context.Background(), "GetPod - %s", name)
defer trace.End(trace.Begin(name, op))
// Look for the pod in our cache of running pods
vp, err := v.podCache.Get(op, namespace, name)
if err != nil {
return nil, err
}
return vp.Pod, nil
}
// GetContainerLogs retrieves the logs of a container by name from the provider.
func (v *VicProvider) GetContainerLogs(ctx context.Context, namespace, podName, containerName string, tail int) (string, error) {
op := trace.NewOperation(context.Background(), "GetContainerLogs - pod[%s], container[%s]", podName, containerName)
defer trace.End(trace.Begin("", op))
return "", nil
}
// Get full pod name as defined in the provider context
// TODO: Implementation
func (p *VicProvider) GetPodFullName(namespace string, pod string) string {
return ""
}
// RunInContainer executes a command in a container in the pod, copying data
// between in/out/err and the container's stdin/stdout/stderr.
func (p *VicProvider) RunInContainer(ctx context.Context, namespace, name, container string, cmd []string, attach providers.AttachIO) error {
log.Printf("receive ExecInContainer %q\n", container)
return nil
}
// GetPodStatus retrieves the status of a pod by name from the provider.
// This function needs to return a status or the reconcile loop will stop running.
func (v *VicProvider) GetPodStatus(ctx context.Context, namespace, name string) (*v1.PodStatus, error) {
op := trace.NewOperation(context.Background(), "GetPodStatus - pod[%s], namespace [%s]", name, namespace)
defer trace.End(trace.Begin("GetPodStatus", op))
now := metav1.NewTime(time.Now())
errorStatus := &v1.PodStatus{
Phase: v1.PodUnknown,
StartTime: &now,
Conditions: []v1.PodCondition{
{
Type: v1.PodInitialized,
Status: v1.ConditionUnknown,
},
{
Type: v1.PodReady,
Status: v1.ConditionUnknown,
},
{
Type: v1.PodScheduled,
Status: v1.ConditionUnknown,
},
},
}
// Look for the pod in our cache of running pods
vp, err := v.podCache.Get(op, namespace, name)
if err != nil {
return errorStatus, err
}
// Instantiate status object
statusReporter, err := operations.NewPodStatus(v.client, v.isolationProxy)
if err != nil {
return errorStatus, err
}
var nodeAddress string
nodeAddresses := v.NodeAddresses(ctx)
if len(nodeAddresses) > 0 {
nodeAddress = nodeAddresses[0].Address
} else {
nodeAddress = "0.0.0.0"
}
status, err := statusReporter.GetStatus(op, vp.ID, name, nodeAddress)
if err != nil {
return errorStatus, err
}
if vp.Pod.Status.StartTime != nil {
status.StartTime = vp.Pod.Status.StartTime
} else {
status.StartTime = &now
}
for _, container := range vp.Pod.Spec.Containers {
status.ContainerStatuses = append(status.ContainerStatuses, v1.ContainerStatus{
Name: container.Name,
Image: container.Image,
Ready: true,
RestartCount: 0,
State: v1.ContainerState{
Running: &v1.ContainerStateRunning{
StartedAt: *status.StartTime,
},
},
})
}
return status, nil
}
// GetPods retrieves a list of all pods running on the provider (can be cached).
func (v *VicProvider) GetPods(ctx context.Context) ([]*v1.Pod, error) {
op := trace.NewOperation(context.Background(), "GetPods")
defer trace.End(trace.Begin("GetPods", op))
vps := v.podCache.GetAll(op)
allPods := make([]*v1.Pod, 0)
for _, vp := range vps {
allPods = append(allPods, vp.Pod)
}
return allPods, nil
}
// Capacity returns a resource list with the capacity constraints of the provider.
func (v *VicProvider) Capacity(ctx context.Context) v1.ResourceList {
op := trace.NewOperation(context.Background(), "VicProvider.Capacity")
defer trace.End(trace.Begin("", op))
if v.systemProxy == nil {
err := NilProxy("VicProvider.Capacity", "SystemProxy")
op.Error(err)
return v1.ResourceList{}
}
info, err := v.systemProxy.VCHInfo(context.Background())
if err != nil {
op.Errorf("VicProvider.Capacity failed to get VCHInfo: %s", err.Error())
}
op.Infof("VCH Config: %# +v\n", pretty.Formatter(info))
return KubeResourcesFromVchInfo(op, info)
}
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), which is polled periodically to update the node status
// within Kubernetes.
func (v *VicProvider) NodeConditions(ctx context.Context) []v1.NodeCondition {
// TODO: Make these dynamic and augment with custom ACI specific conditions of interest
return []v1.NodeCondition{
{
Type: "Ready",
Status: v1.ConditionTrue,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletReady",
Message: "kubelet is ready.",
},
{
Type: "OutOfDisk",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasSufficientDisk",
Message: "kubelet has sufficient disk space available",
},
{
Type: "MemoryPressure",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasSufficientMemory",
Message: "kubelet has sufficient memory available",
},
{
Type: "DiskPressure",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasNoDiskPressure",
Message: "kubelet has no disk pressure",
},
{
Type: "NetworkUnavailable",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "RouteCreated",
Message: "RouteController created a route",
},
}
}
// NodeAddresses returns a list of addresses for the node status
// within Kubernetes.
func (v *VicProvider) NodeAddresses(ctx context.Context) []v1.NodeAddress {
addrs, err := net.InterfaceAddrs()
if err != nil {
return []v1.NodeAddress{}
}
var outAddresses []v1.NodeAddress
for _, addr := range addrs {
ipnet, ok := addr.(*net.IPNet)
if !ok || ipnet.IP.IsLoopback() || ipnet.IP.To4() == nil {
continue
}
outAddress := v1.NodeAddress{
Type: v1.NodeInternalIP,
Address: ipnet.IP.String(),
}
outAddresses = append(outAddresses, outAddress)
}
return outAddresses
}
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
// within Kubernetes.
func (v *VicProvider) NodeDaemonEndpoints(ctx context.Context) *v1.NodeDaemonEndpoints {
return &v1.NodeDaemonEndpoints{
KubeletEndpoint: v1.DaemonEndpoint{
Port: 80,
},
}
}
// OperatingSystem returns the operating system the provider is for.
func (v *VicProvider) OperatingSystem() string {
return v.os
}
//------------------------------------
// Utility Functions
//------------------------------------
// KubeResourcesFromVchInfo returns a K8s node resource list, given the VCHInfo
func KubeResourcesFromVchInfo(op trace.Operation, info *models.VCHInfo) v1.ResourceList {
nr := make(v1.ResourceList)
if info != nil {
cores := utils.CpuFrequencyToCores(info.CPUMhz, "Mhz")
// translate CPU resources. K8s wants cores. We have virtual cores based on mhz.
cpuQ := resource.Quantity{}
cpuQ.Set(cores)
nr[v1.ResourceCPU] = cpuQ
memQstr := utils.MemsizeToBinaryString(info.Memory, "Mb")
// translate memory resources. K8s wants bytes.
memQ, err := resource.ParseQuantity(memQstr)
if err == nil {
nr[v1.ResourceMemory] = memQ
} else {
op.Errorf("KubeResourcesFromVchInfo, cannot parse MEM quantity: %s, err: %s", memQstr, err)
}
}
// Estimate the available pod count, based on memory
podCount := utils.MemsizeToMaxPodCount(info.Memory, "Mb")
containerCountQ := resource.Quantity{}
containerCountQ.Set(podCount)
nr[v1.ResourcePods] = containerCountQ
op.Infof("Capacity Resource Config: %# +v\n", pretty.Formatter(nr))
return nr
}
func NilProxy(caller, proxyName string) error {
return fmt.Errorf("%s: %s not valid", caller, proxyName)
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,188 +0,0 @@
package ansiterm
const LogEnv = "DEBUG_TERMINAL"
// ANSI constants
// References:
// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
// -- http://en.wikipedia.org/wiki/ANSI_escape_code
// -- http://vt100.net/emu/dec_ansi_parser
// -- http://vt100.net/emu/vt500_parser.svg
// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
// -- http://www.inwap.com/pdp10/ansicode.txt
const (
// ECMA-48 Set Graphics Rendition
// Note:
// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
// -- Windows does not expose the per-window cursor (i.e., caret) blink times
ANSI_SGR_RESET = 0
ANSI_SGR_BOLD = 1
ANSI_SGR_DIM = 2
_ANSI_SGR_ITALIC = 3
ANSI_SGR_UNDERLINE = 4
_ANSI_SGR_BLINKSLOW = 5
_ANSI_SGR_BLINKFAST = 6
ANSI_SGR_REVERSE = 7
_ANSI_SGR_INVISIBLE = 8
_ANSI_SGR_LINETHROUGH = 9
_ANSI_SGR_FONT_00 = 10
_ANSI_SGR_FONT_01 = 11
_ANSI_SGR_FONT_02 = 12
_ANSI_SGR_FONT_03 = 13
_ANSI_SGR_FONT_04 = 14
_ANSI_SGR_FONT_05 = 15
_ANSI_SGR_FONT_06 = 16
_ANSI_SGR_FONT_07 = 17
_ANSI_SGR_FONT_08 = 18
_ANSI_SGR_FONT_09 = 19
_ANSI_SGR_FONT_10 = 20
_ANSI_SGR_DOUBLEUNDERLINE = 21
ANSI_SGR_BOLD_DIM_OFF = 22
_ANSI_SGR_ITALIC_OFF = 23
ANSI_SGR_UNDERLINE_OFF = 24
_ANSI_SGR_BLINK_OFF = 25
_ANSI_SGR_RESERVED_00 = 26
ANSI_SGR_REVERSE_OFF = 27
_ANSI_SGR_INVISIBLE_OFF = 28
_ANSI_SGR_LINETHROUGH_OFF = 29
ANSI_SGR_FOREGROUND_BLACK = 30
ANSI_SGR_FOREGROUND_RED = 31
ANSI_SGR_FOREGROUND_GREEN = 32
ANSI_SGR_FOREGROUND_YELLOW = 33
ANSI_SGR_FOREGROUND_BLUE = 34
ANSI_SGR_FOREGROUND_MAGENTA = 35
ANSI_SGR_FOREGROUND_CYAN = 36
ANSI_SGR_FOREGROUND_WHITE = 37
_ANSI_SGR_RESERVED_01 = 38
ANSI_SGR_FOREGROUND_DEFAULT = 39
ANSI_SGR_BACKGROUND_BLACK = 40
ANSI_SGR_BACKGROUND_RED = 41
ANSI_SGR_BACKGROUND_GREEN = 42
ANSI_SGR_BACKGROUND_YELLOW = 43
ANSI_SGR_BACKGROUND_BLUE = 44
ANSI_SGR_BACKGROUND_MAGENTA = 45
ANSI_SGR_BACKGROUND_CYAN = 46
ANSI_SGR_BACKGROUND_WHITE = 47
_ANSI_SGR_RESERVED_02 = 48
ANSI_SGR_BACKGROUND_DEFAULT = 49
// 50 - 65: Unsupported
ANSI_MAX_CMD_LENGTH = 4096
MAX_INPUT_EVENTS = 128
DEFAULT_WIDTH = 80
DEFAULT_HEIGHT = 24
ANSI_BEL = 0x07
ANSI_BACKSPACE = 0x08
ANSI_TAB = 0x09
ANSI_LINE_FEED = 0x0A
ANSI_VERTICAL_TAB = 0x0B
ANSI_FORM_FEED = 0x0C
ANSI_CARRIAGE_RETURN = 0x0D
ANSI_ESCAPE_PRIMARY = 0x1B
ANSI_ESCAPE_SECONDARY = 0x5B
ANSI_OSC_STRING_ENTRY = 0x5D
ANSI_COMMAND_FIRST = 0x40
ANSI_COMMAND_LAST = 0x7E
DCS_ENTRY = 0x90
CSI_ENTRY = 0x9B
OSC_STRING = 0x9D
ANSI_PARAMETER_SEP = ";"
ANSI_CMD_G0 = '('
ANSI_CMD_G1 = ')'
ANSI_CMD_G2 = '*'
ANSI_CMD_G3 = '+'
ANSI_CMD_DECPNM = '>'
ANSI_CMD_DECPAM = '='
ANSI_CMD_OSC = ']'
ANSI_CMD_STR_TERM = '\\'
KEY_CONTROL_PARAM_2 = ";2"
KEY_CONTROL_PARAM_3 = ";3"
KEY_CONTROL_PARAM_4 = ";4"
KEY_CONTROL_PARAM_5 = ";5"
KEY_CONTROL_PARAM_6 = ";6"
KEY_CONTROL_PARAM_7 = ";7"
KEY_CONTROL_PARAM_8 = ";8"
KEY_ESC_CSI = "\x1B["
KEY_ESC_N = "\x1BN"
KEY_ESC_O = "\x1BO"
FILL_CHARACTER = ' '
)
func getByteRange(start byte, end byte) []byte {
bytes := make([]byte, 0, 32)
for i := start; i <= end; i++ {
bytes = append(bytes, byte(i))
}
return bytes
}
var toGroundBytes = getToGroundBytes()
var executors = getExecuteBytes()
// SPACE 20+A0 hex Always and everywhere a blank space
// Intermediate 20-2F hex !"#$%&'()*+,-./
var intermeds = getByteRange(0x20, 0x2F)
// Parameters 30-3F hex 0123456789:;<=>?
// CSI Parameters 30-39, 3B hex 0123456789;
var csiParams = getByteRange(0x30, 0x3F)
var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
var upperCase = getByteRange(0x40, 0x5F)
// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
var lowerCase = getByteRange(0x60, 0x7E)
// Alphabetics 40-7E hex (all of upper and lower case)
var alphabetics = append(upperCase, lowerCase...)
var printables = getByteRange(0x20, 0x7F)
var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
var escapeToGroundBytes = getEscapeToGroundBytes()
// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
// byte ranges below
func getEscapeToGroundBytes() []byte {
escapeToGroundBytes := getByteRange(0x30, 0x4F)
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
return escapeToGroundBytes
}
func getExecuteBytes() []byte {
executeBytes := getByteRange(0x00, 0x17)
executeBytes = append(executeBytes, 0x19)
executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
return executeBytes
}
func getToGroundBytes() []byte {
groundBytes := []byte{0x18}
groundBytes = append(groundBytes, 0x1A)
groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
groundBytes = append(groundBytes, 0x99)
groundBytes = append(groundBytes, 0x9A)
groundBytes = append(groundBytes, 0x9C)
return groundBytes
}
// Delete 7F hex Always and everywhere ignored
// C1 Control 80-9F hex 32 additional control characters
// G1 Displayable A1-FE hex 94 additional displayable characters
// Special A0+FF hex Same as SPACE and DELETE

View File

@@ -1,7 +0,0 @@
package ansiterm
type ansiContext struct {
currentChar byte
paramBuffer []byte
interBuffer []byte
}

View File

@@ -1,49 +0,0 @@
package ansiterm
type csiEntryState struct {
baseState
}
func (csiState csiEntryState) Handle(b byte) (s state, e error) {
csiState.parser.logf("CsiEntry::Handle %#x", b)
nextState, err := csiState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(alphabetics, b):
return csiState.parser.ground, nil
case sliceContains(csiCollectables, b):
return csiState.parser.csiParam, nil
case sliceContains(executors, b):
return csiState, csiState.parser.execute()
}
return csiState, nil
}
func (csiState csiEntryState) Transition(s state) error {
csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
csiState.baseState.Transition(s)
switch s {
case csiState.parser.ground:
return csiState.parser.csiDispatch()
case csiState.parser.csiParam:
switch {
case sliceContains(csiParams, csiState.parser.context.currentChar):
csiState.parser.collectParam()
case sliceContains(intermeds, csiState.parser.context.currentChar):
csiState.parser.collectInter()
}
}
return nil
}
func (csiState csiEntryState) Enter() error {
csiState.parser.clear()
return nil
}

View File

@@ -1,38 +0,0 @@
package ansiterm
type csiParamState struct {
baseState
}
func (csiState csiParamState) Handle(b byte) (s state, e error) {
csiState.parser.logf("CsiParam::Handle %#x", b)
nextState, err := csiState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(alphabetics, b):
return csiState.parser.ground, nil
case sliceContains(csiCollectables, b):
csiState.parser.collectParam()
return csiState, nil
case sliceContains(executors, b):
return csiState, csiState.parser.execute()
}
return csiState, nil
}
func (csiState csiParamState) Transition(s state) error {
csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
csiState.baseState.Transition(s)
switch s {
case csiState.parser.ground:
return csiState.parser.csiDispatch()
}
return nil
}

View File

@@ -1,36 +0,0 @@
package ansiterm
type escapeIntermediateState struct {
baseState
}
func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
escState.parser.logf("escapeIntermediateState::Handle %#x", b)
nextState, err := escState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(intermeds, b):
return escState, escState.parser.collectInter()
case sliceContains(executors, b):
return escState, escState.parser.execute()
case sliceContains(escapeIntermediateToGroundBytes, b):
return escState.parser.ground, nil
}
return escState, nil
}
func (escState escapeIntermediateState) Transition(s state) error {
escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
escState.baseState.Transition(s)
switch s {
case escState.parser.ground:
return escState.parser.escDispatch()
}
return nil
}

View File

@@ -1,47 +0,0 @@
package ansiterm
type escapeState struct {
baseState
}
func (escState escapeState) Handle(b byte) (s state, e error) {
escState.parser.logf("escapeState::Handle %#x", b)
nextState, err := escState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case b == ANSI_ESCAPE_SECONDARY:
return escState.parser.csiEntry, nil
case b == ANSI_OSC_STRING_ENTRY:
return escState.parser.oscString, nil
case sliceContains(executors, b):
return escState, escState.parser.execute()
case sliceContains(escapeToGroundBytes, b):
return escState.parser.ground, nil
case sliceContains(intermeds, b):
return escState.parser.escapeIntermediate, nil
}
return escState, nil
}
func (escState escapeState) Transition(s state) error {
escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
escState.baseState.Transition(s)
switch s {
case escState.parser.ground:
return escState.parser.escDispatch()
case escState.parser.escapeIntermediate:
return escState.parser.collectInter()
}
return nil
}
func (escState escapeState) Enter() error {
escState.parser.clear()
return nil
}

View File

@@ -1,90 +0,0 @@
package ansiterm
type AnsiEventHandler interface {
// Print
Print(b byte) error
// Execute C0 commands
Execute(b byte) error
// CUrsor Up
CUU(int) error
// CUrsor Down
CUD(int) error
// CUrsor Forward
CUF(int) error
// CUrsor Backward
CUB(int) error
// Cursor to Next Line
CNL(int) error
// Cursor to Previous Line
CPL(int) error
// Cursor Horizontal position Absolute
CHA(int) error
// Vertical line Position Absolute
VPA(int) error
// CUrsor Position
CUP(int, int) error
// Horizontal and Vertical Position (depends on PUM)
HVP(int, int) error
// Text Cursor Enable Mode
DECTCEM(bool) error
// Origin Mode
DECOM(bool) error
// 132 Column Mode
DECCOLM(bool) error
// Erase in Display
ED(int) error
// Erase in Line
EL(int) error
// Insert Line
IL(int) error
// Delete Line
DL(int) error
// Insert Character
ICH(int) error
// Delete Character
DCH(int) error
// Set Graphics Rendition
SGR([]int) error
// Pan Down
SU(int) error
// Pan Up
SD(int) error
// Device Attributes
DA([]string) error
// Set Top and Bottom Margins
DECSTBM(int, int) error
// Index
IND() error
// Reverse Index
RI() error
// Flush updates from previous commands
Flush() error
}

View File

@@ -1,24 +0,0 @@
package ansiterm
type groundState struct {
baseState
}
func (gs groundState) Handle(b byte) (s state, e error) {
gs.parser.context.currentChar = b
nextState, err := gs.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(printables, b):
return gs, gs.parser.print()
case sliceContains(executors, b):
return gs, gs.parser.execute()
}
return gs, nil
}

View File

@@ -1,31 +0,0 @@
package ansiterm
type oscStringState struct {
baseState
}
func (oscState oscStringState) Handle(b byte) (s state, e error) {
oscState.parser.logf("OscString::Handle %#x", b)
nextState, err := oscState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case isOscStringTerminator(b):
return oscState.parser.ground, nil
}
return oscState, nil
}
// See below for OSC string terminators for linux
// http://man7.org/linux/man-pages/man4/console_codes.4.html
func isOscStringTerminator(b byte) bool {
if b == ANSI_BEL || b == 0x5C {
return true
}
return false
}

View File

@@ -1,151 +0,0 @@
package ansiterm
import (
"errors"
"log"
"os"
)
type AnsiParser struct {
currState state
eventHandler AnsiEventHandler
context *ansiContext
csiEntry state
csiParam state
dcsEntry state
escape state
escapeIntermediate state
error state
ground state
oscString state
stateMap []state
logf func(string, ...interface{})
}
type Option func(*AnsiParser)
func WithLogf(f func(string, ...interface{})) Option {
return func(ap *AnsiParser) {
ap.logf = f
}
}
func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
ap := &AnsiParser{
eventHandler: evtHandler,
context: &ansiContext{},
}
for _, o := range opts {
o(ap)
}
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
logFile, _ := os.Create("ansiParser.log")
logger := log.New(logFile, "", log.LstdFlags)
if ap.logf != nil {
l := ap.logf
ap.logf = func(s string, v ...interface{}) {
l(s, v...)
logger.Printf(s, v...)
}
} else {
ap.logf = logger.Printf
}
}
if ap.logf == nil {
ap.logf = func(string, ...interface{}) {}
}
ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
ap.error = errorState{baseState{name: "Error", parser: ap}}
ap.ground = groundState{baseState{name: "Ground", parser: ap}}
ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
ap.stateMap = []state{
ap.csiEntry,
ap.csiParam,
ap.dcsEntry,
ap.escape,
ap.escapeIntermediate,
ap.error,
ap.ground,
ap.oscString,
}
ap.currState = getState(initialState, ap.stateMap)
ap.logf("CreateParser: parser %p", ap)
return ap
}
func getState(name string, states []state) state {
for _, el := range states {
if el.Name() == name {
return el
}
}
return nil
}
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
for i, b := range bytes {
if err := ap.handle(b); err != nil {
return i, err
}
}
return len(bytes), ap.eventHandler.Flush()
}
func (ap *AnsiParser) handle(b byte) error {
ap.context.currentChar = b
newState, err := ap.currState.Handle(b)
if err != nil {
return err
}
if newState == nil {
ap.logf("WARNING: newState is nil")
return errors.New("New state of 'nil' is invalid.")
}
if newState != ap.currState {
if err := ap.changeState(newState); err != nil {
return err
}
}
return nil
}
func (ap *AnsiParser) changeState(newState state) error {
ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
// Exit old state
if err := ap.currState.Exit(); err != nil {
ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
return err
}
// Perform transition action
if err := ap.currState.Transition(newState); err != nil {
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
return err
}
// Enter new state
if err := newState.Enter(); err != nil {
ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
return err
}
ap.currState = newState
return nil
}

View File

@@ -1,99 +0,0 @@
package ansiterm
import (
"strconv"
)
func parseParams(bytes []byte) ([]string, error) {
paramBuff := make([]byte, 0, 0)
params := []string{}
for _, v := range bytes {
if v == ';' {
if len(paramBuff) > 0 {
// Completed parameter, append it to the list
s := string(paramBuff)
params = append(params, s)
paramBuff = make([]byte, 0, 0)
}
} else {
paramBuff = append(paramBuff, v)
}
}
// Last parameter may not be terminated with ';'
if len(paramBuff) > 0 {
s := string(paramBuff)
params = append(params, s)
}
return params, nil
}
func parseCmd(context ansiContext) (string, error) {
return string(context.currentChar), nil
}
func getInt(params []string, dflt int) int {
i := getInts(params, 1, dflt)[0]
return i
}
func getInts(params []string, minCount int, dflt int) []int {
ints := []int{}
for _, v := range params {
i, _ := strconv.Atoi(v)
// Zero is mapped to the default value in VT100.
if i == 0 {
i = dflt
}
ints = append(ints, i)
}
if len(ints) < minCount {
remaining := minCount - len(ints)
for i := 0; i < remaining; i++ {
ints = append(ints, dflt)
}
}
return ints
}
func (ap *AnsiParser) modeDispatch(param string, set bool) error {
switch param {
case "?3":
return ap.eventHandler.DECCOLM(set)
case "?6":
return ap.eventHandler.DECOM(set)
case "?25":
return ap.eventHandler.DECTCEM(set)
}
return nil
}
func (ap *AnsiParser) hDispatch(params []string) error {
if len(params) == 1 {
return ap.modeDispatch(params[0], true)
}
return nil
}
func (ap *AnsiParser) lDispatch(params []string) error {
if len(params) == 1 {
return ap.modeDispatch(params[0], false)
}
return nil
}
func getEraseParam(params []string) int {
param := getInt(params, 0)
if param < 0 || 3 < param {
param = 0
}
return param
}

View File

@@ -1,119 +0,0 @@
package ansiterm
func (ap *AnsiParser) collectParam() error {
currChar := ap.context.currentChar
ap.logf("collectParam %#x", currChar)
ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
return nil
}
func (ap *AnsiParser) collectInter() error {
currChar := ap.context.currentChar
ap.logf("collectInter %#x", currChar)
ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
return nil
}
func (ap *AnsiParser) escDispatch() error {
cmd, _ := parseCmd(*ap.context)
intermeds := ap.context.interBuffer
ap.logf("escDispatch currentChar: %#x", ap.context.currentChar)
ap.logf("escDispatch: %v(%v)", cmd, intermeds)
switch cmd {
case "D": // IND
return ap.eventHandler.IND()
case "E": // NEL, equivalent to CRLF
err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
if err == nil {
err = ap.eventHandler.Execute(ANSI_LINE_FEED)
}
return err
case "M": // RI
return ap.eventHandler.RI()
}
return nil
}
func (ap *AnsiParser) csiDispatch() error {
cmd, _ := parseCmd(*ap.context)
params, _ := parseParams(ap.context.paramBuffer)
ap.logf("Parsed params: %v with length: %d", params, len(params))
ap.logf("csiDispatch: %v(%v)", cmd, params)
switch cmd {
case "@":
return ap.eventHandler.ICH(getInt(params, 1))
case "A":
return ap.eventHandler.CUU(getInt(params, 1))
case "B":
return ap.eventHandler.CUD(getInt(params, 1))
case "C":
return ap.eventHandler.CUF(getInt(params, 1))
case "D":
return ap.eventHandler.CUB(getInt(params, 1))
case "E":
return ap.eventHandler.CNL(getInt(params, 1))
case "F":
return ap.eventHandler.CPL(getInt(params, 1))
case "G":
return ap.eventHandler.CHA(getInt(params, 1))
case "H":
ints := getInts(params, 2, 1)
x, y := ints[0], ints[1]
return ap.eventHandler.CUP(x, y)
case "J":
param := getEraseParam(params)
return ap.eventHandler.ED(param)
case "K":
param := getEraseParam(params)
return ap.eventHandler.EL(param)
case "L":
return ap.eventHandler.IL(getInt(params, 1))
case "M":
return ap.eventHandler.DL(getInt(params, 1))
case "P":
return ap.eventHandler.DCH(getInt(params, 1))
case "S":
return ap.eventHandler.SU(getInt(params, 1))
case "T":
return ap.eventHandler.SD(getInt(params, 1))
case "c":
return ap.eventHandler.DA(params)
case "d":
return ap.eventHandler.VPA(getInt(params, 1))
case "f":
ints := getInts(params, 2, 1)
x, y := ints[0], ints[1]
return ap.eventHandler.HVP(x, y)
case "h":
return ap.hDispatch(params)
case "l":
return ap.lDispatch(params)
case "m":
return ap.eventHandler.SGR(getInts(params, 1, 0))
case "r":
ints := getInts(params, 2, 1)
top, bottom := ints[0], ints[1]
return ap.eventHandler.DECSTBM(top, bottom)
default:
ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context)
return nil
}
}
func (ap *AnsiParser) print() error {
return ap.eventHandler.Print(ap.context.currentChar)
}
func (ap *AnsiParser) clear() error {
ap.context = &ansiContext{}
return nil
}
func (ap *AnsiParser) execute() error {
return ap.eventHandler.Execute(ap.context.currentChar)
}

View File

@@ -1,71 +0,0 @@
package ansiterm
type stateID int
type state interface {
Enter() error
Exit() error
Handle(byte) (state, error)
Name() string
Transition(state) error
}
type baseState struct {
name string
parser *AnsiParser
}
func (base baseState) Enter() error {
return nil
}
func (base baseState) Exit() error {
return nil
}
func (base baseState) Handle(b byte) (s state, e error) {
switch {
case b == CSI_ENTRY:
return base.parser.csiEntry, nil
case b == DCS_ENTRY:
return base.parser.dcsEntry, nil
case b == ANSI_ESCAPE_PRIMARY:
return base.parser.escape, nil
case b == OSC_STRING:
return base.parser.oscString, nil
case sliceContains(toGroundBytes, b):
return base.parser.ground, nil
}
return nil, nil
}
func (base baseState) Name() string {
return base.name
}
func (base baseState) Transition(s state) error {
if s == base.parser.ground {
execBytes := []byte{0x18}
execBytes = append(execBytes, 0x1A)
execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
execBytes = append(execBytes, 0x99)
execBytes = append(execBytes, 0x9A)
if sliceContains(execBytes, base.parser.context.currentChar) {
return base.parser.execute()
}
}
return nil
}
type dcsEntryState struct {
baseState
}
type errorState struct {
baseState
}

View File

@@ -1,21 +0,0 @@
package ansiterm
import (
"strconv"
)
func sliceContains(bytes []byte, b byte) bool {
for _, v := range bytes {
if v == b {
return true
}
}
return false
}
func convertBytesToInteger(bytes []byte) int {
s := string(bytes)
i, _ := strconv.Atoi(s)
return i
}

View File

@@ -1,182 +0,0 @@
// +build windows
package winterm
import (
"fmt"
"os"
"strconv"
"strings"
"syscall"
"github.com/Azure/go-ansiterm"
)
// Windows keyboard constants
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
const (
VK_PRIOR = 0x21 // PAGE UP key
VK_NEXT = 0x22 // PAGE DOWN key
VK_END = 0x23 // END key
VK_HOME = 0x24 // HOME key
VK_LEFT = 0x25 // LEFT ARROW key
VK_UP = 0x26 // UP ARROW key
VK_RIGHT = 0x27 // RIGHT ARROW key
VK_DOWN = 0x28 // DOWN ARROW key
VK_SELECT = 0x29 // SELECT key
VK_PRINT = 0x2A // PRINT key
VK_EXECUTE = 0x2B // EXECUTE key
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
VK_INSERT = 0x2D // INS key
VK_DELETE = 0x2E // DEL key
VK_HELP = 0x2F // HELP key
VK_F1 = 0x70 // F1 key
VK_F2 = 0x71 // F2 key
VK_F3 = 0x72 // F3 key
VK_F4 = 0x73 // F4 key
VK_F5 = 0x74 // F5 key
VK_F6 = 0x75 // F6 key
VK_F7 = 0x76 // F7 key
VK_F8 = 0x77 // F8 key
VK_F9 = 0x78 // F9 key
VK_F10 = 0x79 // F10 key
VK_F11 = 0x7A // F11 key
VK_F12 = 0x7B // F12 key
RIGHT_ALT_PRESSED = 0x0001
LEFT_ALT_PRESSED = 0x0002
RIGHT_CTRL_PRESSED = 0x0004
LEFT_CTRL_PRESSED = 0x0008
SHIFT_PRESSED = 0x0010
NUMLOCK_ON = 0x0020
SCROLLLOCK_ON = 0x0040
CAPSLOCK_ON = 0x0080
ENHANCED_KEY = 0x0100
)
type ansiCommand struct {
CommandBytes []byte
Command string
Parameters []string
IsSpecial bool
}
func newAnsiCommand(command []byte) *ansiCommand {
if isCharacterSelectionCmdChar(command[1]) {
// Is Character Set Selection commands
return &ansiCommand{
CommandBytes: command,
Command: string(command),
IsSpecial: true,
}
}
// last char is command character
lastCharIndex := len(command) - 1
ac := &ansiCommand{
CommandBytes: command,
Command: string(command[lastCharIndex]),
IsSpecial: false,
}
// more than a single escape
if lastCharIndex != 0 {
start := 1
// skip if double char escape sequence
if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
start++
}
// convert this to GetNextParam method
ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
}
return ac
}
func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
if index < 0 || index >= len(ac.Parameters) {
return defaultValue
}
param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
if err != nil {
return defaultValue
}
return int16(param)
}
func (ac *ansiCommand) String() string {
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
bytesToHex(ac.CommandBytes),
ac.Command,
strings.Join(ac.Parameters, "\",\""))
}
// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
func isAnsiCommandChar(b byte) bool {
switch {
case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
return true
case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
// non-CSI escape sequence terminator
return true
case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
// String escape sequence terminator
return true
}
return false
}
func isXtermOscSequence(command []byte, current byte) bool {
return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
}
func isCharacterSelectionCmdChar(b byte) bool {
return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
}
// bytesToHex converts a slice of bytes to a human-readable string.
func bytesToHex(b []byte) string {
hex := make([]string, len(b))
for i, ch := range b {
hex[i] = fmt.Sprintf("%X", ch)
}
return strings.Join(hex, "")
}
// ensureInRange adjusts the passed value, if necessary, to ensure it is within
// the passed min / max range.
func ensureInRange(n int16, min int16, max int16) int16 {
if n < min {
return min
} else if n > max {
return max
} else {
return n
}
}
func GetStdFile(nFile int) (*os.File, uintptr) {
var file *os.File
switch nFile {
case syscall.STD_INPUT_HANDLE:
file = os.Stdin
case syscall.STD_OUTPUT_HANDLE:
file = os.Stdout
case syscall.STD_ERROR_HANDLE:
file = os.Stderr
default:
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
}
fd, err := syscall.GetStdHandle(nFile)
if err != nil {
panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err))
}
return file, uintptr(fd)
}

View File

@@ -1,327 +0,0 @@
// +build windows
package winterm
import (
"fmt"
"syscall"
"unsafe"
)
//===========================================================================================================
// IMPORTANT NOTE:
//
// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
// variables) the pointers reference *before* the API completes.
//
// As a result, in those cases, the code must hint that the variables remain in active by invoking the
// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
// require unsafe pointers.
//
// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
// the garbage collector the variables remain in use if:
//
// -- The value is not a pointer (e.g., int32, struct)
// -- The value is not referenced by the method after passing the pointer to Windows
//
// See http://golang.org/doc/go1.3.
//===========================================================================================================
var (
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
)
// Windows Console constants
const (
// Console modes
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_LINE_INPUT = 0x0002
ENABLE_ECHO_INPUT = 0x0004
ENABLE_WINDOW_INPUT = 0x0008
ENABLE_MOUSE_INPUT = 0x0010
ENABLE_INSERT_MODE = 0x0020
ENABLE_QUICK_EDIT_MODE = 0x0040
ENABLE_EXTENDED_FLAGS = 0x0080
ENABLE_AUTO_POSITION = 0x0100
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
// Character attributes
// Note:
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
// Clearing all foreground or background colors results in black; setting all creates white.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
FOREGROUND_BLUE uint16 = 0x0001
FOREGROUND_GREEN uint16 = 0x0002
FOREGROUND_RED uint16 = 0x0004
FOREGROUND_INTENSITY uint16 = 0x0008
FOREGROUND_MASK uint16 = 0x000F
BACKGROUND_BLUE uint16 = 0x0010
BACKGROUND_GREEN uint16 = 0x0020
BACKGROUND_RED uint16 = 0x0040
BACKGROUND_INTENSITY uint16 = 0x0080
BACKGROUND_MASK uint16 = 0x00F0
COMMON_LVB_MASK uint16 = 0xFF00
COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
COMMON_LVB_UNDERSCORE uint16 = 0x8000
// Input event types
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
KEY_EVENT = 0x0001
MOUSE_EVENT = 0x0002
WINDOW_BUFFER_SIZE_EVENT = 0x0004
MENU_EVENT = 0x0008
FOCUS_EVENT = 0x0010
// WaitForSingleObject return codes
WAIT_ABANDONED = 0x00000080
WAIT_FAILED = 0xFFFFFFFF
WAIT_SIGNALED = 0x0000000
WAIT_TIMEOUT = 0x00000102
// WaitForSingleObject wait duration
WAIT_INFINITE = 0xFFFFFFFF
WAIT_ONE_SECOND = 1000
WAIT_HALF_SECOND = 500
WAIT_QUARTER_SECOND = 250
)
// Windows API Console types
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
type (
CHAR_INFO struct {
UnicodeChar uint16
Attributes uint16
}
CONSOLE_CURSOR_INFO struct {
Size uint32
Visible int32
}
CONSOLE_SCREEN_BUFFER_INFO struct {
Size COORD
CursorPosition COORD
Attributes uint16
Window SMALL_RECT
MaximumWindowSize COORD
}
COORD struct {
X int16
Y int16
}
SMALL_RECT struct {
Left int16
Top int16
Right int16
Bottom int16
}
// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
INPUT_RECORD struct {
EventType uint16
KeyEvent KEY_EVENT_RECORD
}
KEY_EVENT_RECORD struct {
KeyDown int32
RepeatCount uint16
VirtualKeyCode uint16
VirtualScanCode uint16
UnicodeChar uint16
ControlKeyState uint32
}
WINDOW_BUFFER_SIZE struct {
Size COORD
}
)
// boolToBOOL converts a Go bool into a Windows int32.
func boolToBOOL(f bool) int32 {
if f {
return int32(1)
} else {
return int32(0)
}
}
// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
return checkError(r1, r2, err)
}
// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
return checkError(r1, r2, err)
}
// SetConsoleCursorPosition location of the console cursor.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
use(coord)
return checkError(r1, r2, err)
}
// GetConsoleMode gets the console mode for given file descriptor
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
func GetConsoleMode(handle uintptr) (mode uint32, err error) {
err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
return mode, err
}
// SetConsoleMode sets the console mode for given file descriptor
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
func SetConsoleMode(handle uintptr, mode uint32) error {
r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
use(mode)
return checkError(r1, r2, err)
}
// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
info := CONSOLE_SCREEN_BUFFER_INFO{}
err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
if err != nil {
return nil, err
}
return &info, nil
}
func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
use(scrollRect)
use(clipRect)
use(destOrigin)
use(char)
return checkError(r1, r2, err)
}
// SetConsoleScreenBufferSize sets the size of the console screen buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
use(coord)
return checkError(r1, r2, err)
}
// SetConsoleTextAttribute sets the attributes of characters written to the
// console screen buffer by the WriteFile or WriteConsole function.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
use(attribute)
return checkError(r1, r2, err)
}
// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
// Note that the size and location must be within and no larger than the backing console screen buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
use(isAbsolute)
use(rect)
return checkError(r1, r2, err)
}
// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
use(buffer)
use(bufferSize)
use(bufferCoord)
return checkError(r1, r2, err)
}
// ReadConsoleInput reads (and removes) data from the console input buffer.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
use(buffer)
return checkError(r1, r2, err)
}
// WaitForSingleObject waits for the passed handle to be signaled.
// It returns true if the handle was signaled; false otherwise.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
switch r1 {
case WAIT_ABANDONED, WAIT_TIMEOUT:
return false, nil
case WAIT_SIGNALED:
return true, nil
}
use(msWait)
return false, err
}
// String helpers
func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
}
func (coord COORD) String() string {
return fmt.Sprintf("%v,%v", coord.X, coord.Y)
}
func (rect SMALL_RECT) String() string {
return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
}
// checkError evaluates the results of a Windows API call and returns the error if it failed.
func checkError(r1, r2 uintptr, err error) error {
// Windows APIs return non-zero to indicate success
if r1 != 0 {
return nil
}
// Return the error if provided, otherwise default to EINVAL
if err != nil {
return err
}
return syscall.EINVAL
}
// coordToPointer converts a COORD into a uintptr (by fooling the type system).
func coordToPointer(c COORD) uintptr {
// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
return uintptr(*((*uint32)(unsafe.Pointer(&c))))
}
// use is a no-op, but the compiler cannot see that it is.
// Calling use(p) ensures that p is kept live until that point.
func use(p interface{}) {}

View File

@@ -1,100 +0,0 @@
// +build windows
package winterm
import "github.com/Azure/go-ansiterm"
const (
FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
)
// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
// request represented by the passed ANSI mode.
func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
switch ansiMode {
// Mode styles
case ansiterm.ANSI_SGR_BOLD:
windowsMode = windowsMode | FOREGROUND_INTENSITY
case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
windowsMode &^= FOREGROUND_INTENSITY
case ansiterm.ANSI_SGR_UNDERLINE:
windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
case ansiterm.ANSI_SGR_REVERSE:
inverted = true
case ansiterm.ANSI_SGR_REVERSE_OFF:
inverted = false
case ansiterm.ANSI_SGR_UNDERLINE_OFF:
windowsMode &^= COMMON_LVB_UNDERSCORE
// Foreground colors
case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
case ansiterm.ANSI_SGR_FOREGROUND_RED:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
// Background colors
case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
// Black with no intensity
windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
case ansiterm.ANSI_SGR_BACKGROUND_RED:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
}
return windowsMode, inverted
}
// invertAttributes inverts the foreground and background colors of a Windows attributes value
func invertAttributes(windowsMode uint16) uint16 {
return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
}

View File

@@ -1,101 +0,0 @@
// +build windows
package winterm
const (
horizontal = iota
vertical
)
func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
if h.originMode {
sr := h.effectiveSr(info.Window)
return SMALL_RECT{
Top: sr.top,
Bottom: sr.bottom,
Left: 0,
Right: info.Size.X - 1,
}
} else {
return SMALL_RECT{
Top: info.Window.Top,
Bottom: info.Window.Bottom,
Left: 0,
Right: info.Size.X - 1,
}
}
}
// setCursorPosition sets the cursor to the specified position, bounded to the screen size
func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
position.X = ensureInRange(position.X, window.Left, window.Right)
position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
err := SetConsoleCursorPosition(h.fd, position)
if err != nil {
return err
}
h.logf("Cursor position set: (%d, %d)", position.X, position.Y)
return err
}
func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
return h.moveCursor(vertical, param)
}
func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
return h.moveCursor(horizontal, param)
}
func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
position := info.CursorPosition
switch moveMode {
case horizontal:
position.X += int16(param)
case vertical:
position.Y += int16(param)
}
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
position := info.CursorPosition
position.X = 0
position.Y += int16(param)
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
position := info.CursorPosition
position.X = int16(param) - 1
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
return err
}
return nil
}

View File

@@ -1,84 +0,0 @@
// +build windows
package winterm
import "github.com/Azure/go-ansiterm"
func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
// Ignore an invalid (negative area) request
if toCoord.Y < fromCoord.Y {
return nil
}
var err error
var coordStart = COORD{}
var coordEnd = COORD{}
xCurrent, yCurrent := fromCoord.X, fromCoord.Y
xEnd, yEnd := toCoord.X, toCoord.Y
// Clear any partial initial line
if xCurrent > 0 {
coordStart.X, coordStart.Y = xCurrent, yCurrent
coordEnd.X, coordEnd.Y = xEnd, yCurrent
err = h.clearRect(attributes, coordStart, coordEnd)
if err != nil {
return err
}
xCurrent = 0
yCurrent += 1
}
// Clear intervening rectangular section
if yCurrent < yEnd {
coordStart.X, coordStart.Y = xCurrent, yCurrent
coordEnd.X, coordEnd.Y = xEnd, yEnd-1
err = h.clearRect(attributes, coordStart, coordEnd)
if err != nil {
return err
}
xCurrent = 0
yCurrent = yEnd
}
// Clear remaining partial ending line
coordStart.X, coordStart.Y = xCurrent, yCurrent
coordEnd.X, coordEnd.Y = xEnd, yEnd
err = h.clearRect(attributes, coordStart, coordEnd)
if err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
width := toCoord.X - fromCoord.X + 1
height := toCoord.Y - fromCoord.Y + 1
size := uint32(width) * uint32(height)
if size <= 0 {
return nil
}
buffer := make([]CHAR_INFO, size)
char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
for i := 0; i < int(size); i++ {
buffer[i] = char
}
err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &region)
if err != nil {
return err
}
return nil
}

View File

@@ -1,118 +0,0 @@
// +build windows
package winterm
// effectiveSr gets the current effective scroll region in buffer coordinates
func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
if top >= bottom {
top = window.Top
bottom = window.Bottom
}
return scrollRegion{top: top, bottom: bottom}
}
func (h *windowsAnsiEventHandler) scrollUp(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
sr := h.effectiveSr(info.Window)
return h.scroll(param, sr, info)
}
func (h *windowsAnsiEventHandler) scrollDown(param int) error {
return h.scrollUp(-param)
}
func (h *windowsAnsiEventHandler) deleteLines(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
start := info.CursorPosition.Y
sr := h.effectiveSr(info.Window)
// Lines cannot be inserted or deleted outside the scrolling region.
if start >= sr.top && start <= sr.bottom {
sr.top = start
return h.scroll(param, sr, info)
} else {
return nil
}
}
func (h *windowsAnsiEventHandler) insertLines(param int) error {
return h.deleteLines(-param)
}
// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
// Copy from and clip to the scroll region (full buffer width)
scrollRect := SMALL_RECT{
Top: sr.top,
Bottom: sr.bottom,
Left: 0,
Right: info.Size.X - 1,
}
// Origin to which area should be copied
destOrigin := COORD{
X: 0,
Y: sr.top - int16(param),
}
char := CHAR_INFO{
UnicodeChar: ' ',
Attributes: h.attributes,
}
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
return h.scrollLine(param, info.CursorPosition, info)
}
func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
return h.deleteCharacters(-param)
}
// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
// Copy from and clip to the scroll region (full buffer width)
scrollRect := SMALL_RECT{
Top: position.Y,
Bottom: position.Y,
Left: position.X,
Right: info.Size.X - 1,
}
// Origin to which area should be copied
destOrigin := COORD{
X: position.X - int16(columns),
Y: position.Y,
}
char := CHAR_INFO{
UnicodeChar: ' ',
Attributes: h.attributes,
}
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
return err
}
return nil
}

View File

@@ -1,9 +0,0 @@
// +build windows
package winterm
// AddInRange increments a value by the passed quantity while ensuring the values
// always remain within the supplied min / max range.
func addInRange(n int16, increment int16, min int16, max int16) int16 {
return ensureInRange(n+increment, min, max)
}

View File

@@ -1,743 +0,0 @@
// +build windows
package winterm
import (
"bytes"
"log"
"os"
"strconv"
"github.com/Azure/go-ansiterm"
)
type windowsAnsiEventHandler struct {
fd uintptr
file *os.File
infoReset *CONSOLE_SCREEN_BUFFER_INFO
sr scrollRegion
buffer bytes.Buffer
attributes uint16
inverted bool
wrapNext bool
drewMarginByte bool
originMode bool
marginByte byte
curInfo *CONSOLE_SCREEN_BUFFER_INFO
curPos COORD
logf func(string, ...interface{})
}
type Option func(*windowsAnsiEventHandler)
func WithLogf(f func(string, ...interface{})) Option {
return func(w *windowsAnsiEventHandler) {
w.logf = f
}
}
func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler {
infoReset, err := GetConsoleScreenBufferInfo(fd)
if err != nil {
return nil
}
h := &windowsAnsiEventHandler{
fd: fd,
file: file,
infoReset: infoReset,
attributes: infoReset.Attributes,
}
for _, o := range opts {
o(h)
}
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
logFile, _ := os.Create("winEventHandler.log")
logger := log.New(logFile, "", log.LstdFlags)
if h.logf != nil {
l := h.logf
h.logf = func(s string, v ...interface{}) {
l(s, v...)
logger.Printf(s, v...)
}
} else {
h.logf = logger.Printf
}
}
if h.logf == nil {
h.logf = func(string, ...interface{}) {}
}
return h
}
type scrollRegion struct {
top int16
bottom int16
}
// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
// current cursor position and scroll region settings, in which case it returns
// true. If no special handling is necessary, then it does nothing and returns
// false.
//
// In the false case, the caller should ensure that a carriage return
// and line feed are inserted or that the text is otherwise wrapped.
func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
if h.wrapNext {
if err := h.Flush(); err != nil {
return false, err
}
h.clearWrap()
}
pos, info, err := h.getCurrentInfo()
if err != nil {
return false, err
}
sr := h.effectiveSr(info.Window)
if pos.Y == sr.bottom {
// Scrolling is necessary. Let Windows automatically scroll if the scrolling region
// is the full window.
if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
if includeCR {
pos.X = 0
h.updatePos(pos)
}
return false, nil
}
// A custom scroll region is active. Scroll the window manually to simulate
// the LF.
if err := h.Flush(); err != nil {
return false, err
}
h.logf("Simulating LF inside scroll region")
if err := h.scrollUp(1); err != nil {
return false, err
}
if includeCR {
pos.X = 0
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return false, err
}
}
return true, nil
} else if pos.Y < info.Window.Bottom {
// Let Windows handle the LF.
pos.Y++
if includeCR {
pos.X = 0
}
h.updatePos(pos)
return false, nil
} else {
// The cursor is at the bottom of the screen but outside the scroll
// region. Skip the LF.
h.logf("Simulating LF outside scroll region")
if includeCR {
if err := h.Flush(); err != nil {
return false, err
}
pos.X = 0
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return false, err
}
}
return true, nil
}
}
// executeLF executes a LF without a CR.
func (h *windowsAnsiEventHandler) executeLF() error {
handled, err := h.simulateLF(false)
if err != nil {
return err
}
if !handled {
// Windows LF will reset the cursor column position. Write the LF
// and restore the cursor position.
pos, _, err := h.getCurrentInfo()
if err != nil {
return err
}
h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
if pos.X != 0 {
if err := h.Flush(); err != nil {
return err
}
h.logf("Resetting cursor position for LF without CR")
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return err
}
}
}
return nil
}
func (h *windowsAnsiEventHandler) Print(b byte) error {
if h.wrapNext {
h.buffer.WriteByte(h.marginByte)
h.clearWrap()
if _, err := h.simulateLF(true); err != nil {
return err
}
}
pos, info, err := h.getCurrentInfo()
if err != nil {
return err
}
if pos.X == info.Size.X-1 {
h.wrapNext = true
h.marginByte = b
} else {
pos.X++
h.updatePos(pos)
h.buffer.WriteByte(b)
}
return nil
}
func (h *windowsAnsiEventHandler) Execute(b byte) error {
switch b {
case ansiterm.ANSI_TAB:
h.logf("Execute(TAB)")
// Move to the next tab stop, but preserve auto-wrap if already set.
if !h.wrapNext {
pos, info, err := h.getCurrentInfo()
if err != nil {
return err
}
pos.X = (pos.X + 8) - pos.X%8
if pos.X >= info.Size.X {
pos.X = info.Size.X - 1
}
if err := h.Flush(); err != nil {
return err
}
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return err
}
}
return nil
case ansiterm.ANSI_BEL:
h.buffer.WriteByte(ansiterm.ANSI_BEL)
return nil
case ansiterm.ANSI_BACKSPACE:
if h.wrapNext {
if err := h.Flush(); err != nil {
return err
}
h.clearWrap()
}
pos, _, err := h.getCurrentInfo()
if err != nil {
return err
}
if pos.X > 0 {
pos.X--
h.updatePos(pos)
h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
}
return nil
case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
// Treat as true LF.
return h.executeLF()
case ansiterm.ANSI_LINE_FEED:
// Simulate a CR and LF for now since there is no way in go-ansiterm
// to tell if the LF should include CR (and more things break when it's
// missing than when it's incorrectly added).
handled, err := h.simulateLF(true)
if handled || err != nil {
return err
}
return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
case ansiterm.ANSI_CARRIAGE_RETURN:
if h.wrapNext {
if err := h.Flush(); err != nil {
return err
}
h.clearWrap()
}
pos, _, err := h.getCurrentInfo()
if err != nil {
return err
}
if pos.X != 0 {
pos.X = 0
h.updatePos(pos)
h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
}
return nil
default:
return nil
}
}
func (h *windowsAnsiEventHandler) CUU(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUU: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorVertical(-param)
}
func (h *windowsAnsiEventHandler) CUD(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUD: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorVertical(param)
}
func (h *windowsAnsiEventHandler) CUF(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUF: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorHorizontal(param)
}
func (h *windowsAnsiEventHandler) CUB(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUB: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorHorizontal(-param)
}
func (h *windowsAnsiEventHandler) CNL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CNL: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorLine(param)
}
func (h *windowsAnsiEventHandler) CPL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CPL: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorLine(-param)
}
func (h *windowsAnsiEventHandler) CHA(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CHA: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.moveCursorColumn(param)
}
func (h *windowsAnsiEventHandler) VPA(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("VPA: [[%d]]", param)
h.clearWrap()
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
window := h.getCursorWindow(info)
position := info.CursorPosition
position.Y = window.Top + int16(param) - 1
return h.setCursorPosition(position, window)
}
func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("CUP: [[%d %d]]", row, col)
h.clearWrap()
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
window := h.getCursorWindow(info)
position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
return h.setCursorPosition(position, window)
}
func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("HVP: [[%d %d]]", row, col)
h.clearWrap()
return h.CUP(row, col)
}
func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
h.clearWrap()
return nil
}
func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)})
h.clearWrap()
h.originMode = enable
return h.CUP(1, 1)
}
func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
h.clearWrap()
if err := h.ED(2); err != nil {
return err
}
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
targetWidth := int16(80)
if use132 {
targetWidth = 132
}
if info.Size.X < targetWidth {
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
h.logf("set buffer failed: %v", err)
return err
}
}
window := info.Window
window.Left = 0
window.Right = targetWidth - 1
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
h.logf("set window failed: %v", err)
return err
}
if info.Size.X > targetWidth {
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
h.logf("set buffer failed: %v", err)
return err
}
}
return SetConsoleCursorPosition(h.fd, COORD{0, 0})
}
func (h *windowsAnsiEventHandler) ED(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("ED: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
// [J -- Erases from the cursor to the end of the screen, including the cursor position.
// [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
// [2J -- Erases the complete display. The cursor does not move.
// Notes:
// -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
var start COORD
var end COORD
switch param {
case 0:
start = info.CursorPosition
end = COORD{info.Size.X - 1, info.Size.Y - 1}
case 1:
start = COORD{0, 0}
end = info.CursorPosition
case 2:
start = COORD{0, 0}
end = COORD{info.Size.X - 1, info.Size.Y - 1}
}
err = h.clearRange(h.attributes, start, end)
if err != nil {
return err
}
// If the whole buffer was cleared, move the window to the top while preserving
// the window-relative cursor position.
if param == 2 {
pos := info.CursorPosition
window := info.Window
pos.Y -= window.Top
window.Bottom -= window.Top
window.Top = 0
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
return err
}
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
return err
}
}
return nil
}
func (h *windowsAnsiEventHandler) EL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("EL: [%v]", strconv.Itoa(param))
h.clearWrap()
// [K -- Erases from the cursor to the end of the line, including the cursor position.
// [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
// [2K -- Erases the complete line.
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
var start COORD
var end COORD
switch param {
case 0:
start = info.CursorPosition
end = COORD{info.Size.X, info.CursorPosition.Y}
case 1:
start = COORD{0, info.CursorPosition.Y}
end = info.CursorPosition
case 2:
start = COORD{0, info.CursorPosition.Y}
end = COORD{info.Size.X, info.CursorPosition.Y}
}
err = h.clearRange(h.attributes, start, end)
if err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) IL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("IL: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.insertLines(param)
}
func (h *windowsAnsiEventHandler) DL(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DL: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.deleteLines(param)
}
func (h *windowsAnsiEventHandler) ICH(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("ICH: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.insertCharacters(param)
}
func (h *windowsAnsiEventHandler) DCH(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DCH: [%v]", strconv.Itoa(param))
h.clearWrap()
return h.deleteCharacters(param)
}
func (h *windowsAnsiEventHandler) SGR(params []int) error {
if err := h.Flush(); err != nil {
return err
}
strings := []string{}
for _, v := range params {
strings = append(strings, strconv.Itoa(v))
}
h.logf("SGR: [%v]", strings)
if len(params) <= 0 {
h.attributes = h.infoReset.Attributes
h.inverted = false
} else {
for _, attr := range params {
if attr == ansiterm.ANSI_SGR_RESET {
h.attributes = h.infoReset.Attributes
h.inverted = false
continue
}
h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
}
}
attributes := h.attributes
if h.inverted {
attributes = invertAttributes(attributes)
}
err := SetConsoleTextAttribute(h.fd, attributes)
if err != nil {
return err
}
return nil
}
func (h *windowsAnsiEventHandler) SU(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("SU: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.scrollUp(param)
}
func (h *windowsAnsiEventHandler) SD(param int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("SD: [%v]", []string{strconv.Itoa(param)})
h.clearWrap()
return h.scrollDown(param)
}
func (h *windowsAnsiEventHandler) DA(params []string) error {
h.logf("DA: [%v]", params)
// DA cannot be implemented because it must send data on the VT100 input stream,
// which is not available to go-ansiterm.
return nil
}
func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
if err := h.Flush(); err != nil {
return err
}
h.logf("DECSTBM: [%d, %d]", top, bottom)
// Windows is 0 indexed, Linux is 1 indexed
h.sr.top = int16(top - 1)
h.sr.bottom = int16(bottom - 1)
// This command also moves the cursor to the origin.
h.clearWrap()
return h.CUP(1, 1)
}
func (h *windowsAnsiEventHandler) RI() error {
if err := h.Flush(); err != nil {
return err
}
h.logf("RI: []")
h.clearWrap()
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
sr := h.effectiveSr(info.Window)
if info.CursorPosition.Y == sr.top {
return h.scrollDown(1)
}
return h.moveCursorVertical(-1)
}
func (h *windowsAnsiEventHandler) IND() error {
h.logf("IND: []")
return h.executeLF()
}
func (h *windowsAnsiEventHandler) Flush() error {
h.curInfo = nil
if h.buffer.Len() > 0 {
h.logf("Flush: [%s]", h.buffer.Bytes())
if _, err := h.buffer.WriteTo(h.file); err != nil {
return err
}
}
if h.wrapNext && !h.drewMarginByte {
h.logf("Flush: drawing margin byte '%c'", h.marginByte)
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return err
}
charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
size := COORD{1, 1}
position := COORD{0, 0}
region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
if err := WriteConsoleOutput(h.fd, charInfo, size, position, &region); err != nil {
return err
}
h.drewMarginByte = true
}
return nil
}
// cacheConsoleInfo ensures that the current console screen information has been queried
// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
if h.curInfo == nil {
info, err := GetConsoleScreenBufferInfo(h.fd)
if err != nil {
return COORD{}, nil, err
}
h.curInfo = info
h.curPos = info.CursorPosition
}
return h.curPos, h.curInfo, nil
}
func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
if h.curInfo == nil {
panic("failed to call getCurrentInfo before calling updatePos")
}
h.curPos = pos
}
// clearWrap clears the state where the cursor is in the margin
// waiting for the next character before wrapping the line. This must
// be done before most operations that act on the cursor.
func (h *windowsAnsiEventHandler) clearWrap() {
h.wrapNext = false
h.drewMarginByte = false
}

View File

@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,280 +0,0 @@
// +build windows
package winio
import (
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"runtime"
"syscall"
"unicode/utf16"
)
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
const (
BackupData = uint32(iota + 1)
BackupEaData
BackupSecurity
BackupAlternateData
BackupLink
BackupPropertyData
BackupObjectId
BackupReparseData
BackupSparseBlock
BackupTxfsData
)
const (
StreamSparseAttributes = uint32(8)
)
const (
WRITE_DAC = 0x40000
WRITE_OWNER = 0x80000
ACCESS_SYSTEM_SECURITY = 0x1000000
)
// BackupHeader represents a backup stream of a file.
type BackupHeader struct {
Id uint32 // The backup stream ID
Attributes uint32 // Stream attributes
Size int64 // The size of the stream in bytes
Name string // The name of the stream (for BackupAlternateData only).
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
}
type win32StreamId struct {
StreamId uint32
Attributes uint32
Size uint64
NameSize uint32
}
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
// of BackupHeader values.
type BackupStreamReader struct {
r io.Reader
bytesLeft int64
}
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
return &BackupStreamReader{r, 0}
}
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
// it was not completely read.
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
if r.bytesLeft > 0 {
if s, ok := r.r.(io.Seeker); ok {
// Make sure Seek on io.SeekCurrent sometimes succeeds
// before trying the actual seek.
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
return nil, err
}
r.bytesLeft = 0
}
}
if _, err := io.Copy(ioutil.Discard, r); err != nil {
return nil, err
}
}
var wsi win32StreamId
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
return nil, err
}
hdr := &BackupHeader{
Id: wsi.StreamId,
Attributes: wsi.Attributes,
Size: int64(wsi.Size),
}
if wsi.NameSize != 0 {
name := make([]uint16, int(wsi.NameSize/2))
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
return nil, err
}
hdr.Name = syscall.UTF16ToString(name)
}
if wsi.StreamId == BackupSparseBlock {
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
return nil, err
}
hdr.Size -= 8
}
r.bytesLeft = hdr.Size
return hdr, nil
}
// Read reads from the current backup stream.
func (r *BackupStreamReader) Read(b []byte) (int, error) {
if r.bytesLeft == 0 {
return 0, io.EOF
}
if int64(len(b)) > r.bytesLeft {
b = b[:r.bytesLeft]
}
n, err := r.r.Read(b)
r.bytesLeft -= int64(n)
if err == io.EOF {
err = io.ErrUnexpectedEOF
} else if r.bytesLeft == 0 && err == nil {
err = io.EOF
}
return n, err
}
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
type BackupStreamWriter struct {
w io.Writer
bytesLeft int64
}
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
return &BackupStreamWriter{w, 0}
}
// WriteHeader writes the next backup stream header and prepares for calls to Write().
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
if w.bytesLeft != 0 {
return fmt.Errorf("missing %d bytes", w.bytesLeft)
}
name := utf16.Encode([]rune(hdr.Name))
wsi := win32StreamId{
StreamId: hdr.Id,
Attributes: hdr.Attributes,
Size: uint64(hdr.Size),
NameSize: uint32(len(name) * 2),
}
if hdr.Id == BackupSparseBlock {
// Include space for the int64 block offset
wsi.Size += 8
}
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
return err
}
if len(name) != 0 {
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
return err
}
}
if hdr.Id == BackupSparseBlock {
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
return err
}
}
w.bytesLeft = hdr.Size
return nil
}
// Write writes to the current backup stream.
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
if w.bytesLeft < int64(len(b)) {
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
}
n, err := w.w.Write(b)
w.bytesLeft -= int64(n)
return n, err
}
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
type BackupFileReader struct {
f *os.File
includeSecurity bool
ctx uintptr
}
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
// Read will attempt to read the security descriptor of the file.
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
r := &BackupFileReader{f, includeSecurity, 0}
return r
}
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
func (r *BackupFileReader) Read(b []byte) (int, error) {
var bytesRead uint32
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
if err != nil {
return 0, &os.PathError{"BackupRead", r.f.Name(), err}
}
runtime.KeepAlive(r.f)
if bytesRead == 0 {
return 0, io.EOF
}
return int(bytesRead), nil
}
// Close frees Win32 resources associated with the BackupFileReader. It does not close
// the underlying file.
func (r *BackupFileReader) Close() error {
if r.ctx != 0 {
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
runtime.KeepAlive(r.f)
r.ctx = 0
}
return nil
}
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
type BackupFileWriter struct {
f *os.File
includeSecurity bool
ctx uintptr
}
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
// Write() will attempt to restore the security descriptor from the stream.
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
w := &BackupFileWriter{f, includeSecurity, 0}
return w
}
// Write restores a portion of the file using the provided backup stream.
func (w *BackupFileWriter) Write(b []byte) (int, error) {
var bytesWritten uint32
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
if err != nil {
return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
}
runtime.KeepAlive(w.f)
if int(bytesWritten) != len(b) {
return int(bytesWritten), errors.New("not all bytes could be written")
}
return len(b), nil
}
// Close frees Win32 resources associated with the BackupFileWriter. It does not
// close the underlying file.
func (w *BackupFileWriter) Close() error {
if w.ctx != 0 {
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
runtime.KeepAlive(w.f)
w.ctx = 0
}
return nil
}
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
// or restore privileges have been acquired.
//
// If the file opened was a directory, it cannot be used with Readdir().
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
winPath, err := syscall.UTF16FromString(path)
if err != nil {
return nil, err
}
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
if err != nil {
err = &os.PathError{Op: "open", Path: path, Err: err}
return nil, err
}
return os.NewFile(uintptr(h), path), nil
}

View File

@@ -1,137 +0,0 @@
package winio
import (
"bytes"
"encoding/binary"
"errors"
)
type fileFullEaInformation struct {
NextEntryOffset uint32
Flags uint8
NameLength uint8
ValueLength uint16
}
var (
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
errEaNameTooLarge = errors.New("extended attribute name too large")
errEaValueTooLarge = errors.New("extended attribute value too large")
)
// ExtendedAttribute represents a single Windows EA.
type ExtendedAttribute struct {
Name string
Value []byte
Flags uint8
}
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
var info fileFullEaInformation
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
if err != nil {
err = errInvalidEaBuffer
return
}
nameOffset := fileFullEaInformationSize
nameLen := int(info.NameLength)
valueOffset := nameOffset + int(info.NameLength) + 1
valueLen := int(info.ValueLength)
nextOffset := int(info.NextEntryOffset)
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
err = errInvalidEaBuffer
return
}
ea.Name = string(b[nameOffset : nameOffset+nameLen])
ea.Value = b[valueOffset : valueOffset+valueLen]
ea.Flags = info.Flags
if info.NextEntryOffset != 0 {
nb = b[info.NextEntryOffset:]
}
return
}
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
for len(b) != 0 {
ea, nb, err := parseEa(b)
if err != nil {
return nil, err
}
eas = append(eas, ea)
b = nb
}
return
}
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
if int(uint8(len(ea.Name))) != len(ea.Name) {
return errEaNameTooLarge
}
if int(uint16(len(ea.Value))) != len(ea.Value) {
return errEaValueTooLarge
}
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
withPadding := (entrySize + 3) &^ 3
nextOffset := uint32(0)
if !last {
nextOffset = withPadding
}
info := fileFullEaInformation{
NextEntryOffset: nextOffset,
Flags: ea.Flags,
NameLength: uint8(len(ea.Name)),
ValueLength: uint16(len(ea.Value)),
}
err := binary.Write(buf, binary.LittleEndian, &info)
if err != nil {
return err
}
_, err = buf.Write([]byte(ea.Name))
if err != nil {
return err
}
err = buf.WriteByte(0)
if err != nil {
return err
}
_, err = buf.Write(ea.Value)
if err != nil {
return err
}
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
if err != nil {
return err
}
return nil
}
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
// buffer for use with BackupWrite, ZwSetEaFile, etc.
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
var buf bytes.Buffer
for i := range eas {
last := false
if i == len(eas)-1 {
last = true
}
err := writeEa(&buf, &eas[i], last)
if err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}

View File

@@ -1,307 +0,0 @@
// +build windows
package winio
import (
"errors"
"io"
"runtime"
"sync"
"sync/atomic"
"syscall"
"time"
)
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
func (b *atomicBool) swap(new bool) bool {
var newInt int32
if new {
newInt = 1
}
return atomic.SwapInt32((*int32)(b), newInt) == 1
}
const (
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
)
var (
ErrFileClosed = errors.New("file has already been closed")
ErrTimeout = &timeoutError{}
)
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }
type timeoutChan chan struct{}
var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle
// ioResult contains the result of an asynchronous IO operation
type ioResult struct {
bytes uint32
err error
}
// ioOperation represents an outstanding asynchronous Win32 IO
type ioOperation struct {
o syscall.Overlapped
ch chan ioResult
}
func initIo() {
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
if err != nil {
panic(err)
}
ioCompletionPort = h
go ioCompletionProcessor(h)
}
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
// It takes ownership of this handle and will close it if it is garbage collected.
type win32File struct {
handle syscall.Handle
wg sync.WaitGroup
wgLock sync.RWMutex
closing atomicBool
readDeadline deadlineHandler
writeDeadline deadlineHandler
}
type deadlineHandler struct {
setLock sync.Mutex
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout atomicBool
}
// makeWin32File makes a new win32File from an existing file handle
func makeWin32File(h syscall.Handle) (*win32File, error) {
f := &win32File{handle: h}
ioInitOnce.Do(initIo)
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
if err != nil {
return nil, err
}
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
if err != nil {
return nil, err
}
f.readDeadline.channel = make(timeoutChan)
f.writeDeadline.channel = make(timeoutChan)
return f, nil
}
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
return makeWin32File(h)
}
// closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() {
f.wgLock.Lock()
// Atomically set that we are closing, releasing the resources only once.
if !f.closing.swap(true) {
f.wgLock.Unlock()
// cancel all IO and wait for it to complete
cancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
syscall.Close(f.handle)
f.handle = 0
} else {
f.wgLock.Unlock()
}
}
// Close closes a win32File.
func (f *win32File) Close() error {
f.closeHandle()
return nil
}
// prepareIo prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) {
f.wgLock.RLock()
if f.closing.isSet() {
f.wgLock.RUnlock()
return nil, ErrFileClosed
}
f.wg.Add(1)
f.wgLock.RUnlock()
c := &ioOperation{}
c.ch = make(chan ioResult)
return c, nil
}
// ioCompletionProcessor processes completed async IOs forever
func ioCompletionProcessor(h syscall.Handle) {
for {
var bytes uint32
var key uintptr
var op *ioOperation
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
if op == nil {
panic(err)
}
op.ch <- ioResult{bytes, err}
}
}
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed.
func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING {
return int(bytes), err
}
if f.closing.isSet() {
cancelIoEx(f.handle, &c.o)
}
var timeout timeoutChan
if d != nil {
d.channelLock.Lock()
timeout = d.channel
d.channelLock.Unlock()
}
var r ioResult
select {
case r = <-c.ch:
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing.isSet() {
err = ErrFileClosed
}
}
case <-timeout:
cancelIoEx(f.handle, &c.o)
r = <-c.ch
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
err = ErrTimeout
}
}
// runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive
// until the channel read is complete.
runtime.KeepAlive(c)
return int(r.bytes), err
}
// Read reads from a file handle.
func (f *win32File) Read(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
defer f.wg.Done()
if f.readDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b)
// Handle EOF conditions.
if err == nil && n == 0 && len(b) != 0 {
return 0, io.EOF
} else if err == syscall.ERROR_BROKEN_PIPE {
return 0, io.EOF
} else {
return n, err
}
}
// Write writes to a file handle.
func (f *win32File) Write(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
defer f.wg.Done()
if f.writeDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b)
return n, err
}
func (f *win32File) SetReadDeadline(deadline time.Time) error {
return f.readDeadline.set(deadline)
}
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
return f.writeDeadline.set(deadline)
}
func (f *win32File) Flush() error {
return syscall.FlushFileBuffers(f.handle)
}
func (d *deadlineHandler) set(deadline time.Time) error {
d.setLock.Lock()
defer d.setLock.Unlock()
if d.timer != nil {
if !d.timer.Stop() {
<-d.channel
}
d.timer = nil
}
d.timedout.setFalse()
select {
case <-d.channel:
d.channelLock.Lock()
d.channel = make(chan struct{})
d.channelLock.Unlock()
default:
}
if deadline.IsZero() {
return nil
}
timeoutIO := func() {
d.timedout.setTrue()
close(d.channel)
}
now := time.Now()
duration := deadline.Sub(now)
if deadline.After(now) {
// Deadline is in the future, set a timer to wait
d.timer = time.AfterFunc(duration, timeoutIO)
} else {
// Deadline is in the past. Cancel all pending IO now.
timeoutIO()
}
return nil
}

View File

@@ -1,60 +0,0 @@
// +build windows
package winio
import (
"os"
"runtime"
"syscall"
"unsafe"
)
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
const (
fileBasicInfo = 0
fileIDInfo = 0x12
)
// FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
FileAttributes uintptr // includes padding
}
// GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return bi, nil
}
// SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return nil
}
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
// unique on a system.
type FileIDInfo struct {
VolumeSerialNumber uint64
FileID [16]byte
}
// GetFileID retrieves the unique (volume, file ID) pair for a file.
func GetFileID(f *os.File) (*FileIDInfo, error) {
fileID := &FileIDInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return fileID, nil
}

View File

@@ -1,424 +0,0 @@
// +build windows
package winio
import (
"errors"
"io"
"net"
"os"
"syscall"
"time"
"unsafe"
)
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
const (
cERROR_PIPE_BUSY = syscall.Errno(231)
cERROR_NO_DATA = syscall.Errno(232)
cERROR_PIPE_CONNECTED = syscall.Errno(535)
cERROR_SEM_TIMEOUT = syscall.Errno(121)
cPIPE_ACCESS_DUPLEX = 0x3
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
cPIPE_UNLIMITED_INSTANCES = 255
cNMPWAIT_USE_DEFAULT_WAIT = 0
cNMPWAIT_NOWAIT = 1
cPIPE_TYPE_MESSAGE = 4
cPIPE_READMODE_MESSAGE = 2
)
var (
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
// This error should match net.errClosing since docker takes a dependency on its text.
ErrPipeListenerClosed = errors.New("use of closed network connection")
errPipeWriteClosed = errors.New("pipe has been closed for write")
)
type win32Pipe struct {
*win32File
path string
}
type win32MessageBytePipe struct {
win32Pipe
writeClosed bool
readEOF bool
}
type pipeAddress string
func (f *win32Pipe) LocalAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *win32Pipe) RemoteAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *win32Pipe) SetDeadline(t time.Time) error {
f.SetReadDeadline(t)
f.SetWriteDeadline(t)
return nil
}
// CloseWrite closes the write side of a message pipe in byte mode.
func (f *win32MessageBytePipe) CloseWrite() error {
if f.writeClosed {
return errPipeWriteClosed
}
err := f.win32File.Flush()
if err != nil {
return err
}
_, err = f.win32File.Write(nil)
if err != nil {
return err
}
f.writeClosed = true
return nil
}
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
// they are used to implement CloseWrite().
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
if f.writeClosed {
return 0, errPipeWriteClosed
}
if len(b) == 0 {
return 0, nil
}
return f.win32File.Write(b)
}
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
// mode pipe will return io.EOF, as will all subsequent reads.
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
if f.readEOF {
return 0, io.EOF
}
n, err := f.win32File.Read(b)
if err == io.EOF {
// If this was the result of a zero-byte read, then
// it is possible that the read was due to a zero-size
// message. Since we are simulating CloseWrite with a
// zero-byte message, ensure that all future Read() calls
// also return EOF.
f.readEOF = true
}
return n, err
}
func (s pipeAddress) Network() string {
return "pipe"
}
func (s pipeAddress) String() string {
return string(s)
}
// DialPipe connects to a named pipe by path, timing out if the connection
// takes longer than the specified duration. If timeout is nil, then the timeout
// is the default timeout established by the pipe server.
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
var absTimeout time.Time
if timeout != nil {
absTimeout = time.Now().Add(*timeout)
}
var err error
var h syscall.Handle
for {
h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err != cERROR_PIPE_BUSY {
break
}
now := time.Now()
var ms uint32
if absTimeout.IsZero() {
ms = cNMPWAIT_USE_DEFAULT_WAIT
} else if now.After(absTimeout) {
ms = cNMPWAIT_NOWAIT
} else {
ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000)
}
err = waitNamedPipe(path, ms)
if err != nil {
if err == cERROR_SEM_TIMEOUT {
return nil, ErrTimeout
}
break
}
}
if err != nil {
return nil, &os.PathError{Op: "open", Path: path, Err: err}
}
var flags uint32
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
if err != nil {
return nil, err
}
var state uint32
err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0)
if err != nil {
return nil, err
}
if state&cPIPE_READMODE_MESSAGE != 0 {
return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")}
}
f, err := makeWin32File(h)
if err != nil {
syscall.Close(h)
return nil, err
}
// If the pipe is in message mode, return a message byte pipe, which
// supports CloseWrite().
if flags&cPIPE_TYPE_MESSAGE != 0 {
return &win32MessageBytePipe{
win32Pipe: win32Pipe{win32File: f, path: path},
}, nil
}
return &win32Pipe{win32File: f, path: path}, nil
}
type acceptResponse struct {
f *win32File
err error
}
type win32PipeListener struct {
firstHandle syscall.Handle
path string
securityDescriptor []byte
config PipeConfig
acceptCh chan (chan acceptResponse)
closeCh chan int
doneCh chan int
}
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
if first {
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
}
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
if c.MessageMode {
mode |= cPIPE_TYPE_MESSAGE
}
sa := &syscall.SecurityAttributes{}
sa.Length = uint32(unsafe.Sizeof(*sa))
if securityDescriptor != nil {
len := uint32(len(securityDescriptor))
sa.SecurityDescriptor = localAlloc(0, len)
defer localFree(sa.SecurityDescriptor)
copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor)
}
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa)
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
return h, nil
}
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
if err != nil {
return nil, err
}
f, err := makeWin32File(h)
if err != nil {
syscall.Close(h)
return nil, err
}
return f, nil
}
func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
p, err := l.makeServerPipe()
if err != nil {
return nil, err
}
// Wait for the client to connect.
ch := make(chan error)
go func(p *win32File) {
ch <- connectPipe(p)
}(p)
select {
case err = <-ch:
if err != nil {
p.Close()
p = nil
}
case <-l.closeCh:
// Abort the connect request by closing the handle.
p.Close()
p = nil
err = <-ch
if err == nil || err == ErrFileClosed {
err = ErrPipeListenerClosed
}
}
return p, err
}
func (l *win32PipeListener) listenerRoutine() {
closed := false
for !closed {
select {
case <-l.closeCh:
closed = true
case responseCh := <-l.acceptCh:
var (
p *win32File
err error
)
for {
p, err = l.makeConnectedServerPipe()
// If the connection was immediately closed by the client, try
// again.
if err != cERROR_NO_DATA {
break
}
}
responseCh <- acceptResponse{p, err}
closed = err == ErrPipeListenerClosed
}
}
syscall.Close(l.firstHandle)
l.firstHandle = 0
// Notify Close() and Accept() callers that the handle has been closed.
close(l.doneCh)
}
// PipeConfig contain configuration for the pipe listener.
type PipeConfig struct {
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
SecurityDescriptor string
// MessageMode determines whether the pipe is in byte or message mode. In either
// case the pipe is read in byte mode by default. The only practical difference in
// this implementation is that CloseWrite() is only supported for message mode pipes;
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
// transferred to the reader (and returned as io.EOF in this implementation)
// when the pipe is in message mode.
MessageMode bool
// InputBufferSize specifies the size the input buffer, in bytes.
InputBufferSize int32
// OutputBufferSize specifies the size the input buffer, in bytes.
OutputBufferSize int32
}
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
// The pipe must not already exist.
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
var (
sd []byte
err error
)
if c == nil {
c = &PipeConfig{}
}
if c.SecurityDescriptor != "" {
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
if err != nil {
return nil, err
}
}
h, err := makeServerPipeHandle(path, sd, c, true)
if err != nil {
return nil, err
}
// Immediately open and then close a client handle so that the named pipe is
// created but not currently accepting connections.
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err != nil {
syscall.Close(h)
return nil, err
}
syscall.Close(h2)
l := &win32PipeListener{
firstHandle: h,
path: path,
securityDescriptor: sd,
config: *c,
acceptCh: make(chan (chan acceptResponse)),
closeCh: make(chan int),
doneCh: make(chan int),
}
go l.listenerRoutine()
return l, nil
}
func connectPipe(p *win32File) error {
c, err := p.prepareIo()
if err != nil {
return err
}
defer p.wg.Done()
err = connectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, nil, 0, err)
if err != nil && err != cERROR_PIPE_CONNECTED {
return err
}
return nil
}
func (l *win32PipeListener) Accept() (net.Conn, error) {
ch := make(chan acceptResponse)
select {
case l.acceptCh <- ch:
response := <-ch
err := response.err
if err != nil {
return nil, err
}
if l.config.MessageMode {
return &win32MessageBytePipe{
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
}, nil
}
return &win32Pipe{win32File: response.f, path: l.path}, nil
case <-l.doneCh:
return nil, ErrPipeListenerClosed
}
}
func (l *win32PipeListener) Close() error {
select {
case l.closeCh <- 1:
<-l.doneCh
case <-l.doneCh:
}
return nil
}
func (l *win32PipeListener) Addr() net.Addr {
return pipeAddress(l.path)
}

View File

@@ -1,202 +0,0 @@
// +build windows
package winio
import (
"bytes"
"encoding/binary"
"fmt"
"runtime"
"sync"
"syscall"
"unicode/utf16"
"golang.org/x/sys/windows"
)
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
//sys revertToSelf() (err error) = advapi32.RevertToSelf
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
const (
SE_PRIVILEGE_ENABLED = 2
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege"
)
const (
securityAnonymous = iota
securityIdentification
securityImpersonation
securityDelegation
)
var (
privNames = make(map[string]uint64)
privNameMutex sync.Mutex
)
// PrivilegeError represents an error enabling privileges.
type PrivilegeError struct {
privileges []uint64
}
func (e *PrivilegeError) Error() string {
s := ""
if len(e.privileges) > 1 {
s = "Could not enable privileges "
} else {
s = "Could not enable privilege "
}
for i, p := range e.privileges {
if i != 0 {
s += ", "
}
s += `"`
s += getPrivilegeName(p)
s += `"`
}
return s
}
// RunWithPrivilege enables a single privilege for a function call.
func RunWithPrivilege(name string, fn func() error) error {
return RunWithPrivileges([]string{name}, fn)
}
// RunWithPrivileges enables privileges for a function call.
func RunWithPrivileges(names []string, fn func() error) error {
privileges, err := mapPrivileges(names)
if err != nil {
return err
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
token, err := newThreadToken()
if err != nil {
return err
}
defer releaseThreadToken(token)
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
if err != nil {
return err
}
return fn()
}
func mapPrivileges(names []string) ([]uint64, error) {
var privileges []uint64
privNameMutex.Lock()
defer privNameMutex.Unlock()
for _, name := range names {
p, ok := privNames[name]
if !ok {
err := lookupPrivilegeValue("", name, &p)
if err != nil {
return nil, err
}
privNames[name] = p
}
privileges = append(privileges, p)
}
return privileges, nil
}
// EnableProcessPrivileges enables privileges globally for the process.
func EnableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
}
// DisableProcessPrivileges disables privileges globally for the process.
func DisableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, 0)
}
func enableDisableProcessPrivilege(names []string, action uint32) error {
privileges, err := mapPrivileges(names)
if err != nil {
return err
}
p, _ := windows.GetCurrentProcess()
var token windows.Token
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
if err != nil {
return err
}
defer token.Close()
return adjustPrivileges(token, privileges, action)
}
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
for _, p := range privileges {
binary.Write(&b, binary.LittleEndian, p)
binary.Write(&b, binary.LittleEndian, action)
}
prevState := make([]byte, b.Len())
reqSize := uint32(0)
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
if !success {
return err
}
if err == ERROR_NOT_ALL_ASSIGNED {
return &PrivilegeError{privileges}
}
return nil
}
func getPrivilegeName(luid uint64) string {
var nameBuffer [256]uint16
bufSize := uint32(len(nameBuffer))
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
if err != nil {
return fmt.Sprintf("<unknown privilege %d>", luid)
}
var displayNameBuffer [256]uint16
displayBufSize := uint32(len(displayNameBuffer))
var langID uint32
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
if err != nil {
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
}
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
}
func newThreadToken() (windows.Token, error) {
err := impersonateSelf(securityImpersonation)
if err != nil {
return 0, err
}
var token windows.Token
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
if err != nil {
rerr := revertToSelf()
if rerr != nil {
panic(rerr)
}
return 0, err
}
return token, nil
}
func releaseThreadToken(h windows.Token) {
err := revertToSelf()
if err != nil {
panic(err)
}
h.Close()
}

View File

@@ -1,128 +0,0 @@
package winio
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"unicode/utf16"
"unsafe"
)
const (
reparseTagMountPoint = 0xA0000003
reparseTagSymlink = 0xA000000C
)
type reparseDataBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset uint16
PrintNameLength uint16
}
// ReparsePoint describes a Win32 symlink or mount point.
type ReparsePoint struct {
Target string
IsMountPoint bool
}
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
// mount point reparse point.
type UnsupportedReparsePointError struct {
Tag uint32
}
func (e *UnsupportedReparsePointError) Error() string {
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
}
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
// or a mount point.
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
tag := binary.LittleEndian.Uint32(b[0:4])
return DecodeReparsePointData(tag, b[8:])
}
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
isMountPoint := false
switch tag {
case reparseTagMountPoint:
isMountPoint = true
case reparseTagSymlink:
default:
return nil, &UnsupportedReparsePointError{tag}
}
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
if !isMountPoint {
nameOffset += 4
}
nameLength := binary.LittleEndian.Uint16(b[6:8])
name := make([]uint16, nameLength/2)
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
if err != nil {
return nil, err
}
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
}
func isDriveLetter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
// mount point.
func EncodeReparsePoint(rp *ReparsePoint) []byte {
// Generate an NT path and determine if this is a relative path.
var ntTarget string
relative := false
if strings.HasPrefix(rp.Target, `\\?\`) {
ntTarget = `\??\` + rp.Target[4:]
} else if strings.HasPrefix(rp.Target, `\\`) {
ntTarget = `\??\UNC\` + rp.Target[2:]
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
ntTarget = `\??\` + rp.Target
} else {
ntTarget = rp.Target
relative = true
}
// The paths must be NUL-terminated even though they are counted strings.
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
size += len(ntTarget16)*2 + len(target16)*2
tag := uint32(reparseTagMountPoint)
if !rp.IsMountPoint {
tag = reparseTagSymlink
size += 4 // Add room for symlink flags
}
data := reparseDataBuffer{
ReparseTag: tag,
ReparseDataLength: uint16(size),
SubstituteNameOffset: 0,
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
PrintNameOffset: uint16(len(ntTarget16) * 2),
PrintNameLength: uint16((len(target16) - 1) * 2),
}
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, &data)
if !rp.IsMountPoint {
flags := uint32(0)
if relative {
flags |= 1
}
binary.Write(&b, binary.LittleEndian, flags)
}
binary.Write(&b, binary.LittleEndian, ntTarget16)
binary.Write(&b, binary.LittleEndian, target16)
return b.Bytes()
}

View File

@@ -1,98 +0,0 @@
// +build windows
package winio
import (
"syscall"
"unsafe"
)
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
//sys localFree(mem uintptr) = LocalFree
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
const (
cERROR_NONE_MAPPED = syscall.Errno(1332)
)
type AccountLookupError struct {
Name string
Err error
}
func (e *AccountLookupError) Error() string {
if e.Name == "" {
return "lookup account: empty account name specified"
}
var s string
switch e.Err {
case cERROR_NONE_MAPPED:
s = "not found"
default:
s = e.Err.Error()
}
return "lookup account " + e.Name + ": " + s
}
type SddlConversionError struct {
Sddl string
Err error
}
func (e *SddlConversionError) Error() string {
return "convert " + e.Sddl + ": " + e.Err.Error()
}
// LookupSidByName looks up the SID of an account by name
func LookupSidByName(name string) (sid string, err error) {
if name == "" {
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
}
var sidSize, sidNameUse, refDomainSize uint32
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
return "", &AccountLookupError{name, err}
}
sidBuffer := make([]byte, sidSize)
refDomainBuffer := make([]uint16, refDomainSize)
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
if err != nil {
return "", &AccountLookupError{name, err}
}
var strBuffer *uint16
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
if err != nil {
return "", &AccountLookupError{name, err}
}
sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
localFree(uintptr(unsafe.Pointer(strBuffer)))
return sid, nil
}
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
var sdBuffer uintptr
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
if err != nil {
return nil, &SddlConversionError{sddl, err}
}
defer localFree(sdBuffer)
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
return sd, nil
}
func SecurityDescriptorToSddl(sd []byte) (string, error) {
var sddl *uint16
// The returned string length seems to including an aribtrary number of terminating NULs.
// Don't use it.
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
if err != nil {
return "", err
}
defer localFree(uintptr(unsafe.Pointer(sddl)))
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
}

View File

@@ -1,3 +0,0 @@
package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go

View File

@@ -1,520 +0,0 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package winio
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
procLocalFree = modkernel32.NewProc("LocalFree")
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
procBackupRead = modkernel32.NewProc("BackupRead")
procBackupWrite = modkernel32.NewProc("BackupWrite")
)
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0)
if newport == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
}
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func waitNamedPipe(name string, timeout uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _waitNamedPipe(_p0, timeout)
}
func _waitNamedPipe(name *uint16, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
ptr = uintptr(r0)
return
}
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(accountName)
if err != nil {
return
}
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
}
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(str)
if err != nil {
return
}
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
}
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localFree(mem uintptr) {
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
return
}
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
len = uint32(r0)
return
}
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
var _p0 uint32
if releaseAll {
_p0 = 1
} else {
_p0 = 0
}
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func impersonateSelf(level uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func revertToSelf() (err error) {
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
var _p0 uint32
if openAsSelf {
_p0 = 1
} else {
_p0 = 0
}
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
h = syscall.Handle(r0)
return
}
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
var _p1 *uint16
_p1, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _lookupPrivilegeValue(_p0, _p1, luid)
}
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeName(_p0, luid, buffer, size)
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
var _p0 *byte
if len(b) > 0 {
_p0 = &b[0]
}
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
var _p0 *byte
if len(b) > 0 {
_p0 = &b[0]
}
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}

View File

@@ -1,12 +0,0 @@
Copyright (c) 2012, Martin Angers
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,379 +0,0 @@
/*
Package purell offers URL normalization as described on the wikipedia page:
http://en.wikipedia.org/wiki/URL_normalization
*/
package purell
import (
"bytes"
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"github.com/PuerkitoBio/urlesc"
"golang.org/x/net/idna"
"golang.org/x/text/unicode/norm"
"golang.org/x/text/width"
)
// A set of normalization flags determines how a URL will
// be normalized.
type NormalizationFlags uint
const (
// Safe normalizations
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
FlagLowercaseHost // http://HOST -> http://host
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
FlagRemoveDefaultPort // http://host:80 -> http://host
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
// Usually safe normalizations
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
// Unsafe normalizations
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
FlagRemoveFragment // http://host/path#fragment -> http://host/path
FlagForceHTTP // https://host -> http://host
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
FlagRemoveWWW // http://www.host/ -> http://host/
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
// Normalizations not in the wikipedia article, required to cover tests cases
// submitted by jehiah
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
// Convenience set of safe normalizations
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
// Convenience set of usually safe normalizations (includes FlagsSafe)
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
// Convenience set of all available flags
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
)
const (
defaultHttpPort = ":80"
defaultHttpsPort = ":443"
)
// Regular expressions used by the normalizations
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
var rxEmptyPort = regexp.MustCompile(`:+$`)
// Map of flags to implementation function.
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
// Since maps have undefined traversing order, make a slice of ordered keys
var flagsOrder = []NormalizationFlags{
FlagLowercaseScheme,
FlagLowercaseHost,
FlagRemoveDefaultPort,
FlagRemoveDirectoryIndex,
FlagRemoveDotSegments,
FlagRemoveFragment,
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
FlagRemoveDuplicateSlashes,
FlagRemoveWWW,
FlagAddWWW,
FlagSortQuery,
FlagDecodeDWORDHost,
FlagDecodeOctalHost,
FlagDecodeHexHost,
FlagRemoveUnnecessaryHostDots,
FlagRemoveEmptyPortSeparator,
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
FlagAddTrailingSlash,
}
// ... and then the map, where order is unimportant
var flags = map[NormalizationFlags]func(*url.URL){
FlagLowercaseScheme: lowercaseScheme,
FlagLowercaseHost: lowercaseHost,
FlagRemoveDefaultPort: removeDefaultPort,
FlagRemoveDirectoryIndex: removeDirectoryIndex,
FlagRemoveDotSegments: removeDotSegments,
FlagRemoveFragment: removeFragment,
FlagForceHTTP: forceHTTP,
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
FlagRemoveWWW: removeWWW,
FlagAddWWW: addWWW,
FlagSortQuery: sortQuery,
FlagDecodeDWORDHost: decodeDWORDHost,
FlagDecodeOctalHost: decodeOctalHost,
FlagDecodeHexHost: decodeHexHost,
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
FlagRemoveTrailingSlash: removeTrailingSlash,
FlagAddTrailingSlash: addTrailingSlash,
}
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
// It takes an URL string as input, as well as the normalization flags.
func MustNormalizeURLString(u string, f NormalizationFlags) string {
result, e := NormalizeURLString(u, f)
if e != nil {
panic(e)
}
return result
}
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
// It takes an URL string as input, as well as the normalization flags.
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
parsed, err := url.Parse(u)
if err != nil {
return "", err
}
if f&FlagLowercaseHost == FlagLowercaseHost {
parsed.Host = strings.ToLower(parsed.Host)
}
// The idna package doesn't fully conform to RFC 5895
// (https://tools.ietf.org/html/rfc5895), so we do it here.
// Taken from Go 1.8 cycle source, courtesy of bradfitz.
// TODO: Remove when (if?) idna package conforms to RFC 5895.
parsed.Host = width.Fold.String(parsed.Host)
parsed.Host = norm.NFC.String(parsed.Host)
if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
return "", err
}
return NormalizeURL(parsed, f), nil
}
// NormalizeURL returns the normalized string.
// It takes a parsed URL object as input, as well as the normalization flags.
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
for _, k := range flagsOrder {
if f&k == k {
flags[k](u)
}
}
return urlesc.Escape(u)
}
func lowercaseScheme(u *url.URL) {
if len(u.Scheme) > 0 {
u.Scheme = strings.ToLower(u.Scheme)
}
}
func lowercaseHost(u *url.URL) {
if len(u.Host) > 0 {
u.Host = strings.ToLower(u.Host)
}
}
func removeDefaultPort(u *url.URL) {
if len(u.Host) > 0 {
scheme := strings.ToLower(u.Scheme)
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
return ""
}
return val
})
}
}
func removeTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if strings.HasSuffix(u.Path, "/") {
u.Path = u.Path[:l-1]
}
} else if l = len(u.Host); l > 0 {
if strings.HasSuffix(u.Host, "/") {
u.Host = u.Host[:l-1]
}
}
}
func addTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
} else if l = len(u.Host); l > 0 {
if !strings.HasSuffix(u.Host, "/") {
u.Host += "/"
}
}
}
func removeDotSegments(u *url.URL) {
if len(u.Path) > 0 {
var dotFree []string
var lastIsDot bool
sections := strings.Split(u.Path, "/")
for _, s := range sections {
if s == ".." {
if len(dotFree) > 0 {
dotFree = dotFree[:len(dotFree)-1]
}
} else if s != "." {
dotFree = append(dotFree, s)
}
lastIsDot = (s == "." || s == "..")
}
// Special case if host does not end with / and new path does not begin with /
u.Path = strings.Join(dotFree, "/")
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
u.Path = "/" + u.Path
}
// Special case if the last segment was a dot, make sure the path ends with a slash
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
}
}
func removeDirectoryIndex(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
}
}
func removeFragment(u *url.URL) {
u.Fragment = ""
}
func forceHTTP(u *url.URL) {
if strings.ToLower(u.Scheme) == "https" {
u.Scheme = "http"
}
}
func removeDuplicateSlashes(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
}
}
func removeWWW(u *url.URL) {
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = u.Host[4:]
}
}
func addWWW(u *url.URL) {
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = "www." + u.Host
}
}
func sortQuery(u *url.URL) {
q := u.Query()
if len(q) > 0 {
arKeys := make([]string, len(q))
i := 0
for k, _ := range q {
arKeys[i] = k
i++
}
sort.Strings(arKeys)
buf := new(bytes.Buffer)
for _, k := range arKeys {
sort.Strings(q[k])
for _, v := range q[k] {
if buf.Len() > 0 {
buf.WriteRune('&')
}
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
}
}
// Rebuild the raw query string
u.RawQuery = buf.String()
}
}
func decodeDWORDHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
var parts [4]int64
dword, _ := strconv.ParseInt(matches[1], 10, 0)
for i, shift := range []uint{24, 16, 8, 0} {
parts[i] = dword >> shift & 0xFF
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
}
}
}
func decodeOctalHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
var parts [4]int64
for i := 1; i <= 4; i++ {
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
}
}
}
func decodeHexHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
// Conversion is safe because of regex validation
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
// Set host as DWORD (base 10) encoded host
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
// The rest is the same as decoding a DWORD host
decodeDWORDHost(u)
}
}
}
func removeUnncessaryHostDots(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
// Trim the leading and trailing dots
u.Host = strings.Trim(matches[1], ".")
if len(matches) > 2 {
u.Host += matches[2]
}
}
}
}
func removeEmptyPortSeparator(u *url.URL) {
if len(u.Host) > 0 {
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
}
}

View File

@@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,180 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package urlesc implements query escaping as per RFC 3986.
// It contains some parts of the net/url package, modified so as to allow
// some reserved characters incorrectly escaped by net/url.
// See https://github.com/golang/go/issues/5684
package urlesc
import (
"bytes"
"net/url"
"strings"
)
type encoding int
const (
encodePath encoding = 1 + iota
encodeUserPassword
encodeQueryComponent
encodeFragment
)
// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 3986.
func shouldEscape(c byte, mode encoding) bool {
// §2.3 Unreserved characters (alphanum)
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
return false
}
switch c {
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
return false
// §2.2 Reserved characters (reserved)
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
switch mode {
case encodePath: // §3.3
// The RFC allows sub-delims and : @.
// '/', '[' and ']' can be used to assign meaning to individual path
// segments. This package only manipulates the path as a whole,
// so we allow those as well. That leaves only ? and # to escape.
return c == '?' || c == '#'
case encodeUserPassword: // §3.2.1
// The RFC allows : and sub-delims in
// userinfo. The parsing of userinfo treats ':' as special so we must escape
// all the gen-delims.
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
case encodeQueryComponent: // §3.4
// The RFC allows / and ?.
return c != '/' && c != '?'
case encodeFragment: // §4.1
// The RFC text is silent but the grammar allows
// everything, so escape nothing but #
return c == '#'
}
}
// Everything else must be escaped.
return true
}
// QueryEscape escapes the string so it can be safely placed
// inside a URL query.
func QueryEscape(s string) string {
return escape(s, encodeQueryComponent)
}
func escape(s string, mode encoding) string {
spaceCount, hexCount := 0, 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c, mode) {
if c == ' ' && mode == encodeQueryComponent {
spaceCount++
} else {
hexCount++
}
}
}
if spaceCount == 0 && hexCount == 0 {
return s
}
t := make([]byte, len(s)+2*hexCount)
j := 0
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ' && mode == encodeQueryComponent:
t[j] = '+'
j++
case shouldEscape(c, mode):
t[j] = '%'
t[j+1] = "0123456789ABCDEF"[c>>4]
t[j+2] = "0123456789ABCDEF"[c&15]
j += 3
default:
t[j] = s[i]
j++
}
}
return string(t)
}
var uiReplacer = strings.NewReplacer(
"%21", "!",
"%27", "'",
"%28", "(",
"%29", ")",
"%2A", "*",
)
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
func unescapeUserinfo(s string) string {
return uiReplacer.Replace(s)
}
// Escape reassembles the URL into a valid URL string.
// The general form of the result is one of:
//
// scheme:opaque
// scheme://userinfo@host/path?query#fragment
//
// If u.Opaque is non-empty, String uses the first form;
// otherwise it uses the second form.
//
// In the second form, the following rules apply:
// - if u.Scheme is empty, scheme: is omitted.
// - if u.User is nil, userinfo@ is omitted.
// - if u.Host is empty, host/ is omitted.
// - if u.Scheme and u.Host are empty and u.User is nil,
// the entire scheme://userinfo@host/ is omitted.
// - if u.Host is non-empty and u.Path begins with a /,
// the form host/path does not add its own /.
// - if u.RawQuery is empty, ?query is omitted.
// - if u.Fragment is empty, #fragment is omitted.
func Escape(u *url.URL) string {
var buf bytes.Buffer
if u.Scheme != "" {
buf.WriteString(u.Scheme)
buf.WriteByte(':')
}
if u.Opaque != "" {
buf.WriteString(u.Opaque)
} else {
if u.Scheme != "" || u.Host != "" || u.User != nil {
buf.WriteString("//")
if ui := u.User; ui != nil {
buf.WriteString(unescapeUserinfo(ui.String()))
buf.WriteByte('@')
}
if h := u.Host; h != "" {
buf.WriteString(h)
}
}
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
}
buf.WriteString(escape(u.Path, encodePath))
}
if u.RawQuery != "" {
buf.WriteByte('?')
buf.WriteString(u.RawQuery)
}
if u.Fragment != "" {
buf.WriteByte('#')
buf.WriteString(escape(u.Fragment, encodeFragment))
}
return buf.String()
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Alex Saskevich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,58 +0,0 @@
package govalidator
// Iterator is the function that accepts element of slice/array and its index
type Iterator func(interface{}, int)
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
type ResultIterator func(interface{}, int) interface{}
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
type ConditionIterator func(interface{}, int) bool
// Each iterates over the slice and apply Iterator to every item
func Each(array []interface{}, iterator Iterator) {
for index, data := range array {
iterator(data, index)
}
}
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
func Map(array []interface{}, iterator ResultIterator) []interface{} {
var result = make([]interface{}, len(array))
for index, data := range array {
result[index] = iterator(data, index)
}
return result
}
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
func Find(array []interface{}, iterator ConditionIterator) interface{} {
for index, data := range array {
if iterator(data, index) {
return data
}
}
return nil
}
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
func Filter(array []interface{}, iterator ConditionIterator) []interface{} {
var result = make([]interface{}, 0)
for index, data := range array {
if iterator(data, index) {
result = append(result, data)
}
}
return result
}
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
func Count(array []interface{}, iterator ConditionIterator) int {
count := 0
for index, data := range array {
if iterator(data, index) {
count = count + 1
}
}
return count
}

View File

@@ -1,64 +0,0 @@
package govalidator
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// ToString convert the input to a string.
func ToString(obj interface{}) string {
res := fmt.Sprintf("%v", obj)
return string(res)
}
// ToJSON convert the input to a valid JSON string
func ToJSON(obj interface{}) (string, error) {
res, err := json.Marshal(obj)
if err != nil {
res = []byte("")
}
return string(res), err
}
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
func ToFloat(str string) (float64, error) {
res, err := strconv.ParseFloat(str, 64)
if err != nil {
res = 0.0
}
return res, err
}
// ToInt convert the input string or any int type to an integer type 64, or 0 if the input is not an integer.
func ToInt(value interface{}) (res int64, err error) {
val := reflect.ValueOf(value)
switch value.(type) {
case int, int8, int16, int32, int64:
res = val.Int()
case uint, uint8, uint16, uint32, uint64:
res = int64(val.Uint())
case string:
if IsInt(val.String()) {
res, err = strconv.ParseInt(val.String(), 0, 64)
if err != nil {
res = 0
}
} else {
err = fmt.Errorf("math: square root of negative number %g", value)
res = 0
}
default:
err = fmt.Errorf("math: square root of negative number %g", value)
res = 0
}
return
}
// ToBoolean convert the input string to a boolean.
func ToBoolean(str string) (bool, error) {
return strconv.ParseBool(str)
}

View File

@@ -1,36 +0,0 @@
package govalidator
import "strings"
// Errors is an array of multiple errors and conforms to the error interface.
type Errors []error
// Errors returns itself.
func (es Errors) Errors() []error {
return es
}
func (es Errors) Error() string {
var errs []string
for _, e := range es {
errs = append(errs, e.Error())
}
return strings.Join(errs, ";")
}
// Error encapsulates a name, an error and whether there's a custom error message or not.
type Error struct {
Name string
Err error
CustomErrorMessageExists bool
// Validator indicates the name of the validator that failed
Validator string
}
func (e Error) Error() string {
if e.CustomErrorMessageExists {
return e.Err.Error()
}
return e.Name + ": " + e.Err.Error()
}

View File

@@ -1,97 +0,0 @@
package govalidator
import (
"math"
"reflect"
)
// Abs returns absolute value of number
func Abs(value float64) float64 {
return math.Abs(value)
}
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
func Sign(value float64) float64 {
if value > 0 {
return 1
} else if value < 0 {
return -1
} else {
return 0
}
}
// IsNegative returns true if value < 0
func IsNegative(value float64) bool {
return value < 0
}
// IsPositive returns true if value > 0
func IsPositive(value float64) bool {
return value > 0
}
// IsNonNegative returns true if value >= 0
func IsNonNegative(value float64) bool {
return value >= 0
}
// IsNonPositive returns true if value <= 0
func IsNonPositive(value float64) bool {
return value <= 0
}
// InRange returns true if value lies between left and right border
func InRangeInt(value, left, right interface{}) bool {
value64, _ := ToInt(value)
left64, _ := ToInt(left)
right64, _ := ToInt(right)
if left64 > right64 {
left64, right64 = right64, left64
}
return value64 >= left64 && value64 <= right64
}
// InRange returns true if value lies between left and right border
func InRangeFloat32(value, left, right float32) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border
func InRangeFloat64(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border, generic type to handle int, float32 or float64, all types must the same type
func InRange(value interface{}, left interface{}, right interface{}) bool {
reflectValue := reflect.TypeOf(value).Kind()
reflectLeft := reflect.TypeOf(left).Kind()
reflectRight := reflect.TypeOf(right).Kind()
if reflectValue == reflect.Int && reflectLeft == reflect.Int && reflectRight == reflect.Int {
return InRangeInt(value.(int), left.(int), right.(int))
} else if reflectValue == reflect.Float32 && reflectLeft == reflect.Float32 && reflectRight == reflect.Float32 {
return InRangeFloat32(value.(float32), left.(float32), right.(float32))
} else if reflectValue == reflect.Float64 && reflectLeft == reflect.Float64 && reflectRight == reflect.Float64 {
return InRangeFloat64(value.(float64), left.(float64), right.(float64))
} else {
return false
}
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return math.Remainder(value, 1) == 0
}
// IsNatural returns true if value is natural number (positive and whole)
func IsNatural(value float64) bool {
return IsWhole(value) && IsPositive(value)
}

View File

@@ -1,97 +0,0 @@
package govalidator
import "regexp"
// Basic regular expressions for validating strings
const (
//Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
URLSubdomain string = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))`
URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
hasLowerCase string = ".*[[:lower:]]"
hasUpperCase string = ".*[[:upper:]]"
)
// Used by IsFilePath func
const (
// Unknown is unresolved OS type
Unknown = iota
// Win is Windows type
Win
// Unix is *nix OS types
Unix
)
var (
userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
//rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxSemver = regexp.MustCompile(Semver)
rxHasLowerCase = regexp.MustCompile(hasLowerCase)
rxHasUpperCase = regexp.MustCompile(hasUpperCase)
)

View File

@@ -1,616 +0,0 @@
package govalidator
import (
"reflect"
"regexp"
"sync"
)
// Validator is a wrapper for a validator function that returns bool and accepts string.
type Validator func(str string) bool
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
// The second parameter should be the context (in the case of validating a struct: the whole object being validated).
type CustomTypeValidator func(i interface{}, o interface{}) bool
// ParamValidator is a wrapper for validator functions that accepts additional parameters.
type ParamValidator func(str string, params ...string) bool
type tagOptionsMap map[string]string
// UnsupportedTypeError is a wrapper for reflect.Type
type UnsupportedTypeError struct {
Type reflect.Type
}
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
type stringValues []reflect.Value
// ParamTagMap is a map of functions accept variants parameters
var ParamTagMap = map[string]ParamValidator{
"length": ByteLength,
"range": Range,
"runelength": RuneLength,
"stringlength": StringLength,
"matches": StringMatches,
"in": isInRaw,
"rsapub": IsRsaPub,
}
// ParamTagRegexMap maps param tags to their respective regexes.
var ParamTagRegexMap = map[string]*regexp.Regexp{
"range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"),
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"),
"runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"),
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
"in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
"rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"),
}
type customTypeTagMap struct {
validators map[string]CustomTypeValidator
sync.RWMutex
}
func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) {
tm.RLock()
defer tm.RUnlock()
v, ok := tm.validators[name]
return v, ok
}
func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) {
tm.Lock()
defer tm.Unlock()
tm.validators[name] = ctv
}
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
// `type UUID [16]byte` (this would be handled as an array of bytes).
var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)}
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
var TagMap = map[string]Validator{
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
}
// ISO3166Entry stores country codes
type ISO3166Entry struct {
EnglishShortName string
FrenchShortName string
Alpha2Code string
Alpha3Code string
Numeric string
}
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
var ISO3166List = []ISO3166Entry{
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"},
{"Albania", "Albanie (l')", "AL", "ALB", "008"},
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"},
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"},
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"},
{"Andorra", "Andorre (l')", "AD", "AND", "020"},
{"Angola", "Angola (l')", "AO", "AGO", "024"},
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"},
{"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"},
{"Argentina", "Argentine (l')", "AR", "ARG", "032"},
{"Australia", "Australie (l')", "AU", "AUS", "036"},
{"Austria", "Autriche (l')", "AT", "AUT", "040"},
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"},
{"Bahrain", "Bahreïn", "BH", "BHR", "048"},
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"},
{"Armenia", "Arménie (l')", "AM", "ARM", "051"},
{"Barbados", "Barbade (la)", "BB", "BRB", "052"},
{"Belgium", "Belgique (la)", "BE", "BEL", "056"},
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"},
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"},
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"},
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"},
{"Botswana", "Botswana (le)", "BW", "BWA", "072"},
{"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"},
{"Brazil", "Brésil (le)", "BR", "BRA", "076"},
{"Belize", "Belize (le)", "BZ", "BLZ", "084"},
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"},
{"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"},
{"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"},
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"},
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"},
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"},
{"Burundi", "Burundi (le)", "BI", "BDI", "108"},
{"Belarus", "Bélarus (le)", "BY", "BLR", "112"},
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"},
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"},
{"Canada", "Canada (le)", "CA", "CAN", "124"},
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"},
{"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"},
{"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"},
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"},
{"Chad", "Tchad (le)", "TD", "TCD", "148"},
{"Chile", "Chili (le)", "CL", "CHL", "152"},
{"China", "Chine (la)", "CN", "CHN", "156"},
{"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"},
{"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"},
{"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"},
{"Colombia", "Colombie (la)", "CO", "COL", "170"},
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"},
{"Mayotte", "Mayotte", "YT", "MYT", "175"},
{"Congo (the)", "Congo (le)", "CG", "COG", "178"},
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"},
{"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"},
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"},
{"Croatia", "Croatie (la)", "HR", "HRV", "191"},
{"Cuba", "Cuba", "CU", "CUB", "192"},
{"Cyprus", "Chypre", "CY", "CYP", "196"},
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"},
{"Benin", "Bénin (le)", "BJ", "BEN", "204"},
{"Denmark", "Danemark (le)", "DK", "DNK", "208"},
{"Dominica", "Dominique (la)", "DM", "DMA", "212"},
{"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"},
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"},
{"El Salvador", "El Salvador", "SV", "SLV", "222"},
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"},
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"},
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"},
{"Estonia", "Estonie (l')", "EE", "EST", "233"},
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"},
{"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"},
{"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"},
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"},
{"Finland", "Finlande (la)", "FI", "FIN", "246"},
{"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"},
{"France", "France (la)", "FR", "FRA", "250"},
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"},
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"},
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"},
{"Djibouti", "Djibouti", "DJ", "DJI", "262"},
{"Gabon", "Gabon (le)", "GA", "GAB", "266"},
{"Georgia", "Géorgie (la)", "GE", "GEO", "268"},
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"},
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"},
{"Germany", "Allemagne (l')", "DE", "DEU", "276"},
{"Ghana", "Ghana (le)", "GH", "GHA", "288"},
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"},
{"Kiribati", "Kiribati", "KI", "KIR", "296"},
{"Greece", "Grèce (la)", "GR", "GRC", "300"},
{"Greenland", "Groenland (le)", "GL", "GRL", "304"},
{"Grenada", "Grenade (la)", "GD", "GRD", "308"},
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"},
{"Guam", "Guam", "GU", "GUM", "316"},
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"},
{"Guinea", "Guinée (la)", "GN", "GIN", "324"},
{"Guyana", "Guyana (le)", "GY", "GUY", "328"},
{"Haiti", "Haïti", "HT", "HTI", "332"},
{"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"},
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"},
{"Honduras", "Honduras (le)", "HN", "HND", "340"},
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"},
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"},
{"Iceland", "Islande (l')", "IS", "ISL", "352"},
{"India", "Inde (l')", "IN", "IND", "356"},
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"},
{"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"},
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"},
{"Ireland", "Irlande (l')", "IE", "IRL", "372"},
{"Israel", "Israël", "IL", "ISR", "376"},
{"Italy", "Italie (l')", "IT", "ITA", "380"},
{"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"},
{"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"},
{"Japan", "Japon (le)", "JP", "JPN", "392"},
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"},
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"},
{"Kenya", "Kenya (le)", "KE", "KEN", "404"},
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"},
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"},
{"Kuwait", "Koweït (le)", "KW", "KWT", "414"},
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"},
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"},
{"Lebanon", "Liban (le)", "LB", "LBN", "422"},
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"},
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"},
{"Liberia", "Libéria (le)", "LR", "LBR", "430"},
{"Libya", "Libye (la)", "LY", "LBY", "434"},
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"},
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"},
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"},
{"Macao", "Macao", "MO", "MAC", "446"},
{"Madagascar", "Madagascar", "MG", "MDG", "450"},
{"Malawi", "Malawi (le)", "MW", "MWI", "454"},
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"},
{"Maldives", "Maldives (les)", "MV", "MDV", "462"},
{"Mali", "Mali (le)", "ML", "MLI", "466"},
{"Malta", "Malte", "MT", "MLT", "470"},
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"},
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"},
{"Mauritius", "Maurice", "MU", "MUS", "480"},
{"Mexico", "Mexique (le)", "MX", "MEX", "484"},
{"Monaco", "Monaco", "MC", "MCO", "492"},
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"},
{"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"},
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"},
{"Montserrat", "Montserrat", "MS", "MSR", "500"},
{"Morocco", "Maroc (le)", "MA", "MAR", "504"},
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"},
{"Oman", "Oman", "OM", "OMN", "512"},
{"Namibia", "Namibie (la)", "NA", "NAM", "516"},
{"Nauru", "Nauru", "NR", "NRU", "520"},
{"Nepal", "Népal (le)", "NP", "NPL", "524"},
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"},
{"Curaçao", "Curaçao", "CW", "CUW", "531"},
{"Aruba", "Aruba", "AW", "ABW", "533"},
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"},
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"},
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"},
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"},
{"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"},
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"},
{"Niger (the)", "Niger (le)", "NE", "NER", "562"},
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"},
{"Niue", "Niue", "NU", "NIU", "570"},
{"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"},
{"Norway", "Norvège (la)", "NO", "NOR", "578"},
{"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"},
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"},
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"},
{"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"},
{"Palau", "Palaos (les)", "PW", "PLW", "585"},
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"},
{"Panama", "Panama (le)", "PA", "PAN", "591"},
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"},
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"},
{"Peru", "Pérou (le)", "PE", "PER", "604"},
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"},
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"},
{"Poland", "Pologne (la)", "PL", "POL", "616"},
{"Portugal", "Portugal (le)", "PT", "PRT", "620"},
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"},
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"},
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"},
{"Qatar", "Qatar (le)", "QA", "QAT", "634"},
{"Réunion", "Réunion (La)", "RE", "REU", "638"},
{"Romania", "Roumanie (la)", "RO", "ROU", "642"},
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"},
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"},
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"},
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"},
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"},
{"Anguilla", "Anguilla", "AI", "AIA", "660"},
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"},
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"},
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"},
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"},
{"San Marino", "Saint-Marin", "SM", "SMR", "674"},
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"},
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"},
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"},
{"Serbia", "Serbie (la)", "RS", "SRB", "688"},
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"},
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"},
{"Singapore", "Singapour", "SG", "SGP", "702"},
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"},
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"},
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"},
{"Somalia", "Somalie (la)", "SO", "SOM", "706"},
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"},
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"},
{"Spain", "Espagne (l')", "ES", "ESP", "724"},
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"},
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"},
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"},
{"Suriname", "Suriname (le)", "SR", "SUR", "740"},
{"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"},
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"},
{"Sweden", "Suède (la)", "SE", "SWE", "752"},
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"},
{"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"},
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"},
{"Thailand", "Thaïlande (la)", "TH", "THA", "764"},
{"Togo", "Togo (le)", "TG", "TGO", "768"},
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"},
{"Tonga", "Tonga (les)", "TO", "TON", "776"},
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"},
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"},
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"},
{"Turkey", "Turquie (la)", "TR", "TUR", "792"},
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"},
{"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"},
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"},
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"},
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"},
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'exRépublique yougoslave de)", "MK", "MKD", "807"},
{"Egypt", "Égypte (l')", "EG", "EGY", "818"},
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"},
{"Guernsey", "Guernesey", "GG", "GGY", "831"},
{"Jersey", "Jersey", "JE", "JEY", "832"},
{"Isle of Man", "Île de Man", "IM", "IMN", "833"},
{"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"},
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"},
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"},
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"},
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"},
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"},
{"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"},
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"},
{"Samoa", "Samoa (le)", "WS", "WSM", "882"},
{"Yemen", "Yémen (le)", "YE", "YEM", "887"},
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"},
}
// ISO4217List is the list of ISO currency codes
var ISO4217List = []string{
"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN",
"BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK",
"DJF", "DKK", "DOP", "DZD",
"EGP", "ERN", "ETB", "EUR",
"FJD", "FKP",
"GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD",
"HKD", "HNL", "HRK", "HTG", "HUF",
"IDR", "ILS", "INR", "IQD", "IRR", "ISK",
"JMD", "JOD", "JPY",
"KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT",
"LAK", "LBP", "LKR", "LRD", "LSL", "LYD",
"MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN",
"NAD", "NGN", "NIO", "NOK", "NPR", "NZD",
"OMR",
"PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG",
"QAR",
"RON", "RSD", "RUB", "RWF",
"SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "SVC", "SYP", "SZL",
"THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS",
"UAH", "UGX", "USD", "USN", "UYI", "UYU", "UZS",
"VEF", "VND", "VUV",
"WST",
"XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX",
"YER",
"ZAR", "ZMW", "ZWL",
}
// ISO693Entry stores ISO language codes
type ISO693Entry struct {
Alpha3bCode string
Alpha2Code string
English string
}
//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json
var ISO693List = []ISO693Entry{
{Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"},
{Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"},
{Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"},
{Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"},
{Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"},
{Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"},
{Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"},
{Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"},
{Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"},
{Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"},
{Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"},
{Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"},
{Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"},
{Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"},
{Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"},
{Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"},
{Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"},
{Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"},
{Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"},
{Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"},
{Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"},
{Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"},
{Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"},
{Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"},
{Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"},
{Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"},
{Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"},
{Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"},
{Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"},
{Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"},
{Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"},
{Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"},
{Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"},
{Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"},
{Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"},
{Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"},
{Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"},
{Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"},
{Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"},
{Alpha3bCode: "eng", Alpha2Code: "en", English: "English"},
{Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"},
{Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"},
{Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"},
{Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"},
{Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"},
{Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"},
{Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"},
{Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"},
{Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"},
{Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"},
{Alpha3bCode: "ger", Alpha2Code: "de", English: "German"},
{Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"},
{Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"},
{Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"},
{Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"},
{Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"},
{Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"},
{Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"},
{Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"},
{Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"},
{Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"},
{Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"},
{Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"},
{Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"},
{Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"},
{Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"},
{Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"},
{Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"},
{Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"},
{Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"},
{Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"},
{Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"},
{Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"},
{Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"},
{Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"},
{Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"},
{Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"},
{Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"},
{Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"},
{Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"},
{Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"},
{Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"},
{Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"},
{Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"},
{Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"},
{Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"},
{Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"},
{Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"},
{Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"},
{Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"},
{Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"},
{Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"},
{Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"},
{Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"},
{Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"},
{Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"},
{Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"},
{Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"},
{Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"},
{Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"},
{Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"},
{Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"},
{Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"},
{Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"},
{Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"},
{Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"},
{Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"},
{Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"},
{Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"},
{Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"},
{Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"},
{Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"},
{Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"},
{Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"},
{Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"},
{Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"},
{Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"},
{Alpha3bCode: "nob", Alpha2Code: "nb", English: "Bokmål, Norwegian; Norwegian Bokmål"},
{Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"},
{Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"},
{Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"},
{Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"},
{Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"},
{Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"},
{Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"},
{Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"},
{Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"},
{Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"},
{Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"},
{Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"},
{Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"},
{Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"},
{Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"},
{Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"},
{Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"},
{Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"},
{Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"},
{Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"},
{Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"},
{Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"},
{Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"},
{Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"},
{Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"},
{Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"},
{Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"},
{Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"},
{Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"},
{Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"},
{Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"},
{Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"},
{Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"},
{Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"},
{Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"},
{Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"},
{Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"},
{Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"},
{Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"},
{Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"},
{Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"},
{Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"},
{Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"},
{Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"},
{Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"},
{Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"},
{Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"},
{Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"},
{Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"},
{Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"},
{Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"},
{Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"},
{Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"},
{Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"},
{Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"},
{Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"},
{Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"},
{Alpha3bCode: "vol", Alpha2Code: "vo", English: "Volapük"},
{Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"},
{Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"},
{Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"},
{Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"},
{Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"},
{Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"},
{Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"},
{Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"},
}

View File

@@ -1,264 +0,0 @@
package govalidator
import (
"errors"
"fmt"
"html"
"math"
"path"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
// Contains check if the string contains the substring.
func Contains(str, substring string) bool {
return strings.Contains(str, substring)
}
// Matches check if string matches the pattern (pattern is regular expression)
// In case of error return false
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
// LeftTrim trim characters from the left-side of the input.
// If second argument is empty, it's will be remove leading spaces.
func LeftTrim(str, chars string) string {
if chars == "" {
return strings.TrimLeftFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("^[" + chars + "]+")
return r.ReplaceAllString(str, "")
}
// RightTrim trim characters from the right-side of the input.
// If second argument is empty, it's will be remove spaces.
func RightTrim(str, chars string) string {
if chars == "" {
return strings.TrimRightFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("[" + chars + "]+$")
return r.ReplaceAllString(str, "")
}
// Trim trim characters from both sides of the input.
// If second argument is empty, it's will be remove spaces.
func Trim(str, chars string) string {
return LeftTrim(RightTrim(str, chars), chars)
}
// WhiteList remove characters that do not appear in the whitelist.
func WhiteList(str, chars string) string {
pattern := "[^" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// BlackList remove characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
func StripLow(str string, keepNewLines bool) string {
chars := ""
if keepNewLines {
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
} else {
chars = "\x00-\x1F\x7F"
}
return BlackList(str, chars)
}
// ReplacePattern replace regular expression pattern in string
func ReplacePattern(str, pattern, replace string) string {
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, replace)
}
// Escape replace <, >, & and " with HTML entities.
var Escape = html.EscapeString
func addSegment(inrune, segment []rune) []rune {
if len(segment) == 0 {
return inrune
}
if len(inrune) != 0 {
inrune = append(inrune, '_')
}
inrune = append(inrune, segment...)
return inrune
}
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
// Ex.: my_func => MyFunc
func UnderscoreToCamelCase(s string) string {
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
}
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
// Ex.: MyFunc => my_func
func CamelCaseToUnderscore(str string) string {
var output []rune
var segment []rune
for _, r := range str {
// not treat number as separate segment
if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) {
output = addSegment(output, segment)
segment = nil
}
segment = append(segment, unicode.ToLower(r))
}
output = addSegment(output, segment)
return string(output)
}
// Reverse return reversed string
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
// GetLines split string by "\n" and return array of lines
func GetLines(s string) []string {
return strings.Split(s, "\n")
}
// GetLine return specified line of multiline string
func GetLine(s string, index int) (string, error) {
lines := GetLines(s)
if index < 0 || index >= len(lines) {
return "", errors.New("line index out of bounds")
}
return lines[index], nil
}
// RemoveTags remove all tags from HTML string
func RemoveTags(s string) string {
return ReplacePattern(s, "<[^>]*>", "")
}
// SafeFileName return safe string that can be used in file names
func SafeFileName(str string) string {
name := strings.ToLower(str)
name = path.Clean(path.Base(name))
name = strings.Trim(name, " ")
separators, err := regexp.Compile(`[ &_=+:]`)
if err == nil {
name = separators.ReplaceAllString(name, "-")
}
legal, err := regexp.Compile(`[^[:alnum:]-.]`)
if err == nil {
name = legal.ReplaceAllString(name, "")
}
for strings.Contains(name, "--") {
name = strings.Replace(name, "--", "-", -1)
}
return name
}
// NormalizeEmail canonicalize an email address.
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
// normalized to @gmail.com.
func NormalizeEmail(str string) (string, error) {
if !IsEmail(str) {
return "", fmt.Errorf("%s is not an email", str)
}
parts := strings.Split(str, "@")
parts[0] = strings.ToLower(parts[0])
parts[1] = strings.ToLower(parts[1])
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
parts[1] = "gmail.com"
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
}
return strings.Join(parts, "@"), nil
}
// Truncate a string to the closest length without breaking words.
func Truncate(str string, length int, ending string) string {
var aftstr, befstr string
if len(str) > length {
words := strings.Fields(str)
before, present := 0, 0
for i := range words {
befstr = aftstr
before = present
aftstr = aftstr + words[i] + " "
present = len(aftstr)
if present > length && i != 0 {
if (length - before) < (present - length) {
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
}
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
}
}
}
return str
}
// PadLeft pad left side of string if size of string is less then indicated pad length
func PadLeft(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, false)
}
// PadRight pad right side of string if size of string is less then indicated pad length
func PadRight(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, false, true)
}
// PadBoth pad sides of string if size of string is less then indicated pad length
func PadBoth(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, true)
}
// PadString either left, right or both sides, not the padding string can be unicode and more then one
// character
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
// When padded length is less then the current string size
if padLen < utf8.RuneCountInString(str) {
return str
}
padLen -= utf8.RuneCountInString(str)
targetLen := padLen
targetLenLeft := targetLen
targetLenRight := targetLen
if padLeft && padRight {
targetLenLeft = padLen / 2
targetLenRight = padLen - targetLenLeft
}
strToRepeatLen := utf8.RuneCountInString(padStr)
repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen)))
repeatedString := strings.Repeat(padStr, repeatTimes)
leftSide := ""
if padLeft {
leftSide = repeatedString[0:targetLenLeft]
}
rightSide := ""
if padRight {
rightSide = repeatedString[0:targetLenRight]
}
return leftSide + str + rightSide
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,182 +0,0 @@
Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Schlesinger <aschlesinger@deis.com>
Aaron Vinson <avinson.public@gmail.com>
Adam Duke <adam.v.duke@gmail.com>
Adam Enger <adamenger@gmail.com>
Adrian Mouat <adrian.mouat@gmail.com>
Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
Alex Chan <alex.chan@metaswitch.com>
Alex Elman <aelman@indeed.com>
Alexey Gladkov <gladkov.alexey@gmail.com>
allencloud <allen.sun@daocloud.io>
amitshukla <ashukla73@hotmail.com>
Amy Lindburg <amy.lindburg@docker.com>
Andrew Hsu <andrewhsu@acm.org>
Andrew Meredith <andymeredith@gmail.com>
Andrew T Nguyen <andrew.nguyen@docker.com>
Andrey Kostov <kostov.andrey@gmail.com>
Andy Goldstein <agoldste@redhat.com>
Anis Elleuch <vadmeste@gmail.com>
Antonio Mercado <amercado@thinknode.com>
Antonio Murdaca <runcom@redhat.com>
Anton Tiurin <noxiouz@yandex.ru>
Anusha Ragunathan <anusha@docker.com>
a-palchikov <deemok@gmail.com>
Arien Holthuizen <aholthuizen@schubergphilis.com>
Arnaud Porterie <arnaud.porterie@docker.com>
Arthur Baars <arthur@semmle.com>
Asuka Suzuki <hello@tanksuzuki.com>
Avi Miller <avi.miller@oracle.com>
Ayose Cazorla <ayosec@gmail.com>
BadZen <dave.trombley@gmail.com>
Ben Bodenmiller <bbodenmiller@hotmail.com>
Ben Firshman <ben@firshman.co.uk>
bin liu <liubin0329@gmail.com>
Brian Bland <brian.bland@docker.com>
burnettk <burnettk@gmail.com>
Carson A <ca@carsonoid.net>
Cezar Sa Espinola <cezarsa@gmail.com>
Charles Smith <charles.smith@docker.com>
Chris Dillon <squarism@gmail.com>
cuiwei13 <cuiwei13@pku.edu.cn>
cyli <cyli@twistedmatrix.com>
Daisuke Fujita <dtanshi45@gmail.com>
Daniel Huhn <daniel@danielhuhn.de>
Darren Shepherd <darren@rancher.com>
Dave Trombley <dave.trombley@gmail.com>
Dave Tucker <dt@docker.com>
David Lawrence <david.lawrence@docker.com>
davidli <wenquan.li@hp.com>
David Verhasselt <david@crowdway.com>
David Xia <dxia@spotify.com>
Dejan Golja <dejan@golja.org>
Derek McGowan <derek@mcgstyle.net>
Diogo Mónica <diogo.monica@gmail.com>
DJ Enriquez <dj.enriquez@infospace.com>
Donald Huang <don.hcd@gmail.com>
Doug Davis <dug@us.ibm.com>
Edgar Lee <edgar.lee@docker.com>
Eric Yang <windfarer@gmail.com>
Fabio Berchtold <jamesclonk@jamesclonk.ch>
Fabio Huser <fabio@fh1.ch>
farmerworking <farmerworking@gmail.com>
Felix Yan <felixonmars@archlinux.org>
Florentin Raud <florentin.raud@gmail.com>
Frank Chen <frankchn@gmail.com>
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
gabriell nascimento <gabriell@bluesoft.com.br>
Gleb Schukin <gschukin@ptsecurity.com>
harche <p.harshal@gmail.com>
Henri Gomez <henri.gomez@gmail.com>
Hua Wang <wanghua.humble@gmail.com>
Hu Keping <hukeping@huawei.com>
HuKeping <hukeping@huawei.com>
Ian Babrou <ibobrik@gmail.com>
igayoso <igayoso@gmail.com>
Jack Griffin <jackpg14@gmail.com>
James Findley <jfindley@fastmail.com>
Jason Freidman <jason.freidman@gmail.com>
Jason Heiss <jheiss@aput.net>
Jeff Nickoloff <jeff@allingeek.com>
Jess Frazelle <acidburn@google.com>
Jessie Frazelle <jessie@docker.com>
jhaohai <jhaohai@foxmail.com>
Jianqing Wang <tsing@jianqing.org>
Jihoon Chung <jihoon@gmail.com>
Joao Fernandes <joao.fernandes@docker.com>
John Mulhausen <john@docker.com>
John Starks <jostarks@microsoft.com>
Jonathan Boulle <jonathanboulle@gmail.com>
Jon Johnson <jonjohnson@google.com>
Jon Poler <jonathan.poler@apcera.com>
Jordan Liggitt <jliggitt@redhat.com>
Josh Chorlton <josh.chorlton@docker.com>
Josh Hawn <josh.hawn@docker.com>
Julien Fernandez <julien.fernandez@gmail.com>
Keerthan Mala <kmala@engineyard.com>
Kelsey Hightower <kelsey.hightower@gmail.com>
Kenneth Lim <kennethlimcp@gmail.com>
Kenny Leung <kleung@google.com>
Ke Xu <leonhartx.k@gmail.com>
liuchang0812 <liuchang0812@gmail.com>
Liu Hua <sdu.liu@huawei.com>
Li Yi <denverdino@gmail.com>
Lloyd Ramey <lnr0626@gmail.com>
Louis Kottmann <louis.kottmann@gmail.com>
Luke Carpenter <x@rubynerd.net>
Marcus Martins <marcus@docker.com>
Mary Anthony <mary@docker.com>
Matt Bentley <mbentley@mbentley.net>
Matt Duch <matt@learnmetrics.com>
Matthew Green <greenmr@live.co.uk>
Matt Moore <mattmoor@google.com>
Matt Robenolt <matt@ydekproductions.com>
Michael Prokop <mika@grml.org>
Michal Minar <miminar@redhat.com>
Michal Minář <miminar@redhat.com>
Mike Brown <brownwm@us.ibm.com>
Miquel Sabaté <msabate@suse.com>
Misty Stanley-Jones <misty@docker.com>
Morgan Bauer <mbauer@us.ibm.com>
moxiegirl <mary@docker.com>
Nathan Sullivan <nathan@nightsys.net>
nevermosby <robolwq@qq.com>
Nghia Tran <tcnghia@gmail.com>
Nikita Tarasov <nikita@mygento.ru>
Noah Treuhaft <noah.treuhaft@docker.com>
Nuutti Kotivuori <nuutti.kotivuori@poplatek.fi>
Oilbeater <liumengxinfly@gmail.com>
Olivier Gambier <olivier@docker.com>
Olivier Jacques <olivier.jacques@hp.com>
Omer Cohen <git@omer.io>
Patrick Devine <patrick.devine@docker.com>
Phil Estes <estesp@linux.vnet.ibm.com>
Philip Misiowiec <philip@atlashealth.com>
Pierre-Yves Ritschard <pyr@spootnik.org>
Qiao Anran <qiaoanran@gmail.com>
Randy Barlow <randy@electronsweatshop.com>
Richard Scothern <richard.scothern@docker.com>
Rodolfo Carvalho <rhcarvalho@gmail.com>
Rusty Conover <rusty@luckydinosaur.com>
Sean Boran <Boran@users.noreply.github.com>
Sebastiaan van Stijn <github@gone.nl>
Sebastien Coavoux <s.coavoux@free.fr>
Serge Dubrouski <sergeyfd@gmail.com>
Sharif Nassar <sharif@mrwacky.com>
Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
Shreyas Karnik <karnik.shreyas@gmail.com>
Simon Thulbourn <simon+github@thulbourn.com>
spacexnice <yaoyao.xyy@alibaba-inc.com>
Spencer Rinehart <anubis@overthemonkey.com>
Stan Hu <stanhu@gmail.com>
Stefan Majewsky <stefan.majewsky@sap.com>
Stefan Weil <sw@weilnetz.de>
Stephen J Day <stephen.day@docker.com>
Sungho Moon <sungho.moon@navercorp.com>
Sven Dowideit <SvenDowideit@home.org.au>
Sylvain Baubeau <sbaubeau@redhat.com>
Ted Reed <ted.reed@gmail.com>
tgic <farmer1992@gmail.com>
Thomas Sjögren <konstruktoid@users.noreply.github.com>
Tianon Gravi <admwiggin@gmail.com>
Tibor Vass <teabee89@gmail.com>
Tonis Tiigi <tonistiigi@gmail.com>
Tony Holdstock-Brown <tony@docker.com>
Trevor Pounds <trevor.pounds@gmail.com>
Troels Thomsen <troels@thomsen.io>
Victoria Bialas <victoria.bialas@docker.com>
Victor Vieux <vieux@docker.com>
Vincent Batts <vbatts@redhat.com>
Vincent Demeester <vincent@sbr.pm>
Vincent Giersch <vincent.giersch@ovh.net>
weiyuan.yl <weiyuan.yl@alibaba-inc.com>
W. Trevor King <wking@tremily.us>
xg.song <xg.song@venusource.com>
xiekeyang <xiekeyang@huawei.com>
Yann ROBERT <yann.robert@anantaplex.fr>
yaoyao.xyy <yaoyao.xyy@alibaba-inc.com>
yixi zhang <yixi@memsql.com>
yuexiao-wang <wang.yuexiao@zte.com.cn>
yuzou <zouyu7@huawei.com>
zhouhaibing089 <zhouhaibing089@gmail.com>
姜继忠 <jizhong.jiangjz@alibaba-inc.com>

View File

@@ -1,202 +0,0 @@
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.

View File

@@ -1,257 +0,0 @@
package distribution
import (
"errors"
"fmt"
"io"
"net/http"
"time"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/reference"
)
var (
// ErrBlobExists returned when blob already exists
ErrBlobExists = errors.New("blob exists")
// ErrBlobDigestUnsupported when blob digest is an unsupported version.
ErrBlobDigestUnsupported = errors.New("unsupported blob digest")
// ErrBlobUnknown when blob is not found.
ErrBlobUnknown = errors.New("unknown blob")
// ErrBlobUploadUnknown returned when upload is not found.
ErrBlobUploadUnknown = errors.New("blob upload unknown")
// ErrBlobInvalidLength returned when the blob has an expected length on
// commit, meaning mismatched with the descriptor or an invalid value.
ErrBlobInvalidLength = errors.New("blob invalid length")
)
// ErrBlobInvalidDigest returned when digest check fails.
type ErrBlobInvalidDigest struct {
Digest digest.Digest
Reason error
}
func (err ErrBlobInvalidDigest) Error() string {
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
err.Digest, err.Reason)
}
// ErrBlobMounted returned when a blob is mounted from another repository
// instead of initiating an upload session.
type ErrBlobMounted struct {
From reference.Canonical
Descriptor Descriptor
}
func (err ErrBlobMounted) Error() string {
return fmt.Sprintf("blob mounted from: %v to: %v",
err.From, err.Descriptor)
}
// Descriptor describes targeted content. Used in conjunction with a blob
// store, a descriptor can be used to fetch, store and target any kind of
// blob. The struct also describes the wire protocol format. Fields should
// only be added but never changed.
type Descriptor struct {
// MediaType describe the type of the content. All text based formats are
// encoded as utf-8.
MediaType string `json:"mediaType,omitempty"`
// Size in bytes of content.
Size int64 `json:"size,omitempty"`
// Digest uniquely identifies the content. A byte stream can be verified
// against against this digest.
Digest digest.Digest `json:"digest,omitempty"`
// URLs contains the source URLs of this content.
URLs []string `json:"urls,omitempty"`
// NOTE: Before adding a field here, please ensure that all
// other options have been exhausted. Much of the type relationships
// depend on the simplicity of this type.
}
// Descriptor returns the descriptor, to make it satisfy the Describable
// interface. Note that implementations of Describable are generally objects
// which can be described, not simply descriptors; this exception is in place
// to make it more convenient to pass actual descriptors to functions that
// expect Describable objects.
func (d Descriptor) Descriptor() Descriptor {
return d
}
// BlobStatter makes blob descriptors available by digest. The service may
// provide a descriptor of a different digest if the provided digest is not
// canonical.
type BlobStatter interface {
// Stat provides metadata about a blob identified by the digest. If the
// blob is unknown to the describer, ErrBlobUnknown will be returned.
Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error)
}
// BlobDeleter enables deleting blobs from storage.
type BlobDeleter interface {
Delete(ctx context.Context, dgst digest.Digest) error
}
// BlobEnumerator enables iterating over blobs from storage
type BlobEnumerator interface {
Enumerate(ctx context.Context, ingester func(dgst digest.Digest) error) error
}
// BlobDescriptorService manages metadata about a blob by digest. Most
// implementations will not expose such an interface explicitly. Such mappings
// should be maintained by interacting with the BlobIngester. Hence, this is
// left off of BlobService and BlobStore.
type BlobDescriptorService interface {
BlobStatter
// SetDescriptor assigns the descriptor to the digest. The provided digest and
// the digest in the descriptor must map to identical content but they may
// differ on their algorithm. The descriptor must have the canonical
// digest of the content and the digest algorithm must match the
// annotators canonical algorithm.
//
// Such a facility can be used to map blobs between digest domains, with
// the restriction that the algorithm of the descriptor must match the
// canonical algorithm (ie sha256) of the annotator.
SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error
// Clear enables descriptors to be unlinked
Clear(ctx context.Context, dgst digest.Digest) error
}
// BlobDescriptorServiceFactory creates middleware for BlobDescriptorService.
type BlobDescriptorServiceFactory interface {
BlobAccessController(svc BlobDescriptorService) BlobDescriptorService
}
// ReadSeekCloser is the primary reader type for blob data, combining
// io.ReadSeeker with io.Closer.
type ReadSeekCloser interface {
io.ReadSeeker
io.Closer
}
// BlobProvider describes operations for getting blob data.
type BlobProvider interface {
// Get returns the entire blob identified by digest along with the descriptor.
Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
// Open provides a ReadSeekCloser to the blob identified by the provided
// descriptor. If the blob is not known to the service, an error will be
// returned.
Open(ctx context.Context, dgst digest.Digest) (ReadSeekCloser, error)
}
// BlobServer can serve blobs via http.
type BlobServer interface {
// ServeBlob attempts to serve the blob, identifed by dgst, via http. The
// service may decide to redirect the client elsewhere or serve the data
// directly.
//
// This handler only issues successful responses, such as 2xx or 3xx,
// meaning it serves data or issues a redirect. If the blob is not
// available, an error will be returned and the caller may still issue a
// response.
//
// The implementation may serve the same blob from a different digest
// domain. The appropriate headers will be set for the blob, unless they
// have already been set by the caller.
ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error
}
// BlobIngester ingests blob data.
type BlobIngester interface {
// Put inserts the content p into the blob service, returning a descriptor
// or an error.
Put(ctx context.Context, mediaType string, p []byte) (Descriptor, error)
// Create allocates a new blob writer to add a blob to this service. The
// returned handle can be written to and later resumed using an opaque
// identifier. With this approach, one can Close and Resume a BlobWriter
// multiple times until the BlobWriter is committed or cancelled.
Create(ctx context.Context, options ...BlobCreateOption) (BlobWriter, error)
// Resume attempts to resume a write to a blob, identified by an id.
Resume(ctx context.Context, id string) (BlobWriter, error)
}
// BlobCreateOption is a general extensible function argument for blob creation
// methods. A BlobIngester may choose to honor any or none of the given
// BlobCreateOptions, which can be specific to the implementation of the
// BlobIngester receiving them.
// TODO (brianbland): unify this with ManifestServiceOption in the future
type BlobCreateOption interface {
Apply(interface{}) error
}
// CreateOptions is a collection of blob creation modifiers relevant to general
// blob storage intended to be configured by the BlobCreateOption.Apply method.
type CreateOptions struct {
Mount struct {
ShouldMount bool
From reference.Canonical
// Stat allows to pass precalculated descriptor to link and return.
// Blob access check will be skipped if set.
Stat *Descriptor
}
}
// BlobWriter provides a handle for inserting data into a blob store.
// Instances should be obtained from BlobWriteService.Writer and
// BlobWriteService.Resume. If supported by the store, a writer can be
// recovered with the id.
type BlobWriter interface {
io.WriteCloser
io.ReaderFrom
// Size returns the number of bytes written to this blob.
Size() int64
// ID returns the identifier for this writer. The ID can be used with the
// Blob service to later resume the write.
ID() string
// StartedAt returns the time this blob write was started.
StartedAt() time.Time
// Commit completes the blob writer process. The content is verified
// against the provided provisional descriptor, which may result in an
// error. Depending on the implementation, written data may be validated
// against the provisional descriptor fields. If MediaType is not present,
// the implementation may reject the commit or assign "application/octet-
// stream" to the blob. The returned descriptor may have a different
// digest depending on the blob store, referred to as the canonical
// descriptor.
Commit(ctx context.Context, provisional Descriptor) (canonical Descriptor, err error)
// Cancel ends the blob write without storing any data and frees any
// associated resources. Any data written thus far will be lost. Cancel
// implementations should allow multiple calls even after a commit that
// result in a no-op. This allows use of Cancel in a defer statement,
// increasing the assurance that it is correctly called.
Cancel(ctx context.Context) error
}
// BlobService combines the operations to access, read and write blobs. This
// can be used to describe remote blob services.
type BlobService interface {
BlobStatter
BlobProvider
BlobIngester
}
// BlobStore represent the entire suite of blob related operations. Such an
// implementation can access, read, write, delete and serve blobs.
type BlobStore interface {
BlobService
BlobServer
BlobDeleter
}

View File

@@ -1,85 +0,0 @@
package context
import (
"sync"
"github.com/docker/distribution/uuid"
"golang.org/x/net/context"
)
// Context is a copy of Context from the golang.org/x/net/context package.
type Context interface {
context.Context
}
// instanceContext is a context that provides only an instance id. It is
// provided as the main background context.
type instanceContext struct {
Context
id string // id of context, logged as "instance.id"
once sync.Once // once protect generation of the id
}
func (ic *instanceContext) Value(key interface{}) interface{} {
if key == "instance.id" {
ic.once.Do(func() {
// We want to lazy initialize the UUID such that we don't
// call a random generator from the package initialization
// code. For various reasons random could not be available
// https://github.com/docker/distribution/issues/782
ic.id = uuid.Generate().String()
})
return ic.id
}
return ic.Context.Value(key)
}
var background = &instanceContext{
Context: context.Background(),
}
// Background returns a non-nil, empty Context. The background context
// provides a single key, "instance.id" that is globally unique to the
// process.
func Background() Context {
return background
}
// WithValue returns a copy of parent in which the value associated with key is
// val. Use context Values only for request-scoped data that transits processes
// and APIs, not for passing optional parameters to functions.
func WithValue(parent Context, key, val interface{}) Context {
return context.WithValue(parent, key, val)
}
// stringMapContext is a simple context implementation that checks a map for a
// key, falling back to a parent if not present.
type stringMapContext struct {
context.Context
m map[string]interface{}
}
// WithValues returns a context that proxies lookups through a map. Only
// supports string keys.
func WithValues(ctx context.Context, m map[string]interface{}) context.Context {
mo := make(map[string]interface{}, len(m)) // make our own copy.
for k, v := range m {
mo[k] = v
}
return stringMapContext{
Context: ctx,
m: mo,
}
}
func (smc stringMapContext) Value(key interface{}) interface{} {
if ks, ok := key.(string); ok {
if v, ok := smc.m[ks]; ok {
return v
}
}
return smc.Context.Value(key)
}

View File

@@ -1,89 +0,0 @@
// Package context provides several utilities for working with
// golang.org/x/net/context in http requests. Primarily, the focus is on
// logging relevant request information but this package is not limited to
// that purpose.
//
// The easiest way to get started is to get the background context:
//
// ctx := context.Background()
//
// The returned context should be passed around your application and be the
// root of all other context instances. If the application has a version, this
// line should be called before anything else:
//
// ctx := context.WithVersion(context.Background(), version)
//
// The above will store the version in the context and will be available to
// the logger.
//
// Logging
//
// The most useful aspect of this package is GetLogger. This function takes
// any context.Context interface and returns the current logger from the
// context. Canonical usage looks like this:
//
// GetLogger(ctx).Infof("something interesting happened")
//
// GetLogger also takes optional key arguments. The keys will be looked up in
// the context and reported with the logger. The following example would
// return a logger that prints the version with each log message:
//
// ctx := context.Context(context.Background(), "version", version)
// GetLogger(ctx, "version").Infof("this log message has a version field")
//
// The above would print out a log message like this:
//
// INFO[0000] this log message has a version field version=v2.0.0-alpha.2.m
//
// When used with WithLogger, we gain the ability to decorate the context with
// loggers that have information from disparate parts of the call stack.
// Following from the version example, we can build a new context with the
// configured logger such that we always print the version field:
//
// ctx = WithLogger(ctx, GetLogger(ctx, "version"))
//
// Since the logger has been pushed to the context, we can now get the version
// field for free with our log messages. Future calls to GetLogger on the new
// context will have the version field:
//
// GetLogger(ctx).Infof("this log message has a version field")
//
// This becomes more powerful when we start stacking loggers. Let's say we
// have the version logger from above but also want a request id. Using the
// context above, in our request scoped function, we place another logger in
// the context:
//
// ctx = context.WithValue(ctx, "http.request.id", "unique id") // called when building request context
// ctx = WithLogger(ctx, GetLogger(ctx, "http.request.id"))
//
// When GetLogger is called on the new context, "http.request.id" will be
// included as a logger field, along with the original "version" field:
//
// INFO[0000] this log message has a version field http.request.id=unique id version=v2.0.0-alpha.2.m
//
// Note that this only affects the new context, the previous context, with the
// version field, can be used independently. Put another way, the new logger,
// added to the request context, is unique to that context and can have
// request scoped varaibles.
//
// HTTP Requests
//
// This package also contains several methods for working with http requests.
// The concepts are very similar to those described above. We simply place the
// request in the context using WithRequest. This makes the request variables
// available. GetRequestLogger can then be called to get request specific
// variables in a log line:
//
// ctx = WithRequest(ctx, req)
// GetRequestLogger(ctx).Infof("request variables")
//
// Like above, if we want to include the request data in all log messages in
// the context, we push the logger to a new context and use that one:
//
// ctx = WithLogger(ctx, GetRequestLogger(ctx))
//
// The concept is fairly powerful and ensures that calls throughout the stack
// can be traced in log messages. Using the fields like "http.request.id", one
// can analyze call flow for a particular request with a simple grep of the
// logs.
package context

View File

@@ -1,366 +0,0 @@
package context
import (
"errors"
"net"
"net/http"
"strings"
"sync"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/uuid"
"github.com/gorilla/mux"
)
// Common errors used with this package.
var (
ErrNoRequestContext = errors.New("no http request in context")
ErrNoResponseWriterContext = errors.New("no http response in context")
)
func parseIP(ipStr string) net.IP {
ip := net.ParseIP(ipStr)
if ip == nil {
log.Warnf("invalid remote IP address: %q", ipStr)
}
return ip
}
// RemoteAddr extracts the remote address of the request, taking into
// account proxy headers.
func RemoteAddr(r *http.Request) string {
if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
proxies := strings.Split(prior, ",")
if len(proxies) > 0 {
remoteAddr := strings.Trim(proxies[0], " ")
if parseIP(remoteAddr) != nil {
return remoteAddr
}
}
}
// X-Real-Ip is less supported, but worth checking in the
// absence of X-Forwarded-For
if realIP := r.Header.Get("X-Real-Ip"); realIP != "" {
if parseIP(realIP) != nil {
return realIP
}
}
return r.RemoteAddr
}
// RemoteIP extracts the remote IP of the request, taking into
// account proxy headers.
func RemoteIP(r *http.Request) string {
addr := RemoteAddr(r)
// Try parsing it as "IP:port"
if ip, _, err := net.SplitHostPort(addr); err == nil {
return ip
}
return addr
}
// WithRequest places the request on the context. The context of the request
// is assigned a unique id, available at "http.request.id". The request itself
// is available at "http.request". Other common attributes are available under
// the prefix "http.request.". If a request is already present on the context,
// this method will panic.
func WithRequest(ctx Context, r *http.Request) Context {
if ctx.Value("http.request") != nil {
// NOTE(stevvooe): This needs to be considered a programming error. It
// is unlikely that we'd want to have more than one request in
// context.
panic("only one request per context")
}
return &httpRequestContext{
Context: ctx,
startedAt: time.Now(),
id: uuid.Generate().String(),
r: r,
}
}
// GetRequest returns the http request in the given context. Returns
// ErrNoRequestContext if the context does not have an http request associated
// with it.
func GetRequest(ctx Context) (*http.Request, error) {
if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok {
return r, nil
}
return nil, ErrNoRequestContext
}
// GetRequestID attempts to resolve the current request id, if possible. An
// error is return if it is not available on the context.
func GetRequestID(ctx Context) string {
return GetStringValue(ctx, "http.request.id")
}
// WithResponseWriter returns a new context and response writer that makes
// interesting response statistics available within the context.
func WithResponseWriter(ctx Context, w http.ResponseWriter) (Context, http.ResponseWriter) {
if closeNotifier, ok := w.(http.CloseNotifier); ok {
irwCN := &instrumentedResponseWriterCN{
instrumentedResponseWriter: instrumentedResponseWriter{
ResponseWriter: w,
Context: ctx,
},
CloseNotifier: closeNotifier,
}
return irwCN, irwCN
}
irw := instrumentedResponseWriter{
ResponseWriter: w,
Context: ctx,
}
return &irw, &irw
}
// GetResponseWriter returns the http.ResponseWriter from the provided
// context. If not present, ErrNoResponseWriterContext is returned. The
// returned instance provides instrumentation in the context.
func GetResponseWriter(ctx Context) (http.ResponseWriter, error) {
v := ctx.Value("http.response")
rw, ok := v.(http.ResponseWriter)
if !ok || rw == nil {
return nil, ErrNoResponseWriterContext
}
return rw, nil
}
// getVarsFromRequest let's us change request vars implementation for testing
// and maybe future changes.
var getVarsFromRequest = mux.Vars
// WithVars extracts gorilla/mux vars and makes them available on the returned
// context. Variables are available at keys with the prefix "vars.". For
// example, if looking for the variable "name", it can be accessed as
// "vars.name". Implementations that are accessing values need not know that
// the underlying context is implemented with gorilla/mux vars.
func WithVars(ctx Context, r *http.Request) Context {
return &muxVarsContext{
Context: ctx,
vars: getVarsFromRequest(r),
}
}
// GetRequestLogger returns a logger that contains fields from the request in
// the current context. If the request is not available in the context, no
// fields will display. Request loggers can safely be pushed onto the context.
func GetRequestLogger(ctx Context) Logger {
return GetLogger(ctx,
"http.request.id",
"http.request.method",
"http.request.host",
"http.request.uri",
"http.request.referer",
"http.request.useragent",
"http.request.remoteaddr",
"http.request.contenttype")
}
// GetResponseLogger reads the current response stats and builds a logger.
// Because the values are read at call time, pushing a logger returned from
// this function on the context will lead to missing or invalid data. Only
// call this at the end of a request, after the response has been written.
func GetResponseLogger(ctx Context) Logger {
l := getLogrusLogger(ctx,
"http.response.written",
"http.response.status",
"http.response.contenttype")
duration := Since(ctx, "http.request.startedat")
if duration > 0 {
l = l.WithField("http.response.duration", duration.String())
}
return l
}
// httpRequestContext makes information about a request available to context.
type httpRequestContext struct {
Context
startedAt time.Time
id string
r *http.Request
}
// Value returns a keyed element of the request for use in the context. To get
// the request itself, query "request". For other components, access them as
// "request.<component>". For example, r.RequestURI
func (ctx *httpRequestContext) Value(key interface{}) interface{} {
if keyStr, ok := key.(string); ok {
if keyStr == "http.request" {
return ctx.r
}
if !strings.HasPrefix(keyStr, "http.request.") {
goto fallback
}
parts := strings.Split(keyStr, ".")
if len(parts) != 3 {
goto fallback
}
switch parts[2] {
case "uri":
return ctx.r.RequestURI
case "remoteaddr":
return RemoteAddr(ctx.r)
case "method":
return ctx.r.Method
case "host":
return ctx.r.Host
case "referer":
referer := ctx.r.Referer()
if referer != "" {
return referer
}
case "useragent":
return ctx.r.UserAgent()
case "id":
return ctx.id
case "startedat":
return ctx.startedAt
case "contenttype":
ct := ctx.r.Header.Get("Content-Type")
if ct != "" {
return ct
}
}
}
fallback:
return ctx.Context.Value(key)
}
type muxVarsContext struct {
Context
vars map[string]string
}
func (ctx *muxVarsContext) Value(key interface{}) interface{} {
if keyStr, ok := key.(string); ok {
if keyStr == "vars" {
return ctx.vars
}
if strings.HasPrefix(keyStr, "vars.") {
keyStr = strings.TrimPrefix(keyStr, "vars.")
}
if v, ok := ctx.vars[keyStr]; ok {
return v
}
}
return ctx.Context.Value(key)
}
// instrumentedResponseWriterCN provides response writer information in a
// context. It implements http.CloseNotifier so that users can detect
// early disconnects.
type instrumentedResponseWriterCN struct {
instrumentedResponseWriter
http.CloseNotifier
}
// instrumentedResponseWriter provides response writer information in a
// context. This variant is only used in the case where CloseNotifier is not
// implemented by the parent ResponseWriter.
type instrumentedResponseWriter struct {
http.ResponseWriter
Context
mu sync.Mutex
status int
written int64
}
func (irw *instrumentedResponseWriter) Write(p []byte) (n int, err error) {
n, err = irw.ResponseWriter.Write(p)
irw.mu.Lock()
irw.written += int64(n)
// Guess the likely status if not set.
if irw.status == 0 {
irw.status = http.StatusOK
}
irw.mu.Unlock()
return
}
func (irw *instrumentedResponseWriter) WriteHeader(status int) {
irw.ResponseWriter.WriteHeader(status)
irw.mu.Lock()
irw.status = status
irw.mu.Unlock()
}
func (irw *instrumentedResponseWriter) Flush() {
if flusher, ok := irw.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
if keyStr, ok := key.(string); ok {
if keyStr == "http.response" {
return irw
}
if !strings.HasPrefix(keyStr, "http.response.") {
goto fallback
}
parts := strings.Split(keyStr, ".")
if len(parts) != 3 {
goto fallback
}
irw.mu.Lock()
defer irw.mu.Unlock()
switch parts[2] {
case "written":
return irw.written
case "status":
return irw.status
case "contenttype":
contentType := irw.Header().Get("Content-Type")
if contentType != "" {
return contentType
}
}
}
fallback:
return irw.Context.Value(key)
}
func (irw *instrumentedResponseWriterCN) Value(key interface{}) interface{} {
if keyStr, ok := key.(string); ok {
if keyStr == "http.response" {
return irw
}
}
return irw.instrumentedResponseWriter.Value(key)
}

View File

@@ -1,116 +0,0 @@
package context
import (
"fmt"
"github.com/Sirupsen/logrus"
"runtime"
)
// Logger provides a leveled-logging interface.
type Logger interface {
// standard logger methods
Print(args ...interface{})
Printf(format string, args ...interface{})
Println(args ...interface{})
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Fatalln(args ...interface{})
Panic(args ...interface{})
Panicf(format string, args ...interface{})
Panicln(args ...interface{})
// Leveled methods, from logrus
Debug(args ...interface{})
Debugf(format string, args ...interface{})
Debugln(args ...interface{})
Error(args ...interface{})
Errorf(format string, args ...interface{})
Errorln(args ...interface{})
Info(args ...interface{})
Infof(format string, args ...interface{})
Infoln(args ...interface{})
Warn(args ...interface{})
Warnf(format string, args ...interface{})
Warnln(args ...interface{})
}
// WithLogger creates a new context with provided logger.
func WithLogger(ctx Context, logger Logger) Context {
return WithValue(ctx, "logger", logger)
}
// GetLoggerWithField returns a logger instance with the specified field key
// and value without affecting the context. Extra specified keys will be
// resolved from the context.
func GetLoggerWithField(ctx Context, key, value interface{}, keys ...interface{}) Logger {
return getLogrusLogger(ctx, keys...).WithField(fmt.Sprint(key), value)
}
// GetLoggerWithFields returns a logger instance with the specified fields
// without affecting the context. Extra specified keys will be resolved from
// the context.
func GetLoggerWithFields(ctx Context, fields map[interface{}]interface{}, keys ...interface{}) Logger {
// must convert from interface{} -> interface{} to string -> interface{} for logrus.
lfields := make(logrus.Fields, len(fields))
for key, value := range fields {
lfields[fmt.Sprint(key)] = value
}
return getLogrusLogger(ctx, keys...).WithFields(lfields)
}
// GetLogger returns the logger from the current context, if present. If one
// or more keys are provided, they will be resolved on the context and
// included in the logger. While context.Value takes an interface, any key
// argument passed to GetLogger will be passed to fmt.Sprint when expanded as
// a logging key field. If context keys are integer constants, for example,
// its recommended that a String method is implemented.
func GetLogger(ctx Context, keys ...interface{}) Logger {
return getLogrusLogger(ctx, keys...)
}
// GetLogrusLogger returns the logrus logger for the context. If one more keys
// are provided, they will be resolved on the context and included in the
// logger. Only use this function if specific logrus functionality is
// required.
func getLogrusLogger(ctx Context, keys ...interface{}) *logrus.Entry {
var logger *logrus.Entry
// Get a logger, if it is present.
loggerInterface := ctx.Value("logger")
if loggerInterface != nil {
if lgr, ok := loggerInterface.(*logrus.Entry); ok {
logger = lgr
}
}
if logger == nil {
fields := logrus.Fields{}
// Fill in the instance id, if we have it.
instanceID := ctx.Value("instance.id")
if instanceID != nil {
fields["instance.id"] = instanceID
}
fields["go.version"] = runtime.Version()
// If no logger is found, just return the standard logger.
logger = logrus.StandardLogger().WithFields(fields)
}
fields := logrus.Fields{}
for _, key := range keys {
v := ctx.Value(key)
if v != nil {
fields[fmt.Sprint(key)] = v
}
}
return logger.WithFields(fields)
}

View File

@@ -1,104 +0,0 @@
package context
import (
"runtime"
"time"
"github.com/docker/distribution/uuid"
)
// WithTrace allocates a traced timing span in a new context. This allows a
// caller to track the time between calling WithTrace and the returned done
// function. When the done function is called, a log message is emitted with a
// "trace.duration" field, corresponding to the elapsed time and a
// "trace.func" field, corresponding to the function that called WithTrace.
//
// The logging keys "trace.id" and "trace.parent.id" are provided to implement
// dapper-like tracing. This function should be complemented with a WithSpan
// method that could be used for tracing distributed RPC calls.
//
// The main benefit of this function is to post-process log messages or
// intercept them in a hook to provide timing data. Trace ids and parent ids
// can also be linked to provide call tracing, if so required.
//
// Here is an example of the usage:
//
// func timedOperation(ctx Context) {
// ctx, done := WithTrace(ctx)
// defer done("this will be the log message")
// // ... function body ...
// }
//
// If the function ran for roughly 1s, such a usage would emit a log message
// as follows:
//
// INFO[0001] this will be the log message trace.duration=1.004575763s trace.func=github.com/docker/distribution/context.traceOperation trace.id=<id> ...
//
// Notice that the function name is automatically resolved, along with the
// package and a trace id is emitted that can be linked with parent ids.
func WithTrace(ctx Context) (Context, func(format string, a ...interface{})) {
if ctx == nil {
ctx = Background()
}
pc, file, line, _ := runtime.Caller(1)
f := runtime.FuncForPC(pc)
ctx = &traced{
Context: ctx,
id: uuid.Generate().String(),
start: time.Now(),
parent: GetStringValue(ctx, "trace.id"),
fnname: f.Name(),
file: file,
line: line,
}
return ctx, func(format string, a ...interface{}) {
GetLogger(ctx,
"trace.duration",
"trace.id",
"trace.parent.id",
"trace.func",
"trace.file",
"trace.line").
Debugf(format, a...)
}
}
// traced represents a context that is traced for function call timing. It
// also provides fast lookup for the various attributes that are available on
// the trace.
type traced struct {
Context
id string
parent string
start time.Time
fnname string
file string
line int
}
func (ts *traced) Value(key interface{}) interface{} {
switch key {
case "trace.start":
return ts.start
case "trace.duration":
return time.Since(ts.start)
case "trace.id":
return ts.id
case "trace.parent.id":
if ts.parent == "" {
return nil // must return nil to signal no parent.
}
return ts.parent
case "trace.func":
return ts.fnname
case "trace.file":
return ts.file
case "trace.line":
return ts.line
}
return ts.Context.Value(key)
}

View File

@@ -1,24 +0,0 @@
package context
import (
"time"
)
// Since looks up key, which should be a time.Time, and returns the duration
// since that time. If the key is not found, the value returned will be zero.
// This is helpful when inferring metrics related to context execution times.
func Since(ctx Context, key interface{}) time.Duration {
if startedAt, ok := ctx.Value(key).(time.Time); ok {
return time.Since(startedAt)
}
return 0
}
// GetStringValue returns a string value from the context. The empty string
// will be returned if not found.
func GetStringValue(ctx Context, key interface{}) (value string) {
if valuev, ok := ctx.Value(key).(string); ok {
value = valuev
}
return value
}

View File

@@ -1,16 +0,0 @@
package context
// WithVersion stores the application version in the context. The new context
// gets a logger to ensure log messages are marked with the application
// version.
func WithVersion(ctx Context, version string) Context {
ctx = WithValue(ctx, "version", version)
// push a new logger onto the stack
return WithLogger(ctx, GetLogger(ctx, "version"))
}
// GetVersion returns the application version from the context. An empty
// string may returned if the version was not set on the context.
func GetVersion(ctx Context) string {
return GetStringValue(ctx, "version")
}

View File

@@ -1,139 +0,0 @@
package digest
import (
"fmt"
"hash"
"io"
"regexp"
"strings"
)
const (
// DigestSha256EmptyTar is the canonical sha256 digest of empty data
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)
// Digest allows simple protection of hex formatted digest strings, prefixed
// by their algorithm. Strings of type Digest have some guarantee of being in
// the correct format and it provides quick access to the components of a
// digest string.
//
// The following is an example of the contents of Digest types:
//
// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
//
// This allows to abstract the digest behind this type and work only in those
// terms.
type Digest string
// NewDigest returns a Digest from alg and a hash.Hash object.
func NewDigest(alg Algorithm, h hash.Hash) Digest {
return NewDigestFromBytes(alg, h.Sum(nil))
}
// NewDigestFromBytes returns a new digest from the byte contents of p.
// Typically, this can come from hash.Hash.Sum(...) or xxx.SumXXX(...)
// functions. This is also useful for rebuilding digests from binary
// serializations.
func NewDigestFromBytes(alg Algorithm, p []byte) Digest {
return Digest(fmt.Sprintf("%s:%x", alg, p))
}
// NewDigestFromHex returns a Digest from alg and a the hex encoded digest.
func NewDigestFromHex(alg, hex string) Digest {
return Digest(fmt.Sprintf("%s:%s", alg, hex))
}
// DigestRegexp matches valid digest types.
var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
// DigestRegexpAnchored matches valid digest types, anchored to the start and end of the match.
var DigestRegexpAnchored = regexp.MustCompile(`^` + DigestRegexp.String() + `$`)
var (
// ErrDigestInvalidFormat returned when digest format invalid.
ErrDigestInvalidFormat = fmt.Errorf("invalid checksum digest format")
// ErrDigestInvalidLength returned when digest has invalid length.
ErrDigestInvalidLength = fmt.Errorf("invalid checksum digest length")
// ErrDigestUnsupported returned when the digest algorithm is unsupported.
ErrDigestUnsupported = fmt.Errorf("unsupported digest algorithm")
)
// ParseDigest parses s and returns the validated digest object. An error will
// be returned if the format is invalid.
func ParseDigest(s string) (Digest, error) {
d := Digest(s)
return d, d.Validate()
}
// FromReader returns the most valid digest for the underlying content using
// the canonical digest algorithm.
func FromReader(rd io.Reader) (Digest, error) {
return Canonical.FromReader(rd)
}
// FromBytes digests the input and returns a Digest.
func FromBytes(p []byte) Digest {
return Canonical.FromBytes(p)
}
// Validate checks that the contents of d is a valid digest, returning an
// error if not.
func (d Digest) Validate() error {
s := string(d)
if !DigestRegexpAnchored.MatchString(s) {
return ErrDigestInvalidFormat
}
i := strings.Index(s, ":")
if i < 0 {
return ErrDigestInvalidFormat
}
// case: "sha256:" with no hex.
if i+1 == len(s) {
return ErrDigestInvalidFormat
}
switch algorithm := Algorithm(s[:i]); algorithm {
case SHA256, SHA384, SHA512:
if algorithm.Size()*2 != len(s[i+1:]) {
return ErrDigestInvalidLength
}
break
default:
return ErrDigestUnsupported
}
return nil
}
// Algorithm returns the algorithm portion of the digest. This will panic if
// the underlying digest is not in a valid format.
func (d Digest) Algorithm() Algorithm {
return Algorithm(d[:d.sepIndex()])
}
// Hex returns the hex digest portion of the digest. This will panic if the
// underlying digest is not in a valid format.
func (d Digest) Hex() string {
return string(d[d.sepIndex()+1:])
}
func (d Digest) String() string {
return string(d)
}
func (d Digest) sepIndex() int {
i := strings.Index(string(d), ":")
if i < 0 {
panic("could not find ':' in digest: " + d)
}
return i
}

View File

@@ -1,155 +0,0 @@
package digest
import (
"crypto"
"fmt"
"hash"
"io"
)
// Algorithm identifies and implementation of a digester by an identifier.
// Note the that this defines both the hash algorithm used and the string
// encoding.
type Algorithm string
// supported digest types
const (
SHA256 Algorithm = "sha256" // sha256 with hex encoding
SHA384 Algorithm = "sha384" // sha384 with hex encoding
SHA512 Algorithm = "sha512" // sha512 with hex encoding
// Canonical is the primary digest algorithm used with the distribution
// project. Other digests may be used but this one is the primary storage
// digest.
Canonical = SHA256
)
var (
// TODO(stevvooe): Follow the pattern of the standard crypto package for
// registration of digests. Effectively, we are a registerable set and
// common symbol access.
// algorithms maps values to hash.Hash implementations. Other algorithms
// may be available but they cannot be calculated by the digest package.
algorithms = map[Algorithm]crypto.Hash{
SHA256: crypto.SHA256,
SHA384: crypto.SHA384,
SHA512: crypto.SHA512,
}
)
// Available returns true if the digest type is available for use. If this
// returns false, New and Hash will return nil.
func (a Algorithm) Available() bool {
h, ok := algorithms[a]
if !ok {
return false
}
// check availability of the hash, as well
return h.Available()
}
func (a Algorithm) String() string {
return string(a)
}
// Size returns number of bytes returned by the hash.
func (a Algorithm) Size() int {
h, ok := algorithms[a]
if !ok {
return 0
}
return h.Size()
}
// Set implemented to allow use of Algorithm as a command line flag.
func (a *Algorithm) Set(value string) error {
if value == "" {
*a = Canonical
} else {
// just do a type conversion, support is queried with Available.
*a = Algorithm(value)
}
return nil
}
// New returns a new digester for the specified algorithm. If the algorithm
// does not have a digester implementation, nil will be returned. This can be
// checked by calling Available before calling New.
func (a Algorithm) New() Digester {
return &digester{
alg: a,
hash: a.Hash(),
}
}
// Hash returns a new hash as used by the algorithm. If not available, the
// method will panic. Check Algorithm.Available() before calling.
func (a Algorithm) Hash() hash.Hash {
if !a.Available() {
// NOTE(stevvooe): A missing hash is usually a programming error that
// must be resolved at compile time. We don't import in the digest
// package to allow users to choose their hash implementation (such as
// when using stevvooe/resumable or a hardware accelerated package).
//
// Applications that may want to resolve the hash at runtime should
// call Algorithm.Available before call Algorithm.Hash().
panic(fmt.Sprintf("%v not available (make sure it is imported)", a))
}
return algorithms[a].New()
}
// FromReader returns the digest of the reader using the algorithm.
func (a Algorithm) FromReader(rd io.Reader) (Digest, error) {
digester := a.New()
if _, err := io.Copy(digester.Hash(), rd); err != nil {
return "", err
}
return digester.Digest(), nil
}
// FromBytes digests the input and returns a Digest.
func (a Algorithm) FromBytes(p []byte) Digest {
digester := a.New()
if _, err := digester.Hash().Write(p); err != nil {
// Writes to a Hash should never fail. None of the existing
// hash implementations in the stdlib or hashes vendored
// here can return errors from Write. Having a panic in this
// condition instead of having FromBytes return an error value
// avoids unnecessary error handling paths in all callers.
panic("write to hash function returned error: " + err.Error())
}
return digester.Digest()
}
// TODO(stevvooe): Allow resolution of verifiers using the digest type and
// this registration system.
// Digester calculates the digest of written data. Writes should go directly
// to the return value of Hash, while calling Digest will return the current
// value of the digest.
type Digester interface {
Hash() hash.Hash // provides direct access to underlying hash instance.
Digest() Digest
}
// digester provides a simple digester definition that embeds a hasher.
type digester struct {
alg Algorithm
hash hash.Hash
}
func (d *digester) Hash() hash.Hash {
return d.hash
}
func (d *digester) Digest() Digest {
return NewDigest(d.alg, d.hash)
}

View File

@@ -1,42 +0,0 @@
// Package digest provides a generalized type to opaquely represent message
// digests and their operations within the registry. The Digest type is
// designed to serve as a flexible identifier in a content-addressable system.
// More importantly, it provides tools and wrappers to work with
// hash.Hash-based digests with little effort.
//
// Basics
//
// The format of a digest is simply a string with two parts, dubbed the
// "algorithm" and the "digest", separated by a colon:
//
// <algorithm>:<digest>
//
// An example of a sha256 digest representation follows:
//
// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
//
// In this case, the string "sha256" is the algorithm and the hex bytes are
// the "digest".
//
// Because the Digest type is simply a string, once a valid Digest is
// obtained, comparisons are cheap, quick and simple to express with the
// standard equality operator.
//
// Verification
//
// The main benefit of using the Digest type is simple verification against a
// given digest. The Verifier interface, modeled after the stdlib hash.Hash
// interface, provides a common write sink for digest verification. After
// writing is complete, calling the Verifier.Verified method will indicate
// whether or not the stream of bytes matches the target digest.
//
// Missing Features
//
// In addition to the above, we intend to add the following features to this
// package:
//
// 1. A Digester type that supports write sink digest calculation.
//
// 2. Suspend and resume of ongoing digest calculations to support efficient digest verification in the registry.
//
package digest

View File

@@ -1,245 +0,0 @@
package digest
import (
"errors"
"sort"
"strings"
"sync"
)
var (
// ErrDigestNotFound is used when a matching digest
// could not be found in a set.
ErrDigestNotFound = errors.New("digest not found")
// ErrDigestAmbiguous is used when multiple digests
// are found in a set. None of the matching digests
// should be considered valid matches.
ErrDigestAmbiguous = errors.New("ambiguous digest string")
)
// Set is used to hold a unique set of digests which
// may be easily referenced by easily referenced by a string
// representation of the digest as well as short representation.
// The uniqueness of the short representation is based on other
// digests in the set. If digests are omitted from this set,
// collisions in a larger set may not be detected, therefore it
// is important to always do short representation lookups on
// the complete set of digests. To mitigate collisions, an
// appropriately long short code should be used.
type Set struct {
mutex sync.RWMutex
entries digestEntries
}
// NewSet creates an empty set of digests
// which may have digests added.
func NewSet() *Set {
return &Set{
entries: digestEntries{},
}
}
// checkShortMatch checks whether two digests match as either whole
// values or short values. This function does not test equality,
// rather whether the second value could match against the first
// value.
func checkShortMatch(alg Algorithm, hex, shortAlg, shortHex string) bool {
if len(hex) == len(shortHex) {
if hex != shortHex {
return false
}
if len(shortAlg) > 0 && string(alg) != shortAlg {
return false
}
} else if !strings.HasPrefix(hex, shortHex) {
return false
} else if len(shortAlg) > 0 && string(alg) != shortAlg {
return false
}
return true
}
// Lookup looks for a digest matching the given string representation.
// If no digests could be found ErrDigestNotFound will be returned
// with an empty digest value. If multiple matches are found
// ErrDigestAmbiguous will be returned with an empty digest value.
func (dst *Set) Lookup(d string) (Digest, error) {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
if len(dst.entries) == 0 {
return "", ErrDigestNotFound
}
var (
searchFunc func(int) bool
alg Algorithm
hex string
)
dgst, err := ParseDigest(d)
if err == ErrDigestInvalidFormat {
hex = d
searchFunc = func(i int) bool {
return dst.entries[i].val >= d
}
} else {
hex = dgst.Hex()
alg = dgst.Algorithm()
searchFunc = func(i int) bool {
if dst.entries[i].val == hex {
return dst.entries[i].alg >= alg
}
return dst.entries[i].val >= hex
}
}
idx := sort.Search(len(dst.entries), searchFunc)
if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
return "", ErrDigestNotFound
}
if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
return dst.entries[idx].digest, nil
}
if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
return "", ErrDigestAmbiguous
}
return dst.entries[idx].digest, nil
}
// Add adds the given digest to the set. An error will be returned
// if the given digest is invalid. If the digest already exists in the
// set, this operation will be a no-op.
func (dst *Set) Add(d Digest) error {
if err := d.Validate(); err != nil {
return err
}
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val {
return dst.entries[i].alg >= entry.alg
}
return dst.entries[i].val >= entry.val
}
idx := sort.Search(len(dst.entries), searchFunc)
if idx == len(dst.entries) {
dst.entries = append(dst.entries, entry)
return nil
} else if dst.entries[idx].digest == d {
return nil
}
entries := append(dst.entries, nil)
copy(entries[idx+1:], entries[idx:len(entries)-1])
entries[idx] = entry
dst.entries = entries
return nil
}
// Remove removes the given digest from the set. An err will be
// returned if the given digest is invalid. If the digest does
// not exist in the set, this operation will be a no-op.
func (dst *Set) Remove(d Digest) error {
if err := d.Validate(); err != nil {
return err
}
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val {
return dst.entries[i].alg >= entry.alg
}
return dst.entries[i].val >= entry.val
}
idx := sort.Search(len(dst.entries), searchFunc)
// Not found if idx is after or value at idx is not digest
if idx == len(dst.entries) || dst.entries[idx].digest != d {
return nil
}
entries := dst.entries
copy(entries[idx:], entries[idx+1:])
entries = entries[:len(entries)-1]
dst.entries = entries
return nil
}
// All returns all the digests in the set
func (dst *Set) All() []Digest {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
retValues := make([]Digest, len(dst.entries))
for i := range dst.entries {
retValues[i] = dst.entries[i].digest
}
return retValues
}
// ShortCodeTable returns a map of Digest to unique short codes. The
// length represents the minimum value, the maximum length may be the
// entire value of digest if uniqueness cannot be achieved without the
// full value. This function will attempt to make short codes as short
// as possible to be unique.
func ShortCodeTable(dst *Set, length int) map[Digest]string {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
m := make(map[Digest]string, len(dst.entries))
l := length
resetIdx := 0
for i := 0; i < len(dst.entries); i++ {
var short string
extended := true
for extended {
extended = false
if len(dst.entries[i].val) <= l {
short = dst.entries[i].digest.String()
} else {
short = dst.entries[i].val[:l]
for j := i + 1; j < len(dst.entries); j++ {
if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
if j > resetIdx {
resetIdx = j
}
extended = true
} else {
break
}
}
if extended {
l++
}
}
}
m[dst.entries[i].digest] = short
if i >= resetIdx {
l = length
}
}
return m
}
type digestEntry struct {
alg Algorithm
val string
digest Digest
}
type digestEntries []*digestEntry
func (d digestEntries) Len() int {
return len(d)
}
func (d digestEntries) Less(i, j int) bool {
if d[i].val != d[j].val {
return d[i].val < d[j].val
}
return d[i].alg < d[j].alg
}
func (d digestEntries) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}

View File

@@ -1,44 +0,0 @@
package digest
import (
"hash"
"io"
)
// Verifier presents a general verification interface to be used with message
// digests and other byte stream verifications. Users instantiate a Verifier
// from one of the various methods, write the data under test to it then check
// the result with the Verified method.
type Verifier interface {
io.Writer
// Verified will return true if the content written to Verifier matches
// the digest.
Verified() bool
}
// NewDigestVerifier returns a verifier that compares the written bytes
// against a passed in digest.
func NewDigestVerifier(d Digest) (Verifier, error) {
if err := d.Validate(); err != nil {
return nil, err
}
return hashVerifier{
hash: d.Algorithm().Hash(),
digest: d,
}, nil
}
type hashVerifier struct {
digest Digest
hash hash.Hash
}
func (hv hashVerifier) Write(p []byte) (n int, err error) {
return hv.hash.Write(p)
}
func (hv hashVerifier) Verified() bool {
return hv.digest == NewDigest(hv.digest.Algorithm(), hv.hash)
}

View File

@@ -1,7 +0,0 @@
// Package distribution will define the interfaces for the components of
// docker distribution. The goal is to allow users to reliably package, ship
// and store content related to docker images.
//
// This is currently a work in progress. More details are available in the
// README.md.
package distribution

View File

@@ -1,115 +0,0 @@
package distribution
import (
"errors"
"fmt"
"strings"
"github.com/docker/distribution/digest"
)
// ErrAccessDenied is returned when an access to a requested resource is
// denied.
var ErrAccessDenied = errors.New("access denied")
// ErrManifestNotModified is returned when a conditional manifest GetByTag
// returns nil due to the client indicating it has the latest version
var ErrManifestNotModified = errors.New("manifest not modified")
// ErrUnsupported is returned when an unimplemented or unsupported action is
// performed
var ErrUnsupported = errors.New("operation unsupported")
// ErrTagUnknown is returned if the given tag is not known by the tag service
type ErrTagUnknown struct {
Tag string
}
func (err ErrTagUnknown) Error() string {
return fmt.Sprintf("unknown tag=%s", err.Tag)
}
// ErrRepositoryUnknown is returned if the named repository is not known by
// the registry.
type ErrRepositoryUnknown struct {
Name string
}
func (err ErrRepositoryUnknown) Error() string {
return fmt.Sprintf("unknown repository name=%s", err.Name)
}
// ErrRepositoryNameInvalid should be used to denote an invalid repository
// name. Reason may set, indicating the cause of invalidity.
type ErrRepositoryNameInvalid struct {
Name string
Reason error
}
func (err ErrRepositoryNameInvalid) Error() string {
return fmt.Sprintf("repository name %q invalid: %v", err.Name, err.Reason)
}
// ErrManifestUnknown is returned if the manifest is not known by the
// registry.
type ErrManifestUnknown struct {
Name string
Tag string
}
func (err ErrManifestUnknown) Error() string {
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
}
// ErrManifestUnknownRevision is returned when a manifest cannot be found by
// revision within a repository.
type ErrManifestUnknownRevision struct {
Name string
Revision digest.Digest
}
func (err ErrManifestUnknownRevision) Error() string {
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
}
// ErrManifestUnverified is returned when the registry is unable to verify
// the manifest.
type ErrManifestUnverified struct{}
func (ErrManifestUnverified) Error() string {
return fmt.Sprintf("unverified manifest")
}
// ErrManifestVerification provides a type to collect errors encountered
// during manifest verification. Currently, it accepts errors of all types,
// but it may be narrowed to those involving manifest verification.
type ErrManifestVerification []error
func (errs ErrManifestVerification) Error() string {
var parts []string
for _, err := range errs {
parts = append(parts, err.Error())
}
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
}
// ErrManifestBlobUnknown returned when a referenced blob cannot be found.
type ErrManifestBlobUnknown struct {
Digest digest.Digest
}
func (err ErrManifestBlobUnknown) Error() string {
return fmt.Sprintf("unknown blob %v on manifest", err.Digest)
}
// ErrManifestNameInvalid should be used to denote an invalid manifest
// name. Reason may set, indicating the cause of invalidity.
type ErrManifestNameInvalid struct {
Name string
Reason error
}
func (err ErrManifestNameInvalid) Error() string {
return fmt.Sprintf("manifest name %q invalid: %v", err.Name, err.Reason)
}

View File

@@ -1,125 +0,0 @@
package distribution
import (
"fmt"
"mime"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
)
// Manifest represents a registry object specifying a set of
// references and an optional target
type Manifest interface {
// References returns a list of objects which make up this manifest.
// A reference is anything which can be represented by a
// distribution.Descriptor. These can consist of layers, resources or other
// manifests.
//
// While no particular order is required, implementations should return
// them from highest to lowest priority. For example, one might want to
// return the base layer before the top layer.
References() []Descriptor
// Payload provides the serialized format of the manifest, in addition to
// the mediatype.
Payload() (mediatype string, payload []byte, err error)
}
// ManifestBuilder creates a manifest allowing one to include dependencies.
// Instances can be obtained from a version-specific manifest package. Manifest
// specific data is passed into the function which creates the builder.
type ManifestBuilder interface {
// Build creates the manifest from his builder.
Build(ctx context.Context) (Manifest, error)
// References returns a list of objects which have been added to this
// builder. The dependencies are returned in the order they were added,
// which should be from base to head.
References() []Descriptor
// AppendReference includes the given object in the manifest after any
// existing dependencies. If the add fails, such as when adding an
// unsupported dependency, an error may be returned.
//
// The destination of the reference is dependent on the manifest type and
// the dependency type.
AppendReference(dependency Describable) error
}
// ManifestService describes operations on image manifests.
type ManifestService interface {
// Exists returns true if the manifest exists.
Exists(ctx context.Context, dgst digest.Digest) (bool, error)
// Get retrieves the manifest specified by the given digest
Get(ctx context.Context, dgst digest.Digest, options ...ManifestServiceOption) (Manifest, error)
// Put creates or updates the given manifest returning the manifest digest
Put(ctx context.Context, manifest Manifest, options ...ManifestServiceOption) (digest.Digest, error)
// Delete removes the manifest specified by the given digest. Deleting
// a manifest that doesn't exist will return ErrManifestNotFound
Delete(ctx context.Context, dgst digest.Digest) error
}
// ManifestEnumerator enables iterating over manifests
type ManifestEnumerator interface {
// Enumerate calls ingester for each manifest.
Enumerate(ctx context.Context, ingester func(digest.Digest) error) error
}
// Describable is an interface for descriptors
type Describable interface {
Descriptor() Descriptor
}
// ManifestMediaTypes returns the supported media types for manifests.
func ManifestMediaTypes() (mediaTypes []string) {
for t := range mappings {
if t != "" {
mediaTypes = append(mediaTypes, t)
}
}
return
}
// UnmarshalFunc implements manifest unmarshalling a given MediaType
type UnmarshalFunc func([]byte) (Manifest, Descriptor, error)
var mappings = make(map[string]UnmarshalFunc, 0)
// UnmarshalManifest looks up manifest unmarshal functions based on
// MediaType
func UnmarshalManifest(ctHeader string, p []byte) (Manifest, Descriptor, error) {
// Need to look up by the actual media type, not the raw contents of
// the header. Strip semicolons and anything following them.
var mediatype string
if ctHeader != "" {
var err error
mediatype, _, err = mime.ParseMediaType(ctHeader)
if err != nil {
return nil, Descriptor{}, err
}
}
unmarshalFunc, ok := mappings[mediatype]
if !ok {
unmarshalFunc, ok = mappings[""]
if !ok {
return nil, Descriptor{}, fmt.Errorf("unsupported manifest mediatype and no default available: %s", mediatype)
}
}
return unmarshalFunc(p)
}
// RegisterManifestSchema registers an UnmarshalFunc for a given schema type. This
// should be called from specific
func RegisterManifestSchema(mediatype string, u UnmarshalFunc) error {
if _, ok := mappings[mediatype]; ok {
return fmt.Errorf("manifest mediatype registration would overwrite existing: %s", mediatype)
}
mappings[mediatype] = u
return nil
}

View File

@@ -1,370 +0,0 @@
// Package reference provides a general type to represent any way of referencing images within the registry.
// Its main purpose is to abstract tags and digests (content-addressable hash).
//
// Grammar
//
// reference := name [ ":" tag ] [ "@" digest ]
// name := [hostname '/'] component ['/' component]*
// hostname := hostcomponent ['.' hostcomponent]* [':' port-number]
// hostcomponent := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/
// component := alpha-numeric [separator alpha-numeric]*
// alpha-numeric := /[a-z0-9]+/
// separator := /[_.]|__|[-]*/
//
// tag := /[\w][\w.-]{0,127}/
//
// digest := digest-algorithm ":" digest-hex
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
// digest-algorithm-separator := /[+.-_]/
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
package reference
import (
"errors"
"fmt"
"path"
"strings"
"github.com/docker/distribution/digest"
)
const (
// NameTotalLengthMax is the maximum total number of characters in a repository name.
NameTotalLengthMax = 255
)
var (
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
ErrReferenceInvalidFormat = errors.New("invalid reference format")
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
ErrTagInvalidFormat = errors.New("invalid tag format")
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
ErrDigestInvalidFormat = errors.New("invalid digest format")
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
ErrNameContainsUppercase = errors.New("repository name must be lowercase")
// ErrNameEmpty is returned for empty, invalid repository names.
ErrNameEmpty = errors.New("repository name must have at least one component")
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
)
// Reference is an opaque object reference identifier that may include
// modifiers such as a hostname, name, tag, and digest.
type Reference interface {
// String returns the full reference
String() string
}
// Field provides a wrapper type for resolving correct reference types when
// working with encoding.
type Field struct {
reference Reference
}
// AsField wraps a reference in a Field for encoding.
func AsField(reference Reference) Field {
return Field{reference}
}
// Reference unwraps the reference type from the field to
// return the Reference object. This object should be
// of the appropriate type to further check for different
// reference types.
func (f Field) Reference() Reference {
return f.reference
}
// MarshalText serializes the field to byte text which
// is the string of the reference.
func (f Field) MarshalText() (p []byte, err error) {
return []byte(f.reference.String()), nil
}
// UnmarshalText parses text bytes by invoking the
// reference parser to ensure the appropriately
// typed reference object is wrapped by field.
func (f *Field) UnmarshalText(p []byte) error {
r, err := Parse(string(p))
if err != nil {
return err
}
f.reference = r
return nil
}
// Named is an object with a full name
type Named interface {
Reference
Name() string
}
// Tagged is an object which has a tag
type Tagged interface {
Reference
Tag() string
}
// NamedTagged is an object including a name and tag.
type NamedTagged interface {
Named
Tag() string
}
// Digested is an object which has a digest
// in which it can be referenced by
type Digested interface {
Reference
Digest() digest.Digest
}
// Canonical reference is an object with a fully unique
// name including a name with hostname and digest
type Canonical interface {
Named
Digest() digest.Digest
}
// SplitHostname splits a named reference into a
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
func SplitHostname(named Named) (string, string) {
name := named.Name()
match := anchoredNameRegexp.FindStringSubmatch(name)
if len(match) != 3 {
return "", name
}
return match[1], match[2]
}
// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: Parse will not handle short digests.
func Parse(s string) (Reference, error) {
matches := ReferenceRegexp.FindStringSubmatch(s)
if matches == nil {
if s == "" {
return nil, ErrNameEmpty
}
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
return nil, ErrNameContainsUppercase
}
return nil, ErrReferenceInvalidFormat
}
if len(matches[1]) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
ref := reference{
name: matches[1],
tag: matches[2],
}
if matches[3] != "" {
var err error
ref.digest, err = digest.ParseDigest(matches[3])
if err != nil {
return nil, err
}
}
r := getBestReferenceType(ref)
if r == nil {
return nil, ErrNameEmpty
}
return r, nil
}
// ParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name, otherwise an error is
// returned.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: ParseNamed will not handle short digests.
func ParseNamed(s string) (Named, error) {
ref, err := Parse(s)
if err != nil {
return nil, err
}
named, isNamed := ref.(Named)
if !isNamed {
return nil, fmt.Errorf("reference %s has no name", ref.String())
}
return named, nil
}
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
func WithName(name string) (Named, error) {
if len(name) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
if !anchoredNameRegexp.MatchString(name) {
return nil, ErrReferenceInvalidFormat
}
return repository(name), nil
}
// WithTag combines the name from "name" and the tag from "tag" to form a
// reference incorporating both the name and the tag.
func WithTag(name Named, tag string) (NamedTagged, error) {
if !anchoredTagRegexp.MatchString(tag) {
return nil, ErrTagInvalidFormat
}
if canonical, ok := name.(Canonical); ok {
return reference{
name: name.Name(),
tag: tag,
digest: canonical.Digest(),
}, nil
}
return taggedReference{
name: name.Name(),
tag: tag,
}, nil
}
// WithDigest combines the name from "name" and the digest from "digest" to form
// a reference incorporating both the name and the digest.
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
if !anchoredDigestRegexp.MatchString(digest.String()) {
return nil, ErrDigestInvalidFormat
}
if tagged, ok := name.(Tagged); ok {
return reference{
name: name.Name(),
tag: tagged.Tag(),
digest: digest,
}, nil
}
return canonicalReference{
name: name.Name(),
digest: digest,
}, nil
}
// Match reports whether ref matches the specified pattern.
// See https://godoc.org/path#Match for supported patterns.
func Match(pattern string, ref Reference) (bool, error) {
matched, err := path.Match(pattern, ref.String())
if namedRef, isNamed := ref.(Named); isNamed && !matched {
matched, _ = path.Match(pattern, namedRef.Name())
}
return matched, err
}
// TrimNamed removes any tag or digest from the named reference.
func TrimNamed(ref Named) Named {
return repository(ref.Name())
}
func getBestReferenceType(ref reference) Reference {
if ref.name == "" {
// Allow digest only references
if ref.digest != "" {
return digestReference(ref.digest)
}
return nil
}
if ref.tag == "" {
if ref.digest != "" {
return canonicalReference{
name: ref.name,
digest: ref.digest,
}
}
return repository(ref.name)
}
if ref.digest == "" {
return taggedReference{
name: ref.name,
tag: ref.tag,
}
}
return ref
}
type reference struct {
name string
tag string
digest digest.Digest
}
func (r reference) String() string {
return r.name + ":" + r.tag + "@" + r.digest.String()
}
func (r reference) Name() string {
return r.name
}
func (r reference) Tag() string {
return r.tag
}
func (r reference) Digest() digest.Digest {
return r.digest
}
type repository string
func (r repository) String() string {
return string(r)
}
func (r repository) Name() string {
return string(r)
}
type digestReference digest.Digest
func (d digestReference) String() string {
return d.String()
}
func (d digestReference) Digest() digest.Digest {
return digest.Digest(d)
}
type taggedReference struct {
name string
tag string
}
func (t taggedReference) String() string {
return t.name + ":" + t.tag
}
func (t taggedReference) Name() string {
return t.name
}
func (t taggedReference) Tag() string {
return t.tag
}
type canonicalReference struct {
name string
digest digest.Digest
}
func (c canonicalReference) String() string {
return c.name + "@" + c.digest.String()
}
func (c canonicalReference) Name() string {
return c.name
}
func (c canonicalReference) Digest() digest.Digest {
return c.digest
}

View File

@@ -1,124 +0,0 @@
package reference
import "regexp"
var (
// alphaNumericRegexp defines the alpha numeric atom, typically a
// component of names. This only allows lower case characters and digits.
alphaNumericRegexp = match(`[a-z0-9]+`)
// separatorRegexp defines the separators allowed to be embedded in name
// components. This allow one period, one or two underscore and multiple
// dashes.
separatorRegexp = match(`(?:[._]|__|[-]*)`)
// nameComponentRegexp restricts registry path component names to start
// with at least one letter or number, with following parts able to be
// separated by one period, one or two underscore and multiple dashes.
nameComponentRegexp = expression(
alphaNumericRegexp,
optional(repeated(separatorRegexp, alphaNumericRegexp)))
// hostnameComponentRegexp restricts the registry hostname component of a
// repository name to start with a component as defined by hostnameRegexp
// and followed by an optional port.
hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
// hostnameRegexp defines the structure of potential hostname components
// that may be part of image names. This is purposely a subset of what is
// allowed by DNS to ensure backwards compatibility with Docker image
// names.
hostnameRegexp = expression(
hostnameComponentRegexp,
optional(repeated(literal(`.`), hostnameComponentRegexp)),
optional(literal(`:`), match(`[0-9]+`)))
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
TagRegexp = match(`[\w][\w.-]{0,127}`)
// anchoredTagRegexp matches valid tag names, anchored at the start and
// end of the matched string.
anchoredTagRegexp = anchored(TagRegexp)
// DigestRegexp matches valid digests.
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
// anchoredDigestRegexp matches valid digests, anchored at the start and
// end of the matched string.
anchoredDigestRegexp = anchored(DigestRegexp)
// NameRegexp is the format for the name component of references. The
// regexp has capturing groups for the hostname and name part omitting
// the separating forward slash from either.
NameRegexp = expression(
optional(hostnameRegexp, literal(`/`)),
nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp)))
// anchoredNameRegexp is used to parse a name value, capturing the
// hostname and trailing components.
anchoredNameRegexp = anchored(
optional(capture(hostnameRegexp), literal(`/`)),
capture(nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp))))
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
ReferenceRegexp = anchored(capture(NameRegexp),
optional(literal(":"), capture(TagRegexp)),
optional(literal("@"), capture(DigestRegexp)))
)
// match compiles the string to a regular expression.
var match = regexp.MustCompile
// literal compiles s into a literal regular expression, escaping any regexp
// reserved characters.
func literal(s string) *regexp.Regexp {
re := match(regexp.QuoteMeta(s))
if _, complete := re.LiteralPrefix(); !complete {
panic("must be a literal")
}
return re
}
// expression defines a full expression, where each regular expression must
// follow the previous.
func expression(res ...*regexp.Regexp) *regexp.Regexp {
var s string
for _, re := range res {
s += re.String()
}
return match(s)
}
// optional wraps the expression in a non-capturing group and makes the
// production optional.
func optional(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `?`)
}
// repeated wraps the regexp in a non-capturing group to get one or more
// matches.
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `+`)
}
// group wraps the regexp in a non-capturing group.
func group(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(?:` + expression(res...).String() + `)`)
}
// capture wraps the expression in a capturing group.
func capture(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(` + expression(res...).String() + `)`)
}
// anchored anchors the regular expression by adding start and end delimiters.
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
return match(`^` + expression(res...).String() + `$`)
}

View File

@@ -1,97 +0,0 @@
package distribution
import (
"github.com/docker/distribution/context"
"github.com/docker/distribution/reference"
)
// Scope defines the set of items that match a namespace.
type Scope interface {
// Contains returns true if the name belongs to the namespace.
Contains(name string) bool
}
type fullScope struct{}
func (f fullScope) Contains(string) bool {
return true
}
// GlobalScope represents the full namespace scope which contains
// all other scopes.
var GlobalScope = Scope(fullScope{})
// Namespace represents a collection of repositories, addressable by name.
// Generally, a namespace is backed by a set of one or more services,
// providing facilities such as registry access, trust, and indexing.
type Namespace interface {
// Scope describes the names that can be used with this Namespace. The
// global namespace will have a scope that matches all names. The scope
// effectively provides an identity for the namespace.
Scope() Scope
// Repository should return a reference to the named repository. The
// registry may or may not have the repository but should always return a
// reference.
Repository(ctx context.Context, name reference.Named) (Repository, error)
// Repositories fills 'repos' with a lexigraphically sorted catalog of repositories
// up to the size of 'repos' and returns the value 'n' for the number of entries
// which were filled. 'last' contains an offset in the catalog, and 'err' will be
// set to io.EOF if there are no more entries to obtain.
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
// Blobs returns a blob enumerator to access all blobs
Blobs() BlobEnumerator
// BlobStatter returns a BlobStatter to control
BlobStatter() BlobStatter
}
// RepositoryEnumerator describes an operation to enumerate repositories
type RepositoryEnumerator interface {
Enumerate(ctx context.Context, ingester func(string) error) error
}
// ManifestServiceOption is a function argument for Manifest Service methods
type ManifestServiceOption interface {
Apply(ManifestService) error
}
// WithTag allows a tag to be passed into Put
func WithTag(tag string) ManifestServiceOption {
return WithTagOption{tag}
}
// WithTagOption holds a tag
type WithTagOption struct{ Tag string }
// Apply conforms to the ManifestServiceOption interface
func (o WithTagOption) Apply(m ManifestService) error {
// no implementation
return nil
}
// Repository is a named collection of manifests and layers.
type Repository interface {
// Named returns the name of the repository.
Named() reference.Named
// Manifests returns a reference to this repository's manifest service.
// with the supplied options applied.
Manifests(ctx context.Context, options ...ManifestServiceOption) (ManifestService, error)
// Blobs returns a reference to this repository's blob service.
Blobs(ctx context.Context) BlobStore
// TODO(stevvooe): The above BlobStore return can probably be relaxed to
// be a BlobService for use with clients. This will allow such
// implementations to avoid implementing ServeBlob.
// Tags returns a reference to this repositories tag service
Tags(ctx context.Context) TagService
}
// TODO(stevvooe): Must add close methods to all these. May want to change the
// way instances are created to better reflect internal dependency
// relationships.

Some files were not shown because too many files have changed in this diff Show More