mirror of
https://github.com/ko-build/ko.git
synced 2024-11-19 18:01:46 +02:00
Initial commit
This commit is contained in:
commit
6354665a42
26
.travis.yml
Normal file
26
.travis.yml
Normal file
@ -0,0 +1,26 @@
|
||||
sudo: false
|
||||
|
||||
dist: trusty
|
||||
|
||||
language:
|
||||
- go
|
||||
|
||||
go:
|
||||
- "1.10"
|
||||
- "1.11"
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
install: true
|
||||
|
||||
script:
|
||||
# Verify that all source files are correctly formatted.
|
||||
- find . -name "*.go" | grep -v vendor/ | xargs gofmt -d -e -l
|
||||
|
||||
- go clean -i
|
||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
# TODO: Set up coverage.
|
||||
#after_success:
|
||||
# - bash <(curl -s https://codecov.io/bash)
|
627
Gopkg.lock
generated
Normal file
627
Gopkg.lock
generated
Normal file
@ -0,0 +1,627 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f9ae348e1f793dcf9ed930ed47136a67343dbd6809c5c91391322267f4476892"
|
||||
name = "github.com/Microsoft/go-winio"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "1a8911d1ed007260465c3bfbbc785ac6915a0bb8"
|
||||
version = "v0.4.12"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4189ee6a3844f555124d9d2656fe7af02fca961c2a9bad9074789df13a0c62e0"
|
||||
name = "github.com/docker/distribution"
|
||||
packages = [
|
||||
"digestset",
|
||||
"reference",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "83389a148052d74ac602f5f1d62f86ff2f3c4aa5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2e5c814aa30dd80f292a7e30b5b01589bb402ac396565a37e879b90d8269a3a2"
|
||||
name = "github.com/docker/docker"
|
||||
packages = [
|
||||
"api",
|
||||
"api/types",
|
||||
"api/types/blkiodev",
|
||||
"api/types/container",
|
||||
"api/types/events",
|
||||
"api/types/filters",
|
||||
"api/types/image",
|
||||
"api/types/mount",
|
||||
"api/types/network",
|
||||
"api/types/registry",
|
||||
"api/types/strslice",
|
||||
"api/types/swarm",
|
||||
"api/types/swarm/runtime",
|
||||
"api/types/time",
|
||||
"api/types/versions",
|
||||
"api/types/volume",
|
||||
"client",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "71cd53e4a197b303c6ba086bd584ffd67a884281"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d339ac52dc03196c71c8403084882308da59b09a1ae4048b6b5f83640557a972"
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = [
|
||||
"nat",
|
||||
"sockets",
|
||||
"tlsconfig",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "7beb39f0b969b075d1325fecb092faf27fd357b6"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:57d39983d01980c1317c2c5c6dd4b5b0c4a804ad2df800f2f6cbcd6a6d05f6ca"
|
||||
name = "github.com/docker/go-units"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:36a5ff9459163d104f2af9776c8db63f3eb4339f527a00a9835c8d562eb116ba"
|
||||
name = "github.com/evanphx/json-patch"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5858425f75500d40c52783dce87d085a483ce135"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c45cef8e0074ea2f8176a051df38553ba997a3616f1ec2d35222b1cf9864881e"
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f83d740263b44fdeef3e1bce6147b5d7283fcad1a693d39639be33993ecf3db1"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "100ba4e885062801d56799d78530b73b178a78f3"
|
||||
version = "v0.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467"
|
||||
name = "github.com/golang/glog"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:0bfbe13936953a98ae3cfe8ed6670d396ad81edf069a806d2f6515d7bb6950df"
|
||||
name = "github.com/google/btree"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d2754cafcab0d22c13541618a8029a70a8959eb3525ff201fe971637e2274cd0"
|
||||
name = "github.com/google/go-cmp"
|
||||
packages = [
|
||||
"cmp",
|
||||
"cmp/cmpopts",
|
||||
"cmp/internal/diff",
|
||||
"cmp/internal/function",
|
||||
"cmp/internal/value",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3af367b6b30c263d47e8895973edcca9a49cf029"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:123ca2e74131111f6302f6e0eb27bbae6d8989b7dae00ca7a624793b4549353b"
|
||||
name = "github.com/google/go-containerregistry"
|
||||
packages = [
|
||||
"pkg/authn",
|
||||
"pkg/name",
|
||||
"pkg/v1",
|
||||
"pkg/v1/daemon",
|
||||
"pkg/v1/empty",
|
||||
"pkg/v1/mutate",
|
||||
"pkg/v1/partial",
|
||||
"pkg/v1/random",
|
||||
"pkg/v1/remote",
|
||||
"pkg/v1/remote/transport",
|
||||
"pkg/v1/stream",
|
||||
"pkg/v1/tarball",
|
||||
"pkg/v1/types",
|
||||
"pkg/v1/v1util",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "6225ca1a4de721ff14f6c4cbbfd141ab462bdb22"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3ee90c0d94da31b442dde97c99635aaafec68d0b8a3c12ee2075c6bdabeec6bb"
|
||||
name = "github.com/google/gofuzz"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:75eb87381d25cc75212f52358df9c3a2719584eaa9685cd510ce28699122f39d"
|
||||
name = "github.com/googleapis/gnostic"
|
||||
packages = [
|
||||
"OpenAPIv2",
|
||||
"compiler",
|
||||
"extensions",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "0c5108395e2debce0d731cf0287ddf7242066aba"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b4395b2a4566c24459af3d04009b39cc21762fc77ec7bf7a1aa905c91e8f018d"
|
||||
name = "github.com/gregjones/httpcache"
|
||||
packages = [
|
||||
".",
|
||||
"diskcache",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3befbb6ad0cc97d4c25d851e9528915809e1a22f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
"json/parser",
|
||||
"json/scanner",
|
||||
"json/token",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3e260afa138eab6492b531a3b3d10ab4cb70512d423faa78b8949dec76e66a21"
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
|
||||
version = "v0.3.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:eaefc85d32c03e5f0c2b88ea2f79fce3d993e2c78316d21319575dd4ea9153ca"
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ab8a2e0c74be9d3be70b3184d9acc634935ded82"
|
||||
version = "1.1.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7"
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c2353362d570a7bfa228149c62842019201cfb71"
|
||||
version = "v1.8.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3e99bcbbb6f34aeb476bffd6bdb27e3c4db29ea870a7ccfc20834b07a3e191bf"
|
||||
name = "github.com/mattmoor/dep-notify"
|
||||
packages = ["pkg/graph"]
|
||||
pruneopts = "UT"
|
||||
revision = "a45dec370a173f35e57abcf844b43eb1d1f988e7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
|
||||
version = "v1.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563"
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c56ad36f5722eb07926c979d5e80676ee007a9e39e7808577b9d87ec92b00460"
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "94122c33edd36123c84d5368cfb2b69df93a0ec8"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ee4d4af67d93cc7644157882329023ce9a7bcfce956a079069a9405521c7cc8d"
|
||||
name = "github.com/opencontainers/go-digest"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
|
||||
version = "v1.0.0-rc1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:11db38d694c130c800d0aefb502fb02519e514dc53d9804ce51d1ad25ec27db6"
|
||||
name = "github.com/opencontainers/image-spec"
|
||||
packages = [
|
||||
"specs-go",
|
||||
"specs-go/v1",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d60099175f88c47cd379c4738d158884749ed235"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3bf17a6e6eaa6ad24152148a631d18662f7212e21637c2699bff3369b7f00fa2"
|
||||
name = "github.com/petar/GoLLRB"
|
||||
packages = ["llrb"]
|
||||
pruneopts = "UT"
|
||||
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0e7775ebbcf00d8dd28ac663614af924411c868dca3d5aa762af0fae3808d852"
|
||||
name = "github.com/peterbourgon/diskv"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5f041e8faa004a95c88a202771f4cc3e991971e6"
|
||||
version = "v2.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3e39bafd6c2f4bf3c76c3bfd16a2e09e016510ad5db90dc02b88e2f565d6d595"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f4711e4db9e9a1d3887343acb72b2bbfc2f686f5"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8c9545af88b134710ab1cd196795e7f2388358d7"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1b753ec16506f5864d26a28b43703c58831255059644351bbcb019b843950900"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "94f6ae3ed3bceceafa716478c5fbf8d29ca601a1"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
|
||||
version = "v1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1b773526998f3dbde3a51a4a5881680c4d237d3600f570d900f97ac93c7ba0a8"
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ssh/terminal"]
|
||||
pruneopts = "UT"
|
||||
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3b8ba1aecf7f576a3c710e7f0fb7f1ded57c4a99a39f81b745c6564423391ac7"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"lex/httplex",
|
||||
"proxy",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "5561cd9b4330353950f399814f427425c0a26fd2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b521f10a2d8fa85c04a8ef4e62f2d1e14d303599a55d64dabf9f5a02f84d35eb"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["errgroup"]
|
||||
pruneopts = "UT"
|
||||
revision = "e225da77a7e68af35c70ccbf71af2b83e6acac3c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:944736317d4f2fb1c1015ef9ca05fd9313b4e19f1128901cc678f636435fa9b0"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "fead79001313d15903fb4605b4a1b781532cd93e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e5a8511f063c38c51ab9ab80e718e9149f692652aeb4e393a8c020dd1bf38ca2"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"encoding",
|
||||
"encoding/internal",
|
||||
"encoding/internal/identifier",
|
||||
"encoding/unicode",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"internal/utf8internal",
|
||||
"language",
|
||||
"runes",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d37b0ef2944431fe9e8ef35c6fffc8990d9e2ca300588df94a6890f3649ae365"
|
||||
name = "golang.org/x/time"
|
||||
packages = ["rate"]
|
||||
pruneopts = "UT"
|
||||
revision = "f51c12702a4d776e4c1fa9b0fabab841babae631"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ef72505cf098abdd34efeea032103377bec06abb61d8a06f002d5d296a4b1185"
|
||||
name = "gopkg.in/inf.v0"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
|
||||
version = "v0.9.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
|
||||
version = "v2.2.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6163e6e8fcdb2c545db15aa5fe492fb5c2d59ad54f58fa1cba07f12ceac6d77b"
|
||||
name = "k8s.io/api"
|
||||
packages = [
|
||||
"admissionregistration/v1alpha1",
|
||||
"admissionregistration/v1beta1",
|
||||
"apps/v1",
|
||||
"apps/v1beta1",
|
||||
"apps/v1beta2",
|
||||
"authentication/v1",
|
||||
"authentication/v1beta1",
|
||||
"authorization/v1",
|
||||
"authorization/v1beta1",
|
||||
"autoscaling/v1",
|
||||
"autoscaling/v2beta1",
|
||||
"batch/v1",
|
||||
"batch/v1beta1",
|
||||
"batch/v2alpha1",
|
||||
"certificates/v1beta1",
|
||||
"core/v1",
|
||||
"events/v1beta1",
|
||||
"extensions/v1beta1",
|
||||
"networking/v1",
|
||||
"policy/v1beta1",
|
||||
"rbac/v1",
|
||||
"rbac/v1alpha1",
|
||||
"rbac/v1beta1",
|
||||
"scheduling/v1alpha1",
|
||||
"scheduling/v1beta1",
|
||||
"settings/v1alpha1",
|
||||
"storage/v1",
|
||||
"storage/v1alpha1",
|
||||
"storage/v1beta1",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4e7be11eab3ffcfc1876898b8272df53785a9504"
|
||||
version = "kubernetes-1.11.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f214c67eb188bc742f0e6f0dd6a8cf22b1fb2538591b82c38bfcc3dcafc5d1d1"
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = [
|
||||
"pkg/api/errors",
|
||||
"pkg/api/meta",
|
||||
"pkg/api/resource",
|
||||
"pkg/apis/meta/v1",
|
||||
"pkg/apis/meta/v1/unstructured",
|
||||
"pkg/apis/meta/v1/unstructured/unstructuredscheme",
|
||||
"pkg/apis/meta/v1beta1",
|
||||
"pkg/conversion",
|
||||
"pkg/conversion/queryparams",
|
||||
"pkg/fields",
|
||||
"pkg/labels",
|
||||
"pkg/runtime",
|
||||
"pkg/runtime/schema",
|
||||
"pkg/runtime/serializer",
|
||||
"pkg/runtime/serializer/json",
|
||||
"pkg/runtime/serializer/protobuf",
|
||||
"pkg/runtime/serializer/recognizer",
|
||||
"pkg/runtime/serializer/streaming",
|
||||
"pkg/runtime/serializer/versioning",
|
||||
"pkg/selection",
|
||||
"pkg/types",
|
||||
"pkg/util/clock",
|
||||
"pkg/util/errors",
|
||||
"pkg/util/framer",
|
||||
"pkg/util/intstr",
|
||||
"pkg/util/json",
|
||||
"pkg/util/net",
|
||||
"pkg/util/runtime",
|
||||
"pkg/util/sets",
|
||||
"pkg/util/validation",
|
||||
"pkg/util/validation/field",
|
||||
"pkg/util/wait",
|
||||
"pkg/util/yaml",
|
||||
"pkg/version",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/reflect",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "def12e63c512da17043b4f0293f52d1006603d9f"
|
||||
version = "kubernetes-1.11.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3627298895c0ee48c7238a0c87398b3b521130d3bd42fcc93d3a4da50e02e8f7"
|
||||
name = "k8s.io/client-go"
|
||||
packages = [
|
||||
"discovery",
|
||||
"kubernetes/scheme",
|
||||
"pkg/apis/clientauthentication",
|
||||
"pkg/apis/clientauthentication/v1alpha1",
|
||||
"pkg/apis/clientauthentication/v1beta1",
|
||||
"pkg/version",
|
||||
"plugin/pkg/client/auth/exec",
|
||||
"rest",
|
||||
"rest/watch",
|
||||
"restmapper",
|
||||
"third_party/forked/golang/template",
|
||||
"tools/auth",
|
||||
"tools/clientcmd",
|
||||
"tools/clientcmd/api",
|
||||
"tools/clientcmd/api/latest",
|
||||
"tools/clientcmd/api/v1",
|
||||
"tools/metrics",
|
||||
"transport",
|
||||
"util/cert",
|
||||
"util/connrotation",
|
||||
"util/flowcontrol",
|
||||
"util/homedir",
|
||||
"util/integer",
|
||||
"util/jsonpath",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "7d04d0e2a0a1a4d4a1cd6baa432a2301492e4e65"
|
||||
version = "v8.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0c3d823feb8cde6712368a2665683d401c330ddc71d8be0a396956a8b09cce70"
|
||||
name = "k8s.io/kubernetes"
|
||||
packages = [
|
||||
"pkg/kubectl/genericclioptions",
|
||||
"pkg/kubectl/genericclioptions/printers",
|
||||
"pkg/kubectl/genericclioptions/resource",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4e209c9383fa00631d124c8adcc011d617339b3c"
|
||||
version = "v1.11.8"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/docker/docker/api/types",
|
||||
"github.com/fsnotify/fsnotify",
|
||||
"github.com/google/go-cmp/cmp",
|
||||
"github.com/google/go-cmp/cmp/cmpopts",
|
||||
"github.com/google/go-containerregistry/pkg/authn",
|
||||
"github.com/google/go-containerregistry/pkg/name",
|
||||
"github.com/google/go-containerregistry/pkg/v1",
|
||||
"github.com/google/go-containerregistry/pkg/v1/daemon",
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate",
|
||||
"github.com/google/go-containerregistry/pkg/v1/random",
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote",
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball",
|
||||
"github.com/mattmoor/dep-notify/pkg/graph",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/spf13/pflag",
|
||||
"github.com/spf13/viper",
|
||||
"golang.org/x/sync/errgroup",
|
||||
"gopkg.in/yaml.v2",
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
78
Gopkg.toml
Normal file
78
Gopkg.toml
Normal file
@ -0,0 +1,78 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
version = "1.4.7"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/google/go-cmp"
|
||||
version = "0.2.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-containerregistry"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattmoor/dep-notify"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/pflag"
|
||||
version = "1.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/viper"
|
||||
version = "1.3.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sync"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
version = "2.2.2"
|
||||
|
||||
[[override]]
|
||||
name = "k8s.io/apimachinery"
|
||||
version = "kubernetes-1.11.3"
|
||||
|
||||
[[override]]
|
||||
name = "k8s.io/api"
|
||||
version = "kubernetes-1.11.3"
|
||||
|
||||
[[override]]
|
||||
name = "k8s.io/kubernetes"
|
||||
version = "1.11.3"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
202
LICENSE
Normal file
202
LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
423
README.md
Normal file
423
README.md
Normal file
@ -0,0 +1,423 @@
|
||||
# ko
|
||||
|
||||
`ko` is a tool for building and deploying Golang applications to Kubernetes.
|
||||
|
||||
[![Build Status](https://travis-ci.org/google/ko.svg?branch=master)](https://travis-ci.org/google/ko)
|
||||
[![GoDoc](https://godoc.org/github.com/google/ko?status.svg)](https://godoc.org/github.com/google/ko)
|
||||
[![Go Report Card](https://goreportcard.com/badge/google/ko)](https://goreportcard.com/report/google/ko)
|
||||
|
||||
## Installation
|
||||
|
||||
`ko` can be installed via:
|
||||
|
||||
```shell
|
||||
go get github.com/google/ko/cmd
|
||||
```
|
||||
|
||||
To update your installation:
|
||||
|
||||
```shell
|
||||
go get -u github.com/google/ko/cmd
|
||||
```
|
||||
|
||||
## The `ko` Model
|
||||
|
||||
`ko` is built around a very simple extension to Go's model for expressing
|
||||
dependencies using [import paths](https://golang.org/doc/code.html#ImportPaths).
|
||||
|
||||
In Go, dependencies are expressed via blocks like:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/google/foo/pkg/hello"
|
||||
"github.com/google/bar/pkg/world"
|
||||
)
|
||||
```
|
||||
|
||||
Similarly (as you can see above), Go binaries can be referenced via import
|
||||
paths like `github.com/google/ko/cmd`.
|
||||
|
||||
**One of the goals of `ko` is to make containers invisible infrastructure.**
|
||||
Simply replace image references in your Kubernetes yaml with the import path for
|
||||
your Go binary, and `ko` will handle containerizing and publishing that
|
||||
container image as needed.
|
||||
|
||||
For example, you might use the following in a Kubernetes `Deployment` resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
foo: bar
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
spec:
|
||||
containers:
|
||||
- name: hello-world
|
||||
# This is the import path for the Go binary to build and run.
|
||||
image: github.com/mattmoor/examples/http/cmd/helloworld
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
```
|
||||
|
||||
### Determining supported import paths
|
||||
|
||||
Similar to other tooling in the Go ecosystem, `ko` expects to execute in the
|
||||
context of your `$GOPATH`. This is used to determine what package(s) `ko`
|
||||
is expected to build.
|
||||
|
||||
Suppose `GOPATH` is `~/gopath` and the current directory is
|
||||
`~/gopath/src/github.com/mattmoor/examples`. `ko` will deduce the base import
|
||||
path to be `github.com/mattmoor/examples`, and any references to subpackages
|
||||
of this will be built, containerized and published.
|
||||
|
||||
For example, any of the following would be matched:
|
||||
* `github.com/mattmoor/examples`
|
||||
* `github.com/mattmoor/examples/cmd/foo`
|
||||
* `github.com/mattmoor/examples/bar`
|
||||
|
||||
### Results
|
||||
|
||||
Employing this convention enables `ko` to have effectively zero configuration
|
||||
and enable very fast development iteration. For
|
||||
[warm-image](https://github.com/mattmoor/warm-image), `ko` is able to
|
||||
build, containerize, and redeploy a non-trivial Kubernetes controller app in
|
||||
seconds (dominated by two `go build`s).
|
||||
|
||||
```shell
|
||||
$ ko apply -f config/
|
||||
2018/07/19 14:56:41 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:56:42 Publishing us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest
|
||||
2018/07/19 14:56:43 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:56:43 mounted blob: sha256:59df9d5b488aea2753ab7774ae41a9a3e96903f87ac699f3505960e744f36f7d
|
||||
2018/07/19 14:56:43 mounted blob: sha256:739b3deec2edb17c512f507894c55c2681f9724191d820cdc01f668330724ca7
|
||||
2018/07/19 14:56:44 us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest: digest: sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326 size: 592
|
||||
2018/07/19 14:56:44 Published us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37@sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326
|
||||
2018/07/19 14:56:45 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/controller
|
||||
2018/07/19 14:56:46 Publishing us.gcr.io/my-project/controller-9e91872fd7c48124dbe6ea83944b87e9:latest
|
||||
2018/07/19 14:56:46 mounted blob: sha256:007782ba6738188a59bf21b4d8e974f218615ee948c6357535d07e7248b2a560
|
||||
2018/07/19 14:56:46 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:56:46 mounted blob: sha256:7fec050f965d7fba3de4bd19739746dce5a5125331b7845bf02185ff5d4cc374
|
||||
2018/07/19 14:56:47 us.gcr.io/my-project/controller-9e91872fd7c48124dbe6ea83944b87e9:latest: digest: sha256:5a81029bb0cfd519c321aeeea2bc1b7dc6488b6c72003d3613442b4d5e4ed14d size: 593
|
||||
2018/07/19 14:56:47 Published us.gcr.io/my-project/controller-9e91872fd7c48124dbe6ea83944b87e9@sha256:5a81029bb0cfd519c321aeeea2bc1b7dc6488b6c72003d3613442b4d5e4ed14d
|
||||
namespace/warmimage-system configured
|
||||
clusterrolebinding.rbac.authorization.k8s.io/warmimage-controller-admin configured
|
||||
deployment.apps/warmimage-controller unchanged
|
||||
serviceaccount/warmimage-controller unchanged
|
||||
customresourcedefinition.apiextensions.k8s.io/warmimages.mattmoor.io configured
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`ko` has four commands, most of which build and publish images as part of
|
||||
their execution. By default, `ko` publishes images to a Docker Registry
|
||||
specified via `KO_DOCKER_REPO`.
|
||||
|
||||
However, these same commands can be directed to operate locally as well via
|
||||
the `--local` or `-L` command (or setting `KO_DOCKER_REPO=ko.local`). See
|
||||
the [`minikube` section](./README.md#with-minikube) for more detail.
|
||||
|
||||
|
||||
### `ko publish`
|
||||
|
||||
`ko publish` simply builds and publishes images for each import path passed as
|
||||
an argument. It prints the images' published digests after each image is published.
|
||||
|
||||
```shell
|
||||
$ ko publish github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:57:34 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:57:35 Publishing us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest
|
||||
2018/07/19 14:57:35 mounted blob: sha256:739b3deec2edb17c512f507894c55c2681f9724191d820cdc01f668330724ca7
|
||||
2018/07/19 14:57:35 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:57:35 mounted blob: sha256:59df9d5b488aea2753ab7774ae41a9a3e96903f87ac699f3505960e744f36f7d
|
||||
2018/07/19 14:57:36 us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest: digest: sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326 size: 592
|
||||
2018/07/19 14:57:36 Published us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37@sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326
|
||||
```
|
||||
|
||||
`ko publish` also supports relative import paths, when in the context of a repo on `GOPATH`.
|
||||
|
||||
```shell
|
||||
$ ko publish ./cmd/sleeper
|
||||
2018/07/19 14:58:16 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:58:16 Publishing us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest
|
||||
2018/07/19 14:58:17 mounted blob: sha256:59df9d5b488aea2753ab7774ae41a9a3e96903f87ac699f3505960e744f36f7d
|
||||
2018/07/19 14:58:17 mounted blob: sha256:739b3deec2edb17c512f507894c55c2681f9724191d820cdc01f668330724ca7
|
||||
2018/07/19 14:58:17 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:58:18 us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest: digest: sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326 size: 592
|
||||
2018/07/19 14:58:18 Published us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37@sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326
|
||||
```
|
||||
|
||||
### `ko resolve`
|
||||
|
||||
`ko resolve` takes Kubernetes yaml files in the style of `kubectl apply`
|
||||
and (based on the [model above](#the-ko-model)) determines the set of
|
||||
Go import paths to build, containerize, and publish.
|
||||
|
||||
The output of `ko resolve` is the concatenated yaml with import paths
|
||||
replaced with published image digests. Following the example above,
|
||||
this would be:
|
||||
|
||||
```shell
|
||||
# Command
|
||||
export PROJECT_ID=$(gcloud config get-value core/project)
|
||||
export KO_DOCKER_REPO="gcr.io/${PROJECT_ID}"
|
||||
ko resolve -f deployment.yaml
|
||||
|
||||
# Output
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello-world
|
||||
# This is the digest of the published image containing the go binary.
|
||||
image: gcr.io/your-project/helloworld-badf00d@sha256:deadbeef
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
```
|
||||
|
||||
Some Docker Registries (e.g. gcr.io) support multi-level repository names. For
|
||||
these registries, it is often useful for discoverability and provenance to
|
||||
preserve the full import path, for this we expose `--preserve-import-paths`,
|
||||
or `-P` for short.
|
||||
|
||||
```shell
|
||||
# Command
|
||||
export PROJECT_ID=$(gcloud config get-value core/project)
|
||||
export KO_DOCKER_REPO="gcr.io/${PROJECT_ID}"
|
||||
ko resolve -P -f deployment.yaml
|
||||
|
||||
# Output
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello-world
|
||||
# This is the digest of the published image containing the go binary
|
||||
# at the embedded import path.
|
||||
image: gcr.io/your-project/github.com/mattmoor/examples/http/cmd/helloworld@sha256:deadbeef
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
```
|
||||
|
||||
It is notable that this is not the default (anymore) because certain popular
|
||||
registries (including Docker Hub) do not support multi-level repository names.
|
||||
|
||||
### `ko apply`
|
||||
|
||||
`ko apply` is intended to parallel `kubectl apply`, but acts on the same
|
||||
resolved output as `ko resolve` emits. It is expected that `ko apply` will act
|
||||
as the vehicle for rapid iteration during development. As changes are made to a
|
||||
particular application, you can run: `ko apply -f unit.yaml` to rapidly
|
||||
rebuild, repush, and redeploy their changes.
|
||||
|
||||
`ko apply` will invoke `kubectl apply` under the covers, and therefore apply
|
||||
to whatever `kubectl` context is active.
|
||||
|
||||
### `ko apply --watch` (EXPERIMENTAL)
|
||||
|
||||
The `--watch` flag (`-W` for short) does an initial `apply` as above, but as it
|
||||
does, it builds up a dependency graph of your program and starts to continuously
|
||||
monitor the filesystem for changes. When a file changes, it re-applies any yamls
|
||||
that are affected.
|
||||
|
||||
For example, if I edit `github.com/foo/bar/pkg/baz/blah.go`, the tool sees that
|
||||
the `github.com/foo/bar/pkg/baz` package has changed, and perhaps both
|
||||
`github.com/foo/bar/cmd/one` and `github.com/foo/bar/cmd/two` consume that library
|
||||
and were referenced by `config/one-deploy.yaml` and `config/two-deploy.yaml`.
|
||||
The edit would effectively result in a re-application of:
|
||||
|
||||
```
|
||||
ko apply -f config/one-deploy.yaml -f config/two-deploy.yaml
|
||||
```
|
||||
|
||||
This flag is still experimental, and feedback is very welcome.
|
||||
|
||||
### `ko delete`
|
||||
|
||||
`ko delete` simply passes through to `kubectl delete`. It is exposed purely out
|
||||
of convenience for cleaning up resources created through `ko apply`.
|
||||
|
||||
|
||||
## With `minikube`
|
||||
|
||||
You can use `ko` with `minikube` via a Docker Registry, but this involves
|
||||
publishing images only to pull them back down to your machine again. To avoid
|
||||
this, `ko` exposes `--local` or `-L` options to instead publish the images to
|
||||
the local machine's Docker daemon.
|
||||
|
||||
This would look something like:
|
||||
|
||||
```shell
|
||||
# Use the minikube docker daemon.
|
||||
eval $(minikube docker-env)
|
||||
|
||||
# Make sure minikube is the current kubectl context.
|
||||
kubectl config use-context minikube
|
||||
|
||||
# Deploy to minikube w/o registry.
|
||||
ko apply -L -f config/
|
||||
|
||||
# This is the same as above.
|
||||
KO_DOCKER_REPO=ko.local ko apply -f config/
|
||||
```
|
||||
|
||||
A caveat of this approach is that it will not work if your container is
|
||||
configured with `imagePullPolicy: Always` because despite having the image
|
||||
locally, a pull is performed to ensure we have the latest version, it still
|
||||
exists, and that access hasn't been revoked. A workaround for this is to
|
||||
use `imagePullPolicy: IfNotPresent`, which should work well with `ko` in
|
||||
all contexts.
|
||||
|
||||
Images will appear in the Docker daemon as `ko.local/import.path.com/foo/cmd/bar`.
|
||||
With `--local` import paths are always preserved (see `--preserve-import-paths`).
|
||||
|
||||
## Configuration via `.ko.yaml`
|
||||
|
||||
While `ko` aims to have zero configuration, there are certain scenarios where
|
||||
you will want to override `ko`'s default behavior. This is done via `.ko.yaml`.
|
||||
|
||||
`.ko.yaml` is put into the directory from which `ko` will be invoked. One can
|
||||
override the directory with the `KO_CONFIG_PATH` environment variable.
|
||||
|
||||
If neither is present, then `ko` will rely on its default behaviors.
|
||||
|
||||
### Overriding the default base image
|
||||
|
||||
By default, `ko` makes use of `gcr.io/distroless/base:latest` as the base image
|
||||
for containers. There are a wide array of scenarios in which overriding this
|
||||
makes sense, for example:
|
||||
1. Pinning to a particular digest of this image for repeatable builds,
|
||||
1. Replacing this streamlined base image with another with better debugging
|
||||
tools (e.g. a shell, like `docker.io/library/ubuntu`).
|
||||
|
||||
The default base image `ko` uses can be changed by simply adding the following
|
||||
line to `.ko.yaml`:
|
||||
|
||||
```yaml
|
||||
defaultBaseImage: gcr.io/another-project/another-image@sha256:deadbeef
|
||||
```
|
||||
|
||||
### Overriding the base for particular imports
|
||||
|
||||
Some of your binaries may have requirements that are a more unique, and you
|
||||
may want to direct `ko` to use a particular base image for just those binaries.
|
||||
|
||||
The base image `ko` uses can be changed by adding the following to `.ko.yaml`:
|
||||
|
||||
```yaml
|
||||
baseImageOverrides:
|
||||
github.com/my-org/my-repo/path/to/binary: docker.io/another/base:latest
|
||||
```
|
||||
|
||||
### Why isn't `KO_DOCKER_REPO` part of `.ko.yaml`?
|
||||
|
||||
Once introduced to `.ko.yaml`, you may find yourself wondering: Why does it
|
||||
not hold the value of `$KO_DOCKER_REPO`?
|
||||
|
||||
The answer is that `.ko.yaml` is expected to sit in the root of a repository,
|
||||
and get checked in and versioned alongside your source code. This also means
|
||||
that the configured values will be shared across developers on a project, which
|
||||
for `KO_DOCKER_REPO` is actually undesireable because each developer is (likely)
|
||||
using their own docker repository and cluster.
|
||||
|
||||
|
||||
## Including static assets
|
||||
|
||||
A question that often comes up after using `ko` for a while is: "How do I
|
||||
include static assets in images produced with `ko`?".
|
||||
|
||||
For this, `ko` builds around an idiom similar to `go test` and `testdata/`.
|
||||
`ko` will include all of the data under `<import path>/kodata/...` in the
|
||||
images it produces.
|
||||
|
||||
These files are placed under `/var/run/ko/...`, but the appropriate mechanism
|
||||
for referencing them should be through the `KO_DATA_PATH` environment variable.
|
||||
The intent of this is to enable users to test things outside of `ko` as follows:
|
||||
|
||||
```shell
|
||||
KO_DATA_PATH=$PWD/cmd/ko/test/kodata go run ./cmd/ko/test/*.go
|
||||
2018/07/19 23:35:20 Hello there
|
||||
```
|
||||
|
||||
This produces identical output to being run within the container locally:
|
||||
|
||||
```shell
|
||||
ko publish -L ./cmd/test
|
||||
2018/07/19 23:36:11 Using base gcr.io/distroless/base:latest for github.com/google/ko/cmd/test
|
||||
2018/07/19 23:36:12 Loading ko.local/github.com/google/ko/cmd/test:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
2018/07/19 23:36:13 Loaded ko.local/github.com/google/ko/cmd/test:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
|
||||
docker run -ti --rm ko.local/github.com/google/ko/cmd/test:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
2018/07/19 23:36:25 Hello there
|
||||
```
|
||||
|
||||
... or on cluster:
|
||||
|
||||
```shell
|
||||
ko apply -f cmd/ko/test/test.yaml
|
||||
2018/07/19 23:38:24 Using base gcr.io/distroless/base:latest for github.com/google/ko/cmd/test
|
||||
2018/07/19 23:38:25 Publishing us.gcr.io/my-project/test-294a7bdc57d85dc6ddeef5ba38a59fe9:latest
|
||||
2018/07/19 23:38:26 mounted blob: sha256:988abcba36b5948da8baa1e3616b94c0b56da814b8f6ff3ae3ac316e375e093a
|
||||
2018/07/19 23:38:26 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 23:38:26 mounted blob: sha256:f24d43c24e22298ed99ea125af6c1b828ae07716968f78cb6d09d4291a13f2d3
|
||||
2018/07/19 23:38:26 mounted blob: sha256:7a7bafbc2ae1bf844c47b33025dd459913a3fece0a94b1f3ced860675be2b79c
|
||||
2018/07/19 23:38:27 us.gcr.io/my-project/test-294a7bdc57d85dc6ddeef5ba38a59fe9:latest: digest: sha256:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577 size: 751
|
||||
2018/07/19 23:38:27 Published us.gcr.io/my-project/test-294a7bdc57d85dc6ddeef5ba38a59fe9@sha256:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
pod/kodata created
|
||||
|
||||
kubectl logs kodata
|
||||
2018/07/19 23:38:29 Hello there
|
||||
```
|
||||
|
||||
## Relevance to Release Management
|
||||
|
||||
`ko` is also useful for helping manage releases. For example, if your project
|
||||
periodically releases a set of images and configuration to launch those images
|
||||
on a Kubernetes cluster, release binaries may be published and the configuration
|
||||
generated via:
|
||||
|
||||
```shell
|
||||
export PROJECT_ID=<YOUR RELEASE PROJECT>
|
||||
export KO_DOCKER_REPO="gcr.io/${PROJECT_ID}"
|
||||
ko resolve -f config/ > release.yaml
|
||||
```
|
||||
|
||||
> Note that in this context it is recommended that you also provide `-P`, if
|
||||
> supported by your Docker registry. This improves users' ability to tie release
|
||||
> binaries back to their source.
|
||||
|
||||
This will publish all of the binary components as container images to
|
||||
`gcr.io/my-releases/...` and create a `release.yaml` file containing all of the
|
||||
configuration for your application with inlined image references.
|
||||
|
||||
This resulting configuration may then be installed onto Kubernetes clusters via:
|
||||
|
||||
```shell
|
||||
kubectl apply -f release.yaml
|
||||
```
|
||||
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This work is based heavily on learnings from having built the
|
||||
[Docker](https://github.com/bazelbuild/rules_docker) and
|
||||
[Kubernetes](https://github.com/bazelbuild/rules_k8s) support for
|
||||
[Bazel](https://bazel.build). That work was presented
|
||||
[here](https://www.youtube.com/watch?v=RS1aiQqgUTA).
|
420
cmd/README.md
Normal file
420
cmd/README.md
Normal file
@ -0,0 +1,420 @@
|
||||
# `ko`
|
||||
|
||||
`ko` is a CLI to support rapid development of Go containers
|
||||
with Kubernetes and minimal configuration.
|
||||
|
||||
## Installation
|
||||
|
||||
`ko` can be installed via:
|
||||
|
||||
```shell
|
||||
go get github.com/google/go-containerregistry/cmd/ko
|
||||
```
|
||||
|
||||
To update your installation:
|
||||
|
||||
```shell
|
||||
go get -u github.com/google/go-containerregistry/cmd/ko
|
||||
```
|
||||
|
||||
## The `ko` Model
|
||||
|
||||
`ko` is built around a very simple extension to Go's model for expressing
|
||||
dependencies using [import paths](https://golang.org/doc/code.html#ImportPaths).
|
||||
|
||||
In Go, dependencies are expressed via blocks like:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
)
|
||||
```
|
||||
|
||||
Similarly (as you can see above), Go binaries can be referenced via import
|
||||
paths like `github.com/google/go-containerregistry/cmd/ko`.
|
||||
|
||||
**One of the goals of `ko` is to make containers invisible infrastructure.**
|
||||
Simply replace image references in your Kubernetes yaml with the import path for
|
||||
your Go binary, and `ko` will handle containerizing and publishing that
|
||||
container image as needed.
|
||||
|
||||
For example, you might use the following in a Kubernetes `Deployment` resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
foo: bar
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
spec:
|
||||
containers:
|
||||
- name: hello-world
|
||||
# This is the import path for the Go binary to build and run.
|
||||
image: github.com/mattmoor/examples/http/cmd/helloworld
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
```
|
||||
|
||||
### Determining supported import paths
|
||||
|
||||
Similar to other tooling in the Go ecosystem, `ko` expects to execute in the
|
||||
context of your `$GOPATH`. This is used to determine what package(s) `ko`
|
||||
is expected to build.
|
||||
|
||||
Suppose `GOPATH` is `~/gopath` and the current directory is
|
||||
`~/gopath/src/github.com/mattmoor/examples`. `ko` will deduce the base import
|
||||
path to be `github.com/mattmoor/examples`, and any references to subpackages
|
||||
of this will be built, containerized and published.
|
||||
|
||||
For example, any of the following would be matched:
|
||||
* `github.com/mattmoor/examples`
|
||||
* `github.com/mattmoor/examples/cmd/foo`
|
||||
* `github.com/mattmoor/examples/bar`
|
||||
|
||||
### Results
|
||||
|
||||
Employing this convention enables `ko` to have effectively zero configuration
|
||||
and enable very fast development iteration. For
|
||||
[warm-image](https://github.com/mattmoor/warm-image), `ko` is able to
|
||||
build, containerize, and redeploy a non-trivial Kubernetes controller app in
|
||||
seconds (dominated by two `go build`s).
|
||||
|
||||
```shell
|
||||
$ ko apply -f config/
|
||||
2018/07/19 14:56:41 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:56:42 Publishing us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest
|
||||
2018/07/19 14:56:43 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:56:43 mounted blob: sha256:59df9d5b488aea2753ab7774ae41a9a3e96903f87ac699f3505960e744f36f7d
|
||||
2018/07/19 14:56:43 mounted blob: sha256:739b3deec2edb17c512f507894c55c2681f9724191d820cdc01f668330724ca7
|
||||
2018/07/19 14:56:44 us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest: digest: sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326 size: 592
|
||||
2018/07/19 14:56:44 Published us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37@sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326
|
||||
2018/07/19 14:56:45 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/controller
|
||||
2018/07/19 14:56:46 Publishing us.gcr.io/my-project/controller-9e91872fd7c48124dbe6ea83944b87e9:latest
|
||||
2018/07/19 14:56:46 mounted blob: sha256:007782ba6738188a59bf21b4d8e974f218615ee948c6357535d07e7248b2a560
|
||||
2018/07/19 14:56:46 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:56:46 mounted blob: sha256:7fec050f965d7fba3de4bd19739746dce5a5125331b7845bf02185ff5d4cc374
|
||||
2018/07/19 14:56:47 us.gcr.io/my-project/controller-9e91872fd7c48124dbe6ea83944b87e9:latest: digest: sha256:5a81029bb0cfd519c321aeeea2bc1b7dc6488b6c72003d3613442b4d5e4ed14d size: 593
|
||||
2018/07/19 14:56:47 Published us.gcr.io/my-project/controller-9e91872fd7c48124dbe6ea83944b87e9@sha256:5a81029bb0cfd519c321aeeea2bc1b7dc6488b6c72003d3613442b4d5e4ed14d
|
||||
namespace/warmimage-system configured
|
||||
clusterrolebinding.rbac.authorization.k8s.io/warmimage-controller-admin configured
|
||||
deployment.apps/warmimage-controller unchanged
|
||||
serviceaccount/warmimage-controller unchanged
|
||||
customresourcedefinition.apiextensions.k8s.io/warmimages.mattmoor.io configured
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`ko` has four commands, most of which build and publish images as part of
|
||||
their execution. By default, `ko` publishes images to a Docker Registry
|
||||
specified via `KO_DOCKER_REPO`.
|
||||
|
||||
However, these same commands can be directed to operate locally as well via
|
||||
the `--local` or `-L` command (or setting `KO_DOCKER_REPO=ko.local`). See
|
||||
the [`minikube` section](./README.md#with-minikube) for more detail.
|
||||
|
||||
|
||||
### `ko publish`
|
||||
|
||||
`ko publish` simply builds and publishes images for each import path passed as
|
||||
an argument. It prints the images' published digests after each image is published.
|
||||
|
||||
```shell
|
||||
$ ko publish github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:57:34 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:57:35 Publishing us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest
|
||||
2018/07/19 14:57:35 mounted blob: sha256:739b3deec2edb17c512f507894c55c2681f9724191d820cdc01f668330724ca7
|
||||
2018/07/19 14:57:35 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:57:35 mounted blob: sha256:59df9d5b488aea2753ab7774ae41a9a3e96903f87ac699f3505960e744f36f7d
|
||||
2018/07/19 14:57:36 us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest: digest: sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326 size: 592
|
||||
2018/07/19 14:57:36 Published us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37@sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326
|
||||
```
|
||||
|
||||
`ko publish` also supports relative import paths, when in the context of a repo on `GOPATH`.
|
||||
|
||||
```shell
|
||||
$ ko publish ./cmd/sleeper
|
||||
2018/07/19 14:58:16 Using base gcr.io/distroless/base:latest for github.com/mattmoor/warm-image/cmd/sleeper
|
||||
2018/07/19 14:58:16 Publishing us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest
|
||||
2018/07/19 14:58:17 mounted blob: sha256:59df9d5b488aea2753ab7774ae41a9a3e96903f87ac699f3505960e744f36f7d
|
||||
2018/07/19 14:58:17 mounted blob: sha256:739b3deec2edb17c512f507894c55c2681f9724191d820cdc01f668330724ca7
|
||||
2018/07/19 14:58:17 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 14:58:18 us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37:latest: digest: sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326 size: 592
|
||||
2018/07/19 14:58:18 Published us.gcr.io/my-project/sleeper-ebdb8b8b13d4bbe1d3592de055016d37@sha256:6c7b96a294cad3ce613aac23c8aca5f9dd12a894354ab276c157fb5c1c2e3326
|
||||
```
|
||||
|
||||
### `ko resolve`
|
||||
|
||||
`ko resolve` takes Kubernetes yaml files in the style of `kubectl apply`
|
||||
and (based on the [model above](#the-ko-model)) determines the set of
|
||||
Go import paths to build, containerize, and publish.
|
||||
|
||||
The output of `ko resolve` is the concatenated yaml with import paths
|
||||
replaced with published image digests. Following the example above,
|
||||
this would be:
|
||||
|
||||
```shell
|
||||
# Command
|
||||
export PROJECT_ID=$(gcloud config get-value core/project)
|
||||
export KO_DOCKER_REPO="gcr.io/${PROJECT_ID}"
|
||||
ko resolve -f deployment.yaml
|
||||
|
||||
# Output
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello-world
|
||||
# This is the digest of the published image containing the go binary.
|
||||
image: gcr.io/your-project/helloworld-badf00d@sha256:deadbeef
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
```
|
||||
|
||||
Some Docker Registries (e.g. gcr.io) support multi-level repository names. For
|
||||
these registries, it is often useful for discoverability and provenance to
|
||||
preserve the full import path, for this we expose `--preserve-import-paths`,
|
||||
or `-P` for short.
|
||||
|
||||
```shell
|
||||
# Command
|
||||
export PROJECT_ID=$(gcloud config get-value core/project)
|
||||
export KO_DOCKER_REPO="gcr.io/${PROJECT_ID}"
|
||||
ko resolve -P -f deployment.yaml
|
||||
|
||||
# Output
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello-world
|
||||
# This is the digest of the published image containing the go binary
|
||||
# at the embedded import path.
|
||||
image: gcr.io/your-project/github.com/mattmoor/examples/http/cmd/helloworld@sha256:deadbeef
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
```
|
||||
|
||||
It is notable that this is not the default (anymore) because certain popular
|
||||
registries (including Docker Hub) do not support multi-level repository names.
|
||||
|
||||
### `ko apply`
|
||||
|
||||
`ko apply` is intended to parallel `kubectl apply`, but acts on the same
|
||||
resolved output as `ko resolve` emits. It is expected that `ko apply` will act
|
||||
as the vehicle for rapid iteration during development. As changes are made to a
|
||||
particular application, you can run: `ko apply -f unit.yaml` to rapidly
|
||||
rebuild, repush, and redeploy their changes.
|
||||
|
||||
`ko apply` will invoke `kubectl apply` under the covers, and therefore apply
|
||||
to whatever `kubectl` context is active.
|
||||
|
||||
### `ko apply --watch` (EXPERIMENTAL)
|
||||
|
||||
The `--watch` flag (`-W` for short) does an initial `apply` as above, but as it
|
||||
does, it builds up a dependency graph of your program and starts to continuously
|
||||
monitor the filesystem for changes. When a file changes, it re-applies any yamls
|
||||
that are affected.
|
||||
|
||||
For example, if I edit `github.com/foo/bar/pkg/baz/blah.go`, the tool sees that
|
||||
the `github.com/foo/bar/pkg/baz` package has changed, and perhaps both
|
||||
`github.com/foo/bar/cmd/one` and `github.com/foo/bar/cmd/two` consume that library
|
||||
and were referenced by `config/one-deploy.yaml` and `config/two-deploy.yaml`.
|
||||
The edit would effectively result in a re-application of:
|
||||
|
||||
```
|
||||
ko apply -f config/one-deploy.yaml -f config/two-deploy.yaml
|
||||
```
|
||||
|
||||
This flag is still experimental, and feedback is very welcome.
|
||||
|
||||
### `ko delete`
|
||||
|
||||
`ko delete` simply passes through to `kubectl delete`. It is exposed purely out
|
||||
of convenience for cleaning up resources created through `ko apply`.
|
||||
|
||||
|
||||
## With `minikube`
|
||||
|
||||
You can use `ko` with `minikube` via a Docker Registry, but this involves
|
||||
publishing images only to pull them back down to your machine again. To avoid
|
||||
this, `ko` exposes `--local` or `-L` options to instead publish the images to
|
||||
the local machine's Docker daemon.
|
||||
|
||||
This would look something like:
|
||||
|
||||
```shell
|
||||
# Use the minikube docker daemon.
|
||||
eval $(minikube docker-env)
|
||||
|
||||
# Make sure minikube is the current kubectl context.
|
||||
kubectl config use-context minikube
|
||||
|
||||
# Deploy to minikube w/o registry.
|
||||
ko apply -L -f config/
|
||||
|
||||
# This is the same as above.
|
||||
KO_DOCKER_REPO=ko.local ko apply -f config/
|
||||
```
|
||||
|
||||
A caveat of this approach is that it will not work if your container is
|
||||
configured with `imagePullPolicy: Always` because despite having the image
|
||||
locally, a pull is performed to ensure we have the latest version, it still
|
||||
exists, and that access hasn't been revoked. A workaround for this is to
|
||||
use `imagePullPolicy: IfNotPresent`, which should work well with `ko` in
|
||||
all contexts.
|
||||
|
||||
Images will appear in the Docker daemon as `ko.local/import.path.com/foo/cmd/bar`.
|
||||
With `--local` import paths are always preserved (see `--preserve-import-paths`).
|
||||
|
||||
## Configuration via `.ko.yaml`
|
||||
|
||||
While `ko` aims to have zero configuration, there are certain scenarios where
|
||||
you will want to override `ko`'s default behavior. This is done via `.ko.yaml`.
|
||||
|
||||
`.ko.yaml` is put into the directory from which `ko` will be invoked. One can
|
||||
override the directory with the `KO_CONFIG_PATH` environment variable.
|
||||
|
||||
If neither is present, then `ko` will rely on its default behaviors.
|
||||
|
||||
### Overriding the default base image
|
||||
|
||||
By default, `ko` makes use of `gcr.io/distroless/base:latest` as the base image
|
||||
for containers. There are a wide array of scenarios in which overriding this
|
||||
makes sense, for example:
|
||||
1. Pinning to a particular digest of this image for repeatable builds,
|
||||
1. Replacing this streamlined base image with another with better debugging
|
||||
tools (e.g. a shell, like `docker.io/library/ubuntu`).
|
||||
|
||||
The default base image `ko` uses can be changed by simply adding the following
|
||||
line to `.ko.yaml`:
|
||||
|
||||
```yaml
|
||||
defaultBaseImage: gcr.io/another-project/another-image@sha256:deadbeef
|
||||
```
|
||||
|
||||
### Overriding the base for particular imports
|
||||
|
||||
Some of your binaries may have requirements that are a more unique, and you
|
||||
may want to direct `ko` to use a particular base image for just those binaries.
|
||||
|
||||
The base image `ko` uses can be changed by adding the following to `.ko.yaml`:
|
||||
|
||||
```yaml
|
||||
baseImageOverrides:
|
||||
github.com/my-org/my-repo/path/to/binary: docker.io/another/base:latest
|
||||
```
|
||||
|
||||
### Why isn't `KO_DOCKER_REPO` part of `.ko.yaml`?
|
||||
|
||||
Once introduced to `.ko.yaml`, you may find yourself wondering: Why does it
|
||||
not hold the value of `$KO_DOCKER_REPO`?
|
||||
|
||||
The answer is that `.ko.yaml` is expected to sit in the root of a repository,
|
||||
and get checked in and versioned alongside your source code. This also means
|
||||
that the configured values will be shared across developers on a project, which
|
||||
for `KO_DOCKER_REPO` is actually undesireable because each developer is (likely)
|
||||
using their own docker repository and cluster.
|
||||
|
||||
|
||||
## Including static assets
|
||||
|
||||
A question that often comes up after using `ko` for a while is: "How do I
|
||||
include static assets in images produced with `ko`?".
|
||||
|
||||
For this, `ko` builds around an idiom similar to `go test` and `testdata/`.
|
||||
`ko` will include all of the data under `<import path>/kodata/...` in the
|
||||
images it produces.
|
||||
|
||||
These files are placed under `/var/run/ko/...`, but the appropriate mechanism
|
||||
for referencing them should be through the `KO_DATA_PATH` environment variable.
|
||||
The intent of this is to enable users to test things outside of `ko` as follows:
|
||||
|
||||
```shell
|
||||
KO_DATA_PATH=$PWD/cmd/ko/test/kodata go run ./cmd/ko/test/*.go
|
||||
2018/07/19 23:35:20 Hello there
|
||||
```
|
||||
|
||||
This produces identical output to being run within the container locally:
|
||||
|
||||
```shell
|
||||
ko publish -L ./cmd/ko/test
|
||||
2018/07/19 23:36:11 Using base gcr.io/distroless/base:latest for github.com/google/go-containerregistry/cmd/ko/test
|
||||
2018/07/19 23:36:12 Loading ko.local/github.com/google/go-containerregistry/cmd/ko/test:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
2018/07/19 23:36:13 Loaded ko.local/github.com/google/go-containerregistry/cmd/ko/test:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
|
||||
docker run -ti --rm ko.local/github.com/google/go-containerregistry/cmd/ko/test:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
2018/07/19 23:36:25 Hello there
|
||||
```
|
||||
|
||||
... or on cluster:
|
||||
|
||||
```shell
|
||||
ko apply -f cmd/ko/test/test.yaml
|
||||
2018/07/19 23:38:24 Using base gcr.io/distroless/base:latest for github.com/google/go-containerregistry/cmd/ko/test
|
||||
2018/07/19 23:38:25 Publishing us.gcr.io/my-project/test-294a7bdc57d85dc6ddeef5ba38a59fe9:latest
|
||||
2018/07/19 23:38:26 mounted blob: sha256:988abcba36b5948da8baa1e3616b94c0b56da814b8f6ff3ae3ac316e375e093a
|
||||
2018/07/19 23:38:26 mounted blob: sha256:57752e7f9593cbfb7101af994b136a369ecc8174332866622db32a264f3fbefd
|
||||
2018/07/19 23:38:26 mounted blob: sha256:f24d43c24e22298ed99ea125af6c1b828ae07716968f78cb6d09d4291a13f2d3
|
||||
2018/07/19 23:38:26 mounted blob: sha256:7a7bafbc2ae1bf844c47b33025dd459913a3fece0a94b1f3ced860675be2b79c
|
||||
2018/07/19 23:38:27 us.gcr.io/my-project/test-294a7bdc57d85dc6ddeef5ba38a59fe9:latest: digest: sha256:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577 size: 751
|
||||
2018/07/19 23:38:27 Published us.gcr.io/my-project/test-294a7bdc57d85dc6ddeef5ba38a59fe9@sha256:703c205bf2f405af520b40536b87aafadcf181562b8faa6690fd2992084c8577
|
||||
pod/kodata created
|
||||
|
||||
kubectl logs kodata
|
||||
2018/07/19 23:38:29 Hello there
|
||||
```
|
||||
|
||||
## Relevance to Release Management
|
||||
|
||||
`ko` is also useful for helping manage releases. For example, if your project
|
||||
periodically releases a set of images and configuration to launch those images
|
||||
on a Kubernetes cluster, release binaries may be published and the configuration
|
||||
generated via:
|
||||
|
||||
```shell
|
||||
export PROJECT_ID=<YOUR RELEASE PROJECT>
|
||||
export KO_DOCKER_REPO="gcr.io/${PROJECT_ID}"
|
||||
ko resolve -f config/ > release.yaml
|
||||
```
|
||||
|
||||
> Note that in this context it is recommended that you also provide `-P`, if
|
||||
> supported by your Docker registry. This improves users' ability to tie release
|
||||
> binaries back to their source.
|
||||
|
||||
This will publish all of the binary components as container images to
|
||||
`gcr.io/my-releases/...` and create a `release.yaml` file containing all of the
|
||||
configuration for your application with inlined image references.
|
||||
|
||||
This resulting configuration may then be installed onto Kubernetes clusters via:
|
||||
|
||||
```shell
|
||||
kubectl apply -f release.yaml
|
||||
```
|
||||
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This work is based heavily on learnings from having built the
|
||||
[Docker](https://github.com/bazelbuild/rules_docker) and
|
||||
[Kubernetes](https://github.com/bazelbuild/rules_k8s) support for
|
||||
[Bazel](https://bazel.build). That work was presented
|
||||
[here](https://www.youtube.com/watch?v=RS1aiQqgUTA).
|
30
cmd/binary.go
Normal file
30
cmd/binary.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// BinaryOptions represents options for the ko binary.
|
||||
type BinaryOptions struct {
|
||||
// Path is the import path of the binary to publish.
|
||||
Path string
|
||||
}
|
||||
|
||||
func addImageArg(cmd *cobra.Command, lo *BinaryOptions) {
|
||||
cmd.Flags().StringVarP(&lo.Path, "image", "i", lo.Path,
|
||||
"The import path of the binary to publish.")
|
||||
}
|
284
cmd/commands.go
Normal file
284
cmd/commands.go
Normal file
@ -0,0 +1,284 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
// runCmd is suitable for use with cobra.Command's Run field.
|
||||
type runCmd func(*cobra.Command, []string)
|
||||
|
||||
// passthru returns a runCmd that simply passes our CLI arguments
|
||||
// through to a binary named command.
|
||||
func passthru(command string) runCmd {
|
||||
return func(_ *cobra.Command, _ []string) {
|
||||
// Start building a command line invocation by passing
|
||||
// through our arguments to command's CLI.
|
||||
cmd := exec.Command(command, os.Args[1:]...)
|
||||
|
||||
// Pass through our environment
|
||||
cmd.Env = os.Environ()
|
||||
// Pass through our stdfoo
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
// Run it.
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatalf("error executing %q command with args: %v; %v", command, os.Args[1:], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addKubeCommands augments our CLI surface with a passthru delete command, and an apply
|
||||
// command that realizes the promise of ko, as outlined here:
|
||||
// https://github.com/google/go-containerregistry/issues/80
|
||||
func addKubeCommands(topLevel *cobra.Command) {
|
||||
topLevel.AddCommand(&cobra.Command{
|
||||
Use: "delete",
|
||||
Short: `See "kubectl help delete" for detailed usage.`,
|
||||
Run: passthru("kubectl"),
|
||||
// We ignore unknown flags to avoid importing everything Go exposes
|
||||
// from our commands.
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
UnknownFlags: true,
|
||||
},
|
||||
})
|
||||
|
||||
koApplyFlags := []string{}
|
||||
lo := &LocalOptions{}
|
||||
bo := &BinaryOptions{}
|
||||
no := &NameOptions{}
|
||||
fo := &FilenameOptions{}
|
||||
ta := &TagsOptions{}
|
||||
apply := &cobra.Command{
|
||||
Use: "apply -f FILENAME",
|
||||
Short: "Apply the input files with image references resolved to built/pushed image digests.",
|
||||
Long: `This sub-command finds import path references within the provided files, builds them into Go binaries, containerizes them, publishes them, and then feeds the resulting yaml into "kubectl apply".`,
|
||||
Example: `
|
||||
# Build and publish import path references to a Docker
|
||||
# Registry as:
|
||||
# ${KO_DOCKER_REPO}/<package name>-<hash of import path>
|
||||
# Then, feed the resulting yaml into "kubectl apply".
|
||||
# When KO_DOCKER_REPO is ko.local, it is the same as if
|
||||
# --local was passed.
|
||||
ko apply -f config/
|
||||
|
||||
# Build and publish import path references to a Docker
|
||||
# Registry preserving import path names as:
|
||||
# ${KO_DOCKER_REPO}/<import path>
|
||||
# Then, feed the resulting yaml into "kubectl apply".
|
||||
ko apply --preserve-import-paths -f config/
|
||||
|
||||
# Build and publish import path references to a Docker
|
||||
# daemon as:
|
||||
# ko.local/<import path>
|
||||
# Then, feed the resulting yaml into "kubectl apply".
|
||||
ko apply --local -f config/
|
||||
|
||||
# Apply from stdin:
|
||||
cat config.yaml | ko apply -f -`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Create a set of ko-specific flags to ignore when passing through
|
||||
// kubectl global flags.
|
||||
ignoreSet := make(map[string]struct{})
|
||||
for _, s := range koApplyFlags {
|
||||
ignoreSet[s] = struct{}{}
|
||||
}
|
||||
|
||||
// Filter out ko flags from what we will pass through to kubectl.
|
||||
kubectlFlags := []string{}
|
||||
cmd.Flags().Visit(func(flag *pflag.Flag) {
|
||||
if _, ok := ignoreSet[flag.Name]; !ok {
|
||||
kubectlFlags = append(kubectlFlags, "--"+flag.Name, flag.Value.String())
|
||||
}
|
||||
})
|
||||
|
||||
// Issue a "kubectl apply" command reading from stdin,
|
||||
// to which we will pipe the resolved files.
|
||||
argv := []string{"apply", "-f", "-"}
|
||||
argv = append(argv, kubectlFlags...)
|
||||
kubectlCmd := exec.Command("kubectl", argv...)
|
||||
|
||||
// Pass through our environment
|
||||
kubectlCmd.Env = os.Environ()
|
||||
// Pass through our std{out,err} and make our resolved buffer stdin.
|
||||
kubectlCmd.Stderr = os.Stderr
|
||||
kubectlCmd.Stdout = os.Stdout
|
||||
|
||||
// Wire up kubectl stdin to resolveFilesToWriter.
|
||||
stdin, err := kubectlCmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatalf("error piping to 'kubectl apply': %v", err)
|
||||
}
|
||||
go resolveFilesToWriter(fo, no, lo, ta, stdin)
|
||||
|
||||
// Run it.
|
||||
if err := kubectlCmd.Run(); err != nil {
|
||||
log.Fatalf("error executing 'kubectl apply': %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
addLocalArg(apply, lo)
|
||||
addNamingArgs(apply, no)
|
||||
addFileArg(apply, fo)
|
||||
addTagsArg(apply, ta)
|
||||
|
||||
// Collect the ko-specific apply flags before registering the kubectl global
|
||||
// flags so that we can ignore them when passing kubectl global flags through
|
||||
// to kubectl.
|
||||
apply.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
koApplyFlags = append(koApplyFlags, flag.Name)
|
||||
})
|
||||
|
||||
// Register the kubectl global flags.
|
||||
kubeConfigFlags := genericclioptions.NewConfigFlags()
|
||||
kubeConfigFlags.AddFlags(apply.Flags())
|
||||
|
||||
topLevel.AddCommand(apply)
|
||||
|
||||
resolve := &cobra.Command{
|
||||
Use: "resolve -f FILENAME",
|
||||
Short: "Print the input files with image references resolved to built/pushed image digests.",
|
||||
Long: `This sub-command finds import path references within the provided files, builds them into Go binaries, containerizes them, publishes them, and prints the resulting yaml.`,
|
||||
Example: `
|
||||
# Build and publish import path references to a Docker
|
||||
# Registry as:
|
||||
# ${KO_DOCKER_REPO}/<package name>-<hash of import path>
|
||||
# When KO_DOCKER_REPO is ko.local, it is the same as if
|
||||
# --local and --preserve-import-paths were passed.
|
||||
ko resolve -f config/
|
||||
|
||||
# Build and publish import path references to a Docker
|
||||
# Registry preserving import path names as:
|
||||
# ${KO_DOCKER_REPO}/<import path>
|
||||
# When KO_DOCKER_REPO is ko.local, it is the same as if
|
||||
# --local was passed.
|
||||
ko resolve --preserve-import-paths -f config/
|
||||
|
||||
# Build and publish import path references to a Docker
|
||||
# daemon as:
|
||||
# ko.local/<import path>
|
||||
# This always preserves import paths.
|
||||
ko resolve --local -f config/`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
resolveFilesToWriter(fo, no, lo, ta, os.Stdout)
|
||||
},
|
||||
}
|
||||
addLocalArg(resolve, lo)
|
||||
addNamingArgs(resolve, no)
|
||||
addFileArg(resolve, fo)
|
||||
addTagsArg(resolve, ta)
|
||||
topLevel.AddCommand(resolve)
|
||||
|
||||
publish := &cobra.Command{
|
||||
Use: "publish IMPORTPATH...",
|
||||
Short: "Build and publish container images from the given importpaths.",
|
||||
Long: `This sub-command builds the provided import paths into Go binaries, containerizes them, and publishes them.`,
|
||||
Example: `
|
||||
# Build and publish import path references to a Docker
|
||||
# Registry as:
|
||||
# ${KO_DOCKER_REPO}/<package name>-<hash of import path>
|
||||
# When KO_DOCKER_REPO is ko.local, it is the same as if
|
||||
# --local and --preserve-import-paths were passed.
|
||||
ko publish github.com/foo/bar/cmd/baz github.com/foo/bar/cmd/blah
|
||||
|
||||
# Build and publish a relative import path as:
|
||||
# ${KO_DOCKER_REPO}/<package name>-<hash of import path>
|
||||
# When KO_DOCKER_REPO is ko.local, it is the same as if
|
||||
# --local and --preserve-import-paths were passed.
|
||||
ko publish ./cmd/blah
|
||||
|
||||
# Build and publish a relative import path as:
|
||||
# ${KO_DOCKER_REPO}/<import path>
|
||||
# When KO_DOCKER_REPO is ko.local, it is the same as if
|
||||
# --local was passed.
|
||||
ko publish --preserve-import-paths ./cmd/blah
|
||||
|
||||
# Build and publish import path references to a Docker
|
||||
# daemon as:
|
||||
# ko.local/<import path>
|
||||
# This always preserves import paths.
|
||||
ko publish --local github.com/foo/bar/cmd/baz github.com/foo/bar/cmd/blah`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
publishImages(args, no, lo, ta)
|
||||
},
|
||||
}
|
||||
addLocalArg(publish, lo)
|
||||
addNamingArgs(publish, no)
|
||||
addTagsArg(publish, ta)
|
||||
topLevel.AddCommand(publish)
|
||||
|
||||
run := &cobra.Command{
|
||||
Use: "run NAME --image=IMPORTPATH",
|
||||
Short: "A variant of `kubectl run` that containerizes IMPORTPATH first.",
|
||||
Long: `This sub-command combines "ko publish" and "kubectl run" to support containerizing and running Go binaries on Kubernetes in a single command.`,
|
||||
Example: `
|
||||
# Publish the --image and run it on Kubernetes as:
|
||||
# ${KO_DOCKER_REPO}/<package name>-<hash of import path>
|
||||
# When KO_DOCKER_REPO is ko.local, it is the same as if
|
||||
# --local and --preserve-import-paths were passed.
|
||||
ko run foo --image=github.com/foo/bar/cmd/baz
|
||||
|
||||
# This supports relative import paths as well.
|
||||
ko run foo --image=./cmd/baz`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
imgs := publishImages([]string{bo.Path}, no, lo, ta)
|
||||
|
||||
// There's only one, but this is the simple way to access the
|
||||
// reference since the import path may have been qualified.
|
||||
for k, v := range imgs {
|
||||
log.Printf("Running %q", k)
|
||||
// Issue a "kubectl run" command with our same arguments,
|
||||
// but supply a second --image to override the one we intercepted.
|
||||
argv := append(os.Args[1:], "--image", v.String())
|
||||
kubectlCmd := exec.Command("kubectl", argv...)
|
||||
|
||||
// Pass through our environment
|
||||
kubectlCmd.Env = os.Environ()
|
||||
// Pass through our std*
|
||||
kubectlCmd.Stderr = os.Stderr
|
||||
kubectlCmd.Stdout = os.Stdout
|
||||
kubectlCmd.Stdin = os.Stdin
|
||||
|
||||
// Run it.
|
||||
if err := kubectlCmd.Run(); err != nil {
|
||||
log.Fatalf("error executing \"kubectl run\": %v", err)
|
||||
}
|
||||
}
|
||||
},
|
||||
// We ignore unknown flags to avoid importing everything Go exposes
|
||||
// from our commands.
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
addLocalArg(run, lo)
|
||||
addNamingArgs(run, no)
|
||||
addImageArg(run, bo)
|
||||
addTagsArg(run, ta)
|
||||
|
||||
topLevel.AddCommand(run)
|
||||
}
|
92
cmd/config.go
Normal file
92
cmd/config.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBaseImage name.Reference
|
||||
baseImageOverrides map[string]name.Reference
|
||||
)
|
||||
|
||||
func getBaseImage(s string) (v1.Image, error) {
|
||||
ref, ok := baseImageOverrides[s]
|
||||
if !ok {
|
||||
ref = defaultBaseImage
|
||||
}
|
||||
log.Printf("Using base %s for %s", ref, s)
|
||||
return remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
}
|
||||
|
||||
func getCreationTime() (*v1.Time, error) {
|
||||
epoch := os.Getenv("SOURCE_DATE_EPOCH")
|
||||
if epoch == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
seconds, err := strconv.ParseInt(epoch, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the environment variable SOURCE_DATE_EPOCH is invalid. It's must be a number of seconds since January 1st 1970, 00:00 UTC, got %v", err)
|
||||
}
|
||||
return &v1.Time{time.Unix(seconds, 0)}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// If omitted, use this base image.
|
||||
viper.SetDefault("defaultBaseImage", "gcr.io/distroless/static:latest")
|
||||
viper.SetConfigName(".ko") // .yaml is implicit
|
||||
|
||||
if override := os.Getenv("KO_CONFIG_PATH"); override != "" {
|
||||
viper.AddConfigPath(override)
|
||||
}
|
||||
|
||||
viper.AddConfigPath("./")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
log.Fatalf("error reading config file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ref := viper.GetString("defaultBaseImage")
|
||||
dbi, err := name.ParseReference(ref, name.WeakValidation)
|
||||
if err != nil {
|
||||
log.Fatalf("'defaultBaseImage': error parsing %q as image reference: %v", ref, err)
|
||||
}
|
||||
defaultBaseImage = dbi
|
||||
|
||||
baseImageOverrides = make(map[string]name.Reference)
|
||||
overrides := viper.GetStringMapString("baseImageOverrides")
|
||||
for k, v := range overrides {
|
||||
bi, err := name.ParseReference(v, name.WeakValidation)
|
||||
if err != nil {
|
||||
log.Fatalf("'baseImageOverrides': error parsing %q as image reference: %v", v, err)
|
||||
}
|
||||
baseImageOverrides[k] = bi
|
||||
}
|
||||
}
|
133
cmd/filestuff.go
Normal file
133
cmd/filestuff.go
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// FilenameOptions is from pkg/kubectl.
|
||||
type FilenameOptions struct {
|
||||
Filenames []string
|
||||
Recursive bool
|
||||
Watch bool
|
||||
}
|
||||
|
||||
func addFileArg(cmd *cobra.Command, fo *FilenameOptions) {
|
||||
// From pkg/kubectl
|
||||
cmd.Flags().StringSliceVarP(&fo.Filenames, "filename", "f", fo.Filenames,
|
||||
"Filename, directory, or URL to files to use to create the resource")
|
||||
cmd.Flags().BoolVarP(&fo.Recursive, "recursive", "R", fo.Recursive,
|
||||
"Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
|
||||
cmd.Flags().BoolVarP(&fo.Watch, "watch", "W", fo.Watch,
|
||||
"Continuously monitor the transitive dependencies of the passed yaml files, and redeploy whenever anything changes.")
|
||||
}
|
||||
|
||||
// Based heavily on pkg/kubectl
|
||||
func enumerateFiles(fo *FilenameOptions) chan string {
|
||||
files := make(chan string)
|
||||
go func() {
|
||||
// When we're done enumerating files, close the channel
|
||||
defer close(files)
|
||||
// When we are in --watch mode, we set up watches on the filesystem locations
|
||||
// that we are supplied and continuously stream files, until we are sent an
|
||||
// interrupt.
|
||||
var watcher *fsnotify.Watcher
|
||||
if fo.Watch {
|
||||
var err error
|
||||
watcher, err = fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatalf("Unexpected error initializing fsnotify: %v", err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
}
|
||||
for _, paths := range fo.Filenames {
|
||||
// Just pass through '-' as it is indicative of stdin.
|
||||
if paths == "-" {
|
||||
files <- paths
|
||||
continue
|
||||
}
|
||||
// For each of the "filenames" we are passed (file or directory) start a
|
||||
// "Walk" to enumerate all of the contained files recursively.
|
||||
err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If this is a directory, skip it if it isn't the current directory we are
|
||||
// processing (unless we are in recursive mode). If we decide to process
|
||||
// the directory, and we're in watch mode, then we set up a watch on the
|
||||
// directory.
|
||||
if fi.IsDir() {
|
||||
if path != paths && !fo.Recursive {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if watcher != nil {
|
||||
watcher.Add(path)
|
||||
}
|
||||
// We don't stream back directories, we just decide to skip them, or not.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't check extension if the filepath was passed explicitly
|
||||
if path != paths {
|
||||
switch filepath.Ext(path) {
|
||||
case ".json", ".yaml":
|
||||
// Process these.
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
// We weren't passed this explicitly, so elide the watch as we
|
||||
// are already watching the directory.
|
||||
} else {
|
||||
// We were passed this directly, and so we may not be watching the
|
||||
// directory, so watch this file explicitly.
|
||||
if watcher != nil {
|
||||
watcher.Add(path)
|
||||
}
|
||||
}
|
||||
|
||||
files <- path
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error enumerating files: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We're done watching the files we were passed and setting up watches.
|
||||
// Now listen for change events from the watches we set up and resend
|
||||
// files that change as if we just saw them (so they can be reprocessed).
|
||||
if watcher != nil {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
switch filepath.Ext(event.Name) {
|
||||
case ".json", ".yaml":
|
||||
files <- event.Name
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
log.Fatalf("Error watching: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return files
|
||||
}
|
52
cmd/flatname.go
Normal file
52
cmd/flatname.go
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NameOptions represents options for the ko binary.
|
||||
type NameOptions struct {
|
||||
// PreserveImportPaths preserves the full import path after KO_DOCKER_REPO.
|
||||
PreserveImportPaths bool
|
||||
// BaseImportPaths uses the base path without MD5 hash after KO_DOCKER_REPO.
|
||||
BaseImportPaths bool
|
||||
}
|
||||
|
||||
func addNamingArgs(cmd *cobra.Command, no *NameOptions) {
|
||||
cmd.Flags().BoolVarP(&no.PreserveImportPaths, "preserve-import-paths", "P", no.PreserveImportPaths,
|
||||
"Whether to preserve the full import path after KO_DOCKER_REPO.")
|
||||
cmd.Flags().BoolVarP(&no.BaseImportPaths, "base-import-paths", "B", no.BaseImportPaths,
|
||||
"Whether to use the base path without MD5 hash after KO_DOCKER_REPO.")
|
||||
}
|
||||
|
||||
func packageWithMD5(importpath string) string {
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(importpath))
|
||||
return filepath.Base(importpath) + "-" + hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func preserveImportPath(importpath string) string {
|
||||
return importpath
|
||||
}
|
||||
|
||||
func baseImportPaths(importpath string) string {
|
||||
return filepath.Base(importpath)
|
||||
}
|
30
cmd/local.go
Normal file
30
cmd/local.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// LocalOptions represents options for the ko binary.
|
||||
type LocalOptions struct {
|
||||
// Local publishes images to a local docker daemon.
|
||||
Local bool
|
||||
}
|
||||
|
||||
func addLocalArg(cmd *cobra.Command, lo *LocalOptions) {
|
||||
cmd.Flags().BoolVarP(&lo.Local, "local", "L", lo.Local,
|
||||
"Whether to publish images to a local docker daemon vs. a registry.")
|
||||
}
|
37
cmd/main.go
Normal file
37
cmd/main.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parent command to which all subcommands are added.
|
||||
cmds := &cobra.Command{
|
||||
Use: "ko",
|
||||
Short: "Rapidly iterate with Go, Containers, and Kubernetes.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
addKubeCommands(cmds)
|
||||
|
||||
if err := cmds.Execute(); err != nil {
|
||||
log.Fatalf("error during command execution: %v", err)
|
||||
}
|
||||
}
|
106
cmd/publish.go
Normal file
106
cmd/publish.go
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
gb "go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
|
||||
"github.com/google/ko/pkg/build"
|
||||
"github.com/google/ko/pkg/publish"
|
||||
)
|
||||
|
||||
func qualifyLocalImport(importpath, gopathsrc, pwd string) (string, error) {
|
||||
if !strings.HasPrefix(pwd, gopathsrc) {
|
||||
return "", fmt.Errorf("pwd (%q) must be on $GOPATH/src (%q) to support local imports", pwd, gopathsrc)
|
||||
}
|
||||
// Given $GOPATH/src and $PWD (which must be within $GOPATH/src), trim
|
||||
// off $GOPATH/src/ from $PWD and append local importpath to get the
|
||||
// fully-qualified importpath.
|
||||
return filepath.Join(strings.TrimPrefix(pwd, gopathsrc+string(filepath.Separator)), importpath), nil
|
||||
}
|
||||
|
||||
func publishImages(importpaths []string, no *NameOptions, lo *LocalOptions, ta *TagsOptions) map[string]name.Reference {
|
||||
opt, err := gobuildOptions()
|
||||
if err != nil {
|
||||
log.Fatalf("error setting up builder options: %v", err)
|
||||
}
|
||||
b, err := build.NewGo(opt...)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating go builder: %v", err)
|
||||
}
|
||||
imgs := make(map[string]name.Reference)
|
||||
for _, importpath := range importpaths {
|
||||
if gb.IsLocalImport(importpath) {
|
||||
// Qualify relative imports to their fully-qualified
|
||||
// import path, assuming $PWD is within $GOPATH/src.
|
||||
gopathsrc := filepath.Join(gb.Default.GOPATH, "src")
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("error getting current working directory: %v", err)
|
||||
}
|
||||
importpath, err = qualifyLocalImport(importpath, gopathsrc, pwd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if !b.IsSupportedReference(importpath) {
|
||||
log.Fatalf("importpath %q is not supported", importpath)
|
||||
}
|
||||
|
||||
img, err := b.Build(importpath)
|
||||
if err != nil {
|
||||
log.Fatalf("error building %q: %v", importpath, err)
|
||||
}
|
||||
var pub publish.Interface
|
||||
repoName := os.Getenv("KO_DOCKER_REPO")
|
||||
|
||||
var namer publish.Namer
|
||||
if no.PreserveImportPaths {
|
||||
namer = preserveImportPath
|
||||
} else if no.BaseImportPaths {
|
||||
namer = baseImportPaths
|
||||
} else {
|
||||
namer = packageWithMD5
|
||||
}
|
||||
|
||||
if lo.Local || repoName == publish.LocalDomain {
|
||||
pub = publish.NewDaemon(namer, ta.Tags)
|
||||
} else {
|
||||
if _, err := name.NewRepository(repoName, name.WeakValidation); err != nil {
|
||||
log.Fatalf("the environment variable KO_DOCKER_REPO must be set to a valid docker repository, got %v", err)
|
||||
}
|
||||
opts := []publish.Option{publish.WithAuthFromKeychain(authn.DefaultKeychain), publish.WithNamer(namer), publish.WithTags(ta.Tags)}
|
||||
pub, err = publish.NewDefault(repoName, opts...)
|
||||
if err != nil {
|
||||
log.Fatalf("error setting up default image publisher: %v", err)
|
||||
}
|
||||
}
|
||||
ref, err := pub.Publish(img, importpath)
|
||||
if err != nil {
|
||||
log.Fatalf("error publishing %s: %v", importpath, err)
|
||||
}
|
||||
imgs[importpath] = ref
|
||||
}
|
||||
return imgs
|
||||
}
|
48
cmd/publish_test.go
Normal file
48
cmd/publish_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQualifyLocalImport(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
importpath, gopathsrc, pwd, want string
|
||||
wantErr bool
|
||||
}{{
|
||||
importpath: "./cmd/foo",
|
||||
gopathsrc: "/home/go/src",
|
||||
pwd: "/home/go/src/github.com/my/repo",
|
||||
want: "github.com/my/repo/cmd/foo",
|
||||
}, {
|
||||
importpath: "./foo",
|
||||
gopathsrc: "/home/go/src",
|
||||
pwd: "/home/go/src/github.com/my/repo/cmd",
|
||||
want: "github.com/my/repo/cmd/foo",
|
||||
}, {
|
||||
// $PWD not on $GOPATH/src
|
||||
importpath: "./cmd/foo",
|
||||
gopathsrc: "/home/go/src",
|
||||
pwd: "/",
|
||||
wantErr: true,
|
||||
}} {
|
||||
got, err := qualifyLocalImport(c.importpath, c.gopathsrc, c.pwd)
|
||||
if gotErr := err != nil; gotErr != c.wantErr {
|
||||
t.Fatalf("qualifyLocalImport returned %v, wanted err? %t", err, c.wantErr)
|
||||
}
|
||||
if got != c.want {
|
||||
t.Fatalf("qualifyLocalImport(%q, %q, %q): got %q, want %q", c.importpath, c.gopathsrc, c.pwd, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
259
cmd/resolve.go
Normal file
259
cmd/resolve.go
Normal file
@ -0,0 +1,259 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/mattmoor/dep-notify/pkg/graph"
|
||||
|
||||
"github.com/google/ko/pkg/build"
|
||||
"github.com/google/ko/pkg/publish"
|
||||
"github.com/google/ko/pkg/resolve"
|
||||
)
|
||||
|
||||
func gobuildOptions() ([]build.Option, error) {
|
||||
creationTime, err := getCreationTime()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts := []build.Option{
|
||||
build.WithBaseImages(getBaseImage),
|
||||
}
|
||||
if creationTime != nil {
|
||||
opts = append(opts, build.WithCreationTime(*creationTime))
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func makeBuilder() (*build.Caching, error) {
|
||||
opt, err := gobuildOptions()
|
||||
if err != nil {
|
||||
log.Fatalf("error setting up builder options: %v", err)
|
||||
}
|
||||
innerBuilder, err := build.NewGo(opt...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// tl;dr Wrap builder in a caching builder.
|
||||
//
|
||||
// The caching builder should on Build calls:
|
||||
// - Check for a valid Build future
|
||||
// - if a valid Build future exists at the time of the request,
|
||||
// then block on it.
|
||||
// - if it does not, then initiate and record a Build future.
|
||||
// - When import paths are "affected" by filesystem changes during a
|
||||
// Watch, then invalidate their build futures *before* we put the
|
||||
// affected yaml files onto the channel
|
||||
//
|
||||
// This will benefit the following key cases:
|
||||
// 1. When the same import path is referenced across multiple yaml files
|
||||
// we can elide subsequent builds by blocking on the same image future.
|
||||
// 2. When an affected yaml file has multiple import paths (mostly unaffected)
|
||||
// we can elide the builds of unchanged import paths.
|
||||
return build.NewCaching(innerBuilder)
|
||||
}
|
||||
|
||||
func makePublisher(no *NameOptions, lo *LocalOptions, ta *TagsOptions) (publish.Interface, error) {
|
||||
// Create the publish.Interface that we will use to publish image references
|
||||
// to either a docker daemon or a container image registry.
|
||||
innerPublisher, err := func() (publish.Interface, error) {
|
||||
namer := func() publish.Namer {
|
||||
if no.PreserveImportPaths {
|
||||
return preserveImportPath
|
||||
}
|
||||
return packageWithMD5
|
||||
}()
|
||||
|
||||
repoName := os.Getenv("KO_DOCKER_REPO")
|
||||
if lo.Local || repoName == publish.LocalDomain {
|
||||
return publish.NewDaemon(namer, ta.Tags), nil
|
||||
}
|
||||
_, err := name.NewRepository(repoName, name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the environment variable KO_DOCKER_REPO must be set to a valid docker repository, got %v", err)
|
||||
}
|
||||
|
||||
return publish.NewDefault(repoName,
|
||||
publish.WithAuthFromKeychain(authn.DefaultKeychain),
|
||||
publish.WithNamer(namer),
|
||||
publish.WithTags(ta.Tags))
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap publisher in a memoizing publisher implementation.
|
||||
return publish.NewCaching(innerPublisher)
|
||||
}
|
||||
|
||||
// resolvedFuture represents a "future" for the bytes of a resolved file.
|
||||
type resolvedFuture chan []byte
|
||||
|
||||
func resolveFilesToWriter(fo *FilenameOptions, no *NameOptions, lo *LocalOptions, ta *TagsOptions, out io.WriteCloser) {
|
||||
defer out.Close()
|
||||
builder, err := makeBuilder()
|
||||
if err != nil {
|
||||
log.Fatalf("error creating builder: %v", err)
|
||||
}
|
||||
|
||||
// Wrap publisher in a memoizing publisher implementation.
|
||||
publisher, err := makePublisher(no, lo, ta)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating publisher: %v", err)
|
||||
}
|
||||
|
||||
// By having this as a channel, we can hook this up to a filesystem
|
||||
// watcher and leave `fs` open to stream the names of yaml files
|
||||
// affected by code changes (including the modification of existing or
|
||||
// creation of new yaml files).
|
||||
fs := enumerateFiles(fo)
|
||||
|
||||
// This tracks filename -> []importpath
|
||||
var sm sync.Map
|
||||
|
||||
var g graph.Interface
|
||||
var errCh chan error
|
||||
if fo.Watch {
|
||||
// Start a dep-notify process that on notifications scans the
|
||||
// file-to-recorded-build map and for each affected file resends
|
||||
// the filename along the channel.
|
||||
g, errCh, err = graph.New(func(ss graph.StringSet) {
|
||||
sm.Range(func(k, v interface{}) bool {
|
||||
key := k.(string)
|
||||
value := v.([]string)
|
||||
|
||||
for _, ip := range value {
|
||||
if ss.Has(ip) {
|
||||
// See the comment above about how "builder" works.
|
||||
builder.Invalidate(ip)
|
||||
fs <- key
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating dep-notify graph: %v", err)
|
||||
}
|
||||
// Cleanup the fsnotify hooks when we're done.
|
||||
defer g.Shutdown()
|
||||
}
|
||||
|
||||
var futures []resolvedFuture
|
||||
for {
|
||||
// Each iteration, if there is anything in the list of futures,
|
||||
// listen to it in addition to the file enumerating channel.
|
||||
// A nil channel is never available to receive on, so if nothing
|
||||
// is available, this will result in us exclusively selecting
|
||||
// on the file enumerating channel.
|
||||
var bf resolvedFuture
|
||||
if len(futures) > 0 {
|
||||
bf = futures[0]
|
||||
} else if fs == nil {
|
||||
// There are no more files to enumerate and the futures
|
||||
// have been drained, so quit.
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case f, ok := <-fs:
|
||||
if !ok {
|
||||
// a nil channel is never available to receive on.
|
||||
// This allows us to drain the list of in-process
|
||||
// futures without this case of the select winning
|
||||
// each time.
|
||||
fs = nil
|
||||
break
|
||||
}
|
||||
|
||||
// Make a new future to use to ship the bytes back and append
|
||||
// it to the list of futures (see comment below about ordering).
|
||||
ch := make(resolvedFuture)
|
||||
futures = append(futures, ch)
|
||||
|
||||
// Kick off the resolution that will respond with its bytes on
|
||||
// the future.
|
||||
go func(f string) {
|
||||
defer close(ch)
|
||||
// Record the builds we do via this builder.
|
||||
recordingBuilder := &build.Recorder{
|
||||
Builder: builder,
|
||||
}
|
||||
b, err := resolveFile(f, recordingBuilder, publisher)
|
||||
if err != nil {
|
||||
// Don't let build errors disrupt the watch.
|
||||
lg := log.Fatalf
|
||||
if fo.Watch {
|
||||
lg = log.Printf
|
||||
}
|
||||
lg("error processing import paths in %q: %v", f, err)
|
||||
return
|
||||
}
|
||||
// Associate with this file the collection of binary import paths.
|
||||
sm.Store(f, recordingBuilder.ImportPaths)
|
||||
ch <- b
|
||||
if fo.Watch {
|
||||
for _, ip := range recordingBuilder.ImportPaths {
|
||||
// Technically we never remove binary targets from the graph,
|
||||
// which will increase our graph's watch load, but the
|
||||
// notifications that they change will result in no affected
|
||||
// yamls, and no new builds or deploys.
|
||||
if err := g.Add(ip); err != nil {
|
||||
log.Fatalf("Error adding importpath to dep graph: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}(f)
|
||||
|
||||
case b, ok := <-bf:
|
||||
// Once the head channel returns something, dequeue it.
|
||||
// We listen to the futures in order to be respectful of
|
||||
// the kubectl apply ordering, which matters!
|
||||
futures = futures[1:]
|
||||
if ok {
|
||||
// Write the next body and a trailing delimiter.
|
||||
// We write the delimeter LAST so that when streamed to
|
||||
// kubectl it knows that the resource is complete and may
|
||||
// be applied.
|
||||
out.Write(append(b, []byte("\n---\n")...))
|
||||
}
|
||||
|
||||
case err := <-errCh:
|
||||
log.Fatalf("Error watching dependencies: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveFile(f string, builder build.Interface, pub publish.Interface) (b []byte, err error) {
|
||||
if f == "-" {
|
||||
b, err = ioutil.ReadAll(os.Stdin)
|
||||
} else {
|
||||
b, err = ioutil.ReadFile(f)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resolve.ImageReferences(b, builder, pub)
|
||||
}
|
29
cmd/tags.go
Normal file
29
cmd/tags.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// TagsOptions holds the list of tags to tag the built image
|
||||
type TagsOptions struct {
|
||||
Tags []string
|
||||
}
|
||||
|
||||
func addTagsArg(cmd *cobra.Command, ta *TagsOptions) {
|
||||
cmd.Flags().StringSliceVarP(&ta.Tags, "tags", "t", []string{"latest"},
|
||||
"Which tags to use for the produced image instead of the default 'latest' tag.")
|
||||
}
|
1
cmd/test/kenobi
Normal file
1
cmd/test/kenobi
Normal file
@ -0,0 +1 @@
|
||||
Hello there
|
1
cmd/test/kodata/kenobi
Symbolic link
1
cmd/test/kodata/kenobi
Symbolic link
@ -0,0 +1 @@
|
||||
../kenobi
|
32
cmd/test/main.go
Normal file
32
cmd/test/main.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dp := os.Getenv("KO_DATA_PATH")
|
||||
file := filepath.Join(dp, "kenobi")
|
||||
bytes, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading %q: %v", file, err)
|
||||
}
|
||||
log.Printf(string(bytes))
|
||||
}
|
24
cmd/test/test.yaml
Normal file
24
cmd/test/test.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright 2018 Google LLC All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: kodata
|
||||
annotations:
|
||||
sidecar.istio.io/inject: "false"
|
||||
spec:
|
||||
containers:
|
||||
- name: obiwan
|
||||
image: github.com/google/go-containerregistry/cmd/ko/test
|
||||
restartPolicy: Never
|
31
pkg/build/build.go
Normal file
31
pkg/build/build.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
// Interface abstracts different methods for turning a supported importpath
|
||||
// reference into a v1.Image.
|
||||
type Interface interface {
|
||||
// IsSupportedReference determines whether the given reference is to an importpath reference
|
||||
// that Ko supports building.
|
||||
// TODO(mattmoor): Verify that some base repo: foo.io/bar can be suffixed with this reference and parsed.
|
||||
IsSupportedReference(string) bool
|
||||
|
||||
// Build turns the given importpath reference into a v1.Image containing the Go binary.
|
||||
Build(string) (v1.Image, error)
|
||||
}
|
17
pkg/build/doc.go
Normal file
17
pkg/build/doc.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package build defines methods for building a v1.Image reference from a
|
||||
// Go binary reference.
|
||||
package build
|
75
pkg/build/future.go
Normal file
75
pkg/build/future.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
func newFuture(work func() (v1.Image, error)) *future {
|
||||
// Create a channel on which to send the result.
|
||||
ch := make(chan *result)
|
||||
// Initiate the actual work, sending its result
|
||||
// along the above channel.
|
||||
go func() {
|
||||
img, err := work()
|
||||
ch <- &result{img: img, err: err}
|
||||
}()
|
||||
// Return a future for the above work. Callers should
|
||||
// call .Get() on this result (as many times as needed).
|
||||
// One of these calls will receive the result, store it,
|
||||
// and close the channel so that the rest of the callers
|
||||
// can consume it.
|
||||
return &future{
|
||||
promise: ch,
|
||||
}
|
||||
}
|
||||
|
||||
type result struct {
|
||||
img v1.Image
|
||||
err error
|
||||
}
|
||||
|
||||
type future struct {
|
||||
m sync.RWMutex
|
||||
|
||||
result *result
|
||||
promise chan *result
|
||||
}
|
||||
|
||||
// Get blocks on the result of the future.
|
||||
func (f *future) Get() (v1.Image, error) {
|
||||
// Block on the promise of a result until we get one.
|
||||
result, ok := <-f.promise
|
||||
if ok {
|
||||
func() {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
// If we got the result, then store it so that
|
||||
// others may access it.
|
||||
f.result = result
|
||||
// Close the promise channel so that others
|
||||
// are signaled that the result is available.
|
||||
close(f.promise)
|
||||
}()
|
||||
}
|
||||
|
||||
f.m.RLock()
|
||||
defer f.m.RUnlock()
|
||||
|
||||
return f.result.img, f.result.err
|
||||
}
|
75
pkg/build/future_test.go
Normal file
75
pkg/build/future_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
func makeImage() (v1.Image, error) {
|
||||
return random.Image(256, 8)
|
||||
}
|
||||
|
||||
func digest(t *testing.T, img v1.Image) string {
|
||||
d, err := img.Digest()
|
||||
if err != nil {
|
||||
t.Fatalf("Digest() = %v", err)
|
||||
}
|
||||
return d.String()
|
||||
}
|
||||
|
||||
func TestSameFutureSameImage(t *testing.T) {
|
||||
f := newFuture(makeImage)
|
||||
|
||||
i1, err := f.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d1 := digest(t, i1)
|
||||
|
||||
i2, err := f.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d2 := digest(t, i2)
|
||||
|
||||
if d1 != d2 {
|
||||
t.Errorf("Got different digests %s and %s", d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffFutureDiffImage(t *testing.T) {
|
||||
f1 := newFuture(makeImage)
|
||||
f2 := newFuture(makeImage)
|
||||
|
||||
i1, err := f1.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d1 := digest(t, i1)
|
||||
|
||||
i2, err := f2.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d2 := digest(t, i2)
|
||||
|
||||
if d1 == d2 {
|
||||
t.Errorf("Got same digest %s, wanted different", d1)
|
||||
}
|
||||
}
|
299
pkg/build/gobuild.go
Normal file
299
pkg/build/gobuild.go
Normal file
@ -0,0 +1,299 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"errors"
|
||||
gb "go/build"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
)
|
||||
|
||||
const appPath = "/ko-app"
|
||||
|
||||
// GetBase takes an importpath and returns a base v1.Image.
|
||||
type GetBase func(string) (v1.Image, error)
|
||||
type builder func(string) (string, error)
|
||||
|
||||
type gobuild struct {
|
||||
getBase GetBase
|
||||
creationTime v1.Time
|
||||
build builder
|
||||
}
|
||||
|
||||
// Option is a functional option for NewGo.
|
||||
type Option func(*gobuildOpener) error
|
||||
|
||||
type gobuildOpener struct {
|
||||
getBase GetBase
|
||||
creationTime v1.Time
|
||||
build builder
|
||||
}
|
||||
|
||||
func (gbo *gobuildOpener) Open() (Interface, error) {
|
||||
if gbo.getBase == nil {
|
||||
return nil, errors.New("a way of providing base images must be specified, see build.WithBaseImages")
|
||||
}
|
||||
return &gobuild{
|
||||
getBase: gbo.getBase,
|
||||
creationTime: gbo.creationTime,
|
||||
build: gbo.build,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewGo returns a build.Interface implementation that:
|
||||
// 1. builds go binaries named by importpath,
|
||||
// 2. containerizes the binary on a suitable base,
|
||||
func NewGo(options ...Option) (Interface, error) {
|
||||
gbo := &gobuildOpener{
|
||||
build: build,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
if err := option(gbo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return gbo.Open()
|
||||
}
|
||||
|
||||
// IsSupportedReference implements build.Interface
|
||||
//
|
||||
// Only valid importpaths that provide commands (i.e., are "package main") are
|
||||
// supported.
|
||||
func (*gobuild) IsSupportedReference(s string) bool {
|
||||
p, err := gb.Import(s, gb.Default.GOPATH, gb.ImportComment)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return p.IsCommand()
|
||||
}
|
||||
|
||||
func build(ip string) (string, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "ko")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
file := filepath.Join(tmpDir, "out")
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", file, ip)
|
||||
|
||||
// Last one wins
|
||||
// TODO(mattmoor): GOARCH=amd64
|
||||
cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GOOS=linux")
|
||||
|
||||
var output bytes.Buffer
|
||||
cmd.Stderr = &output
|
||||
cmd.Stdout = &output
|
||||
|
||||
log.Printf("Building %s", ip)
|
||||
if err := cmd.Run(); err != nil {
|
||||
os.RemoveAll(tmpDir)
|
||||
log.Printf("Unexpected error running \"go build\": %v\n%v", err, output.String())
|
||||
return "", err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func tarBinary(binary string) (*bytes.Buffer, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tar.NewWriter(buf)
|
||||
defer tw.Close()
|
||||
|
||||
file, err := os.Open(binary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header := &tar.Header{
|
||||
Name: appPath,
|
||||
Size: stat.Size(),
|
||||
Typeflag: tar.TypeReg,
|
||||
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
|
||||
// under which it was created. Additionally, windows can only set 0222,
|
||||
// 0444, or 0666, none of which are executable.
|
||||
Mode: 0555,
|
||||
}
|
||||
// write the header to the tarball archive
|
||||
if err := tw.WriteHeader(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// copy the file data to the tarball
|
||||
if _, err := io.Copy(tw, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func kodataPath(s string) (string, error) {
|
||||
p, err := gb.Import(s, gb.Default.GOPATH, gb.ImportComment)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(p.Dir, "kodata"), nil
|
||||
}
|
||||
|
||||
// Where kodata lives in the image.
|
||||
const kodataRoot = "/var/run/ko"
|
||||
|
||||
func tarKoData(importpath string) (*bytes.Buffer, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tar.NewWriter(buf)
|
||||
defer tw.Close()
|
||||
|
||||
root, err := kodataPath(importpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if path == root {
|
||||
// Add an entry for /var/run/ko
|
||||
return tw.WriteHeader(&tar.Header{
|
||||
Name: kodataRoot,
|
||||
Typeflag: tar.TypeDir,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Skip other directories.
|
||||
if info.Mode().IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Chase symlinks.
|
||||
info, err = os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open the file to copy it into the tarball.
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Copy the file into the image tarball.
|
||||
newPath := filepath.Join(kodataRoot, path[len(root):])
|
||||
if err := tw.WriteHeader(&tar.Header{
|
||||
Name: newPath,
|
||||
Size: info.Size(),
|
||||
Typeflag: tar.TypeReg,
|
||||
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
|
||||
// under which it was created. Additionally, windows can only set 0222,
|
||||
// 0444, or 0666, none of which are executable.
|
||||
Mode: 0555,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(tw, file)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Build implements build.Interface
|
||||
func (gb *gobuild) Build(s string) (v1.Image, error) {
|
||||
// Do the build into a temporary file.
|
||||
file, err := gb.build(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(filepath.Dir(file))
|
||||
|
||||
var layers []v1.Layer
|
||||
// Create a layer from the kodata directory under this import path.
|
||||
dataLayerBuf, err := tarKoData(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataLayerBytes := dataLayerBuf.Bytes()
|
||||
dataLayer, err := tarball.LayerFromOpener(func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewBuffer(dataLayerBytes)), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers = append(layers, dataLayer)
|
||||
|
||||
// Construct a tarball with the binary and produce a layer.
|
||||
binaryLayerBuf, err := tarBinary(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
binaryLayerBytes := binaryLayerBuf.Bytes()
|
||||
binaryLayer, err := tarball.LayerFromOpener(func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewBuffer(binaryLayerBytes)), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers = append(layers, binaryLayer)
|
||||
|
||||
// Determine the appropriate base image for this import path.
|
||||
base, err := gb.getBase(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Augment the base image with our application layer.
|
||||
withApp, err := mutate.AppendLayers(base, layers...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start from a copy of the base image's config file, and set
|
||||
// the entrypoint to our app.
|
||||
cfg, err := withApp.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg = cfg.DeepCopy()
|
||||
cfg.Config.Entrypoint = []string{appPath}
|
||||
cfg.Config.Env = append(cfg.Config.Env, "KO_DATA_PATH="+kodataRoot)
|
||||
|
||||
image, err := mutate.Config(withApp, cfg.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
empty := v1.Time{}
|
||||
if gb.creationTime != empty {
|
||||
return mutate.CreatedAt(image, gb.creationTime)
|
||||
}
|
||||
return image, nil
|
||||
}
|
313
pkg/build/gobuild_test.go
Normal file
313
pkg/build/gobuild_test.go
Normal file
@ -0,0 +1,313 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"testing"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
func TestGoBuildIsSupportedRef(t *testing.T) {
|
||||
base, err := random.Image(1024, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
|
||||
ng, err := NewGo(WithBaseImages(func(string) (v1.Image, error) { return base, nil }))
|
||||
if err != nil {
|
||||
t.Fatalf("NewGo() = %v", err)
|
||||
}
|
||||
|
||||
// Supported import paths.
|
||||
for _, importpath := range []string{
|
||||
filepath.FromSlash("github.com/google/go-containerregistry/cmd/crane"),
|
||||
filepath.FromSlash("github.com/google/go-containerregistry/vendor/k8s.io/code-generator/cmd/deepcopy-gen"), // vendored commands work too.
|
||||
filepath.FromSlash("github.com/google/ko/cmd"),
|
||||
} {
|
||||
t.Run(importpath, func(t *testing.T) {
|
||||
if !ng.IsSupportedReference(importpath) {
|
||||
t.Errorf("IsSupportedReference(%q) = false, want true", importpath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Unsupported import paths.
|
||||
for _, importpath := range []string{
|
||||
filepath.FromSlash("github.com/google/go-containerregistry/v1/remote"), // not a command.
|
||||
filepath.FromSlash("github.com/google/go-containerregistry/pkg/foo"), // does not exist.
|
||||
} {
|
||||
t.Run(importpath, func(t *testing.T) {
|
||||
if ng.IsSupportedReference(importpath) {
|
||||
t.Errorf("IsSupportedReference(%v) = true, want false", importpath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A helper method we use to substitute for the default "build" method.
|
||||
func writeTempFile(s string) (string, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "ko")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
file, err := ioutil.TempFile(tmpDir, "out")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err := file.WriteString(filepath.ToSlash(s)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return file.Name(), nil
|
||||
}
|
||||
|
||||
func TestGoBuildNoKoData(t *testing.T) {
|
||||
baseLayers := int64(3)
|
||||
base, err := random.Image(1024, baseLayers)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
importpath := "github.com/google/go-containerregistry"
|
||||
|
||||
creationTime := v1.Time{time.Unix(5000, 0)}
|
||||
ng, err := NewGo(
|
||||
WithCreationTime(creationTime),
|
||||
WithBaseImages(func(string) (v1.Image, error) { return base, nil }),
|
||||
withBuilder(writeTempFile),
|
||||
)
|
||||
|
||||
img, err := ng.Build(filepath.Join(importpath, "cmd", "crane"))
|
||||
if err != nil {
|
||||
t.Fatalf("Build() = %v", err)
|
||||
}
|
||||
|
||||
ls, err := img.Layers()
|
||||
if err != nil {
|
||||
t.Fatalf("Layers() = %v", err)
|
||||
}
|
||||
|
||||
// Check that we have the expected number of layers.
|
||||
t.Run("check layer count", func(t *testing.T) {
|
||||
// We get a layer for the go binary and a layer for the kodata/
|
||||
if got, want := int64(len(ls)), baseLayers+2; got != want {
|
||||
t.Fatalf("len(Layers()) = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
// Check that rebuilding the image again results in the same image digest.
|
||||
t.Run("check determinism", func(t *testing.T) {
|
||||
expectedHash := v1.Hash{
|
||||
Algorithm: "sha256",
|
||||
Hex: "a688c9bc444d0a34cbc24abd62aa2fa263f61f2060963bb7a4fc3fa92075a2bf",
|
||||
}
|
||||
appLayer := ls[baseLayers+1]
|
||||
|
||||
if got, err := appLayer.Digest(); err != nil {
|
||||
t.Errorf("Digest() = %v", err)
|
||||
} else if got != expectedHash {
|
||||
t.Errorf("Digest() = %v, want %v", got, expectedHash)
|
||||
}
|
||||
})
|
||||
|
||||
// Check that the entrypoint of the image is configured to invoke our Go application
|
||||
t.Run("check entrypoint", func(t *testing.T) {
|
||||
cfg, err := img.ConfigFile()
|
||||
if err != nil {
|
||||
t.Errorf("ConfigFile() = %v", err)
|
||||
}
|
||||
entrypoint := cfg.Config.Entrypoint
|
||||
if got, want := len(entrypoint), 1; got != want {
|
||||
t.Errorf("len(entrypoint) = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := entrypoint[0], appPath; got != want {
|
||||
t.Errorf("entrypoint = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("check creation time", func(t *testing.T) {
|
||||
cfg, err := img.ConfigFile()
|
||||
if err != nil {
|
||||
t.Errorf("ConfigFile() = %v", err)
|
||||
}
|
||||
|
||||
actual := cfg.Created
|
||||
if actual.Time != creationTime.Time {
|
||||
t.Errorf("created = %v, want %v", actual, creationTime)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoBuild(t *testing.T) {
|
||||
baseLayers := int64(3)
|
||||
base, err := random.Image(1024, baseLayers)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
importpath := "github.com/google/go-containerregistry"
|
||||
|
||||
creationTime := v1.Time{time.Unix(5000, 0)}
|
||||
ng, err := NewGo(
|
||||
WithCreationTime(creationTime),
|
||||
WithBaseImages(func(string) (v1.Image, error) { return base, nil }),
|
||||
withBuilder(writeTempFile),
|
||||
)
|
||||
|
||||
img, err := ng.Build(filepath.Join(importpath, "cmd", "ko", "test"))
|
||||
if err != nil {
|
||||
t.Fatalf("Build() = %v", err)
|
||||
}
|
||||
|
||||
ls, err := img.Layers()
|
||||
if err != nil {
|
||||
t.Fatalf("Layers() = %v", err)
|
||||
}
|
||||
|
||||
// Check that we have the expected number of layers.
|
||||
t.Run("check layer count", func(t *testing.T) {
|
||||
// We get a layer for the go binary and a layer for the kodata/
|
||||
if got, want := int64(len(ls)), baseLayers+2; got != want {
|
||||
t.Fatalf("len(Layers()) = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
// Check that rebuilding the image again results in the same image digest.
|
||||
t.Run("check determinism", func(t *testing.T) {
|
||||
expectedHash := v1.Hash{
|
||||
Algorithm: "sha256",
|
||||
Hex: "71912d718600c5a2b8db3a127a14073bba61dded0dac8e1a6ebdeb4a37f2ce8d",
|
||||
}
|
||||
appLayer := ls[baseLayers+1]
|
||||
|
||||
if got, err := appLayer.Digest(); err != nil {
|
||||
t.Errorf("Digest() = %v", err)
|
||||
} else if got != expectedHash {
|
||||
t.Errorf("Digest() = %v, want %v", got, expectedHash)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("check app layer contents", func(t *testing.T) {
|
||||
expectedHash := v1.Hash{
|
||||
Algorithm: "sha256",
|
||||
Hex: "63b6e090921b79b61e7f5fba44d2ea0f81215d9abac3d005dda7cb9a1f8a025d",
|
||||
}
|
||||
appLayer := ls[baseLayers]
|
||||
|
||||
if got, err := appLayer.Digest(); err != nil {
|
||||
t.Errorf("Digest() = %v", err)
|
||||
} else if got != expectedHash {
|
||||
t.Errorf("Digest() = %v, want %v", got, expectedHash)
|
||||
}
|
||||
|
||||
r, err := appLayer.Uncompressed()
|
||||
if err != nil {
|
||||
t.Errorf("Uncompressed() = %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
tr := tar.NewReader(r)
|
||||
if _, err := tr.Next(); err == io.EOF {
|
||||
t.Errorf("Layer contained no files")
|
||||
}
|
||||
})
|
||||
|
||||
// Check that the kodata layer contains the expected data (even though it was a symlink
|
||||
// outside kodata).
|
||||
t.Run("check kodata", func(t *testing.T) {
|
||||
dataLayer := ls[baseLayers]
|
||||
r, err := dataLayer.Uncompressed()
|
||||
if err != nil {
|
||||
t.Errorf("Uncompressed() = %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
found := false
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Errorf("Next() = %v", err)
|
||||
continue
|
||||
}
|
||||
if header.Name != filepath.Join(kodataRoot, "kenobi") {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
body, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
t.Errorf("ReadAll() = %v", err)
|
||||
} else if want, got := "Hello there\n", string(body); got != want {
|
||||
t.Errorf("ReadAll() = %v, wanted %v", got, want)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Didn't find expected file in tarball")
|
||||
}
|
||||
})
|
||||
|
||||
// Check that the entrypoint of the image is configured to invoke our Go application
|
||||
t.Run("check entrypoint", func(t *testing.T) {
|
||||
cfg, err := img.ConfigFile()
|
||||
if err != nil {
|
||||
t.Errorf("ConfigFile() = %v", err)
|
||||
}
|
||||
entrypoint := cfg.Config.Entrypoint
|
||||
if got, want := len(entrypoint), 1; got != want {
|
||||
t.Errorf("len(entrypoint) = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := entrypoint[0], appPath; got != want {
|
||||
t.Errorf("entrypoint = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
// Check that the environment contains the KO_DATA_PATH environment variable.
|
||||
t.Run("check KO_DATA_PATH env var", func(t *testing.T) {
|
||||
cfg, err := img.ConfigFile()
|
||||
if err != nil {
|
||||
t.Errorf("ConfigFile() = %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, entry := range cfg.Config.Env {
|
||||
if entry == "KO_DATA_PATH="+kodataRoot {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Didn't find expected file in tarball.")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("check creation time", func(t *testing.T) {
|
||||
cfg, err := img.ConfigFile()
|
||||
if err != nil {
|
||||
t.Errorf("ConfigFile() = %v", err)
|
||||
}
|
||||
|
||||
actual := cfg.Created
|
||||
if actual.Time != creationTime.Time {
|
||||
t.Errorf("created = %v, want %v", actual, creationTime)
|
||||
}
|
||||
})
|
||||
}
|
46
pkg/build/options.go
Normal file
46
pkg/build/options.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
// WithBaseImages is a functional option for overriding the base images
|
||||
// that are used for different images.
|
||||
func WithBaseImages(gb GetBase) Option {
|
||||
return func(gbo *gobuildOpener) error {
|
||||
gbo.getBase = gb
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCreationTime is a functional option for overriding the creation
|
||||
// time given to images.
|
||||
func WithCreationTime(t v1.Time) Option {
|
||||
return func(gbo *gobuildOpener) error {
|
||||
gbo.creationTime = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// withBuilder is a functional option for overriding the way go binaries
|
||||
// are built. This is exposed for testing.
|
||||
func withBuilder(b builder) Option {
|
||||
return func(gbo *gobuildOpener) error {
|
||||
gbo.build = b
|
||||
return nil
|
||||
}
|
||||
}
|
46
pkg/build/recorder.go
Normal file
46
pkg/build/recorder.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
// Recorder composes with another Interface to record the built import paths.
|
||||
type Recorder struct {
|
||||
m sync.Mutex
|
||||
ImportPaths []string
|
||||
Builder Interface
|
||||
}
|
||||
|
||||
// Recorder implements Interface
|
||||
var _ Interface = (*Recorder)(nil)
|
||||
|
||||
// IsSupportedReference implements Interface
|
||||
func (r *Recorder) IsSupportedReference(ip string) bool {
|
||||
return r.Builder.IsSupportedReference(ip)
|
||||
}
|
||||
|
||||
// Build implements Interface
|
||||
func (r *Recorder) Build(ip string) (v1.Image, error) {
|
||||
func() {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
r.ImportPaths = append(r.ImportPaths, ip)
|
||||
}()
|
||||
return r.Builder.Build(ip)
|
||||
}
|
121
pkg/build/recorder_test.go
Normal file
121
pkg/build/recorder_test.go
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
type fake struct {
|
||||
isr func(string) bool
|
||||
b func(string) (v1.Image, error)
|
||||
}
|
||||
|
||||
var _ Interface = (*fake)(nil)
|
||||
|
||||
// IsSupportedReference implements Interface
|
||||
func (r *fake) IsSupportedReference(ip string) bool {
|
||||
return r.isr(ip)
|
||||
}
|
||||
|
||||
// Build implements Interface
|
||||
func (r *fake) Build(ip string) (v1.Image, error) {
|
||||
return r.b(ip)
|
||||
}
|
||||
|
||||
func TestISRPassThrough(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{{
|
||||
name: "empty string",
|
||||
}, {
|
||||
name: "non-empty string",
|
||||
input: "asdf asdf asdf",
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
called := false
|
||||
inner := &fake{
|
||||
isr: func(ip string) bool {
|
||||
called = true
|
||||
if ip != test.input {
|
||||
t.Errorf("ISR = %v, wanted %v", ip, test.input)
|
||||
}
|
||||
return true
|
||||
},
|
||||
}
|
||||
rec := &Recorder{
|
||||
Builder: inner,
|
||||
}
|
||||
rec.IsSupportedReference(test.input)
|
||||
if !called {
|
||||
t.Error("IsSupportedReference wasn't called, wanted called")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRecording(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputs []string
|
||||
}{{
|
||||
name: "no calls",
|
||||
}, {
|
||||
name: "one call",
|
||||
inputs: []string{
|
||||
"github.com/foo/bar",
|
||||
},
|
||||
}, {
|
||||
name: "two calls",
|
||||
inputs: []string{
|
||||
"github.com/foo/bar",
|
||||
"github.com/foo/baz",
|
||||
},
|
||||
}, {
|
||||
name: "duplicates",
|
||||
inputs: []string{
|
||||
"github.com/foo/bar",
|
||||
"github.com/foo/baz",
|
||||
"github.com/foo/bar",
|
||||
"github.com/foo/baz",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
inner := &fake{
|
||||
b: func(ip string) (v1.Image, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
rec := &Recorder{
|
||||
Builder: inner,
|
||||
}
|
||||
for _, in := range test.inputs {
|
||||
rec.Build(in)
|
||||
}
|
||||
if diff := cmp.Diff(test.inputs, rec.ImportPaths); diff != "" {
|
||||
t.Errorf("Build (-want, +got): %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
79
pkg/build/shared.go
Normal file
79
pkg/build/shared.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
// Caching wraps a builder implementation in a layer that shares build results
|
||||
// for the same inputs using a simple "future" implementation. Cached results
|
||||
// may be invalidated by calling Invalidate with the same input passed to Build.
|
||||
type Caching struct {
|
||||
inner Interface
|
||||
|
||||
m sync.Mutex
|
||||
results map[string]*future
|
||||
}
|
||||
|
||||
// Caching implements Interface
|
||||
var _ Interface = (*Caching)(nil)
|
||||
|
||||
// NewCaching wraps the provided build.Interface in an implementation that
|
||||
// shares build results for a given path until the result has been invalidated.
|
||||
func NewCaching(inner Interface) (*Caching, error) {
|
||||
return &Caching{
|
||||
inner: inner,
|
||||
results: make(map[string]*future),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Build implements Interface
|
||||
func (c *Caching) Build(ip string) (v1.Image, error) {
|
||||
f := func() *future {
|
||||
// Lock the map of futures.
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
// If a future for "ip" exists, then return it.
|
||||
f, ok := c.results[ip]
|
||||
if ok {
|
||||
return f
|
||||
}
|
||||
// Otherwise create and record a future for a Build of "ip".
|
||||
f = newFuture(func() (v1.Image, error) {
|
||||
return c.inner.Build(ip)
|
||||
})
|
||||
c.results[ip] = f
|
||||
return f
|
||||
}()
|
||||
|
||||
return f.Get()
|
||||
}
|
||||
|
||||
// IsSupportedReference implements Interface
|
||||
func (c *Caching) IsSupportedReference(ip string) bool {
|
||||
return c.inner.IsSupportedReference(ip)
|
||||
}
|
||||
|
||||
// Invalidate removes an import path's cached results.
|
||||
func (c *Caching) Invalidate(ip string) {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
delete(c.results, ip)
|
||||
}
|
94
pkg/build/shared_test.go
Normal file
94
pkg/build/shared_test.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
type slowbuild struct {
|
||||
sleep time.Duration
|
||||
}
|
||||
|
||||
// slowbuild implements Interface
|
||||
var _ Interface = (*slowbuild)(nil)
|
||||
|
||||
func (sb *slowbuild) IsSupportedReference(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (sb *slowbuild) Build(string) (v1.Image, error) {
|
||||
time.Sleep(sb.sleep)
|
||||
return random.Image(256, 8)
|
||||
}
|
||||
|
||||
func TestCaching(t *testing.T) {
|
||||
duration := 100 * time.Millisecond
|
||||
ip := "foo"
|
||||
|
||||
sb := &slowbuild{duration}
|
||||
cb, _ := NewCaching(sb)
|
||||
|
||||
if !cb.IsSupportedReference(ip) {
|
||||
t.Errorf("ISR(%q) = false, wanted true", ip)
|
||||
}
|
||||
|
||||
previousDigest := "not-a-digest"
|
||||
// Each iteration, we test that the first build is slow and subsequent
|
||||
// builds are fast and return the same image. Then we invalidate the
|
||||
// cache and iterate.
|
||||
for idx := 0; idx < 3; idx++ {
|
||||
start := time.Now()
|
||||
img1, err := cb.Build(ip)
|
||||
if err != nil {
|
||||
t.Errorf("Build() = %v", err)
|
||||
}
|
||||
end := time.Now()
|
||||
|
||||
elapsed := end.Sub(start)
|
||||
if elapsed < duration {
|
||||
t.Errorf("Elapsed time %v, wanted >= %s", elapsed, duration)
|
||||
}
|
||||
d1 := digest(t, img1)
|
||||
|
||||
if d1 == previousDigest {
|
||||
t.Errorf("Got same digest as previous iteration, wanted different: %v", d1)
|
||||
}
|
||||
previousDigest = d1
|
||||
|
||||
start = time.Now()
|
||||
img2, err := cb.Build(ip)
|
||||
if err != nil {
|
||||
t.Errorf("Build() = %v", err)
|
||||
}
|
||||
end = time.Now()
|
||||
|
||||
elapsed = end.Sub(start)
|
||||
if elapsed >= duration {
|
||||
t.Errorf("Elapsed time %v, wanted < %s", elapsed, duration)
|
||||
}
|
||||
d2 := digest(t, img2)
|
||||
|
||||
if d1 != d2 {
|
||||
t.Error("Got different images, wanted same")
|
||||
}
|
||||
|
||||
cb.Invalidate(ip)
|
||||
}
|
||||
}
|
16
pkg/doc.go
Normal file
16
pkg/doc.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package ko holds libraries used to implement the ko CLI.
|
||||
package ko
|
80
pkg/publish/daemon.go
Normal file
80
pkg/publish/daemon.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
||||
)
|
||||
|
||||
const (
|
||||
// LocalDomain is a sentinel "registry" that represents side-loading images into the daemon.
|
||||
LocalDomain = "ko.local"
|
||||
)
|
||||
|
||||
// demon is intentionally misspelled to avoid name collision (and drive Jon nuts).
|
||||
type demon struct {
|
||||
namer Namer
|
||||
tags []string
|
||||
}
|
||||
|
||||
// NewDaemon returns a new publish.Interface that publishes images to a container daemon.
|
||||
func NewDaemon(namer Namer, tags []string) Interface {
|
||||
return &demon{namer, tags}
|
||||
}
|
||||
|
||||
// Publish implements publish.Interface
|
||||
func (d *demon) Publish(img v1.Image, s string) (name.Reference, error) {
|
||||
// https://github.com/google/go-containerregistry/issues/212
|
||||
s = strings.ToLower(s)
|
||||
|
||||
h, err := img.Digest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
digestTag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", LocalDomain, d.namer(s), h.Hex), name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Loading %v", digestTag)
|
||||
if _, err := daemon.Write(digestTag, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Loaded %v", digestTag)
|
||||
|
||||
for _, tagName := range d.tags {
|
||||
log.Printf("Adding tag %v", tagName)
|
||||
tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", LocalDomain, d.namer(s), tagName), name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = daemon.Tag(digestTag, tag)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Added tag %v", tagName)
|
||||
}
|
||||
|
||||
return &digestTag, nil
|
||||
}
|
89
pkg/publish/daemon_test.go
Normal file
89
pkg/publish/daemon_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
type MockImageLoader struct{}
|
||||
|
||||
var Tags []string
|
||||
|
||||
func (m *MockImageLoader) ImageLoad(_ context.Context, _ io.Reader, _ bool) (types.ImageLoadResponse, error) {
|
||||
return types.ImageLoadResponse{
|
||||
Body: ioutil.NopCloser(strings.NewReader("Loaded")),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockImageLoader) ImageTag(ctx context.Context, source, target string) error {
|
||||
Tags = append(Tags, target)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
daemon.GetImageLoader = func() (daemon.ImageLoader, error) {
|
||||
return &MockImageLoader{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemon(t *testing.T) {
|
||||
importpath := "github.com/google/go-containerregistry/cmd/ko"
|
||||
img, err := random.Image(1024, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
|
||||
def := NewDaemon(md5Hash, []string{})
|
||||
if d, err := def.Publish(img, importpath); err != nil {
|
||||
t.Errorf("Publish() = %v", err)
|
||||
} else if got, want := d.String(), "ko.local/"+md5Hash(importpath); !strings.HasPrefix(got, want) {
|
||||
t.Errorf("Publish() = %v, wanted prefix %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonTags(t *testing.T) {
|
||||
Tags = nil
|
||||
|
||||
importpath := "github.com/google/go-containerregistry/cmd/ko"
|
||||
img, err := random.Image(1024, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
|
||||
def := NewDaemon(md5Hash, []string{"v2.0.0", "v1.2.3", "production"})
|
||||
if d, err := def.Publish(img, importpath); err != nil {
|
||||
t.Errorf("Publish() = %v", err)
|
||||
} else if got, want := d.String(), "ko.local/"+md5Hash(importpath); !strings.HasPrefix(got, want) {
|
||||
t.Errorf("Publish() = %v, wanted prefix %v", got, want)
|
||||
}
|
||||
|
||||
expected := []string{"ko.local/d502d3a1d9858acbab6106d78a0e05f0:v2.0.0", "ko.local/d502d3a1d9858acbab6106d78a0e05f0:v1.2.3", "ko.local/d502d3a1d9858acbab6106d78a0e05f0:production"}
|
||||
|
||||
for i, v := range expected {
|
||||
if Tags[i] != v {
|
||||
t.Errorf("Expected tag %v got %v", v, Tags[i])
|
||||
}
|
||||
}
|
||||
}
|
121
pkg/publish/default.go
Normal file
121
pkg/publish/default.go
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
// defalt is intentionally misspelled to avoid keyword collision (and drive Jon nuts).
|
||||
type defalt struct {
|
||||
base string
|
||||
t http.RoundTripper
|
||||
auth authn.Authenticator
|
||||
namer Namer
|
||||
tags []string
|
||||
}
|
||||
|
||||
// Option is a functional option for NewDefault.
|
||||
type Option func(*defaultOpener) error
|
||||
|
||||
type defaultOpener struct {
|
||||
base string
|
||||
t http.RoundTripper
|
||||
auth authn.Authenticator
|
||||
namer Namer
|
||||
tags []string
|
||||
}
|
||||
|
||||
// Namer is a function from a supported import path to the portion of the resulting
|
||||
// image name that follows the "base" repository name.
|
||||
type Namer func(string) string
|
||||
|
||||
// identity is the default namer, so import paths are affixed as-is under the repository
|
||||
// name for maximum clarity, e.g.
|
||||
// gcr.io/foo/github.com/bar/baz/cmd/blah
|
||||
// ^--base--^ ^-------import path-------^
|
||||
func identity(in string) string { return in }
|
||||
|
||||
// As some registries do not support pushing an image by digest, the default tag for pushing
|
||||
// is the 'latest' tag.
|
||||
var defaultTags = []string{"latest"}
|
||||
|
||||
func (do *defaultOpener) Open() (Interface, error) {
|
||||
return &defalt{
|
||||
base: do.base,
|
||||
t: do.t,
|
||||
auth: do.auth,
|
||||
namer: do.namer,
|
||||
tags: do.tags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewDefault returns a new publish.Interface that publishes references under the provided base
|
||||
// repository using the default keychain to authenticate and the default naming scheme.
|
||||
func NewDefault(base string, options ...Option) (Interface, error) {
|
||||
do := &defaultOpener{
|
||||
base: base,
|
||||
t: http.DefaultTransport,
|
||||
auth: authn.Anonymous,
|
||||
namer: identity,
|
||||
tags: defaultTags,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
if err := option(do); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return do.Open()
|
||||
}
|
||||
|
||||
// Publish implements publish.Interface
|
||||
func (d *defalt) Publish(img v1.Image, s string) (name.Reference, error) {
|
||||
// https://github.com/google/go-containerregistry/issues/212
|
||||
s = strings.ToLower(s)
|
||||
|
||||
for _, tagName := range d.tags {
|
||||
tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", d.base, d.namer(s), tagName), name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Publishing %v", tag)
|
||||
// TODO: This is slow because we have to load the image multiple times.
|
||||
// Figure out some way to publish the manifest with another tag.
|
||||
if err := remote.Write(tag, img, d.auth, d.t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
h, err := img.Digest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dig, err := name.NewDigest(fmt.Sprintf("%s/%s@%s", d.base, d.namer(s), h), name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Published %v", dig)
|
||||
return &dig, nil
|
||||
}
|
223
pkg/publish/default_test.go
Normal file
223
pkg/publish/default_test.go
Normal file
@ -0,0 +1,223 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
img, err := random.Image(1024, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
base := "blah"
|
||||
importpath := "github.com/Google/go-containerregistry/cmd/crane"
|
||||
expectedRepo := fmt.Sprintf("%s/%s", base, strings.ToLower(importpath))
|
||||
headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
|
||||
initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
|
||||
manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
|
||||
http.Error(w, "NotFound", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch r.URL.Path {
|
||||
case "/v2/":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case initiatePath:
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
|
||||
}
|
||||
http.Error(w, "Mounted", http.StatusCreated)
|
||||
case manifestPath:
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
|
||||
}
|
||||
http.Error(w, "Created", http.StatusCreated)
|
||||
default:
|
||||
t.Fatalf("Unexpected path: %v", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
u, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse(%v) = %v", server.URL, err)
|
||||
}
|
||||
tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo), name.WeakValidation)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTag() = %v", err)
|
||||
}
|
||||
|
||||
repoName := fmt.Sprintf("%s/%s", u.Host, base)
|
||||
def, err := NewDefault(repoName)
|
||||
if err != nil {
|
||||
t.Errorf("NewDefault() = %v", err)
|
||||
}
|
||||
if d, err := def.Publish(img, importpath); err != nil {
|
||||
t.Errorf("Publish() = %v", err)
|
||||
} else if !strings.HasPrefix(d.String(), tag.Repository.String()) {
|
||||
t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository)
|
||||
}
|
||||
}
|
||||
|
||||
func md5Hash(s string) string {
|
||||
// md5 as hex.
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(s))
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func TestDefaultWithCustomNamer(t *testing.T) {
|
||||
img, err := random.Image(1024, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
base := "blah"
|
||||
importpath := "github.com/Google/go-containerregistry/cmd/crane"
|
||||
expectedRepo := fmt.Sprintf("%s/%s", base, md5Hash(strings.ToLower(importpath)))
|
||||
headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
|
||||
initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
|
||||
manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
|
||||
http.Error(w, "NotFound", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch r.URL.Path {
|
||||
case "/v2/":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case initiatePath:
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
|
||||
}
|
||||
http.Error(w, "Mounted", http.StatusCreated)
|
||||
case manifestPath:
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
|
||||
}
|
||||
http.Error(w, "Created", http.StatusCreated)
|
||||
default:
|
||||
t.Fatalf("Unexpected path: %v", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
u, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse(%v) = %v", server.URL, err)
|
||||
}
|
||||
tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo), name.WeakValidation)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTag() = %v", err)
|
||||
}
|
||||
|
||||
repoName := fmt.Sprintf("%s/%s", u.Host, base)
|
||||
|
||||
def, err := NewDefault(repoName, WithNamer(md5Hash))
|
||||
if err != nil {
|
||||
t.Errorf("NewDefault() = %v", err)
|
||||
}
|
||||
if d, err := def.Publish(img, importpath); err != nil {
|
||||
t.Errorf("Publish() = %v", err)
|
||||
} else if !strings.HasPrefix(d.String(), repoName) {
|
||||
t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository)
|
||||
} else if !strings.HasSuffix(d.Context().String(), md5Hash(strings.ToLower(importpath))) {
|
||||
t.Errorf("Publish() = %v, wanted suffix %v", d.Context(), md5Hash(importpath))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultWithTags(t *testing.T) {
|
||||
img, err := random.Image(1024, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("random.Image() = %v", err)
|
||||
}
|
||||
base := "blah"
|
||||
importpath := "github.com/Google/go-containerregistry/cmd/crane"
|
||||
expectedRepo := fmt.Sprintf("%s/%s", base, strings.ToLower(importpath))
|
||||
headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo)
|
||||
initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo)
|
||||
manifestPath := fmt.Sprintf("/v2/%s/manifests/", expectedRepo)
|
||||
|
||||
createdTags := make(map[string]struct{})
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath {
|
||||
http.Error(w, "NotFound", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case r.URL.Path == "/v2/":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case r.URL.Path == initiatePath:
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost)
|
||||
}
|
||||
http.Error(w, "Mounted", http.StatusCreated)
|
||||
case strings.HasPrefix(r.URL.Path, manifestPath):
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut)
|
||||
}
|
||||
|
||||
createdTags[strings.TrimPrefix(r.URL.Path, manifestPath)] = struct{}{}
|
||||
|
||||
http.Error(w, "Created", http.StatusCreated)
|
||||
default:
|
||||
t.Fatalf("Unexpected path: %v", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
u, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse(%v) = %v", server.URL, err)
|
||||
}
|
||||
tag, err := name.NewTag(fmt.Sprintf("%s/%s:notLatest", u.Host, expectedRepo), name.WeakValidation)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTag() = %v", err)
|
||||
}
|
||||
|
||||
repoName := fmt.Sprintf("%s/%s", u.Host, base)
|
||||
|
||||
def, err := NewDefault(repoName, WithTags([]string{"notLatest", "v1.2.3"}))
|
||||
if err != nil {
|
||||
t.Errorf("NewDefault() = %v", err)
|
||||
}
|
||||
if d, err := def.Publish(img, importpath); err != nil {
|
||||
t.Errorf("Publish() = %v", err)
|
||||
} else if !strings.HasPrefix(d.String(), repoName) {
|
||||
t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository)
|
||||
} else if !strings.HasSuffix(d.Context().String(), strings.ToLower(importpath)) {
|
||||
t.Errorf("Publish() = %v, wanted suffix %v", d.Context(), md5Hash(importpath))
|
||||
}
|
||||
|
||||
if _, ok := createdTags["notLatest"]; !ok {
|
||||
t.Errorf("Tag notLatest was not created.")
|
||||
}
|
||||
|
||||
if _, ok := createdTags["v1.2.3"]; !ok {
|
||||
t.Errorf("Tag v1.2.3 was not created.")
|
||||
}
|
||||
}
|
17
pkg/publish/doc.go
Normal file
17
pkg/publish/doc.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package publish defines methods for publishing a v1.Image reference and
|
||||
// returning the published digest for embedding back into a Kubernetes yaml.
|
||||
package publish
|
75
pkg/publish/future.go
Normal file
75
pkg/publish/future.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
)
|
||||
|
||||
func newFuture(work func() (name.Reference, error)) *future {
|
||||
// Create a channel on which to send the result.
|
||||
ch := make(chan *result)
|
||||
// Initiate the actual work, sending its result
|
||||
// along the above channel.
|
||||
go func() {
|
||||
ref, err := work()
|
||||
ch <- &result{ref: ref, err: err}
|
||||
}()
|
||||
// Return a future for the above work. Callers should
|
||||
// call .Get() on this result (as many times as needed).
|
||||
// One of these calls will receive the result, store it,
|
||||
// and close the channel so that the rest of the callers
|
||||
// can consume it.
|
||||
return &future{
|
||||
promise: ch,
|
||||
}
|
||||
}
|
||||
|
||||
type result struct {
|
||||
ref name.Reference
|
||||
err error
|
||||
}
|
||||
|
||||
type future struct {
|
||||
m sync.RWMutex
|
||||
|
||||
result *result
|
||||
promise chan *result
|
||||
}
|
||||
|
||||
// Get blocks on the result of the future.
|
||||
func (f *future) Get() (name.Reference, error) {
|
||||
// Block on the promise of a result until we get one.
|
||||
result, ok := <-f.promise
|
||||
if ok {
|
||||
func() {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
// If we got the result, then store it so that
|
||||
// others may access it.
|
||||
f.result = result
|
||||
// Close the promise channel so that others
|
||||
// are signaled that the result is available.
|
||||
close(f.promise)
|
||||
}()
|
||||
}
|
||||
|
||||
f.m.RLock()
|
||||
defer f.m.RUnlock()
|
||||
|
||||
return f.result.ref, f.result.err
|
||||
}
|
76
pkg/publish/future_test.go
Normal file
76
pkg/publish/future_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
func makeRef() (name.Reference, error) {
|
||||
img, err := random.Image(256, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := img.Digest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return name.NewDigest(fmt.Sprintf("gcr.io/foo/bar@%s", d), name.WeakValidation)
|
||||
}
|
||||
|
||||
func TestSameFutureSameReference(t *testing.T) {
|
||||
f := newFuture(makeRef)
|
||||
|
||||
ref1, err := f.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d1 := ref1.String()
|
||||
|
||||
ref2, err := f.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d2 := ref2.String()
|
||||
|
||||
if d1 != d2 {
|
||||
t.Errorf("Got different digests %s and %s", d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffFutureDiffReference(t *testing.T) {
|
||||
f1 := newFuture(makeRef)
|
||||
f2 := newFuture(makeRef)
|
||||
|
||||
ref1, err := f1.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d1 := ref1.String()
|
||||
|
||||
ref2, err := f2.Get()
|
||||
if err != nil {
|
||||
t.Errorf("Get() = %v", err)
|
||||
}
|
||||
d2 := ref2.String()
|
||||
|
||||
if d1 == d2 {
|
||||
t.Errorf("Got same digest %s, wanted different", d1)
|
||||
}
|
||||
}
|
82
pkg/publish/options.go
Normal file
82
pkg/publish/options.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
)
|
||||
|
||||
// WithTransport is a functional option for overriding the default transport
|
||||
// on a default publisher.
|
||||
func WithTransport(t http.RoundTripper) Option {
|
||||
return func(i *defaultOpener) error {
|
||||
i.t = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuth is a functional option for overriding the default authenticator
|
||||
// on a default publisher.
|
||||
func WithAuth(auth authn.Authenticator) Option {
|
||||
return func(i *defaultOpener) error {
|
||||
i.auth = auth
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthFromKeychain is a functional option for overriding the default
|
||||
// authenticator on a default publisher using an authn.Keychain
|
||||
func WithAuthFromKeychain(keys authn.Keychain) Option {
|
||||
return func(i *defaultOpener) error {
|
||||
// We parse this lazily because it is a repository prefix, which
|
||||
// means that docker.io/mattmoor actually gets interpreted as
|
||||
// docker.io/library/mattmoor, which gets tricky when we start
|
||||
// appending things to it in the publisher.
|
||||
repo, err := name.NewRepository(i.base, name.WeakValidation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth, err := keys.Resolve(repo.Registry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if auth == authn.Anonymous {
|
||||
log.Println("No matching credentials were found, falling back on anonymous")
|
||||
}
|
||||
i.auth = auth
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamer is a functional option for overriding the image naming behavior
|
||||
// in our default publisher.
|
||||
func WithNamer(n Namer) Option {
|
||||
return func(i *defaultOpener) error {
|
||||
i.namer = n
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTags is a functional option for overriding the image tags
|
||||
func WithTags(tags []string) Option {
|
||||
return func(i *defaultOpener) error {
|
||||
i.tags = tags
|
||||
return nil
|
||||
}
|
||||
}
|
28
pkg/publish/publish.go
Normal file
28
pkg/publish/publish.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
// Interface abstracts different methods for publishing images.
|
||||
type Interface interface {
|
||||
// Publish uploads the given v1.Image to a registry incorporating the
|
||||
// provided string into the image's repository name. Returns the digest
|
||||
// of the published image.
|
||||
Publish(v1.Image, string) (name.Reference, error)
|
||||
}
|
76
pkg/publish/shared.go
Normal file
76
pkg/publish/shared.go
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
// caching wraps a publisher implementation in a layer that shares publish results
|
||||
// for the same inputs using a simple "future" implementation.
|
||||
type caching struct {
|
||||
inner Interface
|
||||
|
||||
m sync.Mutex
|
||||
results map[string]*entry
|
||||
}
|
||||
|
||||
// entry holds the last image published and the result of publishing it for a
|
||||
// particular reference.
|
||||
type entry struct {
|
||||
img v1.Image
|
||||
f *future
|
||||
}
|
||||
|
||||
// caching implements Interface
|
||||
var _ Interface = (*caching)(nil)
|
||||
|
||||
// NewCaching wraps the provided publish.Interface in an implementation that
|
||||
// shares publish results for a given path until the passed image object changes.
|
||||
func NewCaching(inner Interface) (Interface, error) {
|
||||
return &caching{
|
||||
inner: inner,
|
||||
results: make(map[string]*entry),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Publish implements Interface
|
||||
func (c *caching) Publish(img v1.Image, ref string) (name.Reference, error) {
|
||||
f := func() *future {
|
||||
// Lock the map of futures.
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
// If a future for "ref" exists, then return it.
|
||||
ent, ok := c.results[ref]
|
||||
if ok {
|
||||
// If the image matches, then return the same future.
|
||||
if ent.img == img {
|
||||
return ent.f
|
||||
}
|
||||
}
|
||||
// Otherwise create and record a future for publishing "img" to "ref".
|
||||
f := newFuture(func() (name.Reference, error) {
|
||||
return c.inner.Publish(img, ref)
|
||||
})
|
||||
c.results[ref] = &entry{img: img, f: f}
|
||||
return f
|
||||
}()
|
||||
|
||||
return f.Get()
|
||||
}
|
88
pkg/publish/shared_test.go
Normal file
88
pkg/publish/shared_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publish
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
type slowpublish struct {
|
||||
sleep time.Duration
|
||||
}
|
||||
|
||||
// slowpublish implements Interface
|
||||
var _ Interface = (*slowpublish)(nil)
|
||||
|
||||
func (sb *slowpublish) Publish(img v1.Image, ref string) (name.Reference, error) {
|
||||
time.Sleep(sb.sleep)
|
||||
return makeRef()
|
||||
}
|
||||
|
||||
func TestCaching(t *testing.T) {
|
||||
duration := 100 * time.Millisecond
|
||||
ref := "foo"
|
||||
|
||||
sb := &slowpublish{duration}
|
||||
cb, _ := NewCaching(sb)
|
||||
|
||||
previousDigest := "not-a-digest"
|
||||
// Each iteration, we test that the first publish is slow and subsequent
|
||||
// publishs are fast and return the same reference. For each of these
|
||||
// iterations we use a new random image, which should invalidate the
|
||||
// cached reference from previous iterations.
|
||||
for idx := 0; idx < 3; idx++ {
|
||||
img, _ := random.Image(256, 8)
|
||||
|
||||
start := time.Now()
|
||||
ref1, err := cb.Publish(img, ref)
|
||||
if err != nil {
|
||||
t.Errorf("Publish() = %v", err)
|
||||
}
|
||||
end := time.Now()
|
||||
|
||||
elapsed := end.Sub(start)
|
||||
if elapsed < duration {
|
||||
t.Errorf("Elapsed time %v, wanted >= %s", elapsed, duration)
|
||||
}
|
||||
d1 := ref1.String()
|
||||
|
||||
if d1 == previousDigest {
|
||||
t.Errorf("Got same digest as previous iteration, wanted different: %v", d1)
|
||||
}
|
||||
previousDigest = d1
|
||||
|
||||
start = time.Now()
|
||||
ref2, err := cb.Publish(img, ref)
|
||||
if err != nil {
|
||||
t.Errorf("Publish() = %v", err)
|
||||
}
|
||||
end = time.Now()
|
||||
|
||||
elapsed = end.Sub(start)
|
||||
if elapsed >= duration {
|
||||
t.Errorf("Elapsed time %v, wanted < %s", elapsed, duration)
|
||||
}
|
||||
d2 := ref2.String()
|
||||
|
||||
if d1 != d2 {
|
||||
t.Error("Got different references, wanted same")
|
||||
}
|
||||
}
|
||||
}
|
16
pkg/resolve/doc.go
Normal file
16
pkg/resolve/doc.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package resolve defines logic for resolving K8s yaml inputs to ko.
|
||||
package resolve
|
138
pkg/resolve/fixed_test.go
Normal file
138
pkg/resolve/fixed_test.go
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package resolve
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
|
||||
"github.com/google/ko/pkg/build"
|
||||
"github.com/google/ko/pkg/publish"
|
||||
)
|
||||
|
||||
var (
|
||||
fixedBaseRepo, _ = name.NewRepository("gcr.io/asdf", name.WeakValidation)
|
||||
testImage, _ = random.Image(1024, 5)
|
||||
)
|
||||
|
||||
func TestFixedPublish(t *testing.T) {
|
||||
hex1 := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
hex2 := "baadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00d"
|
||||
f := newFixedPublish(fixedBaseRepo, map[string]v1.Hash{
|
||||
"foo": {
|
||||
Algorithm: "sha256",
|
||||
Hex: hex1,
|
||||
},
|
||||
"bar": {
|
||||
Algorithm: "sha256",
|
||||
Hex: hex2,
|
||||
},
|
||||
})
|
||||
|
||||
fooDigest, err := f.Publish(nil, "foo")
|
||||
if err != nil {
|
||||
t.Errorf("Publish(foo) = %v", err)
|
||||
}
|
||||
if got, want := fooDigest.String(), "gcr.io/asdf/foo@sha256:"+hex1; got != want {
|
||||
t.Errorf("Publish(foo) = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
barDigest, err := f.Publish(nil, "bar")
|
||||
if err != nil {
|
||||
t.Errorf("Publish(bar) = %v", err)
|
||||
}
|
||||
if got, want := barDigest.String(), "gcr.io/asdf/bar@sha256:"+hex2; got != want {
|
||||
t.Errorf("Publish(bar) = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
d, err := f.Publish(nil, "baz")
|
||||
if err == nil {
|
||||
t.Errorf("Publish(baz) = %v, want error", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixedBuild(t *testing.T) {
|
||||
f := newFixedBuild(map[string]v1.Image{
|
||||
"asdf": testImage,
|
||||
})
|
||||
|
||||
if got, want := f.IsSupportedReference("asdf"), true; got != want {
|
||||
t.Errorf("IsSupportedReference(asdf) = %v, want %v", got, want)
|
||||
}
|
||||
if got, err := f.Build("asdf"); err != nil {
|
||||
t.Errorf("Build(asdf) = %v, want %v", err, testImage)
|
||||
} else if got != testImage {
|
||||
t.Errorf("Build(asdf) = %v, want %v", got, testImage)
|
||||
}
|
||||
|
||||
if got, want := f.IsSupportedReference("blah"), false; got != want {
|
||||
t.Errorf("IsSupportedReference(blah) = %v, want %v", got, want)
|
||||
}
|
||||
if got, err := f.Build("blah"); err == nil {
|
||||
t.Errorf("Build(blah) = %v, want error", got)
|
||||
}
|
||||
}
|
||||
|
||||
type fixedBuild struct {
|
||||
entries map[string]v1.Image
|
||||
}
|
||||
|
||||
// newFixedBuild returns a build.Interface implementation that simply resolves
|
||||
// particular references to fixed v1.Image objects
|
||||
func newFixedBuild(entries map[string]v1.Image) build.Interface {
|
||||
return &fixedBuild{entries}
|
||||
}
|
||||
|
||||
// IsSupportedReference implements build.Interface
|
||||
func (f *fixedBuild) IsSupportedReference(s string) bool {
|
||||
_, ok := f.entries[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Build implements build.Interface
|
||||
func (f *fixedBuild) Build(s string) (v1.Image, error) {
|
||||
if img, ok := f.entries[s]; ok {
|
||||
return img, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported reference: %q", s)
|
||||
}
|
||||
|
||||
type fixedPublish struct {
|
||||
base name.Repository
|
||||
entries map[string]v1.Hash
|
||||
}
|
||||
|
||||
// newFixedPublish returns a publish.Interface implementation that simply
|
||||
// resolves particular references to fixed name.Digest references.
|
||||
func newFixedPublish(base name.Repository, entries map[string]v1.Hash) publish.Interface {
|
||||
return &fixedPublish{base, entries}
|
||||
}
|
||||
|
||||
// Publish implements publish.Interface
|
||||
func (f *fixedPublish) Publish(_ v1.Image, s string) (name.Reference, error) {
|
||||
h, ok := f.entries[s]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported importpath: %q", s)
|
||||
}
|
||||
d, err := name.NewDigest(fmt.Sprintf("%s/%s@%s", f.base, s, h), name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
}
|
159
pkg/resolve/resolve.go
Normal file
159
pkg/resolve/resolve.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package resolve
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/google/ko/pkg/build"
|
||||
"github.com/google/ko/pkg/publish"
|
||||
)
|
||||
|
||||
// ImageReferences resolves supported references to images within the input yaml
|
||||
// to published image digests.
|
||||
func ImageReferences(input []byte, builder build.Interface, publisher publish.Interface) ([]byte, error) {
|
||||
// First, walk the input objects and collect a list of supported references
|
||||
refs := make(map[string]struct{})
|
||||
// The loop is to support multi-document yaml files.
|
||||
// This is handled by using a yaml.Decoder and reading objects until io.EOF, see:
|
||||
// https://github.com/go-yaml/yaml/blob/v2.2.1/yaml.go#L124
|
||||
decoder := yaml.NewDecoder(bytes.NewBuffer(input))
|
||||
for {
|
||||
var obj interface{}
|
||||
if err := decoder.Decode(&obj); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// This simply returns the replaced object, which we discard during the gathering phase.
|
||||
if _, err := replaceRecursive(obj, func(ref string) (string, error) {
|
||||
if builder.IsSupportedReference(ref) {
|
||||
refs[ref] = struct{}{}
|
||||
}
|
||||
return ref, nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Next, perform parallel builds for each of the supported references.
|
||||
var sm sync.Map
|
||||
var errg errgroup.Group
|
||||
for ref := range refs {
|
||||
ref := ref
|
||||
errg.Go(func() error {
|
||||
img, err := builder.Build(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
digest, err := publisher.Publish(img, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sm.Store(ref, digest.String())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Last, walk the inputs again and replace the supported references with their published images.
|
||||
decoder = yaml.NewDecoder(bytes.NewBuffer(input))
|
||||
buf := bytes.NewBuffer(nil)
|
||||
encoder := yaml.NewEncoder(buf)
|
||||
for {
|
||||
var obj interface{}
|
||||
if err := decoder.Decode(&obj); err != nil {
|
||||
if err == io.EOF {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// Recursively walk input, replacing supported reference with our computed digests.
|
||||
obj2, err := replaceRecursive(obj, func(ref string) (string, error) {
|
||||
if !builder.IsSupportedReference(ref) {
|
||||
return ref, nil
|
||||
}
|
||||
if val, ok := sm.Load(ref); ok {
|
||||
return val.(string), nil
|
||||
}
|
||||
return "", fmt.Errorf("resolved reference to %q not found", ref)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := encoder.Encode(obj2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type replaceString func(string) (string, error)
|
||||
|
||||
// replaceRecursive walks the provided untyped object recursively by switching
|
||||
// on the type of the object at each level. It supports walking through the
|
||||
// keys and values of maps, and the elements of an array. When a leaf of type
|
||||
// string is encountered, this will call the provided replaceString function on
|
||||
// it. This function does not support walking through struct types, but also
|
||||
// should not need to as the input is expected to be the result of parsing yaml
|
||||
// or json into an interface{}, which should only produce primitives, maps and
|
||||
// arrays. This function will return a copy of the object rebuilt by the walk
|
||||
// with the replacements made.
|
||||
func replaceRecursive(obj interface{}, rs replaceString) (interface{}, error) {
|
||||
switch typed := obj.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m2 := make(map[interface{}]interface{}, len(typed))
|
||||
for k, v := range typed {
|
||||
k2, err := replaceRecursive(k, rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v2, err := replaceRecursive(v, rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m2[k2] = v2
|
||||
}
|
||||
return m2, nil
|
||||
|
||||
case []interface{}:
|
||||
a2 := make([]interface{}, len(typed))
|
||||
for idx, v := range typed {
|
||||
v2, err := replaceRecursive(v, rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a2[idx] = v2
|
||||
}
|
||||
return a2, nil
|
||||
|
||||
case string:
|
||||
// call our replaceString on this string leaf.
|
||||
return rs(typed)
|
||||
|
||||
default:
|
||||
// leave other leaves alone.
|
||||
return typed, nil
|
||||
}
|
||||
}
|
341
pkg/resolve/resolve_test.go
Normal file
341
pkg/resolve/resolve_test.go
Normal file
@ -0,0 +1,341 @@
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package resolve
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
)
|
||||
|
||||
var (
|
||||
fooRef = "github.com/awesomesauce/foo"
|
||||
foo = mustRandom()
|
||||
fooHash = mustDigest(foo)
|
||||
barRef = "github.com/awesomesauce/bar"
|
||||
bar = mustRandom()
|
||||
barHash = mustDigest(bar)
|
||||
bazRef = "github.com/awesomesauce/baz"
|
||||
baz = mustRandom()
|
||||
bazHash = mustDigest(baz)
|
||||
testBuilder = newFixedBuild(map[string]v1.Image{
|
||||
fooRef: foo,
|
||||
barRef: bar,
|
||||
bazRef: baz,
|
||||
})
|
||||
testHashes = map[string]v1.Hash{
|
||||
fooRef: fooHash,
|
||||
barRef: barHash,
|
||||
bazRef: bazHash,
|
||||
}
|
||||
)
|
||||
|
||||
func TestYAMLArrays(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
refs []string
|
||||
hashes []v1.Hash
|
||||
base name.Repository
|
||||
}{{
|
||||
desc: "singleton array",
|
||||
refs: []string{fooRef},
|
||||
hashes: []v1.Hash{fooHash},
|
||||
base: mustRepository("gcr.io/mattmoor"),
|
||||
}, {
|
||||
desc: "singleton array (different base)",
|
||||
refs: []string{fooRef},
|
||||
hashes: []v1.Hash{fooHash},
|
||||
base: mustRepository("gcr.io/jasonhall"),
|
||||
}, {
|
||||
desc: "two element array",
|
||||
refs: []string{fooRef, barRef},
|
||||
hashes: []v1.Hash{fooHash, barHash},
|
||||
base: mustRepository("gcr.io/jonjohnson"),
|
||||
}, {
|
||||
desc: "empty array",
|
||||
refs: []string{},
|
||||
hashes: []v1.Hash{},
|
||||
base: mustRepository("gcr.io/blah"),
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
inputStructured := test.refs
|
||||
inputYAML, err := yaml.Marshal(inputStructured)
|
||||
if err != nil {
|
||||
t.Fatalf("yaml.Marshal(%v) = %v", inputStructured, err)
|
||||
}
|
||||
|
||||
outYAML, err := ImageReferences(inputYAML, testBuilder, newFixedPublish(test.base, testHashes))
|
||||
if err != nil {
|
||||
t.Fatalf("ImageReferences(%v) = %v", string(inputYAML), err)
|
||||
}
|
||||
var outStructured []string
|
||||
if err := yaml.Unmarshal(outYAML, &outStructured); err != nil {
|
||||
t.Errorf("yaml.Unmarshal(%v) = %v", string(outYAML), err)
|
||||
}
|
||||
|
||||
if want, got := len(inputStructured), len(outStructured); want != got {
|
||||
t.Errorf("ImageReferences(%v) = %v, want %v", string(inputYAML), got, want)
|
||||
}
|
||||
|
||||
var expectedStructured []string
|
||||
for i, ref := range test.refs {
|
||||
hash := test.hashes[i]
|
||||
expectedStructured = append(expectedStructured,
|
||||
computeDigest(test.base, ref, hash))
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expectedStructured, outStructured, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("ImageReferences(%v); (-want +got) = %v", string(inputYAML), diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLMaps(t *testing.T) {
|
||||
base := mustRepository("gcr.io/mattmoor")
|
||||
tests := []struct {
|
||||
desc string
|
||||
input map[string]string
|
||||
expected map[string]string
|
||||
}{{
|
||||
desc: "simple value",
|
||||
input: map[string]string{"image": fooRef},
|
||||
expected: map[string]string{"image": computeDigest(base, fooRef, fooHash)},
|
||||
}, {
|
||||
desc: "simple key",
|
||||
input: map[string]string{bazRef: "blah"},
|
||||
expected: map[string]string{
|
||||
computeDigest(base, bazRef, bazHash): "blah",
|
||||
},
|
||||
}, {
|
||||
desc: "key and value",
|
||||
input: map[string]string{fooRef: barRef},
|
||||
expected: map[string]string{
|
||||
computeDigest(base, fooRef, fooHash): computeDigest(base, barRef, barHash),
|
||||
},
|
||||
}, {
|
||||
desc: "empty map",
|
||||
input: map[string]string{},
|
||||
expected: map[string]string{},
|
||||
}, {
|
||||
desc: "multiple values",
|
||||
input: map[string]string{
|
||||
"arg1": fooRef,
|
||||
"arg2": barRef,
|
||||
},
|
||||
expected: map[string]string{
|
||||
"arg1": computeDigest(base, fooRef, fooHash),
|
||||
"arg2": computeDigest(base, barRef, barHash),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
inputStructured := test.input
|
||||
inputYAML, err := yaml.Marshal(inputStructured)
|
||||
if err != nil {
|
||||
t.Fatalf("yaml.Marshal(%v) = %v", inputStructured, err)
|
||||
}
|
||||
|
||||
outYAML, err := ImageReferences(inputYAML, testBuilder, newFixedPublish(base, testHashes))
|
||||
if err != nil {
|
||||
t.Fatalf("ImageReferences(%v) = %v", string(inputYAML), err)
|
||||
}
|
||||
var outStructured map[string]string
|
||||
if err := yaml.Unmarshal(outYAML, &outStructured); err != nil {
|
||||
t.Errorf("yaml.Unmarshal(%v) = %v", string(outYAML), err)
|
||||
}
|
||||
|
||||
if want, got := len(inputStructured), len(outStructured); want != got {
|
||||
t.Errorf("ImageReferences(%v) = %v, want %v", string(inputYAML), got, want)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(test.expected, outStructured, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("ImageReferences(%v); (-want +got) = %v", string(inputYAML), diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// object has public fields to avoid `yaml:"foo"` annotations.
|
||||
type object struct {
|
||||
S string
|
||||
M map[string]object
|
||||
A []object
|
||||
P *object
|
||||
}
|
||||
|
||||
func TestYAMLObject(t *testing.T) {
|
||||
base := mustRepository("gcr.io/bazinga")
|
||||
tests := []struct {
|
||||
desc string
|
||||
input *object
|
||||
expected *object
|
||||
}{{
|
||||
desc: "empty object",
|
||||
input: &object{},
|
||||
expected: &object{},
|
||||
}, {
|
||||
desc: "string field",
|
||||
input: &object{S: fooRef},
|
||||
expected: &object{S: computeDigest(base, fooRef, fooHash)},
|
||||
}, {
|
||||
desc: "map field",
|
||||
input: &object{M: map[string]object{"blah": {S: fooRef}}},
|
||||
expected: &object{M: map[string]object{"blah": {S: computeDigest(base, fooRef, fooHash)}}},
|
||||
}, {
|
||||
desc: "array field",
|
||||
input: &object{A: []object{{S: fooRef}}},
|
||||
expected: &object{A: []object{{S: computeDigest(base, fooRef, fooHash)}}},
|
||||
}, {
|
||||
desc: "pointer field",
|
||||
input: &object{P: &object{S: fooRef}},
|
||||
expected: &object{P: &object{S: computeDigest(base, fooRef, fooHash)}},
|
||||
}, {
|
||||
desc: "deep field",
|
||||
input: &object{M: map[string]object{"blah": {A: []object{{P: &object{S: fooRef}}}}}},
|
||||
expected: &object{M: map[string]object{"blah": {A: []object{{P: &object{S: computeDigest(base, fooRef, fooHash)}}}}}},
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
inputStructured := test.input
|
||||
inputYAML, err := yaml.Marshal(inputStructured)
|
||||
if err != nil {
|
||||
t.Fatalf("yaml.Marshal(%v) = %v", inputStructured, err)
|
||||
}
|
||||
|
||||
outYAML, err := ImageReferences(inputYAML, testBuilder, newFixedPublish(base, testHashes))
|
||||
if err != nil {
|
||||
t.Fatalf("ImageReferences(%v) = %v", string(inputYAML), err)
|
||||
}
|
||||
var outStructured *object
|
||||
if err := yaml.Unmarshal(outYAML, &outStructured); err != nil {
|
||||
t.Errorf("yaml.Unmarshal(%v) = %v", string(outYAML), err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(test.expected, outStructured, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("ImageReferences(%v); (-want +got) = %v", string(inputYAML), diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiDocumentYAMLs(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
refs []string
|
||||
hashes []v1.Hash
|
||||
base name.Repository
|
||||
}{{
|
||||
desc: "two string documents",
|
||||
refs: []string{fooRef, barRef},
|
||||
hashes: []v1.Hash{fooHash, barHash},
|
||||
base: mustRepository("gcr.io/multi-pass"),
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
encoder := yaml.NewEncoder(buf)
|
||||
for _, input := range test.refs {
|
||||
if err := encoder.Encode(input); err != nil {
|
||||
t.Fatalf("Encode(%v) = %v", input, err)
|
||||
}
|
||||
}
|
||||
inputYAML := buf.Bytes()
|
||||
|
||||
outYAML, err := ImageReferences(inputYAML, testBuilder, newFixedPublish(test.base, testHashes))
|
||||
if err != nil {
|
||||
t.Fatalf("ImageReferences(%v) = %v", string(inputYAML), err)
|
||||
}
|
||||
|
||||
buf = bytes.NewBuffer(outYAML)
|
||||
decoder := yaml.NewDecoder(buf)
|
||||
var outStructured []string
|
||||
for {
|
||||
var output string
|
||||
if err := decoder.Decode(&output); err == nil {
|
||||
outStructured = append(outStructured, output)
|
||||
} else if err == io.EOF {
|
||||
outStructured = append(outStructured, output)
|
||||
break
|
||||
} else {
|
||||
t.Errorf("yaml.Unmarshal(%v) = %v", string(outYAML), err)
|
||||
}
|
||||
}
|
||||
|
||||
var expectedStructured []string
|
||||
for i, ref := range test.refs {
|
||||
hash := test.hashes[i]
|
||||
expectedStructured = append(expectedStructured,
|
||||
computeDigest(test.base, ref, hash))
|
||||
}
|
||||
// The multi-document output always seems to leave a trailing --- so we end up with
|
||||
// an extra empty element.
|
||||
expectedStructured = append(expectedStructured, "")
|
||||
|
||||
if want, got := len(expectedStructured), len(outStructured); want != got {
|
||||
t.Errorf("ImageReferences(%v) = %v, want %v", string(inputYAML), got, want)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expectedStructured, outStructured, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("ImageReferences(%v); (-want +got) = %v", string(inputYAML), diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustRandom() v1.Image {
|
||||
img, err := random.Image(5, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func mustRepository(s string) name.Repository {
|
||||
n, err := name.NewRepository(s, name.WeakValidation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func mustDigest(img v1.Image) v1.Hash {
|
||||
d, err := img.Digest()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func computeDigest(base name.Repository, ref string, h v1.Hash) string {
|
||||
d, err := newFixedPublish(base, map[string]v1.Hash{ref: h}).Publish(nil, ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d.String()
|
||||
}
|
1
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.exe
|
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
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.
|
||||
|
22
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# go-winio
|
||||
|
||||
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||
for using named pipes as a net transport.
|
||||
|
||||
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
||||
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
||||
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
||||
package.
|
||||
|
||||
Please see the LICENSE file for licensing information.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of
|
||||
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||
see the [Code of Conduct
|
||||
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||
questions or comments.
|
||||
|
||||
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
|
||||
for another named pipe implementation.
|
27
vendor/github.com/Microsoft/go-winio/archive/tar/LICENSE
generated
vendored
Normal file
27
vendor/github.com/Microsoft/go-winio/archive/tar/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
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.
|
280
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
280
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
@ -0,0 +1,280 @@
|
||||
// +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
|
||||
}
|
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
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
|
||||
}
|
307
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
307
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
@ -0,0 +1,307 @@
|
||||
// +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
|
||||
}
|
61
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
61
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
// +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 uint32
|
||||
pad uint32 // 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
|
||||
}
|
421
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
421
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
@ -0,0 +1,421 @@
|
||||
// +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 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
|
||||
} else if err == syscall.ERROR_MORE_DATA {
|
||||
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
||||
// and the message still has more bytes. Treat this as a success, since
|
||||
// this package presents all named pipes as byte streams.
|
||||
err = nil
|
||||
}
|
||||
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 we use
|
||||
// a default timeout of 5 seconds. (We do not use WaitNamedPipe.)
|
||||
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||
var absTimeout time.Time
|
||||
if timeout != nil {
|
||||
absTimeout = time.Now().Add(*timeout)
|
||||
} else {
|
||||
absTimeout = time.Now().Add(time.Second * 2)
|
||||
}
|
||||
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
|
||||
}
|
||||
if time.Now().After(absTimeout) {
|
||||
return nil, ErrTimeout
|
||||
}
|
||||
|
||||
// Wait 10 msec and try again. This is a rather simplistic
|
||||
// view, as we always try each 10 milliseconds.
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// Create a client handle and connect it. This results in the pipe
|
||||
// instance always existing, so that clients see ERROR_PIPE_BUSY
|
||||
// rather than ERROR_FILE_NOT_FOUND. This ties the first instance
|
||||
// up so that no other instances can be used. This would have been
|
||||
// cleaner if the Win32 API matched CreateFile with ConnectNamedPipe
|
||||
// instead of CreateNamedPipe. (Apparently created named pipes are
|
||||
// considered to be in listening state regardless of whether any
|
||||
// active calls to ConnectNamedPipe are outstanding.)
|
||||
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
|
||||
}
|
||||
// Close the client handle. The server side of the instance will
|
||||
// still be busy, leading to ERROR_PIPE_BUSY instead of
|
||||
// ERROR_NOT_FOUND, as long as we don't close the server handle,
|
||||
// or disconnect the client with DisconnectNamedPipe.
|
||||
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)
|
||||
}
|
202
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
202
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
// +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()
|
||||
}
|
128
vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
128
vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
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()
|
||||
}
|
98
vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
98
vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
// +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
|
||||
}
|
3
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
3
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
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
|
520
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
520
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
@ -0,0 +1,520 @@
|
||||
// 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
|
||||
}
|
182
vendor/github.com/docker/distribution/AUTHORS
generated
vendored
Normal file
182
vendor/github.com/docker/distribution/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
a-palchikov <deemok@gmail.com>
|
||||
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>
|
||||
Anton Tiurin <noxiouz@yandex.ru>
|
||||
Antonio Mercado <amercado@thinknode.com>
|
||||
Antonio Murdaca <runcom@redhat.com>
|
||||
Anusha Ragunathan <anusha@docker.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>
|
||||
David Verhasselt <david@crowdway.com>
|
||||
David Xia <dxia@spotify.com>
|
||||
davidli <wenquan.li@hp.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>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Hua Wang <wanghua.humble@gmail.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>
|
||||
Jon Johnson <jonjohnson@google.com>
|
||||
Jon Poler <jonathan.poler@apcera.com>
|
||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||
Jordan Liggitt <jliggitt@redhat.com>
|
||||
Josh Chorlton <josh.chorlton@docker.com>
|
||||
Josh Hawn <josh.hawn@docker.com>
|
||||
Julien Fernandez <julien.fernandez@gmail.com>
|
||||
Ke Xu <leonhartx.k@gmail.com>
|
||||
Keerthan Mala <kmala@engineyard.com>
|
||||
Kelsey Hightower <kelsey.hightower@gmail.com>
|
||||
Kenneth Lim <kennethlimcp@gmail.com>
|
||||
Kenny Leung <kleung@google.com>
|
||||
Li Yi <denverdino@gmail.com>
|
||||
Liu Hua <sdu.liu@huawei.com>
|
||||
liuchang0812 <liuchang0812@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>
|
||||
Matt Moore <mattmoor@google.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Matthew Green <greenmr@live.co.uk>
|
||||
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@apache.org>
|
||||
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>
|
||||
Victor Vieux <vieux@docker.com>
|
||||
Victoria Bialas <victoria.bialas@docker.com>
|
||||
Vincent Batts <vbatts@redhat.com>
|
||||
Vincent Demeester <vincent@sbr.pm>
|
||||
Vincent Giersch <vincent.giersch@ovh.net>
|
||||
W. Trevor King <wking@tremily.us>
|
||||
weiyuan.yl <weiyuan.yl@alibaba-inc.com>
|
||||
xg.song <xg.song@venusource.com>
|
||||
xiekeyang <xiekeyang@huawei.com>
|
||||
Yann ROBERT <yann.robert@anantaplex.fr>
|
||||
yaoyao.xyy <yaoyao.xyy@alibaba-inc.com>
|
||||
yuexiao-wang <wang.yuexiao@zte.com.cn>
|
||||
yuzou <zouyu7@huawei.com>
|
||||
zhouhaibing089 <zhouhaibing089@gmail.com>
|
||||
姜继忠 <jizhong.jiangjz@alibaba-inc.com>
|
202
vendor/github.com/docker/distribution/LICENSE
generated
vendored
Normal file
202
vendor/github.com/docker/distribution/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
247
vendor/github.com/docker/distribution/digestset/set.go
generated
vendored
Normal file
247
vendor/github.com/docker/distribution/digestset/set.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
||||
package digestset
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
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 digest.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.Digest, error) {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
if len(dst.entries) == 0 {
|
||||
return "", ErrDigestNotFound
|
||||
}
|
||||
var (
|
||||
searchFunc func(int) bool
|
||||
alg digest.Algorithm
|
||||
hex string
|
||||
)
|
||||
dgst, err := digest.Parse(d)
|
||||
if err == digest.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.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.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.Digest {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
retValues := make([]digest.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.Digest]string {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
m := make(map[digest.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 digest.Algorithm
|
||||
val string
|
||||
digest 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]
|
||||
}
|
42
vendor/github.com/docker/distribution/reference/helpers.go
generated
vendored
Normal file
42
vendor/github.com/docker/distribution/reference/helpers.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package reference
|
||||
|
||||
import "path"
|
||||
|
||||
// IsNameOnly returns true if reference only contains a repo name.
|
||||
func IsNameOnly(ref Named) bool {
|
||||
if _, ok := ref.(NamedTagged); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := ref.(Canonical); ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FamiliarName returns the familiar name string
|
||||
// for the given named, familiarizing if needed.
|
||||
func FamiliarName(ref Named) string {
|
||||
if nn, ok := ref.(normalizedNamed); ok {
|
||||
return nn.Familiar().Name()
|
||||
}
|
||||
return ref.Name()
|
||||
}
|
||||
|
||||
// FamiliarString returns the familiar string representation
|
||||
// for the given reference, familiarizing if needed.
|
||||
func FamiliarString(ref Reference) string {
|
||||
if nn, ok := ref.(normalizedNamed); ok {
|
||||
return nn.Familiar().String()
|
||||
}
|
||||
return ref.String()
|
||||
}
|
||||
|
||||
// FamiliarMatch reports whether ref matches the specified pattern.
|
||||
// See https://godoc.org/path#Match for supported patterns.
|
||||
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
|
||||
matched, err := path.Match(pattern, FamiliarString(ref))
|
||||
if namedRef, isNamed := ref.(Named); isNamed && !matched {
|
||||
matched, _ = path.Match(pattern, FamiliarName(namedRef))
|
||||
}
|
||||
return matched, err
|
||||
}
|
170
vendor/github.com/docker/distribution/reference/normalize.go
generated
vendored
Normal file
170
vendor/github.com/docker/distribution/reference/normalize.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
package reference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/digestset"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
legacyDefaultDomain = "index.docker.io"
|
||||
defaultDomain = "docker.io"
|
||||
officialRepoName = "library"
|
||||
defaultTag = "latest"
|
||||
)
|
||||
|
||||
// normalizedNamed represents a name which has been
|
||||
// normalized and has a familiar form. A familiar name
|
||||
// is what is used in Docker UI. An example normalized
|
||||
// name is "docker.io/library/ubuntu" and corresponding
|
||||
// familiar name of "ubuntu".
|
||||
type normalizedNamed interface {
|
||||
Named
|
||||
Familiar() Named
|
||||
}
|
||||
|
||||
// ParseNormalizedNamed parses a string into a named reference
|
||||
// transforming a familiar name from Docker UI to a fully
|
||||
// qualified reference. If the value may be an identifier
|
||||
// use ParseAnyReference.
|
||||
func ParseNormalizedNamed(s string) (Named, error) {
|
||||
if ok := anchoredIdentifierRegexp.MatchString(s); ok {
|
||||
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
|
||||
}
|
||||
domain, remainder := splitDockerDomain(s)
|
||||
var remoteName string
|
||||
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
|
||||
remoteName = remainder[:tagSep]
|
||||
} else {
|
||||
remoteName = remainder
|
||||
}
|
||||
if strings.ToLower(remoteName) != remoteName {
|
||||
return nil, errors.New("invalid reference format: repository name must be lowercase")
|
||||
}
|
||||
|
||||
ref, err := Parse(domain + "/" + remainder)
|
||||
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
|
||||
}
|
||||
|
||||
// splitDockerDomain splits a repository name to domain and remotename string.
|
||||
// If no valid domain is found, the default domain is used. Repository name
|
||||
// needs to be already validated before.
|
||||
func splitDockerDomain(name string) (domain, remainder string) {
|
||||
i := strings.IndexRune(name, '/')
|
||||
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
|
||||
domain, remainder = defaultDomain, name
|
||||
} else {
|
||||
domain, remainder = name[:i], name[i+1:]
|
||||
}
|
||||
if domain == legacyDefaultDomain {
|
||||
domain = defaultDomain
|
||||
}
|
||||
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
|
||||
remainder = officialRepoName + "/" + remainder
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// familiarizeName returns a shortened version of the name familiar
|
||||
// to to the Docker UI. Familiar names have the default domain
|
||||
// "docker.io" and "library/" repository prefix removed.
|
||||
// For example, "docker.io/library/redis" will have the familiar
|
||||
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
|
||||
// Returns a familiarized named only reference.
|
||||
func familiarizeName(named namedRepository) repository {
|
||||
repo := repository{
|
||||
domain: named.Domain(),
|
||||
path: named.Path(),
|
||||
}
|
||||
|
||||
if repo.domain == defaultDomain {
|
||||
repo.domain = ""
|
||||
// Handle official repositories which have the pattern "library/<official repo name>"
|
||||
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
|
||||
repo.path = split[1]
|
||||
}
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func (r reference) Familiar() Named {
|
||||
return reference{
|
||||
namedRepository: familiarizeName(r.namedRepository),
|
||||
tag: r.tag,
|
||||
digest: r.digest,
|
||||
}
|
||||
}
|
||||
|
||||
func (r repository) Familiar() Named {
|
||||
return familiarizeName(r)
|
||||
}
|
||||
|
||||
func (t taggedReference) Familiar() Named {
|
||||
return taggedReference{
|
||||
namedRepository: familiarizeName(t.namedRepository),
|
||||
tag: t.tag,
|
||||
}
|
||||
}
|
||||
|
||||
func (c canonicalReference) Familiar() Named {
|
||||
return canonicalReference{
|
||||
namedRepository: familiarizeName(c.namedRepository),
|
||||
digest: c.digest,
|
||||
}
|
||||
}
|
||||
|
||||
// TagNameOnly adds the default tag "latest" to a reference if it only has
|
||||
// a repo name.
|
||||
func TagNameOnly(ref Named) Named {
|
||||
if IsNameOnly(ref) {
|
||||
namedTagged, err := WithTag(ref, defaultTag)
|
||||
if err != nil {
|
||||
// Default tag must be valid, to create a NamedTagged
|
||||
// type with non-validated input the WithTag function
|
||||
// should be used instead
|
||||
panic(err)
|
||||
}
|
||||
return namedTagged
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
// ParseAnyReference parses a reference string as a possible identifier,
|
||||
// full digest, or familiar name.
|
||||
func ParseAnyReference(ref string) (Reference, error) {
|
||||
if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
|
||||
return digestReference("sha256:" + ref), nil
|
||||
}
|
||||
if dgst, err := digest.Parse(ref); err == nil {
|
||||
return digestReference(dgst), nil
|
||||
}
|
||||
|
||||
return ParseNormalizedNamed(ref)
|
||||
}
|
||||
|
||||
// ParseAnyReferenceWithSet parses a reference string as a possible short
|
||||
// identifier to be matched in a digest set, a full digest, or familiar name.
|
||||
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
|
||||
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
|
||||
dgst, err := ds.Lookup(ref)
|
||||
if err == nil {
|
||||
return digestReference(dgst), nil
|
||||
}
|
||||
} else {
|
||||
if dgst, err := digest.Parse(ref); err == nil {
|
||||
return digestReference(dgst), nil
|
||||
}
|
||||
}
|
||||
|
||||
return ParseNormalizedNamed(ref)
|
||||
}
|
433
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
Normal file
433
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
Normal file
@ -0,0 +1,433 @@
|
||||
// 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 := [domain '/'] path-component ['/' path-component]*
|
||||
// domain := domain-component ['.' domain-component]* [':' port-number]
|
||||
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||
// port-number := /[0-9]+/
|
||||
// path-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
|
||||
//
|
||||
// identifier := /[a-f0-9]{64}/
|
||||
// short-identifier := /[a-f0-9]{6,64}/
|
||||
package reference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/go-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)
|
||||
|
||||
// ErrNameNotCanonical is returned when a name is not canonical.
|
||||
ErrNameNotCanonical = errors.New("repository name must be canonical")
|
||||
)
|
||||
|
||||
// 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 domain and digest
|
||||
type Canonical interface {
|
||||
Named
|
||||
Digest() digest.Digest
|
||||
}
|
||||
|
||||
// namedRepository is a reference to a repository with a name.
|
||||
// A namedRepository has both domain and path components.
|
||||
type namedRepository interface {
|
||||
Named
|
||||
Domain() string
|
||||
Path() string
|
||||
}
|
||||
|
||||
// Domain returns the domain part of the Named reference
|
||||
func Domain(named Named) string {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Domain()
|
||||
}
|
||||
domain, _ := splitDomain(named.Name())
|
||||
return domain
|
||||
}
|
||||
|
||||
// Path returns the name without the domain part of the Named reference
|
||||
func Path(named Named) (name string) {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Path()
|
||||
}
|
||||
_, path := splitDomain(named.Name())
|
||||
return path
|
||||
}
|
||||
|
||||
func splitDomain(name string) (string, string) {
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if len(match) != 3 {
|
||||
return "", name
|
||||
}
|
||||
return match[1], match[2]
|
||||
}
|
||||
|
||||
// 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
|
||||
// DEPRECATED: Use Domain or Path
|
||||
func SplitHostname(named Named) (string, string) {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Domain(), r.Path()
|
||||
}
|
||||
return splitDomain(named.Name())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var repo repository
|
||||
|
||||
nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
|
||||
if nameMatch != nil && len(nameMatch) == 3 {
|
||||
repo.domain = nameMatch[1]
|
||||
repo.path = nameMatch[2]
|
||||
} else {
|
||||
repo.domain = ""
|
||||
repo.path = matches[1]
|
||||
}
|
||||
|
||||
ref := reference{
|
||||
namedRepository: repo,
|
||||
tag: matches[2],
|
||||
}
|
||||
if matches[3] != "" {
|
||||
var err error
|
||||
ref.digest, err = digest.Parse(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 and be in the canonical
|
||||
// form, 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) {
|
||||
named, err := ParseNormalizedNamed(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if named.String() != s {
|
||||
return nil, ErrNameNotCanonical
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if match == nil || len(match) != 3 {
|
||||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
return repository{
|
||||
domain: match[1],
|
||||
path: match[2],
|
||||
}, 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
|
||||
}
|
||||
var repo repository
|
||||
if r, ok := name.(namedRepository); ok {
|
||||
repo.domain = r.Domain()
|
||||
repo.path = r.Path()
|
||||
} else {
|
||||
repo.path = name.Name()
|
||||
}
|
||||
if canonical, ok := name.(Canonical); ok {
|
||||
return reference{
|
||||
namedRepository: repo,
|
||||
tag: tag,
|
||||
digest: canonical.Digest(),
|
||||
}, nil
|
||||
}
|
||||
return taggedReference{
|
||||
namedRepository: repo,
|
||||
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
|
||||
}
|
||||
var repo repository
|
||||
if r, ok := name.(namedRepository); ok {
|
||||
repo.domain = r.Domain()
|
||||
repo.path = r.Path()
|
||||
} else {
|
||||
repo.path = name.Name()
|
||||
}
|
||||
if tagged, ok := name.(Tagged); ok {
|
||||
return reference{
|
||||
namedRepository: repo,
|
||||
tag: tagged.Tag(),
|
||||
digest: digest,
|
||||
}, nil
|
||||
}
|
||||
return canonicalReference{
|
||||
namedRepository: repo,
|
||||
digest: digest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TrimNamed removes any tag or digest from the named reference.
|
||||
func TrimNamed(ref Named) Named {
|
||||
domain, path := SplitHostname(ref)
|
||||
return repository{
|
||||
domain: domain,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
namedRepository: ref.namedRepository,
|
||||
digest: ref.digest,
|
||||
}
|
||||
}
|
||||
return ref.namedRepository
|
||||
}
|
||||
if ref.digest == "" {
|
||||
return taggedReference{
|
||||
namedRepository: ref.namedRepository,
|
||||
tag: ref.tag,
|
||||
}
|
||||
}
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
type reference struct {
|
||||
namedRepository
|
||||
tag string
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
func (r reference) String() string {
|
||||
return r.Name() + ":" + r.tag + "@" + r.digest.String()
|
||||
}
|
||||
|
||||
func (r reference) Tag() string {
|
||||
return r.tag
|
||||
}
|
||||
|
||||
func (r reference) Digest() digest.Digest {
|
||||
return r.digest
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
domain string
|
||||
path string
|
||||
}
|
||||
|
||||
func (r repository) String() string {
|
||||
return r.Name()
|
||||
}
|
||||
|
||||
func (r repository) Name() string {
|
||||
if r.domain == "" {
|
||||
return r.path
|
||||
}
|
||||
return r.domain + "/" + r.path
|
||||
}
|
||||
|
||||
func (r repository) Domain() string {
|
||||
return r.domain
|
||||
}
|
||||
|
||||
func (r repository) Path() string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
type digestReference digest.Digest
|
||||
|
||||
func (d digestReference) String() string {
|
||||
return digest.Digest(d).String()
|
||||
}
|
||||
|
||||
func (d digestReference) Digest() digest.Digest {
|
||||
return digest.Digest(d)
|
||||
}
|
||||
|
||||
type taggedReference struct {
|
||||
namedRepository
|
||||
tag string
|
||||
}
|
||||
|
||||
func (t taggedReference) String() string {
|
||||
return t.Name() + ":" + t.tag
|
||||
}
|
||||
|
||||
func (t taggedReference) Tag() string {
|
||||
return t.tag
|
||||
}
|
||||
|
||||
type canonicalReference struct {
|
||||
namedRepository
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
func (c canonicalReference) String() string {
|
||||
return c.Name() + "@" + c.digest.String()
|
||||
}
|
||||
|
||||
func (c canonicalReference) Digest() digest.Digest {
|
||||
return c.digest
|
||||
}
|
143
vendor/github.com/docker/distribution/reference/regexp.go
generated
vendored
Normal file
143
vendor/github.com/docker/distribution/reference/regexp.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
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)))
|
||||
|
||||
// domainComponentRegexp restricts the registry domain component of a
|
||||
// repository name to start with a component as defined by DomainRegexp
|
||||
// and followed by an optional port.
|
||||
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
|
||||
|
||||
// DomainRegexp defines the structure of potential domain 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.
|
||||
DomainRegexp = expression(
|
||||
domainComponentRegexp,
|
||||
optional(repeated(literal(`.`), domainComponentRegexp)),
|
||||
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 domain and name part omitting
|
||||
// the separating forward slash from either.
|
||||
NameRegexp = expression(
|
||||
optional(DomainRegexp, literal(`/`)),
|
||||
nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp)))
|
||||
|
||||
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||
// domain and trailing components.
|
||||
anchoredNameRegexp = anchored(
|
||||
optional(capture(DomainRegexp), 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)))
|
||||
|
||||
// IdentifierRegexp is the format for string identifier used as a
|
||||
// content addressable identifier using sha256. These identifiers
|
||||
// are like digests without the algorithm, since sha256 is used.
|
||||
IdentifierRegexp = match(`([a-f0-9]{64})`)
|
||||
|
||||
// ShortIdentifierRegexp is the format used to represent a prefix
|
||||
// of an identifier. A prefix may be used to match a sha256 identifier
|
||||
// within a list of trusted identifiers.
|
||||
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
|
||||
|
||||
// anchoredIdentifierRegexp is used to check or match an
|
||||
// identifier value, anchored at start and end of string.
|
||||
anchoredIdentifierRegexp = anchored(IdentifierRegexp)
|
||||
|
||||
// anchoredShortIdentifierRegexp is used to check if a value
|
||||
// is a possible identifier prefix, anchored at start and end
|
||||
// of string.
|
||||
anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp)
|
||||
)
|
||||
|
||||
// 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() + `$`)
|
||||
}
|
1984
vendor/github.com/docker/docker/AUTHORS
generated
vendored
Normal file
1984
vendor/github.com/docker/docker/AUTHORS
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
191
vendor/github.com/docker/docker/LICENSE
generated
vendored
Normal file
191
vendor/github.com/docker/docker/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://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
|
||||
|
||||
Copyright 2013-2017 Docker, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://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.
|
19
vendor/github.com/docker/docker/NOTICE
generated
vendored
Normal file
19
vendor/github.com/docker/docker/NOTICE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Docker
|
||||
Copyright 2012-2017 Docker, Inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
|
||||
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
42
vendor/github.com/docker/docker/api/README.md
generated
vendored
Normal file
42
vendor/github.com/docker/docker/api/README.md
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# Working on the Engine API
|
||||
|
||||
The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon.
|
||||
|
||||
It consists of various components in this repository:
|
||||
|
||||
- `api/swagger.yaml` A Swagger definition of the API.
|
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this.
|
||||
- `cli/` The command-line client.
|
||||
- `client/` The Go client used by the command-line client. It can also be used by third-party Go programs.
|
||||
- `daemon/` The daemon, which serves the API.
|
||||
|
||||
## Swagger definition
|
||||
|
||||
The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to:
|
||||
|
||||
1. Automatically generate documentation.
|
||||
2. Automatically generate the Go server and client. (A work-in-progress.)
|
||||
3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc.
|
||||
|
||||
## Updating the API documentation
|
||||
|
||||
The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, edit this file to represent the change in the documentation.
|
||||
|
||||
The file is split into two main sections:
|
||||
|
||||
- `definitions`, which defines re-usable objects used in requests and responses
|
||||
- `paths`, which defines the API endpoints (and some inline objects which don't need to be reusable)
|
||||
|
||||
To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section.
|
||||
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919).
|
||||
|
||||
`swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful when making edits to ensure you are doing the right thing.
|
||||
|
||||
## Viewing the API documentation
|
||||
|
||||
When you make edits to `swagger.yaml`, you may want to check the generated API documentation to ensure it renders correctly.
|
||||
|
||||
Run `make swagger-docs` and a preview will be running at `http://localhost`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation.
|
||||
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io).
|
11
vendor/github.com/docker/docker/api/common.go
generated
vendored
Normal file
11
vendor/github.com/docker/docker/api/common.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const (
|
||||
// DefaultVersion of Current REST API
|
||||
DefaultVersion = "1.38"
|
||||
|
||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||
// command to specify that no base image is to be used.
|
||||
NoBaseImageSpecifier = "scratch"
|
||||
)
|
6
vendor/github.com/docker/docker/api/common_unix.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker/api/common_unix.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// +build !windows
|
||||
|
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// MinVersion represents Minimum REST API version supported
|
||||
const MinVersion = "1.12"
|
8
vendor/github.com/docker/docker/api/common_windows.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker/api/common_windows.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// MinVersion represents Minimum REST API version supported
|
||||
// Technically the first daemon API version released on Windows is v1.25 in
|
||||
// engine version 1.13. However, some clients are explicitly using downlevel
|
||||
// APIs (e.g. docker-compose v2.1 file format) and that is just too restrictive.
|
||||
// Hence also allowing 1.24 on Windows.
|
||||
const MinVersion string = "1.24"
|
12
vendor/github.com/docker/docker/api/swagger-gen.yaml
generated
vendored
Normal file
12
vendor/github.com/docker/docker/api/swagger-gen.yaml
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
layout:
|
||||
models:
|
||||
- name: definition
|
||||
source: asset:model
|
||||
target: "{{ joinFilePath .Target .ModelPackage }}"
|
||||
file_name: "{{ (snakize (pascalize .Name)) }}.go"
|
||||
operations:
|
||||
- name: handler
|
||||
source: asset:serverOperation
|
||||
target: "{{ joinFilePath .Target .APIPackage .Package }}"
|
||||
file_name: "{{ (snakize (pascalize .Name)) }}.go"
|
10122
vendor/github.com/docker/docker/api/swagger.yaml
generated
vendored
Normal file
10122
vendor/github.com/docker/docker/api/swagger.yaml
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
vendor/github.com/docker/docker/api/types/auth.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/api/types/auth.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package types // import "github.com/docker/docker/api/types"
|
||||
|
||||
// AuthConfig contains authorization information for connecting to a Registry
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Auth string `json:"auth,omitempty"`
|
||||
|
||||
// Email is an optional value associated with the username.
|
||||
// This field is deprecated and will be removed in a later
|
||||
// version of docker.
|
||||
Email string `json:"email,omitempty"`
|
||||
|
||||
ServerAddress string `json:"serveraddress,omitempty"`
|
||||
|
||||
// IdentityToken is used to authenticate the user and get
|
||||
// an access token for the registry.
|
||||
IdentityToken string `json:"identitytoken,omitempty"`
|
||||
|
||||
// RegistryToken is a bearer token to be sent to a registry
|
||||
RegistryToken string `json:"registrytoken,omitempty"`
|
||||
}
|
23
vendor/github.com/docker/docker/api/types/blkiodev/blkio.go
generated
vendored
Normal file
23
vendor/github.com/docker/docker/api/types/blkiodev/blkio.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package blkiodev // import "github.com/docker/docker/api/types/blkiodev"
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WeightDevice is a structure that holds device:weight pair
|
||||
type WeightDevice struct {
|
||||
Path string
|
||||
Weight uint16
|
||||
}
|
||||
|
||||
func (w *WeightDevice) String() string {
|
||||
return fmt.Sprintf("%s:%d", w.Path, w.Weight)
|
||||
}
|
||||
|
||||
// ThrottleDevice is a structure that holds device:rate_per_second pair
|
||||
type ThrottleDevice struct {
|
||||
Path string
|
||||
Rate uint64
|
||||
}
|
||||
|
||||
func (t *ThrottleDevice) String() string {
|
||||
return fmt.Sprintf("%s:%d", t.Path, t.Rate)
|
||||
}
|
390
vendor/github.com/docker/docker/api/types/client.go
generated
vendored
Normal file
390
vendor/github.com/docker/docker/api/types/client.go
generated
vendored
Normal file
@ -0,0 +1,390 @@
|
||||
package types // import "github.com/docker/docker/api/types"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// CheckpointCreateOptions holds parameters to create a checkpoint from a container
|
||||
type CheckpointCreateOptions struct {
|
||||
CheckpointID string
|
||||
CheckpointDir string
|
||||
Exit bool
|
||||
}
|
||||
|
||||
// CheckpointListOptions holds parameters to list checkpoints for a container
|
||||
type CheckpointListOptions struct {
|
||||
CheckpointDir string
|
||||
}
|
||||
|
||||
// CheckpointDeleteOptions holds parameters to delete a checkpoint from a container
|
||||
type CheckpointDeleteOptions struct {
|
||||
CheckpointID string
|
||||
CheckpointDir string
|
||||
}
|
||||
|
||||
// ContainerAttachOptions holds parameters to attach to a container.
|
||||
type ContainerAttachOptions struct {
|
||||
Stream bool
|
||||
Stdin bool
|
||||
Stdout bool
|
||||
Stderr bool
|
||||
DetachKeys string
|
||||
Logs bool
|
||||
}
|
||||
|
||||
// ContainerCommitOptions holds parameters to commit changes into a container.
|
||||
type ContainerCommitOptions struct {
|
||||
Reference string
|
||||
Comment string
|
||||
Author string
|
||||
Changes []string
|
||||
Pause bool
|
||||
Config *container.Config
|
||||
}
|
||||
|
||||
// ContainerExecInspect holds information returned by exec inspect.
|
||||
type ContainerExecInspect struct {
|
||||
ExecID string
|
||||
ContainerID string
|
||||
Running bool
|
||||
ExitCode int
|
||||
Pid int
|
||||
}
|
||||
|
||||
// ContainerListOptions holds parameters to list containers with.
|
||||
type ContainerListOptions struct {
|
||||
Quiet bool
|
||||
Size bool
|
||||
All bool
|
||||
Latest bool
|
||||
Since string
|
||||
Before string
|
||||
Limit int
|
||||
Filters filters.Args
|
||||
}
|
||||
|
||||
// ContainerLogsOptions holds parameters to filter logs with.
|
||||
type ContainerLogsOptions struct {
|
||||
ShowStdout bool
|
||||
ShowStderr bool
|
||||
Since string
|
||||
Until string
|
||||
Timestamps bool
|
||||
Follow bool
|
||||
Tail string
|
||||
Details bool
|
||||
}
|
||||
|
||||
// ContainerRemoveOptions holds parameters to remove containers.
|
||||
type ContainerRemoveOptions struct {
|
||||
RemoveVolumes bool
|
||||
RemoveLinks bool
|
||||
Force bool
|
||||
}
|
||||
|
||||
// ContainerStartOptions holds parameters to start containers.
|
||||
type ContainerStartOptions struct {
|
||||
CheckpointID string
|
||||
CheckpointDir string
|
||||
}
|
||||
|
||||
// CopyToContainerOptions holds information
|
||||
// about files to copy into a container
|
||||
type CopyToContainerOptions struct {
|
||||
AllowOverwriteDirWithFile bool
|
||||
CopyUIDGID bool
|
||||
}
|
||||
|
||||
// EventsOptions holds parameters to filter events with.
|
||||
type EventsOptions struct {
|
||||
Since string
|
||||
Until string
|
||||
Filters filters.Args
|
||||
}
|
||||
|
||||
// NetworkListOptions holds parameters to filter the list of networks with.
|
||||
type NetworkListOptions struct {
|
||||
Filters filters.Args
|
||||
}
|
||||
|
||||
// HijackedResponse holds connection information for a hijacked request.
|
||||
type HijackedResponse struct {
|
||||
Conn net.Conn
|
||||
Reader *bufio.Reader
|
||||
}
|
||||
|
||||
// Close closes the hijacked connection and reader.
|
||||
func (h *HijackedResponse) Close() {
|
||||
h.Conn.Close()
|
||||
}
|
||||
|
||||
// CloseWriter is an interface that implements structs
|
||||
// that close input streams to prevent from writing.
|
||||
type CloseWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
// CloseWrite closes a readWriter for writing.
|
||||
func (h *HijackedResponse) CloseWrite() error {
|
||||
if conn, ok := h.Conn.(CloseWriter); ok {
|
||||
return conn.CloseWrite()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImageBuildOptions holds the information
|
||||
// necessary to build images.
|
||||
type ImageBuildOptions struct {
|
||||
Tags []string
|
||||
SuppressOutput bool
|
||||
RemoteContext string
|
||||
NoCache bool
|
||||
Remove bool
|
||||
ForceRemove bool
|
||||
PullParent bool
|
||||
Isolation container.Isolation
|
||||
CPUSetCPUs string
|
||||
CPUSetMems string
|
||||
CPUShares int64
|
||||
CPUQuota int64
|
||||
CPUPeriod int64
|
||||
Memory int64
|
||||
MemorySwap int64
|
||||
CgroupParent string
|
||||
NetworkMode string
|
||||
ShmSize int64
|
||||
Dockerfile string
|
||||
Ulimits []*units.Ulimit
|
||||
// BuildArgs needs to be a *string instead of just a string so that
|
||||
// we can tell the difference between "" (empty string) and no value
|
||||
// at all (nil). See the parsing of buildArgs in
|
||||
// api/server/router/build/build_routes.go for even more info.
|
||||
BuildArgs map[string]*string
|
||||
AuthConfigs map[string]AuthConfig
|
||||
Context io.Reader
|
||||
Labels map[string]string
|
||||
// squash the resulting image's layers to the parent
|
||||
// preserves the original image and creates a new one from the parent with all
|
||||
// the changes applied to a single layer
|
||||
Squash bool
|
||||
// CacheFrom specifies images that are used for matching cache. Images
|
||||
// specified here do not need to have a valid parent chain to match cache.
|
||||
CacheFrom []string
|
||||
SecurityOpt []string
|
||||
ExtraHosts []string // List of extra hosts
|
||||
Target string
|
||||
SessionID string
|
||||
Platform string
|
||||
}
|
||||
|
||||
// ImageBuildResponse holds information
|
||||
// returned by a server after building
|
||||
// an image.
|
||||
type ImageBuildResponse struct {
|
||||
Body io.ReadCloser
|
||||
OSType string
|
||||
}
|
||||
|
||||
// ImageCreateOptions holds information to create images.
|
||||
type ImageCreateOptions struct {
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
|
||||
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
|
||||
}
|
||||
|
||||
// ImageImportSource holds source information for ImageImport
|
||||
type ImageImportSource struct {
|
||||
Source io.Reader // Source is the data to send to the server to create this image from. You must set SourceName to "-" to leverage this.
|
||||
SourceName string // SourceName is the name of the image to pull. Set to "-" to leverage the Source attribute.
|
||||
}
|
||||
|
||||
// ImageImportOptions holds information to import images from the client host.
|
||||
type ImageImportOptions struct {
|
||||
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
|
||||
Message string // Message is the message to tag the image with
|
||||
Changes []string // Changes are the raw changes to apply to this image
|
||||
Platform string // Platform is the target platform of the image
|
||||
}
|
||||
|
||||
// ImageListOptions holds parameters to filter the list of images with.
|
||||
type ImageListOptions struct {
|
||||
All bool
|
||||
Filters filters.Args
|
||||
}
|
||||
|
||||
// ImageLoadResponse returns information to the client about a load process.
|
||||
type ImageLoadResponse struct {
|
||||
// Body must be closed to avoid a resource leak
|
||||
Body io.ReadCloser
|
||||
JSON bool
|
||||
}
|
||||
|
||||
// ImagePullOptions holds information to pull images.
|
||||
type ImagePullOptions struct {
|
||||
All bool
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||
PrivilegeFunc RequestPrivilegeFunc
|
||||
Platform string
|
||||
}
|
||||
|
||||
// RequestPrivilegeFunc is a function interface that
|
||||
// clients can supply to retry operations after
|
||||
// getting an authorization error.
|
||||
// This function returns the registry authentication
|
||||
// header value in base 64 format, or an error
|
||||
// if the privilege request fails.
|
||||
type RequestPrivilegeFunc func() (string, error)
|
||||
|
||||
//ImagePushOptions holds information to push images.
|
||||
type ImagePushOptions ImagePullOptions
|
||||
|
||||
// ImageRemoveOptions holds parameters to remove images.
|
||||
type ImageRemoveOptions struct {
|
||||
Force bool
|
||||
PruneChildren bool
|
||||
}
|
||||
|
||||
// ImageSearchOptions holds parameters to search images with.
|
||||
type ImageSearchOptions struct {
|
||||
RegistryAuth string
|
||||
PrivilegeFunc RequestPrivilegeFunc
|
||||
Filters filters.Args
|
||||
Limit int
|
||||
}
|
||||
|
||||
// ResizeOptions holds parameters to resize a tty.
|
||||
// It can be used to resize container ttys and
|
||||
// exec process ttys too.
|
||||
type ResizeOptions struct {
|
||||
Height uint
|
||||
Width uint
|
||||
}
|
||||
|
||||
// NodeListOptions holds parameters to list nodes with.
|
||||
type NodeListOptions struct {
|
||||
Filters filters.Args
|
||||
}
|
||||
|
||||
// NodeRemoveOptions holds parameters to remove nodes with.
|
||||
type NodeRemoveOptions struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
// ServiceCreateOptions contains the options to use when creating a service.
|
||||
type ServiceCreateOptions struct {
|
||||
// EncodedRegistryAuth is the encoded registry authorization credentials to
|
||||
// use when updating the service.
|
||||
//
|
||||
// This field follows the format of the X-Registry-Auth header.
|
||||
EncodedRegistryAuth string
|
||||
|
||||
// QueryRegistry indicates whether the service update requires
|
||||
// contacting a registry. A registry may be contacted to retrieve
|
||||
// the image digest and manifest, which in turn can be used to update
|
||||
// platform or other information about the service.
|
||||
QueryRegistry bool
|
||||
}
|
||||
|
||||
// ServiceCreateResponse contains the information returned to a client
|
||||
// on the creation of a new service.
|
||||
type ServiceCreateResponse struct {
|
||||
// ID is the ID of the created service.
|
||||
ID string
|
||||
// Warnings is a set of non-fatal warning messages to pass on to the user.
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Values for RegistryAuthFrom in ServiceUpdateOptions
|
||||
const (
|
||||
RegistryAuthFromSpec = "spec"
|
||||
RegistryAuthFromPreviousSpec = "previous-spec"
|
||||
)
|
||||
|
||||
// ServiceUpdateOptions contains the options to be used for updating services.
|
||||
type ServiceUpdateOptions struct {
|
||||
// EncodedRegistryAuth is the encoded registry authorization credentials to
|
||||
// use when updating the service.
|
||||
//
|
||||
// This field follows the format of the X-Registry-Auth header.
|
||||
EncodedRegistryAuth string
|
||||
|
||||
// TODO(stevvooe): Consider moving the version parameter of ServiceUpdate
|
||||
// into this field. While it does open API users up to racy writes, most
|
||||
// users may not need that level of consistency in practice.
|
||||
|
||||
// RegistryAuthFrom specifies where to find the registry authorization
|
||||
// credentials if they are not given in EncodedRegistryAuth. Valid
|
||||
// values are "spec" and "previous-spec".
|
||||
RegistryAuthFrom string
|
||||
|
||||
// Rollback indicates whether a server-side rollback should be
|
||||
// performed. When this is set, the provided spec will be ignored.
|
||||
// The valid values are "previous" and "none". An empty value is the
|
||||
// same as "none".
|
||||
Rollback string
|
||||
|
||||
// QueryRegistry indicates whether the service update requires
|
||||
// contacting a registry. A registry may be contacted to retrieve
|
||||
// the image digest and manifest, which in turn can be used to update
|
||||
// platform or other information about the service.
|
||||
QueryRegistry bool
|
||||
}
|
||||
|
||||
// ServiceListOptions holds parameters to list services with.
|
||||
type ServiceListOptions struct {
|
||||
Filters filters.Args
|
||||
}
|
||||
|
||||
// ServiceInspectOptions holds parameters related to the "service inspect"
|
||||
// operation.
|
||||
type ServiceInspectOptions struct {
|
||||
InsertDefaults bool
|
||||
}
|
||||
|
||||
// TaskListOptions holds parameters to list tasks with.
|
||||
type TaskListOptions struct {
|
||||
Filters filters.Args
|
||||
}
|
||||
|
||||
// PluginRemoveOptions holds parameters to remove plugins.
|
||||
type PluginRemoveOptions struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
// PluginEnableOptions holds parameters to enable plugins.
|
||||
type PluginEnableOptions struct {
|
||||
Timeout int
|
||||
}
|
||||
|
||||
// PluginDisableOptions holds parameters to disable plugins.
|
||||
type PluginDisableOptions struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
// PluginInstallOptions holds parameters to install a plugin.
|
||||
type PluginInstallOptions struct {
|
||||
Disabled bool
|
||||
AcceptAllPermissions bool
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||
RemoteRef string // RemoteRef is the plugin name on the registry
|
||||
PrivilegeFunc RequestPrivilegeFunc
|
||||
AcceptPermissionsFunc func(PluginPrivileges) (bool, error)
|
||||
Args []string
|
||||
}
|
||||
|
||||
// SwarmUnlockKeyResponse contains the response for Engine API:
|
||||
// GET /swarm/unlockkey
|
||||
type SwarmUnlockKeyResponse struct {
|
||||
// UnlockKey is the unlock key in ASCII-armored format.
|
||||
UnlockKey string
|
||||
}
|
||||
|
||||
// PluginCreateOptions hold all options to plugin create.
|
||||
type PluginCreateOptions struct {
|
||||
RepoName string
|
||||
}
|
57
vendor/github.com/docker/docker/api/types/configs.go
generated
vendored
Normal file
57
vendor/github.com/docker/docker/api/types/configs.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package types // import "github.com/docker/docker/api/types"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
// configs holds structs used for internal communication between the
|
||||
// frontend (such as an http server) and the backend (such as the
|
||||
// docker daemon).
|
||||
|
||||
// ContainerCreateConfig is the parameter set to ContainerCreate()
|
||||
type ContainerCreateConfig struct {
|
||||
Name string
|
||||
Config *container.Config
|
||||
HostConfig *container.HostConfig
|
||||
NetworkingConfig *network.NetworkingConfig
|
||||
AdjustCPUShares bool
|
||||
}
|
||||
|
||||
// ContainerRmConfig holds arguments for the container remove
|
||||
// operation. This struct is used to tell the backend what operations
|
||||
// to perform.
|
||||
type ContainerRmConfig struct {
|
||||
ForceRemove, RemoveVolume, RemoveLink bool
|
||||
}
|
||||
|
||||
// ExecConfig is a small subset of the Config struct that holds the configuration
|
||||
// for the exec feature of docker.
|
||||
type ExecConfig struct {
|
||||
User string // User that will run the command
|
||||
Privileged bool // Is the container in privileged mode
|
||||
Tty bool // Attach standard streams to a tty.
|
||||
AttachStdin bool // Attach the standard input, makes possible user interaction
|
||||
AttachStderr bool // Attach the standard error
|
||||
AttachStdout bool // Attach the standard output
|
||||
Detach bool // Execute in detach mode
|
||||
DetachKeys string // Escape keys for detach
|
||||
Env []string // Environment variables
|
||||
WorkingDir string // Working directory
|
||||
Cmd []string // Execution commands and args
|
||||
}
|
||||
|
||||
// PluginRmConfig holds arguments for plugin remove.
|
||||
type PluginRmConfig struct {
|
||||
ForceRemove bool
|
||||
}
|
||||
|
||||
// PluginEnableConfig holds arguments for plugin enable
|
||||
type PluginEnableConfig struct {
|
||||
Timeout int
|
||||
}
|
||||
|
||||
// PluginDisableConfig holds arguments for plugin disable.
|
||||
type PluginDisableConfig struct {
|
||||
ForceDisable bool
|
||||
}
|
69
vendor/github.com/docker/docker/api/types/container/config.go
generated
vendored
Normal file
69
vendor/github.com/docker/docker/api/types/container/config.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
package container // import "github.com/docker/docker/api/types/container"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
// MinimumDuration puts a minimum on user configured duration.
|
||||
// This is to prevent API error on time unit. For example, API may
|
||||
// set 3 as healthcheck interval with intention of 3 seconds, but
|
||||
// Docker interprets it as 3 nanoseconds.
|
||||
const MinimumDuration = 1 * time.Millisecond
|
||||
|
||||
// HealthConfig holds configuration settings for the HEALTHCHECK feature.
|
||||
type HealthConfig struct {
|
||||
// Test is the test to perform to check that the container is healthy.
|
||||
// An empty slice means to inherit the default.
|
||||
// The options are:
|
||||
// {} : inherit healthcheck
|
||||
// {"NONE"} : disable healthcheck
|
||||
// {"CMD", args...} : exec arguments directly
|
||||
// {"CMD-SHELL", command} : run command with system's default shell
|
||||
Test []string `json:",omitempty"`
|
||||
|
||||
// Zero means to inherit. Durations are expressed as integer nanoseconds.
|
||||
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
|
||||
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
|
||||
StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.
|
||||
|
||||
// Retries is the number of consecutive failures needed to consider a container as unhealthy.
|
||||
// Zero means inherit.
|
||||
Retries int `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Config contains the configuration data about a container.
|
||||
// It should hold only portable information about the container.
|
||||
// Here, "portable" means "independent from the host we are running on".
|
||||
// Non-portable information *should* appear in HostConfig.
|
||||
// All fields added to this struct must be marked `omitempty` to keep getting
|
||||
// predictable hashes from the old `v1Compatibility` configuration.
|
||||
type Config struct {
|
||||
Hostname string // Hostname
|
||||
Domainname string // Domainname
|
||||
User string // User that will run the command(s) inside the container, also support user:group
|
||||
AttachStdin bool // Attach the standard input, makes possible user interaction
|
||||
AttachStdout bool // Attach the standard output
|
||||
AttachStderr bool // Attach the standard error
|
||||
ExposedPorts nat.PortSet `json:",omitempty"` // List of exposed ports
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string // List of environment variable to set in the container
|
||||
Cmd strslice.StrSlice // Command to run when starting the container
|
||||
Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy
|
||||
ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific)
|
||||
Image string // Name of the image as it was passed by the operator (e.g. could be symbolic)
|
||||
Volumes map[string]struct{} // List of volumes (mounts) used for the container
|
||||
WorkingDir string // Current directory (PWD) in the command will be launched
|
||||
Entrypoint strslice.StrSlice // Entrypoint to run when starting the container
|
||||
NetworkDisabled bool `json:",omitempty"` // Is network disabled
|
||||
MacAddress string `json:",omitempty"` // Mac Address of the container
|
||||
OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile
|
||||
Labels map[string]string // List of labels set to this container
|
||||
StopSignal string `json:",omitempty"` // Signal to stop a container
|
||||
StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container
|
||||
Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
|
||||
}
|
21
vendor/github.com/docker/docker/api/types/container/container_changes.go
generated
vendored
Normal file
21
vendor/github.com/docker/docker/api/types/container/container_changes.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package container
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file was generated by `swagger generate operation`
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ContainerChangeResponseItem change item in response to ContainerChanges operation
|
||||
// swagger:model ContainerChangeResponseItem
|
||||
type ContainerChangeResponseItem struct {
|
||||
|
||||
// Kind of change
|
||||
// Required: true
|
||||
Kind uint8 `json:"Kind"`
|
||||
|
||||
// Path to file that has changed
|
||||
// Required: true
|
||||
Path string `json:"Path"`
|
||||
}
|
21
vendor/github.com/docker/docker/api/types/container/container_create.go
generated
vendored
Normal file
21
vendor/github.com/docker/docker/api/types/container/container_create.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package container
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file was generated by `swagger generate operation`
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ContainerCreateCreatedBody OK response to ContainerCreate operation
|
||||
// swagger:model ContainerCreateCreatedBody
|
||||
type ContainerCreateCreatedBody struct {
|
||||
|
||||
// The ID of the created container
|
||||
// Required: true
|
||||
ID string `json:"Id"`
|
||||
|
||||
// Warnings encountered when creating the container
|
||||
// Required: true
|
||||
Warnings []string `json:"Warnings"`
|
||||
}
|
21
vendor/github.com/docker/docker/api/types/container/container_top.go
generated
vendored
Normal file
21
vendor/github.com/docker/docker/api/types/container/container_top.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package container
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file was generated by `swagger generate operation`
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ContainerTopOKBody OK response to ContainerTop operation
|
||||
// swagger:model ContainerTopOKBody
|
||||
type ContainerTopOKBody struct {
|
||||
|
||||
// Each process running in the container, where each is process is an array of values corresponding to the titles
|
||||
// Required: true
|
||||
Processes [][]string `json:"Processes"`
|
||||
|
||||
// The ps column titles
|
||||
// Required: true
|
||||
Titles []string `json:"Titles"`
|
||||
}
|
17
vendor/github.com/docker/docker/api/types/container/container_update.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/api/types/container/container_update.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package container
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file was generated by `swagger generate operation`
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ContainerUpdateOKBody OK response to ContainerUpdate operation
|
||||
// swagger:model ContainerUpdateOKBody
|
||||
type ContainerUpdateOKBody struct {
|
||||
|
||||
// warnings
|
||||
// Required: true
|
||||
Warnings []string `json:"Warnings"`
|
||||
}
|
29
vendor/github.com/docker/docker/api/types/container/container_wait.go
generated
vendored
Normal file
29
vendor/github.com/docker/docker/api/types/container/container_wait.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package container
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file was generated by `swagger generate operation`
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ContainerWaitOKBodyError container waiting error, if any
|
||||
// swagger:model ContainerWaitOKBodyError
|
||||
type ContainerWaitOKBodyError struct {
|
||||
|
||||
// Details of an error
|
||||
Message string `json:"Message,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerWaitOKBody OK response to ContainerWait operation
|
||||
// swagger:model ContainerWaitOKBody
|
||||
type ContainerWaitOKBody struct {
|
||||
|
||||
// error
|
||||
// Required: true
|
||||
Error *ContainerWaitOKBodyError `json:"Error"`
|
||||
|
||||
// Exit code of the container
|
||||
// Required: true
|
||||
StatusCode int64 `json:"StatusCode"`
|
||||
}
|
406
vendor/github.com/docker/docker/api/types/container/host_config.go
generated
vendored
Normal file
406
vendor/github.com/docker/docker/api/types/container/host_config.go
generated
vendored
Normal file
@ -0,0 +1,406 @@
|
||||
package container // import "github.com/docker/docker/api/types/container"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/blkiodev"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// Isolation represents the isolation technology of a container. The supported
|
||||
// values are platform specific
|
||||
type Isolation string
|
||||
|
||||
// IsDefault indicates the default isolation technology of a container. On Linux this
|
||||
// is the native driver. On Windows, this is a Windows Server Container.
|
||||
func (i Isolation) IsDefault() bool {
|
||||
return strings.ToLower(string(i)) == "default" || string(i) == ""
|
||||
}
|
||||
|
||||
// IsHyperV indicates the use of a Hyper-V partition for isolation
|
||||
func (i Isolation) IsHyperV() bool {
|
||||
return strings.ToLower(string(i)) == "hyperv"
|
||||
}
|
||||
|
||||
// IsProcess indicates the use of process isolation
|
||||
func (i Isolation) IsProcess() bool {
|
||||
return strings.ToLower(string(i)) == "process"
|
||||
}
|
||||
|
||||
const (
|
||||
// IsolationEmpty is unspecified (same behavior as default)
|
||||
IsolationEmpty = Isolation("")
|
||||
// IsolationDefault is the default isolation mode on current daemon
|
||||
IsolationDefault = Isolation("default")
|
||||
// IsolationProcess is process isolation mode
|
||||
IsolationProcess = Isolation("process")
|
||||
// IsolationHyperV is HyperV isolation mode
|
||||
IsolationHyperV = Isolation("hyperv")
|
||||
)
|
||||
|
||||
// IpcMode represents the container ipc stack.
|
||||
type IpcMode string
|
||||
|
||||
// IsPrivate indicates whether the container uses its own private ipc namespace which can not be shared.
|
||||
func (n IpcMode) IsPrivate() bool {
|
||||
return n == "private"
|
||||
}
|
||||
|
||||
// IsHost indicates whether the container shares the host's ipc namespace.
|
||||
func (n IpcMode) IsHost() bool {
|
||||
return n == "host"
|
||||
}
|
||||
|
||||
// IsShareable indicates whether the container's ipc namespace can be shared with another container.
|
||||
func (n IpcMode) IsShareable() bool {
|
||||
return n == "shareable"
|
||||
}
|
||||
|
||||
// IsContainer indicates whether the container uses another container's ipc namespace.
|
||||
func (n IpcMode) IsContainer() bool {
|
||||
parts := strings.SplitN(string(n), ":", 2)
|
||||
return len(parts) > 1 && parts[0] == "container"
|
||||
}
|
||||
|
||||
// IsNone indicates whether container IpcMode is set to "none".
|
||||
func (n IpcMode) IsNone() bool {
|
||||
return n == "none"
|
||||
}
|
||||
|
||||
// IsEmpty indicates whether container IpcMode is empty
|
||||
func (n IpcMode) IsEmpty() bool {
|
||||
return n == ""
|
||||
}
|
||||
|
||||
// Valid indicates whether the ipc mode is valid.
|
||||
func (n IpcMode) Valid() bool {
|
||||
return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer()
|
||||
}
|
||||
|
||||
// Container returns the name of the container ipc stack is going to be used.
|
||||
func (n IpcMode) Container() string {
|
||||
parts := strings.SplitN(string(n), ":", 2)
|
||||
if len(parts) > 1 && parts[0] == "container" {
|
||||
return parts[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// NetworkMode represents the container network stack.
|
||||
type NetworkMode string
|
||||
|
||||
// IsNone indicates whether container isn't using a network stack.
|
||||
func (n NetworkMode) IsNone() bool {
|
||||
return n == "none"
|
||||
}
|
||||
|
||||
// IsDefault indicates whether container uses the default network stack.
|
||||
func (n NetworkMode) IsDefault() bool {
|
||||
return n == "default"
|
||||
}
|
||||
|
||||
// IsPrivate indicates whether container uses its private network stack.
|
||||
func (n NetworkMode) IsPrivate() bool {
|
||||
return !(n.IsHost() || n.IsContainer())
|
||||
}
|
||||
|
||||
// IsContainer indicates whether container uses a container network stack.
|
||||
func (n NetworkMode) IsContainer() bool {
|
||||
parts := strings.SplitN(string(n), ":", 2)
|
||||
return len(parts) > 1 && parts[0] == "container"
|
||||
}
|
||||
|
||||
// ConnectedContainer is the id of the container which network this container is connected to.
|
||||
func (n NetworkMode) ConnectedContainer() string {
|
||||
parts := strings.SplitN(string(n), ":", 2)
|
||||
if len(parts) > 1 {
|
||||
return parts[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//UserDefined indicates user-created network
|
||||
func (n NetworkMode) UserDefined() string {
|
||||
if n.IsUserDefined() {
|
||||
return string(n)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UsernsMode represents userns mode in the container.
|
||||
type UsernsMode string
|
||||
|
||||
// IsHost indicates whether the container uses the host's userns.
|
||||
func (n UsernsMode) IsHost() bool {
|
||||
return n == "host"
|
||||
}
|
||||
|
||||
// IsPrivate indicates whether the container uses the a private userns.
|
||||
func (n UsernsMode) IsPrivate() bool {
|
||||
return !(n.IsHost())
|
||||
}
|
||||
|
||||
// Valid indicates whether the userns is valid.
|
||||
func (n UsernsMode) Valid() bool {
|
||||
parts := strings.Split(string(n), ":")
|
||||
switch mode := parts[0]; mode {
|
||||
case "", "host":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CgroupSpec represents the cgroup to use for the container.
|
||||
type CgroupSpec string
|
||||
|
||||
// IsContainer indicates whether the container is using another container cgroup
|
||||
func (c CgroupSpec) IsContainer() bool {
|
||||
parts := strings.SplitN(string(c), ":", 2)
|
||||
return len(parts) > 1 && parts[0] == "container"
|
||||
}
|
||||
|
||||
// Valid indicates whether the cgroup spec is valid.
|
||||
func (c CgroupSpec) Valid() bool {
|
||||
return c.IsContainer() || c == ""
|
||||
}
|
||||
|
||||
// Container returns the name of the container whose cgroup will be used.
|
||||
func (c CgroupSpec) Container() string {
|
||||
parts := strings.SplitN(string(c), ":", 2)
|
||||
if len(parts) > 1 {
|
||||
return parts[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UTSMode represents the UTS namespace of the container.
|
||||
type UTSMode string
|
||||
|
||||
// IsPrivate indicates whether the container uses its private UTS namespace.
|
||||
func (n UTSMode) IsPrivate() bool {
|
||||
return !(n.IsHost())
|
||||
}
|
||||
|
||||
// IsHost indicates whether the container uses the host's UTS namespace.
|
||||
func (n UTSMode) IsHost() bool {
|
||||
return n == "host"
|
||||
}
|
||||
|
||||
// Valid indicates whether the UTS namespace is valid.
|
||||
func (n UTSMode) Valid() bool {
|
||||
parts := strings.Split(string(n), ":")
|
||||
switch mode := parts[0]; mode {
|
||||
case "", "host":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// PidMode represents the pid namespace of the container.
|
||||
type PidMode string
|
||||
|
||||
// IsPrivate indicates whether the container uses its own new pid namespace.
|
||||
func (n PidMode) IsPrivate() bool {
|
||||
return !(n.IsHost() || n.IsContainer())
|
||||
}
|
||||
|
||||
// IsHost indicates whether the container uses the host's pid namespace.
|
||||
func (n PidMode) IsHost() bool {
|
||||
return n == "host"
|
||||
}
|
||||
|
||||
// IsContainer indicates whether the container uses a container's pid namespace.
|
||||
func (n PidMode) IsContainer() bool {
|
||||
parts := strings.SplitN(string(n), ":", 2)
|
||||
return len(parts) > 1 && parts[0] == "container"
|
||||
}
|
||||
|
||||
// Valid indicates whether the pid namespace is valid.
|
||||
func (n PidMode) Valid() bool {
|
||||
parts := strings.Split(string(n), ":")
|
||||
switch mode := parts[0]; mode {
|
||||
case "", "host":
|
||||
case "container":
|
||||
if len(parts) != 2 || parts[1] == "" {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Container returns the name of the container whose pid namespace is going to be used.
|
||||
func (n PidMode) Container() string {
|
||||
parts := strings.SplitN(string(n), ":", 2)
|
||||
if len(parts) > 1 {
|
||||
return parts[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeviceMapping represents the device mapping between the host and the container.
|
||||
type DeviceMapping struct {
|
||||
PathOnHost string
|
||||
PathInContainer string
|
||||
CgroupPermissions string
|
||||
}
|
||||
|
||||
// RestartPolicy represents the restart policies of the container.
|
||||
type RestartPolicy struct {
|
||||
Name string
|
||||
MaximumRetryCount int
|
||||
}
|
||||
|
||||
// IsNone indicates whether the container has the "no" restart policy.
|
||||
// This means the container will not automatically restart when exiting.
|
||||
func (rp *RestartPolicy) IsNone() bool {
|
||||
return rp.Name == "no" || rp.Name == ""
|
||||
}
|
||||
|
||||
// IsAlways indicates whether the container has the "always" restart policy.
|
||||
// This means the container will automatically restart regardless of the exit status.
|
||||
func (rp *RestartPolicy) IsAlways() bool {
|
||||
return rp.Name == "always"
|
||||
}
|
||||
|
||||
// IsOnFailure indicates whether the container has the "on-failure" restart policy.
|
||||
// This means the container will automatically restart of exiting with a non-zero exit status.
|
||||
func (rp *RestartPolicy) IsOnFailure() bool {
|
||||
return rp.Name == "on-failure"
|
||||
}
|
||||
|
||||
// IsUnlessStopped indicates whether the container has the
|
||||
// "unless-stopped" restart policy. This means the container will
|
||||
// automatically restart unless user has put it to stopped state.
|
||||
func (rp *RestartPolicy) IsUnlessStopped() bool {
|
||||
return rp.Name == "unless-stopped"
|
||||
}
|
||||
|
||||
// IsSame compares two RestartPolicy to see if they are the same
|
||||
func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool {
|
||||
return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount
|
||||
}
|
||||
|
||||
// LogMode is a type to define the available modes for logging
|
||||
// These modes affect how logs are handled when log messages start piling up.
|
||||
type LogMode string
|
||||
|
||||
// Available logging modes
|
||||
const (
|
||||
LogModeUnset = ""
|
||||
LogModeBlocking LogMode = "blocking"
|
||||
LogModeNonBlock LogMode = "non-blocking"
|
||||
)
|
||||
|
||||
// LogConfig represents the logging configuration of the container.
|
||||
type LogConfig struct {
|
||||
Type string
|
||||
Config map[string]string
|
||||
}
|
||||
|
||||
// Resources contains container's resources (cgroups config, ulimits...)
|
||||
type Resources struct {
|
||||
// Applicable to all platforms
|
||||
CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
NanoCPUs int64 `json:"NanoCpus"` // CPU quota in units of 10<sup>-9</sup> CPUs.
|
||||
|
||||
// Applicable to UNIX platforms
|
||||
CgroupParent string // Parent cgroup.
|
||||
BlkioWeight uint16 // Block IO weight (relative weight vs. other containers)
|
||||
BlkioWeightDevice []*blkiodev.WeightDevice
|
||||
BlkioDeviceReadBps []*blkiodev.ThrottleDevice
|
||||
BlkioDeviceWriteBps []*blkiodev.ThrottleDevice
|
||||
BlkioDeviceReadIOps []*blkiodev.ThrottleDevice
|
||||
BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice
|
||||
CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
|
||||
CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota
|
||||
CPURealtimePeriod int64 `json:"CpuRealtimePeriod"` // CPU real-time period
|
||||
CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` // CPU real-time runtime
|
||||
CpusetCpus string // CpusetCpus 0-2, 0,1
|
||||
CpusetMems string // CpusetMems 0-2, 0,1
|
||||
Devices []DeviceMapping // List of devices to map inside the container
|
||||
DeviceCgroupRules []string // List of rule to be added to the device cgroup
|
||||
DiskQuota int64 // Disk limit (in bytes)
|
||||
KernelMemory int64 // Kernel memory limit (in bytes)
|
||||
MemoryReservation int64 // Memory soft limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
|
||||
MemorySwappiness *int64 // Tuning container memory swappiness behaviour
|
||||
OomKillDisable *bool // Whether to disable OOM Killer or not
|
||||
PidsLimit int64 // Setting pids limit for a container
|
||||
Ulimits []*units.Ulimit // List of ulimits to be set in the container
|
||||
|
||||
// Applicable to Windows
|
||||
CPUCount int64 `json:"CpuCount"` // CPU count
|
||||
CPUPercent int64 `json:"CpuPercent"` // CPU percent
|
||||
IOMaximumIOps uint64 // Maximum IOps for the container system drive
|
||||
IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive
|
||||
}
|
||||
|
||||
// UpdateConfig holds the mutable attributes of a Container.
|
||||
// Those attributes can be updated at runtime.
|
||||
type UpdateConfig struct {
|
||||
// Contains container's resources (cgroups, ulimits)
|
||||
Resources
|
||||
RestartPolicy RestartPolicy
|
||||
}
|
||||
|
||||
// HostConfig the non-portable Config structure of a container.
|
||||
// Here, "non-portable" means "dependent of the host we are running on".
|
||||
// Portable information *should* appear in Config.
|
||||
type HostConfig struct {
|
||||
// Applicable to all platforms
|
||||
Binds []string // List of volume bindings for this container
|
||||
ContainerIDFile string // File (path) where the containerId is written
|
||||
LogConfig LogConfig // Configuration of the logs for this container
|
||||
NetworkMode NetworkMode // Network mode to use for the container
|
||||
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
|
||||
RestartPolicy RestartPolicy // Restart policy to be used for the container
|
||||
AutoRemove bool // Automatically remove container when it exits
|
||||
VolumeDriver string // Name of the volume driver used to mount volumes
|
||||
VolumesFrom []string // List of volumes to take from other container
|
||||
|
||||
// Applicable to UNIX platforms
|
||||
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
|
||||
CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
|
||||
DNS []string `json:"Dns"` // List of DNS server to lookup
|
||||
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
|
||||
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
|
||||
ExtraHosts []string // List of extra hosts
|
||||
GroupAdd []string // List of additional groups that the container process will run as
|
||||
IpcMode IpcMode // IPC namespace to use for the container
|
||||
Cgroup CgroupSpec // Cgroup to use for the container
|
||||
Links []string // List of links (in the name:alias form)
|
||||
OomScoreAdj int // Container preference for OOM-killing
|
||||
PidMode PidMode // PID namespace to use for the container
|
||||
Privileged bool // Is the container in privileged mode
|
||||
PublishAllPorts bool // Should docker publish all exposed port for the container
|
||||
ReadonlyRootfs bool // Is the container root filesystem in read-only
|
||||
SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux.
|
||||
StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container.
|
||||
Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
|
||||
UTSMode UTSMode // UTS namespace to use for the container
|
||||
UsernsMode UsernsMode // The user namespace to use for the container
|
||||
ShmSize int64 // Total shm memory usage
|
||||
Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container
|
||||
Runtime string `json:",omitempty"` // Runtime to use with this container
|
||||
|
||||
// Applicable to Windows
|
||||
ConsoleSize [2]uint // Initial console size (height,width)
|
||||
Isolation Isolation // Isolation technology of the container (e.g. default, hyperv)
|
||||
|
||||
// Contains container's resources (cgroups, ulimits)
|
||||
Resources
|
||||
|
||||
// Mounts specs used by the container
|
||||
Mounts []mount.Mount `json:",omitempty"`
|
||||
|
||||
// Run a custom init inside the container, if null, use the daemon's configured settings
|
||||
Init *bool `json:",omitempty"`
|
||||
}
|
41
vendor/github.com/docker/docker/api/types/container/hostconfig_unix.go
generated
vendored
Normal file
41
vendor/github.com/docker/docker/api/types/container/hostconfig_unix.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
// +build !windows
|
||||
|
||||
package container // import "github.com/docker/docker/api/types/container"
|
||||
|
||||
// IsValid indicates if an isolation technology is valid
|
||||
func (i Isolation) IsValid() bool {
|
||||
return i.IsDefault()
|
||||
}
|
||||
|
||||
// NetworkName returns the name of the network stack.
|
||||
func (n NetworkMode) NetworkName() string {
|
||||
if n.IsBridge() {
|
||||
return "bridge"
|
||||
} else if n.IsHost() {
|
||||
return "host"
|
||||
} else if n.IsContainer() {
|
||||
return "container"
|
||||
} else if n.IsNone() {
|
||||
return "none"
|
||||
} else if n.IsDefault() {
|
||||
return "default"
|
||||
} else if n.IsUserDefined() {
|
||||
return n.UserDefined()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsBridge indicates whether container uses the bridge network stack
|
||||
func (n NetworkMode) IsBridge() bool {
|
||||
return n == "bridge"
|
||||
}
|
||||
|
||||
// IsHost indicates whether container uses the host network stack.
|
||||
func (n NetworkMode) IsHost() bool {
|
||||
return n == "host"
|
||||
}
|
||||
|
||||
// IsUserDefined indicates user-created network
|
||||
func (n NetworkMode) IsUserDefined() bool {
|
||||
return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer()
|
||||
}
|
40
vendor/github.com/docker/docker/api/types/container/hostconfig_windows.go
generated
vendored
Normal file
40
vendor/github.com/docker/docker/api/types/container/hostconfig_windows.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
package container // import "github.com/docker/docker/api/types/container"
|
||||
|
||||
// IsBridge indicates whether container uses the bridge network stack
|
||||
// in windows it is given the name NAT
|
||||
func (n NetworkMode) IsBridge() bool {
|
||||
return n == "nat"
|
||||
}
|
||||
|
||||
// IsHost indicates whether container uses the host network stack.
|
||||
// returns false as this is not supported by windows
|
||||
func (n NetworkMode) IsHost() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUserDefined indicates user-created network
|
||||
func (n NetworkMode) IsUserDefined() bool {
|
||||
return !n.IsDefault() && !n.IsNone() && !n.IsBridge() && !n.IsContainer()
|
||||
}
|
||||
|
||||
// IsValid indicates if an isolation technology is valid
|
||||
func (i Isolation) IsValid() bool {
|
||||
return i.IsDefault() || i.IsHyperV() || i.IsProcess()
|
||||
}
|
||||
|
||||
// NetworkName returns the name of the network stack.
|
||||
func (n NetworkMode) NetworkName() string {
|
||||
if n.IsDefault() {
|
||||
return "default"
|
||||
} else if n.IsBridge() {
|
||||
return "nat"
|
||||
} else if n.IsNone() {
|
||||
return "none"
|
||||
} else if n.IsContainer() {
|
||||
return "container"
|
||||
} else if n.IsUserDefined() {
|
||||
return n.UserDefined()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
22
vendor/github.com/docker/docker/api/types/container/waitcondition.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/api/types/container/waitcondition.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package container // import "github.com/docker/docker/api/types/container"
|
||||
|
||||
// WaitCondition is a type used to specify a container state for which
|
||||
// to wait.
|
||||
type WaitCondition string
|
||||
|
||||
// Possible WaitCondition Values.
|
||||
//
|
||||
// WaitConditionNotRunning (default) is used to wait for any of the non-running
|
||||
// states: "created", "exited", "dead", "removing", or "removed".
|
||||
//
|
||||
// WaitConditionNextExit is used to wait for the next time the state changes
|
||||
// to a non-running state. If the state is currently "created" or "exited",
|
||||
// this would cause Wait() to block until either the container runs and exits
|
||||
// or is removed.
|
||||
//
|
||||
// WaitConditionRemoved is used to wait for the container to be removed.
|
||||
const (
|
||||
WaitConditionNotRunning WaitCondition = "not-running"
|
||||
WaitConditionNextExit WaitCondition = "next-exit"
|
||||
WaitConditionRemoved WaitCondition = "removed"
|
||||
)
|
13
vendor/github.com/docker/docker/api/types/error_response.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/api/types/error_response.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package types
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ErrorResponse Represents an error.
|
||||
// swagger:model ErrorResponse
|
||||
type ErrorResponse struct {
|
||||
|
||||
// The error message.
|
||||
// Required: true
|
||||
Message string `json:"message"`
|
||||
}
|
52
vendor/github.com/docker/docker/api/types/events/events.go
generated
vendored
Normal file
52
vendor/github.com/docker/docker/api/types/events/events.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
package events // import "github.com/docker/docker/api/types/events"
|
||||
|
||||
const (
|
||||
// ContainerEventType is the event type that containers generate
|
||||
ContainerEventType = "container"
|
||||
// DaemonEventType is the event type that daemon generate
|
||||
DaemonEventType = "daemon"
|
||||
// ImageEventType is the event type that images generate
|
||||
ImageEventType = "image"
|
||||
// NetworkEventType is the event type that networks generate
|
||||
NetworkEventType = "network"
|
||||
// PluginEventType is the event type that plugins generate
|
||||
PluginEventType = "plugin"
|
||||
// VolumeEventType is the event type that volumes generate
|
||||
VolumeEventType = "volume"
|
||||
// ServiceEventType is the event type that services generate
|
||||
ServiceEventType = "service"
|
||||
// NodeEventType is the event type that nodes generate
|
||||
NodeEventType = "node"
|
||||
// SecretEventType is the event type that secrets generate
|
||||
SecretEventType = "secret"
|
||||
// ConfigEventType is the event type that configs generate
|
||||
ConfigEventType = "config"
|
||||
)
|
||||
|
||||
// Actor describes something that generates events,
|
||||
// like a container, or a network, or a volume.
|
||||
// It has a defined name and a set or attributes.
|
||||
// The container attributes are its labels, other actors
|
||||
// can generate these attributes from other properties.
|
||||
type Actor struct {
|
||||
ID string
|
||||
Attributes map[string]string
|
||||
}
|
||||
|
||||
// Message represents the information an event contains
|
||||
type Message struct {
|
||||
// Deprecated information from JSONMessage.
|
||||
// With data only in container events.
|
||||
Status string `json:"status,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
From string `json:"from,omitempty"`
|
||||
|
||||
Type string
|
||||
Action string
|
||||
Actor Actor
|
||||
// Engine events are local scope. Cluster events are swarm scope.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
|
||||
Time int64 `json:"time,omitempty"`
|
||||
TimeNano int64 `json:"timeNano,omitempty"`
|
||||
}
|
350
vendor/github.com/docker/docker/api/types/filters/parse.go
generated
vendored
Normal file
350
vendor/github.com/docker/docker/api/types/filters/parse.go
generated
vendored
Normal file
@ -0,0 +1,350 @@
|
||||
/*Package filters provides tools for encoding a mapping of keys to a set of
|
||||
multiple values.
|
||||
*/
|
||||
package filters // import "github.com/docker/docker/api/types/filters"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
)
|
||||
|
||||
// Args stores a mapping of keys to a set of multiple values.
|
||||
type Args struct {
|
||||
fields map[string]map[string]bool
|
||||
}
|
||||
|
||||
// KeyValuePair are used to initialize a new Args
|
||||
type KeyValuePair struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Arg creates a new KeyValuePair for initializing Args
|
||||
func Arg(key, value string) KeyValuePair {
|
||||
return KeyValuePair{Key: key, Value: value}
|
||||
}
|
||||
|
||||
// NewArgs returns a new Args populated with the initial args
|
||||
func NewArgs(initialArgs ...KeyValuePair) Args {
|
||||
args := Args{fields: map[string]map[string]bool{}}
|
||||
for _, arg := range initialArgs {
|
||||
args.Add(arg.Key, arg.Value)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// ParseFlag parses a key=value string and adds it to an Args.
|
||||
//
|
||||
// Deprecated: Use Args.Add()
|
||||
func ParseFlag(arg string, prev Args) (Args, error) {
|
||||
filters := prev
|
||||
if len(arg) == 0 {
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
if !strings.Contains(arg, "=") {
|
||||
return filters, ErrBadFormat
|
||||
}
|
||||
|
||||
f := strings.SplitN(arg, "=", 2)
|
||||
|
||||
name := strings.ToLower(strings.TrimSpace(f[0]))
|
||||
value := strings.TrimSpace(f[1])
|
||||
|
||||
filters.Add(name, value)
|
||||
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
// ErrBadFormat is an error returned when a filter is not in the form key=value
|
||||
//
|
||||
// Deprecated: this error will be removed in a future version
|
||||
var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
|
||||
|
||||
// ToParam encodes the Args as args JSON encoded string
|
||||
//
|
||||
// Deprecated: use ToJSON
|
||||
func ToParam(a Args) (string, error) {
|
||||
return ToJSON(a)
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte representation of the Args
|
||||
func (args Args) MarshalJSON() ([]byte, error) {
|
||||
if len(args.fields) == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
return json.Marshal(args.fields)
|
||||
}
|
||||
|
||||
// ToJSON returns the Args as a JSON encoded string
|
||||
func ToJSON(a Args) (string, error) {
|
||||
if a.Len() == 0 {
|
||||
return "", nil
|
||||
}
|
||||
buf, err := json.Marshal(a)
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
// ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22
|
||||
// then the encoded format will use an older legacy format where the values are a
|
||||
// list of strings, instead of a set.
|
||||
//
|
||||
// Deprecated: Use ToJSON
|
||||
func ToParamWithVersion(version string, a Args) (string, error) {
|
||||
if a.Len() == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if version != "" && versions.LessThan(version, "1.22") {
|
||||
buf, err := json.Marshal(convertArgsToSlice(a.fields))
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
return ToJSON(a)
|
||||
}
|
||||
|
||||
// FromParam decodes a JSON encoded string into Args
|
||||
//
|
||||
// Deprecated: use FromJSON
|
||||
func FromParam(p string) (Args, error) {
|
||||
return FromJSON(p)
|
||||
}
|
||||
|
||||
// FromJSON decodes a JSON encoded string into Args
|
||||
func FromJSON(p string) (Args, error) {
|
||||
args := NewArgs()
|
||||
|
||||
if p == "" {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
raw := []byte(p)
|
||||
err := json.Unmarshal(raw, &args)
|
||||
if err == nil {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Fallback to parsing arguments in the legacy slice format
|
||||
deprecated := map[string][]string{}
|
||||
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
||||
return args, err
|
||||
}
|
||||
|
||||
args.fields = deprecatedArgs(deprecated)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates the Args from JSON encode bytes
|
||||
func (args Args) UnmarshalJSON(raw []byte) error {
|
||||
if len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(raw, &args.fields)
|
||||
}
|
||||
|
||||
// Get returns the list of values associated with the key
|
||||
func (args Args) Get(key string) []string {
|
||||
values := args.fields[key]
|
||||
if values == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
slice := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
slice = append(slice, key)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// Add a new value to the set of values
|
||||
func (args Args) Add(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
args.fields[key][value] = true
|
||||
} else {
|
||||
args.fields[key] = map[string]bool{value: true}
|
||||
}
|
||||
}
|
||||
|
||||
// Del removes a value from the set
|
||||
func (args Args) Del(key, value string) {
|
||||
if _, ok := args.fields[key]; ok {
|
||||
delete(args.fields[key], value)
|
||||
if len(args.fields[key]) == 0 {
|
||||
delete(args.fields, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of keys in the mapping
|
||||
func (args Args) Len() int {
|
||||
return len(args.fields)
|
||||
}
|
||||
|
||||
// MatchKVList returns true if all the pairs in sources exist as key=value
|
||||
// pairs in the mapping at key, or if there are no values at key.
|
||||
func (args Args) MatchKVList(key string, sources map[string]string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
|
||||
//do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(sources) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for value := range fieldValues {
|
||||
testKV := strings.SplitN(value, "=", 2)
|
||||
|
||||
v, ok := sources[testKV[0]]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if len(testKV) == 2 && testKV[1] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Match returns true if any of the values at key match the source string
|
||||
func (args Args) Match(field, source string) bool {
|
||||
if args.ExactMatch(field, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[field]
|
||||
for name2match := range fieldValues {
|
||||
match, err := regexp.MatchString(name2match, source)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ExactMatch returns true if the source matches exactly one of the values.
|
||||
func (args Args) ExactMatch(key, source string) bool {
|
||||
fieldValues, ok := args.fields[key]
|
||||
//do not filter if there is no filter set or cannot determine filter
|
||||
if !ok || len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// UniqueExactMatch returns true if there is only one value and the source
|
||||
// matches exactly the value.
|
||||
func (args Args) UniqueExactMatch(key, source string) bool {
|
||||
fieldValues := args.fields[key]
|
||||
//do not filter if there is no filter set or cannot determine filter
|
||||
if len(fieldValues) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(args.fields[key]) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// try to match full name value to avoid O(N) regular expression matching
|
||||
return fieldValues[source]
|
||||
}
|
||||
|
||||
// FuzzyMatch returns true if the source matches exactly one value, or the
|
||||
// source has one of the values as a prefix.
|
||||
func (args Args) FuzzyMatch(key, source string) bool {
|
||||
if args.ExactMatch(key, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
fieldValues := args.fields[key]
|
||||
for prefix := range fieldValues {
|
||||
if strings.HasPrefix(source, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Include returns true if the key exists in the mapping
|
||||
//
|
||||
// Deprecated: use Contains
|
||||
func (args Args) Include(field string) bool {
|
||||
_, ok := args.fields[field]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Contains returns true if the key exists in the mapping
|
||||
func (args Args) Contains(field string) bool {
|
||||
_, ok := args.fields[field]
|
||||
return ok
|
||||
}
|
||||
|
||||
type invalidFilter string
|
||||
|
||||
func (e invalidFilter) Error() string {
|
||||
return "Invalid filter '" + string(e) + "'"
|
||||
}
|
||||
|
||||
func (invalidFilter) InvalidParameter() {}
|
||||
|
||||
// Validate compared the set of accepted keys against the keys in the mapping.
|
||||
// An error is returned if any mapping keys are not in the accepted set.
|
||||
func (args Args) Validate(accepted map[string]bool) error {
|
||||
for name := range args.fields {
|
||||
if !accepted[name] {
|
||||
return invalidFilter(name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkValues iterates over the list of values for a key in the mapping and calls
|
||||
// op() for each value. If op returns an error the iteration stops and the
|
||||
// error is returned.
|
||||
func (args Args) WalkValues(field string, op func(value string) error) error {
|
||||
if _, ok := args.fields[field]; !ok {
|
||||
return nil
|
||||
}
|
||||
for v := range args.fields[field] {
|
||||
if err := op(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
||||
m := map[string]map[string]bool{}
|
||||
for k, v := range d {
|
||||
values := map[string]bool{}
|
||||
for _, vv := range v {
|
||||
values[vv] = true
|
||||
}
|
||||
m[k] = values
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
|
||||
m := map[string][]string{}
|
||||
for k, v := range f {
|
||||
values := []string{}
|
||||
for kk := range v {
|
||||
if v[kk] {
|
||||
values = append(values, kk)
|
||||
}
|
||||
}
|
||||
m[k] = values
|
||||
}
|
||||
return m
|
||||
}
|
17
vendor/github.com/docker/docker/api/types/graph_driver_data.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/api/types/graph_driver_data.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package types
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// GraphDriverData Information about a container's graph driver.
|
||||
// swagger:model GraphDriverData
|
||||
type GraphDriverData struct {
|
||||
|
||||
// data
|
||||
// Required: true
|
||||
Data map[string]string `json:"Data"`
|
||||
|
||||
// name
|
||||
// Required: true
|
||||
Name string `json:"Name"`
|
||||
}
|
13
vendor/github.com/docker/docker/api/types/id_response.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/api/types/id_response.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package types
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// IDResponse Response to an API call that returns just an Id
|
||||
// swagger:model IdResponse
|
||||
type IDResponse struct {
|
||||
|
||||
// The id of the newly created object.
|
||||
// Required: true
|
||||
ID string `json:"Id"`
|
||||
}
|
37
vendor/github.com/docker/docker/api/types/image/image_history.go
generated
vendored
Normal file
37
vendor/github.com/docker/docker/api/types/image/image_history.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
package image
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file was generated by `swagger generate operation`
|
||||
//
|
||||
// See hack/generate-swagger-api.sh
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// HistoryResponseItem individual image layer information in response to ImageHistory operation
|
||||
// swagger:model HistoryResponseItem
|
||||
type HistoryResponseItem struct {
|
||||
|
||||
// comment
|
||||
// Required: true
|
||||
Comment string `json:"Comment"`
|
||||
|
||||
// created
|
||||
// Required: true
|
||||
Created int64 `json:"Created"`
|
||||
|
||||
// created by
|
||||
// Required: true
|
||||
CreatedBy string `json:"CreatedBy"`
|
||||
|
||||
// Id
|
||||
// Required: true
|
||||
ID string `json:"Id"`
|
||||
|
||||
// size
|
||||
// Required: true
|
||||
Size int64 `json:"Size"`
|
||||
|
||||
// tags
|
||||
// Required: true
|
||||
Tags []string `json:"Tags"`
|
||||
}
|
15
vendor/github.com/docker/docker/api/types/image_delete_response_item.go
generated
vendored
Normal file
15
vendor/github.com/docker/docker/api/types/image_delete_response_item.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package types
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ImageDeleteResponseItem image delete response item
|
||||
// swagger:model ImageDeleteResponseItem
|
||||
type ImageDeleteResponseItem struct {
|
||||
|
||||
// The image ID of an image that was deleted
|
||||
Deleted string `json:"Deleted,omitempty"`
|
||||
|
||||
// The image ID of an image that was untagged
|
||||
Untagged string `json:"Untagged,omitempty"`
|
||||
}
|
49
vendor/github.com/docker/docker/api/types/image_summary.go
generated
vendored
Normal file
49
vendor/github.com/docker/docker/api/types/image_summary.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package types
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ImageSummary image summary
|
||||
// swagger:model ImageSummary
|
||||
type ImageSummary struct {
|
||||
|
||||
// containers
|
||||
// Required: true
|
||||
Containers int64 `json:"Containers"`
|
||||
|
||||
// created
|
||||
// Required: true
|
||||
Created int64 `json:"Created"`
|
||||
|
||||
// Id
|
||||
// Required: true
|
||||
ID string `json:"Id"`
|
||||
|
||||
// labels
|
||||
// Required: true
|
||||
Labels map[string]string `json:"Labels"`
|
||||
|
||||
// parent Id
|
||||
// Required: true
|
||||
ParentID string `json:"ParentId"`
|
||||
|
||||
// repo digests
|
||||
// Required: true
|
||||
RepoDigests []string `json:"RepoDigests"`
|
||||
|
||||
// repo tags
|
||||
// Required: true
|
||||
RepoTags []string `json:"RepoTags"`
|
||||
|
||||
// shared size
|
||||
// Required: true
|
||||
SharedSize int64 `json:"SharedSize"`
|
||||
|
||||
// size
|
||||
// Required: true
|
||||
Size int64 `json:"Size"`
|
||||
|
||||
// virtual size
|
||||
// Required: true
|
||||
VirtualSize int64 `json:"VirtualSize"`
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user