mirror of https://github.com/docker/buildx.git
vendor: add buildkit
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
62faee5f07
commit
8b7c38e61a
22
go.mod
22
go.mod
|
@ -2,7 +2,6 @@ module github.com/tonistiigi/buildx
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
github.com/Microsoft/go-winio v0.4.12 // indirect
|
|
||||||
github.com/Microsoft/hcsshim v0.8.6 // indirect
|
github.com/Microsoft/hcsshim v0.8.6 // indirect
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||||
|
@ -14,8 +13,8 @@ require (
|
||||||
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
||||||
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
||||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
||||||
github.com/containerd/cgroups v0.0.0-20190226200435-dbea6f2bd416 // indirect
|
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50
|
||||||
github.com/containerd/containerd v1.2.5 // indirect
|
github.com/containerd/containerd v1.3.0-0.20190321141026-ceba56893a76
|
||||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
||||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
|
||||||
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 // indirect
|
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 // indirect
|
||||||
|
@ -23,25 +22,19 @@ require (
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
|
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
|
||||||
github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0
|
github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0
|
||||||
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 // indirect
|
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 // indirect
|
||||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible // indirect
|
|
||||||
github.com/docker/docker v1.14.0-0.20190319210016-827cb09f8796 // indirect
|
|
||||||
github.com/docker/docker-credential-helpers v0.6.1 // indirect
|
github.com/docker/docker-credential-helpers v0.6.1 // indirect
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad // indirect
|
|
||||||
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96 // indirect
|
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96 // indirect
|
||||||
github.com/docker/go-units v0.3.3 // indirect
|
|
||||||
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
|
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||||
github.com/godbus/dbus v4.1.0+incompatible // indirect
|
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||||
github.com/gogo/googleapis v1.1.0 // indirect
|
github.com/gogo/googleapis v1.1.0 // indirect
|
||||||
github.com/gogo/protobuf v1.2.1 // indirect
|
github.com/gogo/protobuf v1.2.1 // indirect
|
||||||
github.com/google/btree v1.0.0 // indirect
|
github.com/google/btree v1.0.0 // indirect
|
||||||
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
||||||
github.com/google/go-cmp v0.2.0 // indirect
|
|
||||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
||||||
github.com/googleapis/gnostic v0.2.0 // indirect
|
github.com/googleapis/gnostic v0.2.0 // indirect
|
||||||
github.com/gorilla/mux v1.7.0 // indirect
|
github.com/gorilla/mux v1.7.0 // indirect
|
||||||
|
@ -55,20 +48,17 @@ require (
|
||||||
github.com/jinzhu/now v1.0.0 // indirect
|
github.com/jinzhu/now v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.6 // indirect
|
github.com/json-iterator/go v1.1.6 // indirect
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
|
||||||
github.com/lib/pq v1.0.0 // indirect
|
github.com/lib/pq v1.0.0 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 // indirect
|
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 // indirect
|
||||||
|
github.com/moby/buildkit v0.4.1-0.20190322070013-03feb5e28f65
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
github.com/opencontainers/image-spec v1.0.1
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
|
||||||
github.com/opencontainers/runc v0.1.1 // indirect
|
|
||||||
github.com/opencontainers/runtime-spec v1.0.1 // indirect
|
github.com/opencontainers/runtime-spec v1.0.1 // indirect
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||||
github.com/pkg/errors v0.8.1 // indirect
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/prometheus/client_golang v0.8.0 // indirect
|
github.com/prometheus/client_golang v0.8.0 // indirect
|
||||||
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612 // indirect
|
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612 // indirect
|
||||||
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 // indirect
|
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 // indirect
|
||||||
|
@ -79,6 +69,7 @@ require (
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect
|
||||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect
|
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||||
google.golang.org/grpc v1.19.1 // indirect
|
google.golang.org/grpc v1.19.1 // indirect
|
||||||
|
@ -86,7 +77,6 @@ require (
|
||||||
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
||||||
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
|
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gotest.tools v2.2.0+incompatible // indirect
|
|
||||||
k8s.io/api v0.0.0-20180712090710-2d6f90ab1293 // indirect
|
k8s.io/api v0.0.0-20180712090710-2d6f90ab1293 // indirect
|
||||||
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d // indirect
|
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d // indirect
|
||||||
k8s.io/client-go v2.0.0-alpha.0.0.20180806134042-1f13a808da65+incompatible // indirect
|
k8s.io/client-go v2.0.0-alpha.0.0.20180806134042-1f13a808da65+incompatible // indirect
|
||||||
|
|
98
go.sum
98
go.sum
|
@ -4,14 +4,17 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
|
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
|
||||||
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
|
github.com/Microsoft/hcsshim v0.8.5/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||||
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
|
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
|
||||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
||||||
|
github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
@ -30,45 +33,61 @@ github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e h1:Qux+lbuMaRzkQyTdzgtz8MgzPtzmaPQy6DXmxpdxT3U=
|
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e h1:Qux+lbuMaRzkQyTdzgtz8MgzPtzmaPQy6DXmxpdxT3U=
|
||||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||||
|
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
github.com/containerd/cgroups v0.0.0-20190226200435-dbea6f2bd416 h1:AaSMvkPaxfZD/OsDVBueAKzY5lnWAqLWgUivNg37WHA=
|
github.com/containerd/cgroups v0.0.0-20190226200435-dbea6f2bd416 h1:AaSMvkPaxfZD/OsDVBueAKzY5lnWAqLWgUivNg37WHA=
|
||||||
github.com/containerd/cgroups v0.0.0-20190226200435-dbea6f2bd416/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
|
github.com/containerd/cgroups v0.0.0-20190226200435-dbea6f2bd416/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
|
||||||
github.com/containerd/containerd v1.2.5 h1:D+s0XmoswfcRJXgmMMlI1vAblp+LTCftRnEjKsgbFPU=
|
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||||
github.com/containerd/containerd v1.2.5/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 h1:WMpHmC6AxwWb9hMqhudkqG7A/p14KiMnl6d3r1iUMjU=
|
||||||
|
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||||
|
github.com/containerd/containerd v1.2.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/containerd/containerd v1.3.0-0.20190321141026-ceba56893a76 h1:+do6jVam4JCI9KPBxTzrJpNpu0ZoMpsexTLAAQYNzaQ=
|
||||||
|
github.com/containerd/containerd v1.3.0-0.20190321141026-ceba56893a76/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/containerd/continuity v0.0.0-20181001140422-bd77b46c8352/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
||||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
|
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4=
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4=
|
||||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||||
|
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||||
|
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||||
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 h1:14r0i3IeJj6zkNLigAJiv/TWSR8EY+pxIjv5tFiT+n8=
|
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 h1:14r0i3IeJj6zkNLigAJiv/TWSR8EY+pxIjv5tFiT+n8=
|
||||||
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 h1:tTngnoO/B6HQnJ+pK8tN7kEAhmhIfaJOutqq/A4/JTM=
|
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 h1:tTngnoO/B6HQnJ+pK8tN7kEAhmhIfaJOutqq/A4/JTM=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||||
|
github.com/docker/cli v0.0.0-20190131223713-234462756460/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0 h1:E7NTtHfZYV+iu35yZ49AbrxqhMHpiOl3FstDYm38vQ0=
|
github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0 h1:E7NTtHfZYV+iu35yZ49AbrxqhMHpiOl3FstDYm38vQ0=
|
||||||
github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 h1:90ytrX1dbzL7Uf/hHiuWwvywC+gikHv4hkAy4CwRTbs=
|
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 h1:90ytrX1dbzL7Uf/hHiuWwvywC+gikHv4hkAy4CwRTbs=
|
||||||
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE=
|
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE=
|
||||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE=
|
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE=
|
||||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v1.14.0-0.20190319210016-827cb09f8796 h1:UK2i+hkrwfmWD0N2XVIww9MHn7pKpFpFko26vyf3bzg=
|
github.com/docker/docker v0.0.0-20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v1.14.0-0.20190319210016-827cb09f8796/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v1.14.0-0.20190319215453-e7b5f7dbe98c h1:rZ+3jNsgjvYgdZ0Nrd4Udrv8rneDbWBohAPuXsTsvGU=
|
||||||
|
github.com/docker/docker v1.14.0-0.20190319215453-e7b5f7dbe98c/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/docker-credential-helpers v0.6.0/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||||
github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g=
|
github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g=
|
||||||
github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||||
|
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad h1:VXIse57M5C6ezDuCPyq6QmMvEJ2xclYKZ35SfkXdm3E=
|
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad h1:VXIse57M5C6ezDuCPyq6QmMvEJ2xclYKZ35SfkXdm3E=
|
||||||
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||||
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96 h1:HVQ/BC7Ze+bcVle903SvZMvncOcG2y3zI2K7i3jEHSM=
|
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96 h1:HVQ/BC7Ze+bcVle903SvZMvncOcG2y3zI2K7i3jEHSM=
|
||||||
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||||
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
github.com/docker/go-units v0.3.1 h1:QAFdsA6jLCnglbqE6mUsHuPcJlntY94DkxHf4deHKIU=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.1/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/docker/libnetwork v0.0.0-20180913200009-36d3bed0e9f4/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
|
||||||
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 h1:k8TfKGeAcDQFFQOGCQMRN04N4a9YrPlRMMKnzAuvM9Q=
|
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 h1:k8TfKGeAcDQFFQOGCQMRN04N4a9YrPlRMMKnzAuvM9Q=
|
||||||
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||||
|
@ -81,10 +100,15 @@ github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZp
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
|
github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g=
|
||||||
|
github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/googleapis v0.0.0-20180501115203-b23578765ee5/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI=
|
github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI=
|
||||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
|
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
@ -100,22 +124,34 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
||||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
|
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
|
||||||
|
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||||
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
||||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||||
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
||||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q=
|
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q=
|
||||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||||
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/ishidawataru/sctp v0.0.0-20180213033435-07191f837fed/go.mod h1:DM4VvS+hD/kDi1U1QsX2fnZowwBhqD0Dk3bRPKF/Oc8=
|
||||||
github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw=
|
github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw=
|
||||||
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||||
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
|
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
|
||||||
|
@ -145,28 +181,40 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 h1:UEWeJCqsIp+93IcMCuqA3KFln2LAUd/tDtoItl0bgJM=
|
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 h1:UEWeJCqsIp+93IcMCuqA3KFln2LAUd/tDtoItl0bgJM=
|
||||||
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19/go.mod h1:WCBAbTOdfhHhz7YXujeZMF7owC4tPb1naKFsgfUISjo=
|
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19/go.mod h1:WCBAbTOdfhHhz7YXujeZMF7owC4tPb1naKFsgfUISjo=
|
||||||
|
github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/moby/buildkit v0.4.1-0.20190322070013-03feb5e28f65 h1:t9GGPuQ85Reqzlnu7Vy7QBb+SrztgASQ5ek8CSEqdyI=
|
||||||
|
github.com/moby/buildkit v0.4.1-0.20190322070013-03feb5e28f65/go.mod h1:hxXlABlNvROrpptYGUMgF2V7VrHpjphi23E63UjlVP4=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
|
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
|
||||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
github.com/opencontainers/runc v1.0.0-rc6/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
github.com/opencontainers/runc v1.0.1-0.20190307181833-2b18fe1d885e h1:+uPGJuuDl61O9GKN/rLHkUCf597mpxmJI06RqMQX81A=
|
||||||
|
github.com/opencontainers/runc v1.0.1-0.20190307181833-2b18fe1d885e/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
|
github.com/opencontainers/runtime-spec v0.0.0-20180909173843-eba862dc2470/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ=
|
github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ=
|
||||||
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
|
github.com/opentracing-contrib/go-stdlib v0.0.0-20171029140428-b1a47cfbdd75/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w=
|
||||||
|
github.com/opentracing/opentracing-go v0.0.0-20171003133519-1361b9cd60be h1:vn0ruyYif1hUWDS2aEUdh6JGUfgK8gOOLpz/iTjb6pQ=
|
||||||
|
github.com/opentracing/opentracing-go v0.0.0-20171003133519-1361b9cd60be/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
|
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
|
||||||
|
@ -177,6 +225,8 @@ github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 h1:osmNoEW2SCW3L
|
||||||
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be h1:MoyXp/VjXUwM0GyDcdwT7Ubea2gxOSHpPaFo3qV+Y2A=
|
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be h1:MoyXp/VjXUwM0GyDcdwT7Ubea2gxOSHpPaFo3qV+Y2A=
|
||||||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/sirupsen/logrus v1.0.3/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||||
|
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
|
github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
|
||||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||||
|
@ -191,57 +241,89 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
||||||
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
|
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
|
||||||
|
github.com/tonistiigi/fsutil v0.0.0-20190319020005-1bdbf124ad49 h1:UFQ7uDVXIH4fFfOb+fISgTl8Ukk0CkGQudHQh980l+0=
|
||||||
|
github.com/tonistiigi/fsutil v0.0.0-20190319020005-1bdbf124ad49/go.mod h1:pzh7kdwkDRh+Bx8J30uqaKJ1M4QrSH/um8fcIXeM8rc=
|
||||||
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||||
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||||
|
github.com/uber/jaeger-client-go v0.0.0-20180103221425-e02c85f9069e/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||||
|
github.com/uber/jaeger-lib v1.2.1/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
|
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
||||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190129210102-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190129210102-ccddf3741a0c h1:MWY7h75sb9ioBR+s5Zgq1JYXxhbZvrSP2okwLi3ItmI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190129210102-ccddf3741a0c/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190303122642-d455e41777fc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g=
|
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g=
|
||||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/genproto v0.0.0-20170523043604-d80a6e20e776/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
|
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
|
||||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxAsBSMQ=
|
gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxAsBSMQ=
|
||||||
gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
|
gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
|
||||||
gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
|
gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
|
||||||
gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
|
gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||||
gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
|
gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
|
||||||
gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
|
gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- "1.10.x"
|
||||||
|
- "1.11.x"
|
||||||
|
|
||||||
|
go_import_path: github.com/containerd/console
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -d
|
||||||
|
- GOOS=openbsd go get -d
|
||||||
|
- GOOS=solaris go get -d
|
||||||
|
- GOOS=windows go get -d
|
||||||
|
- go get -u github.com/vbatts/git-validation
|
||||||
|
- go get -u github.com/kunalkushwaha/ltag
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- pushd ..; git clone https://github.com/containerd/project; popd
|
||||||
|
|
||||||
|
script:
|
||||||
|
- DCO_VERBOSITY=-q ../project/script/validate/dco
|
||||||
|
- ../project/script/validate/fileheader ../project/
|
||||||
|
- go test -race
|
||||||
|
- GOOS=openbsd go build
|
||||||
|
- GOOS=openbsd go test -c
|
||||||
|
- GOOS=solaris go build
|
||||||
|
- GOOS=solaris go test -c
|
||||||
|
- GOOS=windows go test
|
|
@ -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 The containerd Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,27 @@
|
||||||
|
# console
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/containerd/console.svg?branch=master)](https://travis-ci.org/containerd/console)
|
||||||
|
|
||||||
|
Golang package for dealing with consoles. Light on deps and a simple API.
|
||||||
|
|
||||||
|
## Modifying the current process
|
||||||
|
|
||||||
|
```go
|
||||||
|
current := console.Current()
|
||||||
|
defer current.Reset()
|
||||||
|
|
||||||
|
if err := current.SetRaw(); err != nil {
|
||||||
|
}
|
||||||
|
ws, err := current.Size()
|
||||||
|
current.Resize(ws)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project details
|
||||||
|
|
||||||
|
console is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
|
||||||
|
As a containerd sub-project, you will find the:
|
||||||
|
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
|
||||||
|
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
|
||||||
|
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotAConsole = errors.New("provided file is not a console")
|
||||||
|
|
||||||
|
type Console interface {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
io.Closer
|
||||||
|
|
||||||
|
// Resize resizes the console to the provided window size
|
||||||
|
Resize(WinSize) error
|
||||||
|
// ResizeFrom resizes the calling console to the size of the
|
||||||
|
// provided console
|
||||||
|
ResizeFrom(Console) error
|
||||||
|
// SetRaw sets the console in raw mode
|
||||||
|
SetRaw() error
|
||||||
|
// DisableEcho disables echo on the console
|
||||||
|
DisableEcho() error
|
||||||
|
// Reset restores the console to its orignal state
|
||||||
|
Reset() error
|
||||||
|
// Size returns the window size of the console
|
||||||
|
Size() (WinSize, error)
|
||||||
|
// Fd returns the console's file descriptor
|
||||||
|
Fd() uintptr
|
||||||
|
// Name returns the console's file name
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WinSize specifies the window size of the console
|
||||||
|
type WinSize struct {
|
||||||
|
// Height of the console
|
||||||
|
Height uint16
|
||||||
|
// Width of the console
|
||||||
|
Width uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current returns the current processes console
|
||||||
|
func Current() Console {
|
||||||
|
c, err := ConsoleFromFile(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
// stdin should always be a console for the design
|
||||||
|
// of this function
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsoleFromFile returns a console using the provided file
|
||||||
|
func ConsoleFromFile(f *os.File) (Console, error) {
|
||||||
|
if err := checkConsole(f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newMaster(f)
|
||||||
|
}
|
|
@ -0,0 +1,275 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxEvents = 128
|
||||||
|
)
|
||||||
|
|
||||||
|
// Epoller manages multiple epoll consoles using edge-triggered epoll api so we
|
||||||
|
// dont have to deal with repeated wake-up of EPOLLER or EPOLLHUP.
|
||||||
|
// For more details, see:
|
||||||
|
// - https://github.com/systemd/systemd/pull/4262
|
||||||
|
// - https://github.com/moby/moby/issues/27202
|
||||||
|
//
|
||||||
|
// Example usage of Epoller and EpollConsole can be as follow:
|
||||||
|
//
|
||||||
|
// epoller, _ := NewEpoller()
|
||||||
|
// epollConsole, _ := epoller.Add(console)
|
||||||
|
// go epoller.Wait()
|
||||||
|
// var (
|
||||||
|
// b bytes.Buffer
|
||||||
|
// wg sync.WaitGroup
|
||||||
|
// )
|
||||||
|
// wg.Add(1)
|
||||||
|
// go func() {
|
||||||
|
// io.Copy(&b, epollConsole)
|
||||||
|
// wg.Done()
|
||||||
|
// }()
|
||||||
|
// // perform I/O on the console
|
||||||
|
// epollConsole.Shutdown(epoller.CloseConsole)
|
||||||
|
// wg.Wait()
|
||||||
|
// epollConsole.Close()
|
||||||
|
type Epoller struct {
|
||||||
|
efd int
|
||||||
|
mu sync.Mutex
|
||||||
|
fdMapping map[int]*EpollConsole
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEpoller returns an instance of epoller with a valid epoll fd.
|
||||||
|
func NewEpoller() (*Epoller, error) {
|
||||||
|
efd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Epoller{
|
||||||
|
efd: efd,
|
||||||
|
fdMapping: make(map[int]*EpollConsole),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add creates an epoll console based on the provided console. The console will
|
||||||
|
// be registered with EPOLLET (i.e. using edge-triggered notification) and its
|
||||||
|
// file descriptor will be set to non-blocking mode. After this, user should use
|
||||||
|
// the return console to perform I/O.
|
||||||
|
func (e *Epoller) Add(console Console) (*EpollConsole, error) {
|
||||||
|
sysfd := int(console.Fd())
|
||||||
|
// Set sysfd to non-blocking mode
|
||||||
|
if err := unix.SetNonblock(sysfd, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := unix.EpollEvent{
|
||||||
|
Events: unix.EPOLLIN | unix.EPOLLOUT | unix.EPOLLRDHUP | unix.EPOLLET,
|
||||||
|
Fd: int32(sysfd),
|
||||||
|
}
|
||||||
|
if err := unix.EpollCtl(e.efd, unix.EPOLL_CTL_ADD, sysfd, &ev); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ef := &EpollConsole{
|
||||||
|
Console: console,
|
||||||
|
sysfd: sysfd,
|
||||||
|
readc: sync.NewCond(&sync.Mutex{}),
|
||||||
|
writec: sync.NewCond(&sync.Mutex{}),
|
||||||
|
}
|
||||||
|
e.mu.Lock()
|
||||||
|
e.fdMapping[sysfd] = ef
|
||||||
|
e.mu.Unlock()
|
||||||
|
return ef, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait starts the loop to wait for its consoles' notifications and signal
|
||||||
|
// appropriate console that it can perform I/O.
|
||||||
|
func (e *Epoller) Wait() error {
|
||||||
|
events := make([]unix.EpollEvent, maxEvents)
|
||||||
|
for {
|
||||||
|
n, err := unix.EpollWait(e.efd, events, -1)
|
||||||
|
if err != nil {
|
||||||
|
// EINTR: The call was interrupted by a signal handler before either
|
||||||
|
// any of the requested events occurred or the timeout expired
|
||||||
|
if err == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
ev := &events[i]
|
||||||
|
// the console is ready to be read from
|
||||||
|
if ev.Events&(unix.EPOLLIN|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
|
||||||
|
if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
|
||||||
|
epfile.signalRead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the console is ready to be written to
|
||||||
|
if ev.Events&(unix.EPOLLOUT|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
|
||||||
|
if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
|
||||||
|
epfile.signalWrite()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseConsole unregisters the console's file descriptor from epoll interface
|
||||||
|
func (e *Epoller) CloseConsole(fd int) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
delete(e.fdMapping, fd)
|
||||||
|
return unix.EpollCtl(e.efd, unix.EPOLL_CTL_DEL, fd, &unix.EpollEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Epoller) getConsole(sysfd int) *EpollConsole {
|
||||||
|
e.mu.Lock()
|
||||||
|
f := e.fdMapping[sysfd]
|
||||||
|
e.mu.Unlock()
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the epoll fd
|
||||||
|
func (e *Epoller) Close() error {
|
||||||
|
return unix.Close(e.efd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EpollConsole acts like a console but registers its file descriptor with an
|
||||||
|
// epoll fd and uses epoll API to perform I/O.
|
||||||
|
type EpollConsole struct {
|
||||||
|
Console
|
||||||
|
readc *sync.Cond
|
||||||
|
writec *sync.Cond
|
||||||
|
sysfd int
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads up to len(p) bytes into p. It returns the number of bytes read
|
||||||
|
// (0 <= n <= len(p)) and any error encountered.
|
||||||
|
//
|
||||||
|
// If the console's read returns EAGAIN or EIO, we assume that it's a
|
||||||
|
// temporary error because the other side went away and wait for the signal
|
||||||
|
// generated by epoll event to continue.
|
||||||
|
func (ec *EpollConsole) Read(p []byte) (n int, err error) {
|
||||||
|
var read int
|
||||||
|
ec.readc.L.Lock()
|
||||||
|
defer ec.readc.L.Unlock()
|
||||||
|
for {
|
||||||
|
read, err = ec.Console.Read(p[n:])
|
||||||
|
n += read
|
||||||
|
if err != nil {
|
||||||
|
var hangup bool
|
||||||
|
if perr, ok := err.(*os.PathError); ok {
|
||||||
|
hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
|
||||||
|
} else {
|
||||||
|
hangup = (err == unix.EAGAIN || err == unix.EIO)
|
||||||
|
}
|
||||||
|
// if the other end disappear, assume this is temporary and wait for the
|
||||||
|
// signal to continue again. Unless we didnt read anything and the
|
||||||
|
// console is already marked as closed then we should exit
|
||||||
|
if hangup && !(n == 0 && len(p) > 0 && ec.closed) {
|
||||||
|
ec.readc.Wait()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if we didnt read anything then return io.EOF to end gracefully
|
||||||
|
if n == 0 && len(p) > 0 && err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
// signal for others that we finished the read
|
||||||
|
ec.readc.Signal()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes len(p) bytes from p to the console. It returns the number of bytes
|
||||||
|
// written from p (0 <= n <= len(p)) and any error encountered that caused
|
||||||
|
// the write to stop early.
|
||||||
|
//
|
||||||
|
// If writes to the console returns EAGAIN or EIO, we assume that it's a
|
||||||
|
// temporary error because the other side went away and wait for the signal
|
||||||
|
// generated by epoll event to continue.
|
||||||
|
func (ec *EpollConsole) Write(p []byte) (n int, err error) {
|
||||||
|
var written int
|
||||||
|
ec.writec.L.Lock()
|
||||||
|
defer ec.writec.L.Unlock()
|
||||||
|
for {
|
||||||
|
written, err = ec.Console.Write(p[n:])
|
||||||
|
n += written
|
||||||
|
if err != nil {
|
||||||
|
var hangup bool
|
||||||
|
if perr, ok := err.(*os.PathError); ok {
|
||||||
|
hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
|
||||||
|
} else {
|
||||||
|
hangup = (err == unix.EAGAIN || err == unix.EIO)
|
||||||
|
}
|
||||||
|
// if the other end disappears, assume this is temporary and wait for the
|
||||||
|
// signal to continue again.
|
||||||
|
if hangup {
|
||||||
|
ec.writec.Wait()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unrecoverable error, break the loop and return the error
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if n < len(p) && err == nil {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
// signal for others that we finished the write
|
||||||
|
ec.writec.Signal()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown closes the file descriptor and signals call waiters for this fd.
|
||||||
|
// It accepts a callback which will be called with the console's fd. The
|
||||||
|
// callback typically will be used to do further cleanup such as unregister the
|
||||||
|
// console's fd from the epoll interface.
|
||||||
|
// User should call Shutdown and wait for all I/O operation to be finished
|
||||||
|
// before closing the console.
|
||||||
|
func (ec *EpollConsole) Shutdown(close func(int) error) error {
|
||||||
|
ec.readc.L.Lock()
|
||||||
|
defer ec.readc.L.Unlock()
|
||||||
|
ec.writec.L.Lock()
|
||||||
|
defer ec.writec.L.Unlock()
|
||||||
|
|
||||||
|
ec.readc.Broadcast()
|
||||||
|
ec.writec.Broadcast()
|
||||||
|
ec.closed = true
|
||||||
|
return close(ec.sysfd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signalRead signals that the console is readable.
|
||||||
|
func (ec *EpollConsole) signalRead() {
|
||||||
|
ec.readc.L.Lock()
|
||||||
|
ec.readc.Signal()
|
||||||
|
ec.readc.L.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// signalWrite signals that the console is writable.
|
||||||
|
func (ec *EpollConsole) signalWrite() {
|
||||||
|
ec.writec.L.Lock()
|
||||||
|
ec.writec.Signal()
|
||||||
|
ec.writec.L.Unlock()
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
// +build darwin freebsd linux openbsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPty creates a new pty pair
|
||||||
|
// The master is returned as the first console and a string
|
||||||
|
// with the path to the pty slave is returned as the second
|
||||||
|
func NewPty() (Console, string, error) {
|
||||||
|
f, err := os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
slave, err := ptsname(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if err := unlockpt(f); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
m, err := newMaster(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return m, slave, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type master struct {
|
||||||
|
f *os.File
|
||||||
|
original *unix.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Read(b []byte) (int, error) {
|
||||||
|
return m.f.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Write(b []byte) (int, error) {
|
||||||
|
return m.f.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Close() error {
|
||||||
|
return m.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Resize(ws WinSize) error {
|
||||||
|
return tcswinsz(m.f.Fd(), ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) ResizeFrom(c Console) error {
|
||||||
|
ws, err := c.Size()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.Resize(ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Reset() error {
|
||||||
|
if m.original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tcset(m.f.Fd(), m.original)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) getCurrent() (unix.Termios, error) {
|
||||||
|
var termios unix.Termios
|
||||||
|
if err := tcget(m.f.Fd(), &termios); err != nil {
|
||||||
|
return unix.Termios{}, err
|
||||||
|
}
|
||||||
|
return termios, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) SetRaw() error {
|
||||||
|
rawState, err := m.getCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawState = cfmakeraw(rawState)
|
||||||
|
rawState.Oflag = rawState.Oflag | unix.OPOST
|
||||||
|
return tcset(m.f.Fd(), &rawState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) DisableEcho() error {
|
||||||
|
rawState, err := m.getCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawState.Lflag = rawState.Lflag &^ unix.ECHO
|
||||||
|
return tcset(m.f.Fd(), &rawState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Size() (WinSize, error) {
|
||||||
|
return tcgwinsz(m.f.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Fd() uintptr {
|
||||||
|
return m.f.Fd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Name() string {
|
||||||
|
return m.f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConsole checks if the provided file is a console
|
||||||
|
func checkConsole(f *os.File) error {
|
||||||
|
var termios unix.Termios
|
||||||
|
if tcget(f.Fd(), &termios) != nil {
|
||||||
|
return ErrNotAConsole
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMaster(f *os.File) (Console, error) {
|
||||||
|
m := &master{
|
||||||
|
f: f,
|
||||||
|
}
|
||||||
|
t, err := m.getCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.original = &t
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
|
||||||
|
// created by us acts normally. In particular, a not-very-well-known default of
|
||||||
|
// Linux unix98 ptys is that they have +onlcr by default. While this isn't a
|
||||||
|
// problem for terminal emulators, because we relay data from the terminal we
|
||||||
|
// also relay that funky line discipline.
|
||||||
|
func ClearONLCR(fd uintptr) error {
|
||||||
|
return setONLCR(fd, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
|
||||||
|
// created by us acts as intended for a terminal emulator.
|
||||||
|
func SetONLCR(fd uintptr) error {
|
||||||
|
return setONLCR(fd, true)
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
vtInputSupported bool
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *master) initStdios() {
|
||||||
|
m.in = windows.Handle(os.Stdin.Fd())
|
||||||
|
if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil {
|
||||||
|
// Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
|
||||||
|
if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
|
||||||
|
vtInputSupported = true
|
||||||
|
}
|
||||||
|
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
||||||
|
// remembers invalid bits on input handles.
|
||||||
|
windows.SetConsoleMode(m.in, m.inMode)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("failed to get console mode for stdin: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.out = windows.Handle(os.Stdout.Fd())
|
||||||
|
if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil {
|
||||||
|
if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
|
||||||
|
m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
} else {
|
||||||
|
windows.SetConsoleMode(m.out, m.outMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("failed to get console mode for stdout: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.err = windows.Handle(os.Stderr.Fd())
|
||||||
|
if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil {
|
||||||
|
if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
|
||||||
|
m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
} else {
|
||||||
|
windows.SetConsoleMode(m.err, m.errMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("failed to get console mode for stderr: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type master struct {
|
||||||
|
in windows.Handle
|
||||||
|
inMode uint32
|
||||||
|
|
||||||
|
out windows.Handle
|
||||||
|
outMode uint32
|
||||||
|
|
||||||
|
err windows.Handle
|
||||||
|
errMode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) SetRaw() error {
|
||||||
|
if err := makeInputRaw(m.in, m.inMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set StdOut and StdErr to raw mode, we ignore failures since
|
||||||
|
// windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of
|
||||||
|
// Windows.
|
||||||
|
|
||||||
|
windows.SetConsoleMode(m.out, m.outMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
|
||||||
|
windows.SetConsoleMode(m.err, m.errMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Reset() error {
|
||||||
|
for _, s := range []struct {
|
||||||
|
fd windows.Handle
|
||||||
|
mode uint32
|
||||||
|
}{
|
||||||
|
{m.in, m.inMode},
|
||||||
|
{m.out, m.outMode},
|
||||||
|
{m.err, m.errMode},
|
||||||
|
} {
|
||||||
|
if err := windows.SetConsoleMode(s.fd, s.mode); err != nil {
|
||||||
|
return errors.Wrap(err, "unable to restore console mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Size() (WinSize, error) {
|
||||||
|
var info windows.ConsoleScreenBufferInfo
|
||||||
|
err := windows.GetConsoleScreenBufferInfo(m.out, &info)
|
||||||
|
if err != nil {
|
||||||
|
return WinSize{}, errors.Wrap(err, "unable to get console info")
|
||||||
|
}
|
||||||
|
|
||||||
|
winsize := WinSize{
|
||||||
|
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||||
|
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
return winsize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Resize(ws WinSize) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) ResizeFrom(c Console) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) DisableEcho() error {
|
||||||
|
mode := m.inMode &^ windows.ENABLE_ECHO_INPUT
|
||||||
|
mode |= windows.ENABLE_PROCESSED_INPUT
|
||||||
|
mode |= windows.ENABLE_LINE_INPUT
|
||||||
|
|
||||||
|
if err := windows.SetConsoleMode(m.in, mode); err != nil {
|
||||||
|
return errors.Wrap(err, "unable to set console to disable echo")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Read(b []byte) (int, error) {
|
||||||
|
return os.Stdin.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Write(b []byte) (int, error) {
|
||||||
|
return os.Stdout.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Fd() uintptr {
|
||||||
|
return uintptr(m.in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// on windows, console can only be made from os.Std{in,out,err}, hence there
|
||||||
|
// isnt a single name here we can use. Return a dummy "console" value in this
|
||||||
|
// case should be sufficient.
|
||||||
|
func (m *master) Name() string {
|
||||||
|
return "console"
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeInputRaw puts the terminal (Windows Console) connected to the given
|
||||||
|
// file descriptor into raw mode
|
||||||
|
func makeInputRaw(fd windows.Handle, mode uint32) error {
|
||||||
|
// See
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||||
|
|
||||||
|
// Disable these modes
|
||||||
|
mode &^= windows.ENABLE_ECHO_INPUT
|
||||||
|
mode &^= windows.ENABLE_LINE_INPUT
|
||||||
|
mode &^= windows.ENABLE_MOUSE_INPUT
|
||||||
|
mode &^= windows.ENABLE_WINDOW_INPUT
|
||||||
|
mode &^= windows.ENABLE_PROCESSED_INPUT
|
||||||
|
|
||||||
|
// Enable these modes
|
||||||
|
mode |= windows.ENABLE_EXTENDED_FLAGS
|
||||||
|
mode |= windows.ENABLE_INSERT_MODE
|
||||||
|
mode |= windows.ENABLE_QUICK_EDIT_MODE
|
||||||
|
|
||||||
|
if vtInputSupported {
|
||||||
|
mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := windows.SetConsoleMode(fd, mode); err != nil {
|
||||||
|
return errors.Wrap(err, "unable to set console to raw mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConsole(f *os.File) error {
|
||||||
|
var mode uint32
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMaster(f *os.File) (Console, error) {
|
||||||
|
if f != os.Stdin && f != os.Stdout && f != os.Stderr {
|
||||||
|
return nil, errors.New("creating a console from a file is not supported on windows")
|
||||||
|
}
|
||||||
|
m := &master{}
|
||||||
|
m.initStdios()
|
||||||
|
return m, nil
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
func ioctl(fd, flag, data uintptr) error {
|
||||||
|
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
var u int32
|
||||||
|
return ioctl(f.Fd(), unix.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCPTYGNAME)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
// This does not exist on FreeBSD, it does not allocate controlling terminals on open
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TCGETS
|
||||||
|
cmdTcSet = unix.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
var u int32
|
||||||
|
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
var u uint32
|
||||||
|
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/dev/pts/%d", u), nil
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// +build openbsd,cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
//#include <stdlib.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
ptspath, err := C.ptsname(C.int(f.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return C.GoString(ptspath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
if _, err := C.grantpt(C.int(f.Fd())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// +build openbsd,!cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Implementing the functions below requires cgo support. Non-cgo stubs
|
||||||
|
// versions are defined below to enable cross-compilation of source code
|
||||||
|
// that depends on these functions, but the resultant cross-compiled
|
||||||
|
// binaries cannot actually be used. If the stub function(s) below are
|
||||||
|
// actually invoked they will display an error message and cause the
|
||||||
|
// calling process to exit.
|
||||||
|
//
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
panic("ptsname() support requires cgo.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
panic("unlockpt() support requires cgo.")
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// +build solaris,cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
//#include <stdlib.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TCGETS
|
||||||
|
cmdTcSet = unix.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
ptspath, err := C.ptsname(C.int(f.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return C.GoString(ptspath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
if _, err := C.grantpt(C.int(f.Fd())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// +build solaris,!cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Implementing the functions below requires cgo support. Non-cgo stubs
|
||||||
|
// versions are defined below to enable cross-compilation of source code
|
||||||
|
// that depends on these functions, but the resultant cross-compiled
|
||||||
|
// binaries cannot actually be used. If the stub function(s) below are
|
||||||
|
// actually invoked they will display an error message and cause the
|
||||||
|
// calling process to exit.
|
||||||
|
//
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TCGETS
|
||||||
|
cmdTcSet = unix.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
panic("ptsname() support requires cgo.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
panic("unlockpt() support requires cgo.")
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
// +build darwin freebsd linux openbsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *unix.Termios) error {
|
||||||
|
termios, err := unix.IoctlGetTermios(int(fd), cmdTcGet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = *termios
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *unix.Termios) error {
|
||||||
|
return unix.IoctlSetTermios(int(fd), cmdTcSet, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcgwinsz(fd uintptr) (WinSize, error) {
|
||||||
|
var ws WinSize
|
||||||
|
|
||||||
|
uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate from unix.Winsize to console.WinSize
|
||||||
|
ws.Height = uws.Row
|
||||||
|
ws.Width = uws.Col
|
||||||
|
ws.x = uws.Xpixel
|
||||||
|
ws.y = uws.Ypixel
|
||||||
|
return ws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcswinsz(fd uintptr, ws WinSize) error {
|
||||||
|
// Translate from console.WinSize to unix.Winsize
|
||||||
|
|
||||||
|
var uws unix.Winsize
|
||||||
|
uws.Row = ws.Height
|
||||||
|
uws.Col = ws.Width
|
||||||
|
uws.Xpixel = ws.x
|
||||||
|
uws.Ypixel = ws.y
|
||||||
|
|
||||||
|
return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, &uws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setONLCR(fd uintptr, enable bool) error {
|
||||||
|
var termios unix.Termios
|
||||||
|
if err := tcget(fd, &termios); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if enable {
|
||||||
|
// Set +onlcr so we can act like a real terminal
|
||||||
|
termios.Oflag |= unix.ONLCR
|
||||||
|
} else {
|
||||||
|
// Set -onlcr so we don't have to deal with \r.
|
||||||
|
termios.Oflag &^= unix.ONLCR
|
||||||
|
}
|
||||||
|
return tcset(fd, &termios)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cfmakeraw(t unix.Termios) unix.Termios {
|
||||||
|
t.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
|
||||||
|
t.Oflag &^= unix.OPOST
|
||||||
|
t.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)
|
||||||
|
t.Cflag &^= (unix.CSIZE | unix.PARENB)
|
||||||
|
t.Cflag &^= unix.CS8
|
||||||
|
t.Cc[unix.VMIN] = 1
|
||||||
|
t.Cc[unix.VTIME] = 0
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ clone_folder: c:\gopath\src\github.com\containerd\containerd
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- release/1.2
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
GOPATH: C:\gopath
|
GOPATH: C:\gopath
|
||||||
|
|
|
@ -25,6 +25,3 @@ Jian Liao <jliao@alauda.io> liaoj <jliao@alauda.io>
|
||||||
Jian Liao <jliao@alauda.io> liaojian <liaojian@Dabllo.local>
|
Jian Liao <jliao@alauda.io> liaojian <liaojian@Dabllo.local>
|
||||||
Rui Cao <ruicao@alauda.io> ruicao <ruicao@alauda.io>
|
Rui Cao <ruicao@alauda.io> ruicao <ruicao@alauda.io>
|
||||||
Xuean Yan <yan.xuean@zte.com.cn> yanxuean <yan.xuean@zte.com.cn>
|
Xuean Yan <yan.xuean@zte.com.cn> yanxuean <yan.xuean@zte.com.cn>
|
||||||
Mike Brown <brownwm@us.ibm.com> Mike Brown <mikebrow@users.noreply.github.com>
|
|
||||||
Ace-Tang <aceapril@126.com>
|
|
||||||
Wei Fu <fuweid89@gmail.com>
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
dist: trusty
|
dist: xenial
|
||||||
sudo: required
|
sudo: required
|
||||||
# setup travis so that we can run containers for integration tests
|
# setup travis so that we can run containers for integration tests
|
||||||
services:
|
services:
|
||||||
|
@ -25,16 +25,16 @@ addons:
|
||||||
- libprotobuf-c0-dev
|
- libprotobuf-c0-dev
|
||||||
- libprotobuf-dev
|
- libprotobuf-dev
|
||||||
- socat
|
- socat
|
||||||
|
- libseccomp-dev
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- TRAVIS_GOOS=linux TEST_RUNTIME=io.containerd.runc.v1 TRAVIS_CGO_ENABLED=1
|
- TRAVIS_GOOS=linux TEST_RUNTIME=io.containerd.runc.v1 TRAVIS_CGO_ENABLED=1
|
||||||
|
- TRAVIS_GOOS=linux TEST_RUNTIME=io.containerd.runc.v2 TRAVIS_CGO_ENABLED=1
|
||||||
- TRAVIS_GOOS=linux TEST_RUNTIME=io.containerd.runtime.v1.linux TRAVIS_CGO_ENABLED=1
|
- TRAVIS_GOOS=linux TEST_RUNTIME=io.containerd.runtime.v1.linux TRAVIS_CGO_ENABLED=1
|
||||||
- TRAVIS_GOOS=darwin TRAVIS_CGO_ENABLED=0
|
- TRAVIS_GOOS=darwin TRAVIS_CGO_ENABLED=0
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- uname -r
|
- uname -r
|
||||||
- sudo apt-get -q update
|
|
||||||
- sudo apt-get install -y libseccomp-dev/trusty-backports
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sudo PATH=$PATH GOPATH=$GOPATH script/setup/install-protobuf
|
- sudo PATH=$PATH GOPATH=$GOPATH script/setup/install-protobuf
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
## containerd Adopters
|
||||||
|
|
||||||
|
A non-exhaustive list of containerd adopters is provided below.
|
||||||
|
|
||||||
|
**_Docker/Moby engine_** - Containerd began life prior to its CNCF adoption as a lower-layer
|
||||||
|
runtime manager for `runc` processes below the Docker engine. Continuing today, containerd
|
||||||
|
has extremely broad production usage as a component of the [Docker engine](https://github.com/docker/docker-ce)
|
||||||
|
stack. Note that this includes any use of the open source [Moby engine project](https://github.com/moby/moby);
|
||||||
|
including the Balena project listed below.
|
||||||
|
|
||||||
|
**_[IBM Cloud Kubernetes Service (IKS)](https://www.ibm.com/cloud/container-service)_** - offers containerd as the CRI runtime for v1.11 and higher versions.
|
||||||
|
|
||||||
|
**_[IBM Cloud Private (ICP)](https://www.ibm.com/cloud/private)_** - IBM's on-premises cloud offering has containerd as a "tech preview" CRI runtime for the Kubernetes offered within this product for the past two releases, and plans to fully migrate to containerd in a future release.
|
||||||
|
|
||||||
|
**_[Google Cloud Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine/)_** - offers containerd as the CRI runtime in **beta** for recent versions of Kubernetes.
|
||||||
|
|
||||||
|
**_Cloud Foundry_** - The [Guardian container manager](https://github.com/cloudfoundry/guardian) for CF has been using OCI runC directly with additional code from CF managing the container image and filesystem interactions, but have recently migrated to use containerd as a replacement for the extra code they had written around runC.
|
||||||
|
|
||||||
|
**_Alibaba's PouchContainer_** - The Alibaba [PouchContainer](https://github.com/alibaba/pouch) project uses containerd as its runtime for a cloud native offering that has unique isolation and image distribution capabilities.
|
||||||
|
|
||||||
|
**_Rancher's Rio project_** - Rancher Labs [Rio](https://github.com/rancher/rio) project uses containerd as the runtime for a combined Kubernetes, Istio, and container "Cloud Native Container Distribution" platform.
|
||||||
|
|
||||||
|
**_Eliot_** - The [Eliot](https://github.com/ernoaapa/eliot) container project for IoT device container management uses containerd as the runtime.
|
||||||
|
|
||||||
|
**_Balena_** - Resin's [Balena](https://github.com/resin-os/balena) container engine, based on moby/moby but for edge, embedded, and IoT use cases, uses the containerd and runc stack in the same way that the Docker engine uses containerd.
|
||||||
|
|
||||||
|
**_LinuxKit_** - the Moby project's [LinuxKit](https://github.com/linuxkit/linuxkit) for building secure, minimal Linux OS images in a container-native model uses containerd as the core runtime for system and service containers.
|
||||||
|
|
||||||
|
**_BuildKit_** - The Moby project's [BuildKit](https://github.com/moby/buildkit) can use either runC or containerd as build execution backends for building container images. BuildKit support has also been built into the Docker engine in recent releases, making BuildKit provide the backend to the `docker build` command.
|
||||||
|
|
||||||
|
**_Azure acs-engine_** - Microsoft Azure's [acs-engine](https://github.com/Azure/acs-engine) open source project has customizable deployment of Kubernetes clusters, where containerd is a selectable container runtime. At some point in the future Azure's AKS service will default to use containerd as the CRI runtime for deployed Kubernetes clusters.
|
||||||
|
|
||||||
|
**_Amazon Firecracker_** - The AWS [Firecracker VMM project](http://firecracker-microvm.io/) has extended containerd with a new snapshotter and v2 shim to allow containerd to drive virtualized container processes via their VMM implementation. More details on their containerd integration are available in [their GitHub project](https://github.com/firecracker-microvm/firecracker-containerd).
|
||||||
|
|
||||||
|
**_Kata Containers_** - The [Kata containers](https://katacontainers.io/) lightweight-virtualized container runtime project integrates with containerd via a custom v2 shim implementation that drives the Kata container runtime.
|
||||||
|
|
||||||
|
**_Other Projects_** - While the above list provides a cross-section of well known uses of containerd, the simplicity and clear API layer for containerd has inspired many smaller projects around providing simple container management platforms. Several examples of building higher layer functionality on top of the containerd base have come from various containerd community participants:
|
||||||
|
- Michael Crosby's [boss](https://github.com/crosbymichael/boss) project,
|
||||||
|
- Evan Hazlett's [stellar](https://github.com/ehazlett/stellar) project,
|
||||||
|
- Paul Knopf's immutable Linux image builder project: [darch](https://github.com/godarch/darch).
|
|
@ -46,6 +46,7 @@ need to satisfy this dependencies in your system:
|
||||||
|
|
||||||
If you're building with seccomp, you'll need to install it with the following:
|
If you're building with seccomp, you'll need to install it with the following:
|
||||||
|
|
||||||
|
* CentOS/Fedora: `yum install libseccomp-devel`
|
||||||
* Debian/Ubuntu: `apt install libseccomp-dev`
|
* Debian/Ubuntu: `apt install libseccomp-dev`
|
||||||
|
|
||||||
At this point you are ready to build `containerd` yourself!
|
At this point you are ready to build `containerd` yourself!
|
||||||
|
@ -79,7 +80,8 @@ result in undefined behavior.
|
||||||
`containerd` uses `make` to create a repeatable build flow. It means that you
|
`containerd` uses `make` to create a repeatable build flow. It means that you
|
||||||
can run:
|
can run:
|
||||||
|
|
||||||
```sudo
|
```
|
||||||
|
cd $GOPATH/src/github.com/containerd/containerd
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -218,8 +220,7 @@ containerd --config config.toml
|
||||||
During the automated CI the unit tests and integration tests are run as part of the PR validation. As a developer you can run these tests locally by using any of the following `Makefile` targets:
|
During the automated CI the unit tests and integration tests are run as part of the PR validation. As a developer you can run these tests locally by using any of the following `Makefile` targets:
|
||||||
- `make test`: run all non-integration tests that do not require `root` privileges
|
- `make test`: run all non-integration tests that do not require `root` privileges
|
||||||
- `make root-test`: run all non-integration tests which require `root`
|
- `make root-test`: run all non-integration tests which require `root`
|
||||||
- `make integration`: run all tests, including integration tests and those which require `root`
|
- `make integration`: run all tests, including integration tests and those which require `root`. `TESTFLAGS_PARALLEL` can be used to control parallelism. For example, `TESTFLAGS_PARALLEL=1 make integration` will lead a non-parallel execution. The default value of `TESTFLAGS_PARALLEL` is **8**.
|
||||||
- `make integration-parallel`: run all tests (integration and root-required included) in parallel mode
|
|
||||||
|
|
||||||
To execute a specific test or set of tests you can use the `go test` capabilities
|
To execute a specific test or set of tests you can use the `go test` capabilities
|
||||||
without using the `Makefile` targets. The following examples show how to specify a test
|
without using the `Makefile` targets. The following examples show how to specify a test
|
||||||
|
|
|
@ -22,6 +22,7 @@ DESTDIR=/usr/local
|
||||||
# Used to populate variables in version package.
|
# Used to populate variables in version package.
|
||||||
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
|
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
|
||||||
REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
|
REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
|
||||||
|
PACKAGE=github.com/containerd/containerd
|
||||||
|
|
||||||
ifneq "$(strip $(shell command -v go 2>/dev/null))" ""
|
ifneq "$(strip $(shell command -v go 2>/dev/null))" ""
|
||||||
GOOS ?= $(shell go env GOOS)
|
GOOS ?= $(shell go env GOOS)
|
||||||
|
@ -50,6 +51,15 @@ else
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifndef GODEBUG
|
||||||
|
EXTRA_LDFLAGS += -s -w
|
||||||
|
DEBUG_GO_GCFLAGS :=
|
||||||
|
DEBUG_TAGS :=
|
||||||
|
else
|
||||||
|
DEBUG_GO_GCFLAGS := -gcflags=all="-N -l"
|
||||||
|
DEBUG_TAGS := static_build
|
||||||
|
endif
|
||||||
|
|
||||||
WHALE = "🇩"
|
WHALE = "🇩"
|
||||||
ONI = "👹"
|
ONI = "👹"
|
||||||
|
|
||||||
|
@ -74,11 +84,15 @@ TEST_REQUIRES_ROOT_PACKAGES=$(filter \
|
||||||
COMMANDS=ctr containerd containerd-stress
|
COMMANDS=ctr containerd containerd-stress
|
||||||
MANPAGES=ctr.1 containerd.1 containerd-config.1 containerd-config.toml.5
|
MANPAGES=ctr.1 containerd.1 containerd-config.1 containerd-config.toml.5
|
||||||
|
|
||||||
|
ifdef BUILDTAGS
|
||||||
|
GO_BUILDTAGS = ${BUILDTAGS}
|
||||||
|
endif
|
||||||
# Build tags seccomp and apparmor are needed by CRI plugin.
|
# Build tags seccomp and apparmor are needed by CRI plugin.
|
||||||
BUILDTAGS ?= seccomp apparmor
|
GO_BUILDTAGS ?= seccomp apparmor
|
||||||
GO_TAGS=$(if $(BUILDTAGS),-tags "$(BUILDTAGS)",)
|
GO_BUILDTAGS += ${DEBUG_TAGS}
|
||||||
GO_LDFLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PKG) $(EXTRA_LDFLAGS)'
|
GO_TAGS=$(if $(GO_BUILDTAGS),-tags "$(GO_BUILDTAGS)",)
|
||||||
SHIM_GO_LDFLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PKG) -extldflags "-static"'
|
GO_LDFLAGS=-ldflags '-X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PACKAGE) $(EXTRA_LDFLAGS)'
|
||||||
|
SHIM_GO_LDFLAGS=-ldflags '-X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PACKAGE) -extldflags "-static" $(EXTRA_LDFLAGS)'
|
||||||
|
|
||||||
#Replaces ":" (*nix), ";" (windows) with newline for easy parsing
|
#Replaces ":" (*nix), ";" (windows) with newline for easy parsing
|
||||||
GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n")
|
GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n")
|
||||||
|
@ -143,7 +157,7 @@ proto-fmt: ## check format of proto files
|
||||||
|
|
||||||
build: ## build the go packages
|
build: ## build the go packages
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
@go build ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} ${GO_LDFLAGS} ${PACKAGES}
|
@go build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} ${GO_LDFLAGS} ${PACKAGES}
|
||||||
|
|
||||||
test: ## run tests, except integration tests and tests that require root
|
test: ## run tests, except integration tests and tests that require root
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
|
@ -166,7 +180,7 @@ FORCE:
|
||||||
# Build a binary from a cmd.
|
# Build a binary from a cmd.
|
||||||
bin/%: cmd/% FORCE
|
bin/%: cmd/% FORCE
|
||||||
@echo "$(WHALE) $@${BINARY_SUFFIX}"
|
@echo "$(WHALE) $@${BINARY_SUFFIX}"
|
||||||
@go build ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@${BINARY_SUFFIX} ${GO_LDFLAGS} ${GO_TAGS} ./$<
|
@go build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@${BINARY_SUFFIX} ${GO_LDFLAGS} ${GO_TAGS} ./$<
|
||||||
|
|
||||||
bin/containerd-shim: cmd/containerd-shim FORCE # set !cgo and omit pie for a static shim build: https://github.com/golang/go/issues/17789#issuecomment-258542220
|
bin/containerd-shim: cmd/containerd-shim FORCE # set !cgo and omit pie for a static shim build: https://github.com/golang/go/issues/17789#issuecomment-258542220
|
||||||
@echo "$(WHALE) bin/containerd-shim"
|
@echo "$(WHALE) bin/containerd-shim"
|
||||||
|
@ -176,6 +190,10 @@ bin/containerd-shim-runc-v1: cmd/containerd-shim-runc-v1 FORCE # set !cgo and om
|
||||||
@echo "$(WHALE) bin/containerd-shim-runc-v1"
|
@echo "$(WHALE) bin/containerd-shim-runc-v1"
|
||||||
@CGO_ENABLED=0 go build ${GO_BUILD_FLAGS} -o bin/containerd-shim-runc-v1 ${SHIM_GO_LDFLAGS} ${GO_TAGS} ./cmd/containerd-shim-runc-v1
|
@CGO_ENABLED=0 go build ${GO_BUILD_FLAGS} -o bin/containerd-shim-runc-v1 ${SHIM_GO_LDFLAGS} ${GO_TAGS} ./cmd/containerd-shim-runc-v1
|
||||||
|
|
||||||
|
bin/containerd-shim-runc-v2: cmd/containerd-shim-runc-v2 FORCE # set !cgo and omit pie for a static shim build: https://github.com/golang/go/issues/17789#issuecomment-258542220
|
||||||
|
@echo "$(WHALE) bin/containerd-shim-runc-v2"
|
||||||
|
@CGO_ENABLED=0 go build ${GO_BUILD_FLAGS} -o bin/containerd-shim-runc-v2 ${SHIM_GO_LDFLAGS} ${GO_TAGS} ./cmd/containerd-shim-runc-v2
|
||||||
|
|
||||||
bin/containerd-shim-runhcs-v1: cmd/containerd-shim-runhcs-v1 FORCE # set !cgo and omit pie for a static shim build: https://github.com/golang/go/issues/17789#issuecomment-258542220
|
bin/containerd-shim-runhcs-v1: cmd/containerd-shim-runhcs-v1 FORCE # set !cgo and omit pie for a static shim build: https://github.com/golang/go/issues/17789#issuecomment-258542220
|
||||||
@echo "$(WHALE) bin/containerd-shim-runhcs-v1${BINARY_SUFFIX}"
|
@echo "$(WHALE) bin/containerd-shim-runhcs-v1${BINARY_SUFFIX}"
|
||||||
@CGO_ENABLED=0 go build ${GO_BUILD_FLAGS} -o bin/containerd-shim-runhcs-v1${BINARY_SUFFIX} ${SHIM_GO_LDFLAGS} ${GO_TAGS} ./cmd/containerd-shim-runhcs-v1
|
@CGO_ENABLED=0 go build ${GO_BUILD_FLAGS} -o bin/containerd-shim-runhcs-v1${BINARY_SUFFIX} ${SHIM_GO_LDFLAGS} ${GO_TAGS} ./cmd/containerd-shim-runhcs-v1
|
||||||
|
|
|
@ -14,7 +14,9 @@
|
||||||
|
|
||||||
|
|
||||||
#linux specific settings
|
#linux specific settings
|
||||||
COMMANDS += containerd-shim containerd-shim-runc-v1
|
WHALE="+"
|
||||||
|
ONI="-"
|
||||||
|
COMMANDS += containerd-shim containerd-shim-runc-v1 containerd-shim-runc-v2
|
||||||
|
|
||||||
# check GOOS for cross compile builds
|
# check GOOS for cross compile builds
|
||||||
ifeq ($(GOOS),linux)
|
ifeq ($(GOOS),linux)
|
||||||
|
|
|
@ -24,10 +24,3 @@ BINARY_SUFFIX=".exe"
|
||||||
ifeq ($(GOARCH),amd64)
|
ifeq ($(GOARCH),amd64)
|
||||||
TESTFLAGS_RACE= -race
|
TESTFLAGS_RACE= -race
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# add support for building the Windows v2 runtime
|
|
||||||
# based on the containerd-shim-runhcs-v1 shim rather
|
|
||||||
# than the existing runtime on hcsshim
|
|
||||||
ifeq (${BUILD_WINDOWS_V2},1)
|
|
||||||
BUILDTAGS += windows_v2
|
|
||||||
endif
|
|
||||||
|
|
|
@ -172,11 +172,9 @@ checkpoint, err := task.Checkpoint(context)
|
||||||
err := client.Push(context, "myregistry/checkpoints/redis:master", checkpoint)
|
err := client.Push(context, "myregistry/checkpoints/redis:master", checkpoint)
|
||||||
|
|
||||||
// on a new machine pull the checkpoint and restore the redis container
|
// on a new machine pull the checkpoint and restore the redis container
|
||||||
image, err := client.Pull(context, "myregistry/checkpoints/redis:master")
|
checkpoint, err := client.Pull(context, "myregistry/checkpoints/redis:master")
|
||||||
|
|
||||||
checkpoint := image.Target()
|
redis, err = client.NewContainer(context, "redis-master", containerd.WithNewSnapshot("redis-rootfs", checkpoint))
|
||||||
|
|
||||||
redis, err = client.NewContainer(context, "redis-master", containerd.WithCheckpoint(checkpoint, "redis-rootfs"))
|
|
||||||
defer container.Delete(context)
|
defer container.Delete(context)
|
||||||
|
|
||||||
task, err = redis.NewTask(context, cio.Stdio, containerd.WithTaskCheckpoint(checkpoint))
|
task, err = redis.NewTask(context, cio.Stdio, containerd.WithTaskCheckpoint(checkpoint))
|
||||||
|
@ -212,11 +210,6 @@ See [PLUGINS.md](PLUGINS.md) for how to create plugins
|
||||||
Please see [RELEASES.md](RELEASES.md) for details on versioning and stability
|
Please see [RELEASES.md](RELEASES.md) for details on versioning and stability
|
||||||
of containerd components.
|
of containerd components.
|
||||||
|
|
||||||
### Development reports.
|
|
||||||
|
|
||||||
Weekly summary on the progress and what is being worked on.
|
|
||||||
https://github.com/containerd/containerd/tree/master/reports
|
|
||||||
|
|
||||||
### Communication
|
### Communication
|
||||||
|
|
||||||
For async communication and long running discussions please use issues and pull requests on the github repo.
|
For async communication and long running discussions please use issues and pull requests on the github repo.
|
||||||
|
@ -224,7 +217,12 @@ This will be the best place to discuss design and implementation.
|
||||||
|
|
||||||
For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development.
|
For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development.
|
||||||
|
|
||||||
**Slack:** https://join.slack.com/t/dockercommunity/shared_invite/enQtNDM4NjAwNDMyOTUwLWZlMDZmYWRjZjk4Zjc5ZGQ5NWZkOWI1Yjk2NGE3ZWVlYjYxM2VhYjczOWIyZDFhZTE3NTUwZWQzMjhmNGYyZTg
|
**Slack:** Catch us in the #containerd and #containerd-dev channels on dockercommunity.slack.com.
|
||||||
|
[Click here for an invite to docker community slack.](https://join.slack.com/t/dockercommunity/shared_invite/enQtNDY4MDc1Mzc0MzIwLTgxZDBlMmM4ZGEyNDc1N2FkMzlhODJkYmE1YTVkYjM1MDE3ZjAwZjBkOGFlOTJkZjRmZGYzNjYyY2M3ZTUxYzQ)
|
||||||
|
|
||||||
|
### Security audit
|
||||||
|
|
||||||
|
A third party security audit was performed by Cure53 in 4Q2018; the [full report](docs/SECURITY_AUDIT.pdf) is available in our docs/ directory.
|
||||||
|
|
||||||
### Reporting security issues
|
### Reporting security issues
|
||||||
|
|
||||||
|
@ -249,3 +247,8 @@ Please find all these core project documents, including the:
|
||||||
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
||||||
|
|
||||||
|
## Adoption
|
||||||
|
|
||||||
|
Interested to see who is using containerd? Are you using containerd in a project?
|
||||||
|
Please add yourself via pull request to our [ADOPTERS.md](./ADOPTERS.md) file.
|
||||||
|
|
|
@ -93,9 +93,9 @@ The current state is available in the following table:
|
||||||
| [0.0](https://github.com/containerd/containerd/releases/tag/0.0.5) | End of Life | Dec 4, 2015 | - |
|
| [0.0](https://github.com/containerd/containerd/releases/tag/0.0.5) | End of Life | Dec 4, 2015 | - |
|
||||||
| [0.1](https://github.com/containerd/containerd/releases/tag/v0.1.0) | End of Life | Mar 21, 2016 | - |
|
| [0.1](https://github.com/containerd/containerd/releases/tag/v0.1.0) | End of Life | Mar 21, 2016 | - |
|
||||||
| [0.2](https://github.com/containerd/containerd/tree/v0.2.x) | End of Life | Apr 21, 2016 | December 5, 2017 |
|
| [0.2](https://github.com/containerd/containerd/tree/v0.2.x) | End of Life | Apr 21, 2016 | December 5, 2017 |
|
||||||
| [1.0](https://github.com/containerd/containerd/releases/tag/v1.0.0) | Active | December 5, 2017 | December 5, 2018 |
|
| [1.0](https://github.com/containerd/containerd/releases/tag/v1.0.3) | End of Life | December 5, 2017 | December 5, 2018 |
|
||||||
| [1.1](https://github.com/containerd/containerd/releases/tag/v1.1.0) | Active | April 23, 2018 | max(April 23, 2019, release of 1.2.0, Kubernetes 1.10 EOL) |
|
| [1.1](https://github.com/containerd/containerd/releases/tag/v1.1.6) | Active | April 23, 2018 | max(April 23, 2019, Kubernetes 1.10 EOL) |
|
||||||
| [1.2](https://github.com/containerd/containerd/releases/tag/v1.2.0) | Active | October 24, 2018 | max(October 24, 2019, release of 1.3.0) |
|
| [1.2](https://github.com/containerd/containerd/releases/tag/v1.2.4) | Active | October 24, 2018 | max(October 24, 2019, release of 1.3.0) |
|
||||||
| [1.3](https://github.com/containerd/containerd/milestone/20) | Next | TBD | max(TBD+1 year, release of 1.4.0) |
|
| [1.3](https://github.com/containerd/containerd/milestone/20) | Next | TBD | max(TBD+1 year, release of 1.4.0) |
|
||||||
|
|
||||||
Note that branches and release from before 1.0 may not follow these rules.
|
Note that branches and release from before 1.0 may not follow these rules.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build freebsd linux openbsd solaris
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd/defaults"
|
"github.com/containerd/containerd/defaults"
|
||||||
|
@ -222,46 +222,76 @@ type DirectIO struct {
|
||||||
cio
|
cio
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ IO = &DirectIO{}
|
var (
|
||||||
|
_ IO = &DirectIO{}
|
||||||
|
_ IO = &logURI{}
|
||||||
|
)
|
||||||
|
|
||||||
// LogFile creates a file on disk that logs the task's STDOUT,STDERR.
|
// LogURI provides the raw logging URI
|
||||||
// If the log file already exists, the logs will be appended to the file.
|
func LogURI(uri *url.URL) Creator {
|
||||||
func LogFile(path string) Creator {
|
|
||||||
return func(_ string) (IO, error) {
|
return func(_ string) (IO, error) {
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
return &logURI{
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
return &logIO{
|
|
||||||
config: Config{
|
config: Config{
|
||||||
Stdout: path,
|
Stdout: uri.String(),
|
||||||
Stderr: path,
|
Stderr: uri.String(),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type logIO struct {
|
// BinaryIO forwards container STDOUT|STDERR directly to a logging binary
|
||||||
|
func BinaryIO(binary string, args map[string]string) Creator {
|
||||||
|
return func(_ string) (IO, error) {
|
||||||
|
uri := &url.URL{
|
||||||
|
Scheme: "binary",
|
||||||
|
Host: binary,
|
||||||
|
}
|
||||||
|
for k, v := range args {
|
||||||
|
uri.Query().Set(k, v)
|
||||||
|
}
|
||||||
|
return &logURI{
|
||||||
|
config: Config{
|
||||||
|
Stdout: uri.String(),
|
||||||
|
Stderr: uri.String(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogFile creates a file on disk that logs the task's STDOUT,STDERR.
|
||||||
|
// If the log file already exists, the logs will be appended to the file.
|
||||||
|
func LogFile(path string) Creator {
|
||||||
|
return func(_ string) (IO, error) {
|
||||||
|
uri := &url.URL{
|
||||||
|
Scheme: "file",
|
||||||
|
Host: path,
|
||||||
|
}
|
||||||
|
return &logURI{
|
||||||
|
config: Config{
|
||||||
|
Stdout: uri.String(),
|
||||||
|
Stderr: uri.String(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logURI struct {
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logIO) Config() Config {
|
func (l *logURI) Config() Config {
|
||||||
return l.config
|
return l.config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logIO) Cancel() {
|
func (l *logURI) Cancel() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logIO) Wait() {
|
func (l *logURI) Wait() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logIO) Close() error {
|
func (l *logURI) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,11 +31,15 @@ const pipeRoot = `\\.\pipe`
|
||||||
|
|
||||||
// NewFIFOSetInDir returns a new set of fifos for the task
|
// NewFIFOSetInDir returns a new set of fifos for the task
|
||||||
func NewFIFOSetInDir(_, id string, terminal bool) (*FIFOSet, error) {
|
func NewFIFOSetInDir(_, id string, terminal bool) (*FIFOSet, error) {
|
||||||
|
stderrPipe := ""
|
||||||
|
if !terminal {
|
||||||
|
stderrPipe = fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id)
|
||||||
|
}
|
||||||
return NewFIFOSet(Config{
|
return NewFIFOSet(Config{
|
||||||
Terminal: terminal,
|
Terminal: terminal,
|
||||||
Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id),
|
Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id),
|
||||||
Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id),
|
Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id),
|
||||||
Stderr: fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id),
|
Stderr: stderrPipe,
|
||||||
}, nil), nil
|
}, nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,14 @@
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -40,7 +43,6 @@ import (
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
contentproxy "github.com/containerd/containerd/content/proxy"
|
contentproxy "github.com/containerd/containerd/content/proxy"
|
||||||
"github.com/containerd/containerd/defaults"
|
"github.com/containerd/containerd/defaults"
|
||||||
"github.com/containerd/containerd/errdefs"
|
|
||||||
"github.com/containerd/containerd/events"
|
"github.com/containerd/containerd/events"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/leases"
|
"github.com/containerd/containerd/leases"
|
||||||
|
@ -51,7 +53,6 @@ import (
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
"github.com/containerd/containerd/remotes/docker"
|
"github.com/containerd/containerd/remotes/docker"
|
||||||
"github.com/containerd/containerd/remotes/docker/schema1"
|
|
||||||
"github.com/containerd/containerd/snapshots"
|
"github.com/containerd/containerd/snapshots"
|
||||||
snproxy "github.com/containerd/containerd/snapshots/proxy"
|
snproxy "github.com/containerd/containerd/snapshots/proxy"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
|
@ -280,6 +281,12 @@ type RemoteContext struct {
|
||||||
// handlers.
|
// handlers.
|
||||||
BaseHandlers []images.Handler
|
BaseHandlers []images.Handler
|
||||||
|
|
||||||
|
// HandlerWrapper wraps the handler which gets sent to dispatch.
|
||||||
|
// Unlike BaseHandlers, this can run before and after the built
|
||||||
|
// in handlers, allowing operations to run on the descriptor
|
||||||
|
// after it has completed transferring.
|
||||||
|
HandlerWrapper func(images.Handler) images.Handler
|
||||||
|
|
||||||
// ConvertSchema1 is whether to convert Docker registry schema 1
|
// ConvertSchema1 is whether to convert Docker registry schema 1
|
||||||
// manifests. If this option is false then any image which resolves
|
// manifests. If this option is false then any image which resolves
|
||||||
// to schema 1 will return an error since schema 1 is not supported.
|
// to schema 1 will return an error since schema 1 is not supported.
|
||||||
|
@ -290,6 +297,13 @@ type RemoteContext struct {
|
||||||
// platforms will be used to create a PlatformMatcher with no ordering
|
// platforms will be used to create a PlatformMatcher with no ordering
|
||||||
// preference.
|
// preference.
|
||||||
Platforms []string
|
Platforms []string
|
||||||
|
|
||||||
|
// MaxConcurrentDownloads is the max concurrent content downloads for each pull.
|
||||||
|
MaxConcurrentDownloads int
|
||||||
|
|
||||||
|
// AppendDistributionSourceLabel allows fetcher to add distribute source
|
||||||
|
// label for each blob content, which doesn't work for legacy schema1.
|
||||||
|
AppendDistributionSourceLabel bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultRemoteContext() *RemoteContext {
|
func defaultRemoteContext() *RemoteContext {
|
||||||
|
@ -341,157 +355,6 @@ func (c *Client) Fetch(ctx context.Context, ref string, opts ...RemoteOpt) (imag
|
||||||
return c.fetch(ctx, fetchCtx, ref, 0)
|
return c.fetch(ctx, fetchCtx, ref, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull downloads the provided content into containerd's content store
|
|
||||||
// and returns a platform specific image object
|
|
||||||
func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image, error) {
|
|
||||||
pullCtx := defaultRemoteContext()
|
|
||||||
for _, o := range opts {
|
|
||||||
if err := o(c, pullCtx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pullCtx.PlatformMatcher == nil {
|
|
||||||
if len(pullCtx.Platforms) > 1 {
|
|
||||||
return nil, errors.New("cannot pull multiplatform image locally, try Fetch")
|
|
||||||
} else if len(pullCtx.Platforms) == 0 {
|
|
||||||
pullCtx.PlatformMatcher = platforms.Default()
|
|
||||||
} else {
|
|
||||||
p, err := platforms.Parse(pullCtx.Platforms[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "invalid platform %s", pullCtx.Platforms[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
pullCtx.PlatformMatcher = platforms.Only(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, done, err := c.WithLease(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer done(ctx)
|
|
||||||
|
|
||||||
img, err := c.fetch(ctx, pullCtx, ref, 1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
i := NewImageWithPlatform(c, img, pullCtx.PlatformMatcher)
|
|
||||||
|
|
||||||
if pullCtx.Unpack {
|
|
||||||
if err := i.Unpack(ctx, pullCtx.Snapshotter); err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to unpack image on snapshotter %s", pullCtx.Snapshotter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string, limit int) (images.Image, error) {
|
|
||||||
store := c.ContentStore()
|
|
||||||
name, desc, err := rCtx.Resolver.Resolve(ctx, ref)
|
|
||||||
if err != nil {
|
|
||||||
return images.Image{}, errors.Wrapf(err, "failed to resolve reference %q", ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher, err := rCtx.Resolver.Fetcher(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return images.Image{}, errors.Wrapf(err, "failed to get fetcher for %q", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
handler images.Handler
|
|
||||||
|
|
||||||
isConvertible bool
|
|
||||||
converterFunc func(context.Context, ocispec.Descriptor) (ocispec.Descriptor, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
if desc.MediaType == images.MediaTypeDockerSchema1Manifest && rCtx.ConvertSchema1 {
|
|
||||||
schema1Converter := schema1.NewConverter(store, fetcher)
|
|
||||||
|
|
||||||
handler = images.Handlers(append(rCtx.BaseHandlers, schema1Converter)...)
|
|
||||||
|
|
||||||
isConvertible = true
|
|
||||||
|
|
||||||
converterFunc = func(ctx context.Context, _ ocispec.Descriptor) (ocispec.Descriptor, error) {
|
|
||||||
return schema1Converter.Convert(ctx)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Get all the children for a descriptor
|
|
||||||
childrenHandler := images.ChildrenHandler(store)
|
|
||||||
// Set any children labels for that content
|
|
||||||
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
|
|
||||||
// Filter children by platforms
|
|
||||||
childrenHandler = images.FilterPlatforms(childrenHandler, rCtx.PlatformMatcher)
|
|
||||||
// Sort and limit manifests if a finite number is needed
|
|
||||||
if limit > 0 {
|
|
||||||
childrenHandler = images.LimitManifests(childrenHandler, rCtx.PlatformMatcher, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set isConvertible to true if there is application/octet-stream media type
|
|
||||||
convertibleHandler := images.HandlerFunc(
|
|
||||||
func(_ context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
||||||
if desc.MediaType == docker.LegacyConfigMediaType {
|
|
||||||
isConvertible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return []ocispec.Descriptor{}, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
handler = images.Handlers(append(rCtx.BaseHandlers,
|
|
||||||
remotes.FetchHandler(store, fetcher),
|
|
||||||
convertibleHandler,
|
|
||||||
childrenHandler,
|
|
||||||
)...)
|
|
||||||
|
|
||||||
converterFunc = func(ctx context.Context, desc ocispec.Descriptor) (ocispec.Descriptor, error) {
|
|
||||||
return docker.ConvertManifest(ctx, store, desc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := images.Dispatch(ctx, handler, desc); err != nil {
|
|
||||||
return images.Image{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if isConvertible {
|
|
||||||
if desc, err = converterFunc(ctx, desc); err != nil {
|
|
||||||
return images.Image{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img := images.Image{
|
|
||||||
Name: name,
|
|
||||||
Target: desc,
|
|
||||||
Labels: rCtx.Labels,
|
|
||||||
}
|
|
||||||
|
|
||||||
is := c.ImageService()
|
|
||||||
for {
|
|
||||||
if created, err := is.Create(ctx, img); err != nil {
|
|
||||||
if !errdefs.IsAlreadyExists(err) {
|
|
||||||
return images.Image{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updated, err := is.Update(ctx, img)
|
|
||||||
if err != nil {
|
|
||||||
// if image was removed, try create again
|
|
||||||
if errdefs.IsNotFound(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return images.Image{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
img = updated
|
|
||||||
} else {
|
|
||||||
img = created
|
|
||||||
}
|
|
||||||
|
|
||||||
return img, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push uploads the provided content to a remote resource
|
// Push uploads the provided content to a remote resource
|
||||||
func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor, opts ...RemoteOpt) error {
|
func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor, opts ...RemoteOpt) error {
|
||||||
pushCtx := defaultRemoteContext()
|
pushCtx := defaultRemoteContext()
|
||||||
|
@ -521,7 +384,21 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.PlatformMatcher, pushCtx.BaseHandlers...)
|
var wrapper func(images.Handler) images.Handler
|
||||||
|
|
||||||
|
if len(pushCtx.BaseHandlers) > 0 {
|
||||||
|
wrapper = func(h images.Handler) images.Handler {
|
||||||
|
h = images.Handlers(append(pushCtx.BaseHandlers, h)...)
|
||||||
|
if pushCtx.HandlerWrapper != nil {
|
||||||
|
h = pushCtx.HandlerWrapper(h)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
} else if pushCtx.HandlerWrapper != nil {
|
||||||
|
wrapper = pushCtx.HandlerWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.PlatformMatcher, wrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetImage returns an existing image
|
// GetImage returns an existing image
|
||||||
|
@ -546,6 +423,45 @@ func (c *Client) ListImages(ctx context.Context, filters ...string) ([]Image, er
|
||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore restores a container from a checkpoint
|
||||||
|
func (c *Client) Restore(ctx context.Context, id string, checkpoint Image, opts ...RestoreOpts) (Container, error) {
|
||||||
|
store := c.ContentStore()
|
||||||
|
index, err := decodeIndex(ctx, store, checkpoint.Target())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, done, err := c.WithLease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer done(ctx)
|
||||||
|
|
||||||
|
copts := []NewContainerOpts{}
|
||||||
|
for _, o := range opts {
|
||||||
|
copts = append(copts, o(ctx, id, c, checkpoint, index))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr, err := c.NewContainer(ctx, id, copts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIndex(ctx context.Context, index *ocispec.Index, client *Client, ref string) (d ocispec.Descriptor, err error) {
|
||||||
|
labels := map[string]string{}
|
||||||
|
for i, m := range index.Manifests {
|
||||||
|
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String()
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(index)
|
||||||
|
if err != nil {
|
||||||
|
return ocispec.Descriptor{}, err
|
||||||
|
}
|
||||||
|
return writeContent(ctx, client.ContentStore(), ocispec.MediaTypeImageIndex, ref, bytes.NewReader(data), content.WithLabels(labels))
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe to events that match one or more of the provided filters.
|
// Subscribe to events that match one or more of the provided filters.
|
||||||
//
|
//
|
||||||
// Callers should listen on both the envelope and errs channels. If the errs
|
// Callers should listen on both the envelope and errs channels. If the errs
|
||||||
|
@ -703,3 +619,20 @@ func (c *Client) Version(ctx context.Context) (Version, error) {
|
||||||
Revision: response.Revision,
|
Revision: response.Revision,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckRuntime returns true if the current runtime matches the expected
|
||||||
|
// runtime. Providing various parts of the runtime schema will match those
|
||||||
|
// parts of the expected runtime
|
||||||
|
func CheckRuntime(current, expected string) bool {
|
||||||
|
cp := strings.Split(current, ".")
|
||||||
|
l := len(cp)
|
||||||
|
for i, p := range strings.Split(expected, ".") {
|
||||||
|
if i > l {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if p != cp[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -178,3 +178,28 @@ func WithImageHandler(h images.Handler) RemoteOpt {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithImageHandlerWrapper wraps the handlers to be called on dispatch.
|
||||||
|
func WithImageHandlerWrapper(w func(images.Handler) images.Handler) RemoteOpt {
|
||||||
|
return func(client *Client, c *RemoteContext) error {
|
||||||
|
c.HandlerWrapper = w
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxConcurrentDownloads sets max concurrent download limit.
|
||||||
|
func WithMaxConcurrentDownloads(max int) RemoteOpt {
|
||||||
|
return func(client *Client, c *RemoteContext) error {
|
||||||
|
c.MaxConcurrentDownloads = max
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAppendDistributionSourceLabel allows fetcher to add distribute source
|
||||||
|
// label for each blob content, which doesn't work for legacy schema1.
|
||||||
|
func WithAppendDistributionSourceLabel() RemoteOpt {
|
||||||
|
return func(_ *Client, c *RemoteContext) error {
|
||||||
|
c.AppendDistributionSourceLabel = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,12 +28,22 @@ import (
|
||||||
"github.com/containerd/containerd/cio"
|
"github.com/containerd/containerd/cio"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
prototypes "github.com/gogo/protobuf/types"
|
prototypes "github.com/gogo/protobuf/types"
|
||||||
|
ver "github.com/opencontainers/image-spec/specs-go"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
checkpointImageNameLabel = "org.opencontainers.image.ref.name"
|
||||||
|
checkpointRuntimeNameLabel = "io.containerd.checkpoint.runtime"
|
||||||
|
checkpointSnapshotterNameLabel = "io.containerd.checkpoint.snapshotter"
|
||||||
|
)
|
||||||
|
|
||||||
// Container is a metadata object for container resources and task creation
|
// Container is a metadata object for container resources and task creation
|
||||||
type Container interface {
|
type Container interface {
|
||||||
// ID identifies the container
|
// ID identifies the container
|
||||||
|
@ -64,6 +74,8 @@ type Container interface {
|
||||||
Extensions(context.Context) (map[string]prototypes.Any, error)
|
Extensions(context.Context) (map[string]prototypes.Any, error)
|
||||||
// Update a container
|
// Update a container
|
||||||
Update(context.Context, ...UpdateContainerOpts) error
|
Update(context.Context, ...UpdateContainerOpts) error
|
||||||
|
// Checkpoint creates a checkpoint image of the current container
|
||||||
|
Checkpoint(context.Context, string, ...CheckpointOpts) (Image, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerFromRecord(client *Client, c containers.Container) *container {
|
func containerFromRecord(client *Client, c containers.Container) *container {
|
||||||
|
@ -217,7 +229,9 @@ func (c *container) NewTask(ctx context.Context, ioCreate cio.Creator, opts ...N
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var info TaskInfo
|
info := TaskInfo{
|
||||||
|
runtime: r.Runtime.Name,
|
||||||
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
if err := o(ctx, c.client, &info); err != nil {
|
if err := o(ctx, c.client, &info); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -272,6 +286,70 @@ func (c *container) Update(ctx context.Context, opts ...UpdateContainerOpts) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *container) Checkpoint(ctx context.Context, ref string, opts ...CheckpointOpts) (Image, error) {
|
||||||
|
index := &ocispec.Index{
|
||||||
|
Versioned: ver.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Annotations: make(map[string]string),
|
||||||
|
}
|
||||||
|
copts := &options.CheckpointOptions{
|
||||||
|
Exit: false,
|
||||||
|
OpenTcp: false,
|
||||||
|
ExternalUnixSockets: false,
|
||||||
|
Terminal: false,
|
||||||
|
FileLocks: true,
|
||||||
|
EmptyNamespaces: nil,
|
||||||
|
}
|
||||||
|
info, err := c.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := c.Image(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, done, err := c.client.WithLease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer done(ctx)
|
||||||
|
|
||||||
|
// add image name to manifest
|
||||||
|
index.Annotations[checkpointImageNameLabel] = img.Name()
|
||||||
|
// add runtime info to index
|
||||||
|
index.Annotations[checkpointRuntimeNameLabel] = info.Runtime.Name
|
||||||
|
// add snapshotter info to index
|
||||||
|
index.Annotations[checkpointSnapshotterNameLabel] = info.Snapshotter
|
||||||
|
|
||||||
|
// process remaining opts
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(ctx, c.client, &info, index, copts); err != nil {
|
||||||
|
err = errdefs.FromGRPC(err)
|
||||||
|
if !errdefs.IsAlreadyExists(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := writeIndex(ctx, index, c.client, c.ID()+"index")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i := images.Image{
|
||||||
|
Name: ref,
|
||||||
|
Target: desc,
|
||||||
|
}
|
||||||
|
checkpoint, err := c.client.ImageService().Create(ctx, i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewImage(c.client, checkpoint), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *container) loadTask(ctx context.Context, ioAttach cio.Attach) (Task, error) {
|
func (c *container) loadTask(ctx context.Context, ioAttach cio.Attach) (Task, error) {
|
||||||
response, err := c.client.TaskService().Get(ctx, &tasks.GetRequest{
|
response, err := c.client.TaskService().Get(ctx, &tasks.GetRequest{
|
||||||
ContainerID: c.id,
|
ContainerID: c.id,
|
||||||
|
|
155
vendor/github.com/containerd/containerd/container_checkpoint_opts.go
generated
vendored
Normal file
155
vendor/github.com/containerd/containerd/container_checkpoint_opts.go
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
tasks "github.com/containerd/containerd/api/services/tasks/v1"
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/diff"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/containerd/containerd/rootfs"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
|
"github.com/containerd/typeurl"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCheckpointRWUnsupported is returned if the container runtime does not support checkpoint
|
||||||
|
ErrCheckpointRWUnsupported = errors.New("rw checkpoint is only supported on v2 runtimes")
|
||||||
|
// ErrMediaTypeNotFound returns an error when a media type in the manifest is unknown
|
||||||
|
ErrMediaTypeNotFound = errors.New("media type not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckpointOpts are options to manage the checkpoint operation
|
||||||
|
type CheckpointOpts func(context.Context, *Client, *containers.Container, *imagespec.Index, *options.CheckpointOptions) error
|
||||||
|
|
||||||
|
// WithCheckpointImage includes the container image in the checkpoint
|
||||||
|
func WithCheckpointImage(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
ir, err := client.ImageService().Get(ctx, c.Image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, ir.Target)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointTask includes the running task
|
||||||
|
func WithCheckpointTask(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
any, err := typeurl.MarshalAny(copts)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
task, err := client.TaskService().Checkpoint(ctx, &tasks.CheckpointTaskRequest{
|
||||||
|
ContainerID: c.ID,
|
||||||
|
Options: any,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, d := range task.Descriptors {
|
||||||
|
platformSpec := platforms.DefaultSpec()
|
||||||
|
index.Manifests = append(index.Manifests, imagespec.Descriptor{
|
||||||
|
MediaType: d.MediaType,
|
||||||
|
Size: d.Size_,
|
||||||
|
Digest: d.Digest,
|
||||||
|
Platform: &platformSpec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// save copts
|
||||||
|
data, err := any.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
desc, err := writeContent(ctx, client.ContentStore(), images.MediaTypeContainerd1CheckpointOptions, c.ID+"-checkpoint-options", r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
desc.Platform = &imagespec.Platform{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, desc)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointRuntime includes the container runtime info
|
||||||
|
func WithCheckpointRuntime(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
if c.Runtime.Options != nil {
|
||||||
|
data, err := c.Runtime.Options.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
desc, err := writeContent(ctx, client.ContentStore(), images.MediaTypeContainerd1CheckpointRuntimeOptions, c.ID+"-runtime-options", r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
desc.Platform = &imagespec.Platform{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, desc)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointRW includes the rw in the checkpoint
|
||||||
|
func WithCheckpointRW(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
diffOpts := []diff.Opt{
|
||||||
|
diff.WithReference(fmt.Sprintf("checkpoint-rw-%s", c.SnapshotKey)),
|
||||||
|
}
|
||||||
|
rw, err := rootfs.CreateDiff(ctx,
|
||||||
|
c.SnapshotKey,
|
||||||
|
client.SnapshotService(c.Snapshotter),
|
||||||
|
client.DiffService(),
|
||||||
|
diffOpts...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
rw.Platform = &imagespec.Platform{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, rw)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointTaskExit causes the task to exit after checkpoint
|
||||||
|
func WithCheckpointTaskExit(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
copts.Exit = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIndexByMediaType returns the index in a manifest for the specified media type
|
||||||
|
func GetIndexByMediaType(index *imagespec.Index, mt string) (*imagespec.Descriptor, error) {
|
||||||
|
for _, d := range index.Manifests {
|
||||||
|
if d.MediaType == mt {
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrMediaTypeNotFound
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/containerd/containerd/snapshots"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
"github.com/gogo/protobuf/types"
|
"github.com/gogo/protobuf/types"
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
|
@ -118,7 +119,7 @@ func WithSnapshot(id string) NewContainerOpts {
|
||||||
|
|
||||||
// WithNewSnapshot allocates a new snapshot to be used by the container as the
|
// WithNewSnapshot allocates a new snapshot to be used by the container as the
|
||||||
// root filesystem in read-write mode
|
// root filesystem in read-write mode
|
||||||
func WithNewSnapshot(id string, i Image) NewContainerOpts {
|
func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
|
||||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
|
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -126,7 +127,7 @@ func WithNewSnapshot(id string, i Image) NewContainerOpts {
|
||||||
}
|
}
|
||||||
setSnapshotterIfEmpty(c)
|
setSnapshotterIfEmpty(c)
|
||||||
parent := identity.ChainID(diffIDs).String()
|
parent := identity.ChainID(diffIDs).String()
|
||||||
if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, id, parent); err != nil {
|
if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, id, parent, opts...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.SnapshotKey = id
|
c.SnapshotKey = id
|
||||||
|
@ -148,7 +149,7 @@ func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Conta
|
||||||
|
|
||||||
// WithNewSnapshotView allocates a new snapshot to be used by the container as the
|
// WithNewSnapshotView allocates a new snapshot to be used by the container as the
|
||||||
// root filesystem in read-only mode
|
// root filesystem in read-only mode
|
||||||
func WithNewSnapshotView(id string, i Image) NewContainerOpts {
|
func WithNewSnapshotView(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
|
||||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
|
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -156,7 +157,7 @@ func WithNewSnapshotView(id string, i Image) NewContainerOpts {
|
||||||
}
|
}
|
||||||
setSnapshotterIfEmpty(c)
|
setSnapshotterIfEmpty(c)
|
||||||
parent := identity.ChainID(diffIDs).String()
|
parent := identity.ChainID(diffIDs).String()
|
||||||
if _, err := client.SnapshotService(c.Snapshotter).View(ctx, id, parent); err != nil {
|
if _, err := client.SnapshotService(c.Snapshotter).View(ctx, id, parent, opts...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.SnapshotKey = id
|
c.SnapshotKey = id
|
||||||
|
|
|
@ -26,81 +26,12 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
protobuf "github.com/gogo/protobuf/types"
|
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithCheckpoint allows a container to be created from the checkpointed information
|
|
||||||
// provided by the descriptor. The image, snapshot, and runtime specifications are
|
|
||||||
// restored on the container
|
|
||||||
func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
|
|
||||||
// set image and rw, and spec
|
|
||||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
|
||||||
var (
|
|
||||||
desc = im.Target()
|
|
||||||
store = client.ContentStore()
|
|
||||||
)
|
|
||||||
index, err := decodeIndex(ctx, store, desc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var rw *v1.Descriptor
|
|
||||||
for _, m := range index.Manifests {
|
|
||||||
switch m.MediaType {
|
|
||||||
case v1.MediaTypeImageLayer:
|
|
||||||
fk := m
|
|
||||||
rw = &fk
|
|
||||||
case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList:
|
|
||||||
config, err := images.Config(ctx, store, m, platforms.Default())
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to resolve image config")
|
|
||||||
}
|
|
||||||
diffIDs, err := images.RootFS(ctx, store, config)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to get rootfs")
|
|
||||||
}
|
|
||||||
setSnapshotterIfEmpty(c)
|
|
||||||
if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, snapshotKey, identity.ChainID(diffIDs).String()); err != nil {
|
|
||||||
if !errdefs.IsAlreadyExists(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Image = index.Annotations["image.name"]
|
|
||||||
case images.MediaTypeContainerd1CheckpointConfig:
|
|
||||||
data, err := content.ReadBlob(ctx, store, m)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to read checkpoint config")
|
|
||||||
}
|
|
||||||
var any protobuf.Any
|
|
||||||
if err := proto.Unmarshal(data, &any); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Spec = &any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rw != nil {
|
|
||||||
// apply the rw snapshot to the new rw layer
|
|
||||||
mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, snapshotKey)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "unable to get mounts for %s", snapshotKey)
|
|
||||||
}
|
|
||||||
if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil {
|
|
||||||
return errors.Wrap(err, "unable to apply rw diff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.SnapshotKey = snapshotKey
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
|
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
|
||||||
// filesystem to be used by a container with user namespaces
|
// filesystem to be used by a container with user namespaces
|
||||||
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
|
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
|
||||||
|
|
150
vendor/github.com/containerd/containerd/container_restore_opts.go
generated
vendored
Normal file
150
vendor/github.com/containerd/containerd/container_restore_opts.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
ptypes "github.com/gogo/protobuf/types"
|
||||||
|
"github.com/opencontainers/image-spec/identity"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrImageNameNotFoundInIndex is returned when the image name is not found in the index
|
||||||
|
ErrImageNameNotFoundInIndex = errors.New("image name not found in index")
|
||||||
|
// ErrRuntimeNameNotFoundInIndex is returned when the runtime is not found in the index
|
||||||
|
ErrRuntimeNameNotFoundInIndex = errors.New("runtime not found in index")
|
||||||
|
// ErrSnapshotterNameNotFoundInIndex is returned when the snapshotter is not found in the index
|
||||||
|
ErrSnapshotterNameNotFoundInIndex = errors.New("snapshotter not found in index")
|
||||||
|
)
|
||||||
|
|
||||||
|
// RestoreOpts are options to manage the restore operation
|
||||||
|
type RestoreOpts func(context.Context, string, *Client, Image, *imagespec.Index) NewContainerOpts
|
||||||
|
|
||||||
|
// WithRestoreImage restores the image for the container
|
||||||
|
func WithRestoreImage(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
name, ok := index.Annotations[checkpointImageNameLabel]
|
||||||
|
if !ok || name == "" {
|
||||||
|
return ErrRuntimeNameNotFoundInIndex
|
||||||
|
}
|
||||||
|
snapshotter, ok := index.Annotations[checkpointSnapshotterNameLabel]
|
||||||
|
if !ok || name == "" {
|
||||||
|
return ErrSnapshotterNameNotFoundInIndex
|
||||||
|
}
|
||||||
|
i, err := client.GetImage(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parent := identity.ChainID(diffIDs).String()
|
||||||
|
if _, err := client.SnapshotService(snapshotter).Prepare(ctx, id, parent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Image = i.Name()
|
||||||
|
c.SnapshotKey = id
|
||||||
|
c.Snapshotter = snapshotter
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreRuntime restores the runtime for the container
|
||||||
|
func WithRestoreRuntime(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
name, ok := index.Annotations[checkpointRuntimeNameLabel]
|
||||||
|
if !ok {
|
||||||
|
return ErrRuntimeNameNotFoundInIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore options if present
|
||||||
|
m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointRuntimeOptions)
|
||||||
|
if err != nil {
|
||||||
|
if err != ErrMediaTypeNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var options *ptypes.Any
|
||||||
|
if m != nil {
|
||||||
|
store := client.ContentStore()
|
||||||
|
data, err := content.ReadBlob(ctx, store, *m)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to read checkpoint runtime")
|
||||||
|
}
|
||||||
|
if err := proto.Unmarshal(data, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Runtime = containers.RuntimeInfo{
|
||||||
|
Name: name,
|
||||||
|
Options: options,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreSpec restores the spec from the checkpoint for the container
|
||||||
|
func WithRestoreSpec(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store := client.ContentStore()
|
||||||
|
data, err := content.ReadBlob(ctx, store, *m)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to read checkpoint config")
|
||||||
|
}
|
||||||
|
var any ptypes.Any
|
||||||
|
if err := proto.Unmarshal(data, &any); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Spec = &any
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreRW restores the rw layer from the checkpoint for the container
|
||||||
|
func WithRestoreRW(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
// apply rw layer
|
||||||
|
rw, err := GetIndexByMediaType(index, imagespec.MediaTypeImageLayerGzip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, c.SnapshotKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,6 +86,10 @@ type RuntimeInfo struct {
|
||||||
|
|
||||||
// Store interacts with the underlying container storage
|
// Store interacts with the underlying container storage
|
||||||
type Store interface {
|
type Store interface {
|
||||||
|
// Get a container using the id.
|
||||||
|
//
|
||||||
|
// Container object is returned on success. If the id is not known to the
|
||||||
|
// store, an error will be returned.
|
||||||
Get(ctx context.Context, id string) (Container, error)
|
Get(ctx context.Context, id string) (Container, error)
|
||||||
|
|
||||||
// List returns containers that match one or more of the provided filters.
|
// List returns containers that match one or more of the provided filters.
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handles locking references
|
||||||
|
|
||||||
|
var (
|
||||||
|
// locks lets us lock in process
|
||||||
|
locks = map[string]struct{}{}
|
||||||
|
locksMu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func tryLock(ref string) error {
|
||||||
|
locksMu.Lock()
|
||||||
|
defer locksMu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := locks[ref]; ok {
|
||||||
|
return errors.Wrapf(errdefs.ErrUnavailable, "ref %s locked", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
locks[ref] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlock(ref string) {
|
||||||
|
locksMu.Lock()
|
||||||
|
defer locksMu.Unlock()
|
||||||
|
|
||||||
|
delete(locks, ref)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readerat implements io.ReaderAt in a completely stateless manner by opening
|
||||||
|
// the referenced file for each call to ReadAt.
|
||||||
|
type sizeReaderAt struct {
|
||||||
|
size int64
|
||||||
|
fp *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra sizeReaderAt) ReadAt(p []byte, offset int64) (int, error) {
|
||||||
|
return ra.fp.ReadAt(p, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra sizeReaderAt) Size() int64 {
|
||||||
|
return ra.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra sizeReaderAt) Close() error {
|
||||||
|
return ra.fp.Close()
|
||||||
|
}
|
|
@ -0,0 +1,656 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/filters"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
buffer := make([]byte, 1<<20)
|
||||||
|
return &buffer
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelStore is used to store mutable labels for digests
|
||||||
|
type LabelStore interface {
|
||||||
|
// Get returns all the labels for the given digest
|
||||||
|
Get(digest.Digest) (map[string]string, error)
|
||||||
|
|
||||||
|
// Set sets all the labels for a given digest
|
||||||
|
Set(digest.Digest, map[string]string) error
|
||||||
|
|
||||||
|
// Update replaces the given labels for a digest,
|
||||||
|
// a key with an empty value removes a label.
|
||||||
|
Update(digest.Digest, map[string]string) (map[string]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store is digest-keyed store for content. All data written into the store is
|
||||||
|
// stored under a verifiable digest.
|
||||||
|
//
|
||||||
|
// Store can generally support multi-reader, single-writer ingest of data,
|
||||||
|
// including resumable ingest.
|
||||||
|
type store struct {
|
||||||
|
root string
|
||||||
|
ls LabelStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStore returns a local content store
|
||||||
|
func NewStore(root string) (content.Store, error) {
|
||||||
|
return NewLabeledStore(root, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLabeledStore returns a new content store using the provided label store
|
||||||
|
//
|
||||||
|
// Note: content stores which are used underneath a metadata store may not
|
||||||
|
// require labels and should use `NewStore`. `NewLabeledStore` is primarily
|
||||||
|
// useful for tests or standalone implementations.
|
||||||
|
func NewLabeledStore(root string, ls LabelStore) (content.Store, error) {
|
||||||
|
if err := os.MkdirAll(filepath.Join(root, "ingest"), 0777); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &store{
|
||||||
|
root: root,
|
||||||
|
ls: ls,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
|
||||||
|
p := s.blobPath(dgst)
|
||||||
|
fi, err := os.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.Info{}, err
|
||||||
|
}
|
||||||
|
var labels map[string]string
|
||||||
|
if s.ls != nil {
|
||||||
|
labels, err = s.ls.Get(dgst)
|
||||||
|
if err != nil {
|
||||||
|
return content.Info{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.info(dgst, fi, labels), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) info(dgst digest.Digest, fi os.FileInfo, labels map[string]string) content.Info {
|
||||||
|
return content.Info{
|
||||||
|
Digest: dgst,
|
||||||
|
Size: fi.Size(),
|
||||||
|
CreatedAt: fi.ModTime(),
|
||||||
|
UpdatedAt: getATime(fi),
|
||||||
|
Labels: labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReaderAt returns an io.ReaderAt for the blob.
|
||||||
|
func (s *store) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
|
||||||
|
p := s.blobPath(desc.Digest)
|
||||||
|
fi, err := os.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Wrapf(errdefs.ErrNotFound, "blob %s expected at %s", desc.Digest, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, err := os.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Wrapf(errdefs.ErrNotFound, "blob %s expected at %s", desc.Digest, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizeReaderAt{size: fi.Size(), fp: fp}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a blob by its digest.
|
||||||
|
//
|
||||||
|
// While this is safe to do concurrently, safe exist-removal logic must hold
|
||||||
|
// some global lock on the store.
|
||||||
|
func (s *store) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||||
|
if err := os.RemoveAll(s.blobPath(dgst)); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
|
||||||
|
if s.ls == nil {
|
||||||
|
return content.Info{}, errors.Wrapf(errdefs.ErrFailedPrecondition, "update not supported on immutable content store")
|
||||||
|
}
|
||||||
|
|
||||||
|
p := s.blobPath(info.Digest)
|
||||||
|
fi, err := os.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = errors.Wrapf(errdefs.ErrNotFound, "content %v", info.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
all bool
|
||||||
|
labels map[string]string
|
||||||
|
)
|
||||||
|
if len(fieldpaths) > 0 {
|
||||||
|
for _, path := range fieldpaths {
|
||||||
|
if strings.HasPrefix(path, "labels.") {
|
||||||
|
if labels == nil {
|
||||||
|
labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimPrefix(path, "labels.")
|
||||||
|
labels[key] = info.Labels[key]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch path {
|
||||||
|
case "labels":
|
||||||
|
all = true
|
||||||
|
labels = info.Labels
|
||||||
|
default:
|
||||||
|
return content.Info{}, errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on content info %q", path, info.Digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
all = true
|
||||||
|
labels = info.Labels
|
||||||
|
}
|
||||||
|
|
||||||
|
if all {
|
||||||
|
err = s.ls.Set(info.Digest, labels)
|
||||||
|
} else {
|
||||||
|
labels, err = s.ls.Update(info.Digest, labels)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return content.Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info = s.info(info.Digest, fi, labels)
|
||||||
|
info.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := os.Chtimes(p, info.UpdatedAt, info.CreatedAt); err != nil {
|
||||||
|
log.G(ctx).WithError(err).Warnf("could not change access time for %s", info.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
|
||||||
|
// TODO: Support filters
|
||||||
|
root := filepath.Join(s.root, "blobs")
|
||||||
|
var alg digest.Algorithm
|
||||||
|
return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !fi.IsDir() && !alg.Available() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): There are few more cases with subdirs that should be
|
||||||
|
// handled in case the layout gets corrupted. This isn't strict enough
|
||||||
|
// and may spew bad data.
|
||||||
|
|
||||||
|
if path == root {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if filepath.Dir(path) == root {
|
||||||
|
alg = digest.Algorithm(filepath.Base(path))
|
||||||
|
|
||||||
|
if !alg.Available() {
|
||||||
|
alg = ""
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// descending into a hash directory
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst := digest.NewDigestFromHex(alg.String(), filepath.Base(path))
|
||||||
|
if err := dgst.Validate(); err != nil {
|
||||||
|
// log error but don't report
|
||||||
|
log.L.WithError(err).WithField("path", path).Error("invalid digest for blob path")
|
||||||
|
// if we see this, it could mean some sort of corruption of the
|
||||||
|
// store or extra paths not expected previously.
|
||||||
|
}
|
||||||
|
|
||||||
|
var labels map[string]string
|
||||||
|
if s.ls != nil {
|
||||||
|
labels, err = s.ls.Get(dgst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fn(s.info(dgst, fi, labels))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) Status(ctx context.Context, ref string) (content.Status, error) {
|
||||||
|
return s.status(s.ingestRoot(ref))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) ListStatuses(ctx context.Context, fs ...string) ([]content.Status, error) {
|
||||||
|
fp, err := os.Open(filepath.Join(s.root, "ingest"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
fis, err := fp.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filter, err := filters.ParseAll(fs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var active []content.Status
|
||||||
|
for _, fi := range fis {
|
||||||
|
p := filepath.Join(s.root, "ingest", fi.Name())
|
||||||
|
stat, err := s.status(p)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): This is a common error if uploads are being
|
||||||
|
// completed while making this listing. Need to consider taking a
|
||||||
|
// lock on the whole store to coordinate this aspect.
|
||||||
|
//
|
||||||
|
// Another option is to cleanup downloads asynchronously and
|
||||||
|
// coordinate this method with the cleanup process.
|
||||||
|
//
|
||||||
|
// For now, we just skip them, as they really don't exist.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Match(adaptStatus(stat)) {
|
||||||
|
active = append(active, stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return active, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkStatusRefs is used to walk all status references
|
||||||
|
// Failed status reads will be logged and ignored, if
|
||||||
|
// this function is called while references are being altered,
|
||||||
|
// these error messages may be produced.
|
||||||
|
func (s *store) WalkStatusRefs(ctx context.Context, fn func(string) error) error {
|
||||||
|
fp, err := os.Open(filepath.Join(s.root, "ingest"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
fis, err := fp.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fis {
|
||||||
|
rf := filepath.Join(s.root, "ingest", fi.Name(), "ref")
|
||||||
|
|
||||||
|
ref, err := readFileString(rf)
|
||||||
|
if err != nil {
|
||||||
|
log.G(ctx).WithError(err).WithField("path", rf).Error("failed to read ingest ref")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fn(ref); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// status works like stat above except uses the path to the ingest.
|
||||||
|
func (s *store) status(ingestPath string) (content.Status, error) {
|
||||||
|
dp := filepath.Join(ingestPath, "data")
|
||||||
|
fi, err := os.Stat(dp)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = errors.Wrap(errdefs.ErrNotFound, err.Error())
|
||||||
|
}
|
||||||
|
return content.Status{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := readFileString(filepath.Join(ingestPath, "ref"))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = errors.Wrap(errdefs.ErrNotFound, err.Error())
|
||||||
|
}
|
||||||
|
return content.Status{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
startedAt, err := readFileTimestamp(filepath.Join(ingestPath, "startedat"))
|
||||||
|
if err != nil {
|
||||||
|
return content.Status{}, errors.Wrapf(err, "could not read startedat")
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt, err := readFileTimestamp(filepath.Join(ingestPath, "updatedat"))
|
||||||
|
if err != nil {
|
||||||
|
return content.Status{}, errors.Wrapf(err, "could not read updatedat")
|
||||||
|
}
|
||||||
|
|
||||||
|
// because we don't write updatedat on every write, the mod time may
|
||||||
|
// actually be more up to date.
|
||||||
|
if fi.ModTime().After(updatedAt) {
|
||||||
|
updatedAt = fi.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.Status{
|
||||||
|
Ref: ref,
|
||||||
|
Offset: fi.Size(),
|
||||||
|
Total: s.total(ingestPath),
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
StartedAt: startedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func adaptStatus(status content.Status) filters.Adaptor {
|
||||||
|
return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
|
||||||
|
if len(fieldpath) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
switch fieldpath[0] {
|
||||||
|
case "ref":
|
||||||
|
return status.Ref, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// total attempts to resolve the total expected size for the write.
|
||||||
|
func (s *store) total(ingestPath string) int64 {
|
||||||
|
totalS, err := readFileString(filepath.Join(ingestPath, "total"))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := strconv.ParseInt(totalS, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// represents a corrupted file, should probably remove.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer begins or resumes the active writer identified by ref. If the writer
|
||||||
|
// is already in use, an error is returned. Only one writer may be in use per
|
||||||
|
// ref at a time.
|
||||||
|
//
|
||||||
|
// The argument `ref` is used to uniquely identify a long-lived writer transaction.
|
||||||
|
func (s *store) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
|
||||||
|
var wOpts content.WriterOpts
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&wOpts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO(AkihiroSuda): we could create a random string or one calculated based on the context
|
||||||
|
// https://github.com/containerd/containerd/issues/2129#issuecomment-380255019
|
||||||
|
if wOpts.Ref == "" {
|
||||||
|
return nil, errors.Wrap(errdefs.ErrInvalidArgument, "ref must not be empty")
|
||||||
|
}
|
||||||
|
var lockErr error
|
||||||
|
for count := uint64(0); count < 10; count++ {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1<<count)))
|
||||||
|
if err := tryLock(wOpts.Ref); err != nil {
|
||||||
|
if !errdefs.IsUnavailable(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lockErr = err
|
||||||
|
} else {
|
||||||
|
lockErr = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lockErr != nil {
|
||||||
|
return nil, lockErr
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := s.writer(ctx, wOpts.Ref, wOpts.Desc.Size, wOpts.Desc.Digest)
|
||||||
|
if err != nil {
|
||||||
|
unlock(wOpts.Ref)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, nil // lock is now held by w.
|
||||||
|
}
|
||||||
|
|
||||||
|
// writer provides the main implementation of the Writer method. The caller
|
||||||
|
// must hold the lock correctly and release on error if there is a problem.
|
||||||
|
func (s *store) writer(ctx context.Context, ref string, total int64, expected digest.Digest) (content.Writer, error) {
|
||||||
|
// TODO(stevvooe): Need to actually store expected here. We have
|
||||||
|
// code in the service that shouldn't be dealing with this.
|
||||||
|
if expected != "" {
|
||||||
|
p := s.blobPath(expected)
|
||||||
|
if _, err := os.Stat(p); err == nil {
|
||||||
|
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path, refp, data := s.ingestPaths(ref)
|
||||||
|
|
||||||
|
var (
|
||||||
|
digester = digest.Canonical.Digester()
|
||||||
|
offset int64
|
||||||
|
startedAt time.Time
|
||||||
|
updatedAt time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensure that the ingest path has been created.
|
||||||
|
if err := os.Mkdir(path, 0755); err != nil {
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := s.status(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed reading status of resume write")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref != status.Ref {
|
||||||
|
// NOTE(stevvooe): This is fairly catastrophic. Either we have some
|
||||||
|
// layout corruption or a hash collision for the ref key.
|
||||||
|
return nil, errors.Wrapf(err, "ref key does not match: %v != %v", ref, status.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if total > 0 && status.Total > 0 && total != status.Total {
|
||||||
|
return nil, errors.Errorf("provided total differs from status: %v != %v", total, status.Total)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): slow slow slow!!, send to goroutine or use resumable hashes
|
||||||
|
fp, err := os.Open(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := bufPool.Get().(*[]byte)
|
||||||
|
offset, err = io.CopyBuffer(digester.Hash(), fp, *p)
|
||||||
|
bufPool.Put(p)
|
||||||
|
fp.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt = status.UpdatedAt
|
||||||
|
startedAt = status.StartedAt
|
||||||
|
total = status.Total
|
||||||
|
} else {
|
||||||
|
startedAt = time.Now()
|
||||||
|
updatedAt = startedAt
|
||||||
|
|
||||||
|
// the ingest is new, we need to setup the target location.
|
||||||
|
// write the ref to a file for later use
|
||||||
|
if err := ioutil.WriteFile(refp, []byte(ref), 0666); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeTimestampFile(filepath.Join(path, "startedat"), startedAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeTimestampFile(filepath.Join(path, "updatedat"), startedAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if total > 0 {
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(path, "total"), []byte(fmt.Sprint(total)), 0666); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, err := os.OpenFile(data, os.O_WRONLY|os.O_CREATE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to open data file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fp.Seek(offset, io.SeekStart); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not seek to current write offset")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &writer{
|
||||||
|
s: s,
|
||||||
|
fp: fp,
|
||||||
|
ref: ref,
|
||||||
|
path: path,
|
||||||
|
offset: offset,
|
||||||
|
total: total,
|
||||||
|
digester: digester,
|
||||||
|
startedAt: startedAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort an active transaction keyed by ref. If the ingest is active, it will
|
||||||
|
// be cancelled. Any resources associated with the ingest will be cleaned.
|
||||||
|
func (s *store) Abort(ctx context.Context, ref string) error {
|
||||||
|
root := s.ingestRoot(ref)
|
||||||
|
if err := os.RemoveAll(root); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return errors.Wrapf(errdefs.ErrNotFound, "ingest ref %q", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) blobPath(dgst digest.Digest) string {
|
||||||
|
return filepath.Join(s.root, "blobs", dgst.Algorithm().String(), dgst.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *store) ingestRoot(ref string) string {
|
||||||
|
dgst := digest.FromString(ref)
|
||||||
|
return filepath.Join(s.root, "ingest", dgst.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ingestPaths are returned. The paths are the following:
|
||||||
|
//
|
||||||
|
// - root: entire ingest directory
|
||||||
|
// - ref: name of the starting ref, must be unique
|
||||||
|
// - data: file where data is written
|
||||||
|
//
|
||||||
|
func (s *store) ingestPaths(ref string) (string, string, string) {
|
||||||
|
var (
|
||||||
|
fp = s.ingestRoot(ref)
|
||||||
|
rp = filepath.Join(fp, "ref")
|
||||||
|
dp = filepath.Join(fp, "data")
|
||||||
|
)
|
||||||
|
|
||||||
|
return fp, rp, dp
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFileString(path string) (string, error) {
|
||||||
|
p, err := ioutil.ReadFile(path)
|
||||||
|
return string(p), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFileTimestamp reads a file with just a timestamp present.
|
||||||
|
func readFileTimestamp(p string) (time.Time, error) {
|
||||||
|
b, err := ioutil.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = errors.Wrap(errdefs.ErrNotFound, err.Error())
|
||||||
|
}
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
if err := t.UnmarshalText(b); err != nil {
|
||||||
|
return time.Time{}, errors.Wrapf(err, "could not parse timestamp file %v", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTimestampFile(p string, t time.Time) error {
|
||||||
|
b, err := t.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return continuity.AtomicWriteFile(p, b, 0666)
|
||||||
|
}
|
35
vendor/github.com/containerd/containerd/content/local/store_unix.go
generated
vendored
Normal file
35
vendor/github.com/containerd/containerd/content/local/store_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// +build linux solaris darwin freebsd
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/sys"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getATime(fi os.FileInfo) time.Time {
|
||||||
|
if st, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||||
|
return sys.StatATimeAsTime(st)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fi.ModTime()
|
||||||
|
}
|
26
vendor/github.com/containerd/containerd/content/local/store_windows.go
generated
vendored
Normal file
26
vendor/github.com/containerd/containerd/content/local/store_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getATime(fi os.FileInfo) time.Time {
|
||||||
|
return fi.ModTime()
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// writer represents a write transaction against the blob store.
|
||||||
|
type writer struct {
|
||||||
|
s *store
|
||||||
|
fp *os.File // opened data file
|
||||||
|
path string // path to writer dir
|
||||||
|
ref string // ref key
|
||||||
|
offset int64
|
||||||
|
total int64
|
||||||
|
digester digest.Digester
|
||||||
|
startedAt time.Time
|
||||||
|
updatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) Status() (content.Status, error) {
|
||||||
|
return content.Status{
|
||||||
|
Ref: w.ref,
|
||||||
|
Offset: w.offset,
|
||||||
|
Total: w.total,
|
||||||
|
StartedAt: w.startedAt,
|
||||||
|
UpdatedAt: w.updatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digest returns the current digest of the content, up to the current write.
|
||||||
|
//
|
||||||
|
// Cannot be called concurrently with `Write`.
|
||||||
|
func (w *writer) Digest() digest.Digest {
|
||||||
|
return w.digester.Digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write p to the transaction.
|
||||||
|
//
|
||||||
|
// Note that writes are unbuffered to the backing file. When writing, it is
|
||||||
|
// recommended to wrap in a bufio.Writer or, preferably, use io.CopyBuffer.
|
||||||
|
func (w *writer) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = w.fp.Write(p)
|
||||||
|
w.digester.Hash().Write(p[:n])
|
||||||
|
w.offset += int64(len(p))
|
||||||
|
w.updatedAt = time.Now()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
|
||||||
|
var base content.Info
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&base); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure even on error the writer is fully closed
|
||||||
|
defer unlock(w.ref)
|
||||||
|
fp := w.fp
|
||||||
|
w.fp = nil
|
||||||
|
|
||||||
|
if fp == nil {
|
||||||
|
return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fp.Sync(); err != nil {
|
||||||
|
fp.Close()
|
||||||
|
return errors.Wrap(err, "sync failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := fp.Stat()
|
||||||
|
closeErr := fp.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "stat on ingest file failed")
|
||||||
|
}
|
||||||
|
if closeErr != nil {
|
||||||
|
return errors.Wrap(err, "failed to close ingest file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if size > 0 && size != fi.Size() {
|
||||||
|
return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit size %d, expected %d", fi.Size(), size)
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst := w.digester.Digest()
|
||||||
|
if expected != "" && expected != dgst {
|
||||||
|
return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit digest %s, expected %s", dgst, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ingest = filepath.Join(w.path, "data")
|
||||||
|
target = w.s.blobPath(dgst)
|
||||||
|
)
|
||||||
|
|
||||||
|
// make sure parent directories of blob exist
|
||||||
|
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(target); err == nil {
|
||||||
|
// collision with the target file!
|
||||||
|
if err := os.RemoveAll(w.path); err != nil {
|
||||||
|
log.G(ctx).WithField("ref", w.ref).WithField("path", w.path).Errorf("failed to remove ingest directory")
|
||||||
|
}
|
||||||
|
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(ingest, target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ingest has now been made available in the content store, attempt to complete
|
||||||
|
// setting metadata but errors should only be logged and not returned since
|
||||||
|
// the content store cannot be cleanly rolled back.
|
||||||
|
|
||||||
|
commitTime := time.Now()
|
||||||
|
if err := os.Chtimes(target, commitTime, commitTime); err != nil {
|
||||||
|
log.G(ctx).WithField("digest", dgst).Errorf("failed to change file time to commit time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up!!
|
||||||
|
if err := os.RemoveAll(w.path); err != nil {
|
||||||
|
log.G(ctx).WithField("ref", w.ref).WithField("path", w.path).Errorf("failed to remove ingest directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.s.ls != nil && base.Labels != nil {
|
||||||
|
if err := w.s.ls.Set(dgst, base.Labels); err != nil {
|
||||||
|
log.G(ctx).WithField("digest", dgst).Errorf("failed to set labels")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change to readonly, more important for read, but provides _some_
|
||||||
|
// protection from this point on. We use the existing perms with a mask
|
||||||
|
// only allowing reads honoring the umask on creation.
|
||||||
|
//
|
||||||
|
// This removes write and exec, only allowing read per the creation umask.
|
||||||
|
//
|
||||||
|
// NOTE: Windows does not support this operation
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
if err := os.Chmod(target, (fi.Mode()&os.ModePerm)&^0333); err != nil {
|
||||||
|
log.G(ctx).WithField("ref", w.ref).Errorf("failed to make readonly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the writer, flushing any unwritten data and leaving the progress in
|
||||||
|
// tact.
|
||||||
|
//
|
||||||
|
// If one needs to resume the transaction, a new writer can be obtained from
|
||||||
|
// `Ingester.Writer` using the same key. The write can then be continued
|
||||||
|
// from it was left off.
|
||||||
|
//
|
||||||
|
// To abandon a transaction completely, first call close then `IngestManager.Abort` to
|
||||||
|
// clean up the associated resources.
|
||||||
|
func (w *writer) Close() (err error) {
|
||||||
|
if w.fp != nil {
|
||||||
|
w.fp.Sync()
|
||||||
|
err = w.fp.Close()
|
||||||
|
writeTimestampFile(filepath.Join(w.path, "updatedat"), w.updatedAt)
|
||||||
|
w.fp = nil
|
||||||
|
unlock(w.ref)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) Truncate(size int64) error {
|
||||||
|
if size != 0 {
|
||||||
|
return errors.New("Truncate: unsupported size")
|
||||||
|
}
|
||||||
|
w.offset = 0
|
||||||
|
w.digester.Hash().Reset()
|
||||||
|
if _, err := w.fp.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.fp.Truncate(0)
|
||||||
|
}
|
|
@ -95,7 +95,7 @@ func FromGRPC(err error) error {
|
||||||
|
|
||||||
msg := rebaseMessage(cls, err)
|
msg := rebaseMessage(cls, err)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
err = errors.Wrapf(cls, msg)
|
err = errors.Wrap(cls, msg)
|
||||||
} else {
|
} else {
|
||||||
err = errors.WithStack(cls)
|
err = errors.WithStack(cls)
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,10 +138,10 @@ func (e *Exchange) Subscribe(ctx context.Context, fs ...string) (ch <-chan *even
|
||||||
)
|
)
|
||||||
|
|
||||||
closeAll := func() {
|
closeAll := func() {
|
||||||
defer close(errq)
|
channel.Close()
|
||||||
defer e.broadcaster.Remove(dst)
|
queue.Close()
|
||||||
defer queue.Close()
|
e.broadcaster.Remove(dst)
|
||||||
defer channel.Close()
|
close(errq)
|
||||||
}
|
}
|
||||||
|
|
||||||
ch = evch
|
ch = evch
|
||||||
|
|
|
@ -20,36 +20,23 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images/oci"
|
||||||
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type exportOpts struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportOpt allows the caller to specify export-specific options
|
|
||||||
type ExportOpt func(c *exportOpts) error
|
|
||||||
|
|
||||||
func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
|
|
||||||
var eopts exportOpts
|
|
||||||
for _, o := range opts {
|
|
||||||
if err := o(&eopts); err != nil {
|
|
||||||
return eopts, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return eopts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export exports an image to a Tar stream.
|
// Export exports an image to a Tar stream.
|
||||||
// OCI format is used by default.
|
// OCI format is used by default.
|
||||||
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
||||||
// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
|
// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
|
||||||
func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
|
func (c *Client) Export(ctx context.Context, desc ocispec.Descriptor, opts ...oci.V1ExporterOpt) (io.ReadCloser, error) {
|
||||||
_, err := resolveExportOpt(opts...) // unused now
|
|
||||||
|
exporter, err := oci.ResolveV1ExportOpt(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
pw.CloseWithError(errors.Wrap(exporter.Export(ctx, c.ContentStore(), desc, pw), "export failed"))
|
pw.CloseWithError(errors.Wrap(exporter.Export(ctx, c.ContentStore(), desc, pw), "export failed"))
|
||||||
|
|
|
@ -71,7 +71,7 @@ func ParseAll(ss ...string) (Filter, error) {
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
f, err := Parse(s)
|
f, err := Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error())
|
return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
fs = append(fs, f)
|
fs = append(fs, f)
|
||||||
|
|
|
@ -45,7 +45,7 @@ var (
|
||||||
// Validate return nil if the string s is a valid identifier.
|
// Validate return nil if the string s is a valid identifier.
|
||||||
//
|
//
|
||||||
// identifiers must be valid domain names according to RFC 1035, section 2.3.1. To
|
// identifiers must be valid domain names according to RFC 1035, section 2.3.1. To
|
||||||
// enforce case insensitvity, all characters must be lower case.
|
// enforce case insensitivity, all characters must be lower case.
|
||||||
//
|
//
|
||||||
// In general, identifiers that pass this validation, should be safe for use as
|
// In general, identifiers that pass this validation, should be safe for use as
|
||||||
// a domain names or filesystem path component.
|
// a domain names or filesystem path component.
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"golang.org/x/sync/semaphore"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -108,19 +109,30 @@ func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) err
|
||||||
// handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse
|
// handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse
|
||||||
// any children.
|
// any children.
|
||||||
//
|
//
|
||||||
|
// A concurrency limiter can be passed in to limit the number of concurrent
|
||||||
|
// handlers running. When limiter is nil, there is no limit.
|
||||||
|
//
|
||||||
// Typically, this function will be used with `FetchHandler`, often composed
|
// Typically, this function will be used with `FetchHandler`, often composed
|
||||||
// with other handlers.
|
// with other handlers.
|
||||||
//
|
//
|
||||||
// If any handler returns an error, the dispatch session will be canceled.
|
// If any handler returns an error, the dispatch session will be canceled.
|
||||||
func Dispatch(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
|
func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error {
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for _, desc := range descs {
|
for _, desc := range descs {
|
||||||
desc := desc
|
desc := desc
|
||||||
|
|
||||||
|
if limiter != nil {
|
||||||
|
if err := limiter.Acquire(ctx, 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
desc := desc
|
desc := desc
|
||||||
|
|
||||||
children, err := handler.Handle(ctx, desc)
|
children, err := handler.Handle(ctx, desc)
|
||||||
|
if limiter != nil {
|
||||||
|
limiter.Release(1)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == ErrSkipDesc {
|
if errors.Cause(err) == ErrSkipDesc {
|
||||||
return nil // don't traverse the children.
|
return nil // don't traverse the children.
|
||||||
|
@ -129,7 +141,7 @@ func Dispatch(ctx context.Context, handler Handler, descs ...ocispec.Descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(children) > 0 {
|
if len(children) > 0 {
|
||||||
return Dispatch(ctx, handler, children...)
|
return Dispatch(ctx, handler, limiter, children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -29,11 +29,14 @@ const (
|
||||||
MediaTypeDockerSchema2Manifest = "application/vnd.docker.distribution.manifest.v2+json"
|
MediaTypeDockerSchema2Manifest = "application/vnd.docker.distribution.manifest.v2+json"
|
||||||
MediaTypeDockerSchema2ManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
|
MediaTypeDockerSchema2ManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
|
||||||
// Checkpoint/Restore Media Types
|
// Checkpoint/Restore Media Types
|
||||||
MediaTypeContainerd1Checkpoint = "application/vnd.containerd.container.criu.checkpoint.criu.tar"
|
MediaTypeContainerd1Checkpoint = "application/vnd.containerd.container.criu.checkpoint.criu.tar"
|
||||||
MediaTypeContainerd1CheckpointPreDump = "application/vnd.containerd.container.criu.checkpoint.predump.tar"
|
MediaTypeContainerd1CheckpointPreDump = "application/vnd.containerd.container.criu.checkpoint.predump.tar"
|
||||||
MediaTypeContainerd1Resource = "application/vnd.containerd.container.resource.tar"
|
MediaTypeContainerd1Resource = "application/vnd.containerd.container.resource.tar"
|
||||||
MediaTypeContainerd1RW = "application/vnd.containerd.container.rw.tar"
|
MediaTypeContainerd1RW = "application/vnd.containerd.container.rw.tar"
|
||||||
MediaTypeContainerd1CheckpointConfig = "application/vnd.containerd.container.checkpoint.config.v1+proto"
|
MediaTypeContainerd1CheckpointConfig = "application/vnd.containerd.container.checkpoint.config.v1+proto"
|
||||||
|
MediaTypeContainerd1CheckpointOptions = "application/vnd.containerd.container.checkpoint.options.v1+proto"
|
||||||
|
MediaTypeContainerd1CheckpointRuntimeName = "application/vnd.containerd.container.checkpoint.runtime.name"
|
||||||
|
MediaTypeContainerd1CheckpointRuntimeOptions = "application/vnd.containerd.container.checkpoint.runtime.options+proto"
|
||||||
// Legacy Docker schema1 manifest
|
// Legacy Docker schema1 manifest
|
||||||
MediaTypeDockerSchema1Manifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
|
MediaTypeDockerSchema1Manifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V1Exporter implements OCI Image Spec v1.
|
||||||
|
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
||||||
|
//
|
||||||
|
// TODO(AkihiroSuda): add V1Exporter{TranslateMediaTypes: true} that transforms media types,
|
||||||
|
// e.g. application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||||
|
// -> application/vnd.oci.image.layer.v1.tar+gzip
|
||||||
|
type V1Exporter struct {
|
||||||
|
AllPlatforms bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// V1ExporterOpt allows the caller to set additional options to a new V1Exporter
|
||||||
|
type V1ExporterOpt func(c *V1Exporter) error
|
||||||
|
|
||||||
|
// DefaultV1Exporter return a default V1Exporter pointer
|
||||||
|
func DefaultV1Exporter() *V1Exporter {
|
||||||
|
return &V1Exporter{
|
||||||
|
AllPlatforms: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveV1ExportOpt return a new V1Exporter with V1ExporterOpt
|
||||||
|
func ResolveV1ExportOpt(opts ...V1ExporterOpt) (*V1Exporter, error) {
|
||||||
|
exporter := DefaultV1Exporter()
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(exporter); err != nil {
|
||||||
|
return exporter, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exporter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAllPlatforms set V1Exporter`s AllPlatforms option
|
||||||
|
func WithAllPlatforms(allPlatforms bool) V1ExporterOpt {
|
||||||
|
return func(c *V1Exporter) error {
|
||||||
|
c.AllPlatforms = allPlatforms
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export implements Exporter.
|
||||||
|
func (oe *V1Exporter) Export(ctx context.Context, store content.Provider, desc ocispec.Descriptor, writer io.Writer) error {
|
||||||
|
tw := tar.NewWriter(writer)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
records := []tarRecord{
|
||||||
|
ociLayoutFile(""),
|
||||||
|
ociIndexRecord(desc),
|
||||||
|
}
|
||||||
|
|
||||||
|
algorithms := map[string]struct{}{}
|
||||||
|
exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
|
records = append(records, blobRecord(store, desc))
|
||||||
|
algorithms[desc.Digest.Algorithm().String()] = struct{}{}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
childrenHandler := images.ChildrenHandler(store)
|
||||||
|
|
||||||
|
if !oe.AllPlatforms {
|
||||||
|
// get local default platform to fetch image manifest
|
||||||
|
childrenHandler = images.FilterPlatforms(childrenHandler, platforms.Any(platforms.DefaultSpec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers := images.Handlers(
|
||||||
|
childrenHandler,
|
||||||
|
images.HandlerFunc(exportHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walk sequentially since the number of fetchs is likely one and doing in
|
||||||
|
// parallel requires locking the export handler
|
||||||
|
if err := images.Walk(ctx, handlers, desc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(algorithms) > 0 {
|
||||||
|
records = append(records, directoryRecord("blobs/", 0755))
|
||||||
|
for alg := range algorithms {
|
||||||
|
records = append(records, directoryRecord("blobs/"+alg+"/", 0755))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeTar(ctx, tw, records)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tarRecord struct {
|
||||||
|
Header *tar.Header
|
||||||
|
CopyTo func(context.Context, io.Writer) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord {
|
||||||
|
path := "blobs/" + desc.Digest.Algorithm().String() + "/" + desc.Digest.Hex()
|
||||||
|
return tarRecord{
|
||||||
|
Header: &tar.Header{
|
||||||
|
Name: path,
|
||||||
|
Mode: 0444,
|
||||||
|
Size: desc.Size,
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
},
|
||||||
|
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
|
||||||
|
r, err := cs.ReaderAt(ctx, desc)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed to get reader")
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
// Verify digest
|
||||||
|
dgstr := desc.Digest.Algorithm().Digester()
|
||||||
|
|
||||||
|
n, err := io.Copy(io.MultiWriter(w, dgstr.Hash()), content.NewReader(r))
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed to copy to tar")
|
||||||
|
}
|
||||||
|
if dgstr.Digest() != desc.Digest {
|
||||||
|
return 0, errors.Errorf("unexpected digest %s copied", dgstr.Digest())
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func directoryRecord(name string, mode int64) tarRecord {
|
||||||
|
return tarRecord{
|
||||||
|
Header: &tar.Header{
|
||||||
|
Name: name,
|
||||||
|
Mode: mode,
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ociLayoutFile(version string) tarRecord {
|
||||||
|
if version == "" {
|
||||||
|
version = ocispec.ImageLayoutVersion
|
||||||
|
}
|
||||||
|
layout := ocispec.ImageLayout{
|
||||||
|
Version: version,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(layout)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tarRecord{
|
||||||
|
Header: &tar.Header{
|
||||||
|
Name: ocispec.ImageLayoutFile,
|
||||||
|
Mode: 0444,
|
||||||
|
Size: int64(len(b)),
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
},
|
||||||
|
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write(b)
|
||||||
|
return int64(n), err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ociIndexRecord(manifests ...ocispec.Descriptor) tarRecord {
|
||||||
|
index := ocispec.Index{
|
||||||
|
Versioned: ocispecs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Manifests: manifests,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(index)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tarRecord{
|
||||||
|
Header: &tar.Header{
|
||||||
|
Name: "index.json",
|
||||||
|
Mode: 0644,
|
||||||
|
Size: int64(len(b)),
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
},
|
||||||
|
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write(b)
|
||||||
|
return int64(n), err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error {
|
||||||
|
sort.Slice(records, func(i, j int) bool {
|
||||||
|
return records[i].Header.Name < records[j].Header.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, record := range records {
|
||||||
|
if err := tw.WriteHeader(record.Header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if record.CopyTo != nil {
|
||||||
|
n, err := record.CopyTo(ctx, tw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != record.Header.Size {
|
||||||
|
return errors.Errorf("unexpected copy size for %s", record.Header.Name)
|
||||||
|
}
|
||||||
|
} else if record.Header.Size > 0 {
|
||||||
|
return errors.Errorf("no content to write to record with non-zero size for %s", record.Header.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -25,14 +25,16 @@ import (
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/images/archive"
|
"github.com/containerd/containerd/images/archive"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type importOpts struct {
|
type importOpts struct {
|
||||||
indexName string
|
indexName string
|
||||||
imageRefT func(string) string
|
imageRefT func(string) string
|
||||||
dgstRefT func(digest.Digest) string
|
dgstRefT func(digest.Digest) string
|
||||||
|
allPlatforms bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportOpt allows the caller to specify import specific options
|
// ImportOpt allows the caller to specify import specific options
|
||||||
|
@ -64,6 +66,14 @@ func WithIndexName(name string) ImportOpt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAllPlatforms is used to import content for all platforms.
|
||||||
|
func WithAllPlatforms(allPlatforms bool) ImportOpt {
|
||||||
|
return func(c *importOpts) error {
|
||||||
|
c.allPlatforms = allPlatforms
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Import imports an image from a Tar stream using reader.
|
// Import imports an image from a Tar stream using reader.
|
||||||
// Caller needs to specify importer. Future version may use oci.v1 as the default.
|
// Caller needs to specify importer. Future version may use oci.v1 as the default.
|
||||||
// Note that unreferrenced blobs may be imported to the content store as well.
|
// Note that unreferrenced blobs may be imported to the content store as well.
|
||||||
|
@ -98,6 +108,10 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
|
||||||
Target: index,
|
Target: index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
var platformMatcher = platforms.All
|
||||||
|
if !iopts.allPlatforms {
|
||||||
|
platformMatcher = platforms.Default()
|
||||||
|
}
|
||||||
|
|
||||||
var handler images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
var handler images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
// Only save images at top level
|
// Only save images at top level
|
||||||
|
@ -141,6 +155,7 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
|
||||||
return idx.Manifests, nil
|
return idx.Manifests, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler = images.FilterPlatforms(handler, platformMatcher)
|
||||||
handler = images.SetChildrenLabels(cs, handler)
|
handler = images.SetChildrenLabels(cs, handler)
|
||||||
if err := images.Walk(ctx, handler, index); err != nil {
|
if err := images.Walk(ctx, handler, index); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -59,7 +59,6 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
|
||||||
if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) (bool, error) {
|
if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) (bool, error) {
|
||||||
d := filepath.Dir(hdr.Name)
|
d := filepath.Dir(hdr.Name)
|
||||||
result := d == "bin"
|
result := d == "bin"
|
||||||
|
@ -73,8 +72,10 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
|
r.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
r.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package labels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxSize = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate a label's key and value are under 4096 bytes
|
||||||
|
func Validate(k, v string) error {
|
||||||
|
if (len(k) + len(v)) > maxSize {
|
||||||
|
if len(k) > 10 {
|
||||||
|
k = k[:10]
|
||||||
|
}
|
||||||
|
return errors.Wrapf(errdefs.ErrInvalidArgument, "label key and value greater than maximum size (%d bytes), key: %s", maxSize, k)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -247,17 +247,8 @@ func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
|
||||||
Root: &specs.Root{},
|
Root: &specs.Root{},
|
||||||
Process: &specs.Process{
|
Process: &specs.Process{
|
||||||
Cwd: `C:\`,
|
Cwd: `C:\`,
|
||||||
ConsoleSize: &specs.Box{
|
|
||||||
Width: 80,
|
|
||||||
Height: 20,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Windows: &specs.Windows{
|
|
||||||
IgnoreFlushesDuringBoot: true,
|
|
||||||
Network: &specs.WindowsNetwork{
|
|
||||||
AllowUnqualifiedDNSQuery: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Windows: &specs.Windows{},
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import (
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/containerd/continuity/fs"
|
"github.com/containerd/continuity/fs"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/opencontainers/runc/libcontainer/user"
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -141,8 +141,10 @@ func WithEnv(environmentVariables []string) SpecOpts {
|
||||||
// replaced by env key or appended to the list
|
// replaced by env key or appended to the list
|
||||||
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||||
cache := make(map[string]int, len(defaults))
|
cache := make(map[string]int, len(defaults))
|
||||||
|
results := make([]string, 0, len(defaults))
|
||||||
for i, e := range defaults {
|
for i, e := range defaults {
|
||||||
parts := strings.SplitN(e, "=", 2)
|
parts := strings.SplitN(e, "=", 2)
|
||||||
|
results = append(results, e)
|
||||||
cache[parts[0]] = i
|
cache[parts[0]] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +152,7 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||||
// Values w/o = means they want this env to be removed/unset.
|
// Values w/o = means they want this env to be removed/unset.
|
||||||
if !strings.Contains(value, "=") {
|
if !strings.Contains(value, "=") {
|
||||||
if i, exists := cache[value]; exists {
|
if i, exists := cache[value]; exists {
|
||||||
defaults[i] = "" // Used to indicate it should be removed
|
results[i] = "" // Used to indicate it should be removed
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -158,21 +160,21 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||||
// Just do a normal set/update
|
// Just do a normal set/update
|
||||||
parts := strings.SplitN(value, "=", 2)
|
parts := strings.SplitN(value, "=", 2)
|
||||||
if i, exists := cache[parts[0]]; exists {
|
if i, exists := cache[parts[0]]; exists {
|
||||||
defaults[i] = value
|
results[i] = value
|
||||||
} else {
|
} else {
|
||||||
defaults = append(defaults, value)
|
results = append(results, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now remove all entries that we want to "unset"
|
// Now remove all entries that we want to "unset"
|
||||||
for i := 0; i < len(defaults); i++ {
|
for i := 0; i < len(results); i++ {
|
||||||
if defaults[i] == "" {
|
if results[i] == "" {
|
||||||
defaults = append(defaults[:i], defaults[i+1:]...)
|
results = append(results[:i], results[i+1:]...)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaults
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithProcessArgs replaces the args on the generated spec
|
// WithProcessArgs replaces the args on the generated spec
|
||||||
|
@ -310,7 +312,7 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
||||||
|
|
||||||
setProcess(s)
|
setProcess(s)
|
||||||
if s.Linux != nil {
|
if s.Linux != nil {
|
||||||
s.Process.Env = append(s.Process.Env, config.Env...)
|
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, config.Env)
|
||||||
cmd := config.Cmd
|
cmd := config.Cmd
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
cmd = args
|
cmd = args
|
||||||
|
@ -332,8 +334,14 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
||||||
// even if there is no specified user in the image config
|
// even if there is no specified user in the image config
|
||||||
return WithAdditionalGIDs("root")(ctx, client, c, s)
|
return WithAdditionalGIDs("root")(ctx, client, c, s)
|
||||||
} else if s.Windows != nil {
|
} else if s.Windows != nil {
|
||||||
s.Process.Env = config.Env
|
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, config.Env)
|
||||||
s.Process.Args = append(config.Entrypoint, config.Cmd...)
|
cmd := config.Cmd
|
||||||
|
if len(args) > 0 {
|
||||||
|
cmd = args
|
||||||
|
}
|
||||||
|
s.Process.Args = append(config.Entrypoint, cmd...)
|
||||||
|
|
||||||
|
s.Process.Cwd = config.WorkingDir
|
||||||
s.Process.User = specs.User{
|
s.Process.User = specs.User{
|
||||||
Username: config.User,
|
Username: config.User,
|
||||||
}
|
}
|
||||||
|
@ -733,9 +741,11 @@ func WithCapabilities(caps []string) SpecOpts {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAllCapabilities sets all linux capabilities for the process
|
// WithAllCapabilities sets all linux capabilities for the process
|
||||||
var WithAllCapabilities = WithCapabilities(getAllCapabilities())
|
var WithAllCapabilities = WithCapabilities(GetAllCapabilities())
|
||||||
|
|
||||||
func getAllCapabilities() []string {
|
// GetAllCapabilities returns all caps up to CAP_LAST_CAP
|
||||||
|
// or CAP_BLOCK_SUSPEND on RHEL6
|
||||||
|
func GetAllCapabilities() []string {
|
||||||
last := capability.CAP_LAST_CAP
|
last := capability.CAP_LAST_CAP
|
||||||
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
||||||
if last == capability.Cap(63) {
|
if last == capability.Cap(63) {
|
||||||
|
@ -751,6 +761,61 @@ func getAllCapabilities() []string {
|
||||||
return caps
|
return caps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func capsContain(caps []string, s string) bool {
|
||||||
|
for _, c := range caps {
|
||||||
|
if c == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeCap(caps *[]string, s string) {
|
||||||
|
for i, c := range *caps {
|
||||||
|
if c == s {
|
||||||
|
*caps = append((*caps)[:i], (*caps)[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAddedCapabilities adds the provided capabilities
|
||||||
|
func WithAddedCapabilities(caps []string) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setCapabilities(s)
|
||||||
|
for _, c := range caps {
|
||||||
|
for _, cl := range []*[]string{
|
||||||
|
&s.Process.Capabilities.Bounding,
|
||||||
|
&s.Process.Capabilities.Effective,
|
||||||
|
&s.Process.Capabilities.Permitted,
|
||||||
|
&s.Process.Capabilities.Inheritable,
|
||||||
|
} {
|
||||||
|
if !capsContain(*cl, c) {
|
||||||
|
*cl = append(*cl, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDroppedCapabilities removes the provided capabilities
|
||||||
|
func WithDroppedCapabilities(caps []string) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setCapabilities(s)
|
||||||
|
for _, c := range caps {
|
||||||
|
for _, cl := range []*[]string{
|
||||||
|
&s.Process.Capabilities.Bounding,
|
||||||
|
&s.Process.Capabilities.Effective,
|
||||||
|
&s.Process.Capabilities.Permitted,
|
||||||
|
&s.Process.Capabilities.Inheritable,
|
||||||
|
} {
|
||||||
|
removeCap(cl, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithAmbientCapabilities set the Linux ambient capabilities for the process
|
// WithAmbientCapabilities set the Linux ambient capabilities for the process
|
||||||
// Ambient capabilities should only be set for non-root users or the caller should
|
// Ambient capabilities should only be set for non-root users or the caller should
|
||||||
// understand how these capabilities are used and set
|
// understand how these capabilities are used and set
|
||||||
|
@ -1026,3 +1091,46 @@ func WithWindowsHyperV(_ context.Context, _ Client, _ *containers.Container, s *
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithMemoryLimit sets the `Linux.LinuxResources.Memory.Limit` section to the
|
||||||
|
// `limit` specified if the `Linux` section is not `nil`. Additionally sets the
|
||||||
|
// `Windows.WindowsResources.Memory.Limit` section if the `Windows` section is
|
||||||
|
// not `nil`.
|
||||||
|
func WithMemoryLimit(limit uint64) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
if s.Linux != nil {
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &specs.LinuxResources{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources.Memory == nil {
|
||||||
|
s.Linux.Resources.Memory = &specs.LinuxMemory{}
|
||||||
|
}
|
||||||
|
l := int64(limit)
|
||||||
|
s.Linux.Resources.Memory.Limit = &l
|
||||||
|
}
|
||||||
|
if s.Windows != nil {
|
||||||
|
if s.Windows.Resources == nil {
|
||||||
|
s.Windows.Resources = &specs.WindowsResources{}
|
||||||
|
}
|
||||||
|
if s.Windows.Resources.Memory == nil {
|
||||||
|
s.Windows.Resources.Memory = &specs.WindowsMemoryResources{}
|
||||||
|
}
|
||||||
|
s.Windows.Resources.Memory.Limit = &limit
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAnnotations appends or replaces the annotations on the spec with the
|
||||||
|
// provided annotations
|
||||||
|
func WithAnnotations(annotations map[string]string) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
if s.Annotations == nil {
|
||||||
|
s.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
for k, v := range annotations {
|
||||||
|
s.Annotations[k] = v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithWindowsCPUCount sets the `Windows.Resources.CPU.Count` section to the
|
||||||
|
// `count` specified.
|
||||||
|
func WithWindowsCPUCount(count uint64) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
if s.Windows.Resources == nil {
|
||||||
|
s.Windows.Resources = &specs.WindowsResources{}
|
||||||
|
}
|
||||||
|
if s.Windows.Resources.CPU == nil {
|
||||||
|
s.Windows.Resources.CPU = &specs.WindowsCPUResources{}
|
||||||
|
}
|
||||||
|
s.Windows.Resources.CPU.Count = &count
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithWindowsIgnoreFlushesDuringBoot sets `Windows.IgnoreFlushesDuringBoot`.
|
||||||
|
func WithWindowsIgnoreFlushesDuringBoot() SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
if s.Windows == nil {
|
||||||
|
s.Windows = &specs.Windows{}
|
||||||
|
}
|
||||||
|
s.Windows.IgnoreFlushesDuringBoot = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithWindowNetworksAllowUnqualifiedDNSQuery sets `Windows.IgnoreFlushesDuringBoot`.
|
||||||
|
func WithWindowNetworksAllowUnqualifiedDNSQuery() SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
if s.Windows == nil {
|
||||||
|
s.Windows = &specs.Windows{}
|
||||||
|
}
|
||||||
|
if s.Windows.Network == nil {
|
||||||
|
s.Windows.Network = &specs.WindowsNetwork{}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Windows.Network.AllowUnqualifiedDNSQuery = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,6 +75,15 @@ const (
|
||||||
GCPlugin Type = "io.containerd.gc.v1"
|
GCPlugin Type = "io.containerd.gc.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RuntimeLinuxV1 is the legacy linux runtime
|
||||||
|
RuntimeLinuxV1 = "io.containerd.runtime.v1.linux"
|
||||||
|
// RuntimeRuncV1 is the runc runtime that supports a single container
|
||||||
|
RuntimeRuncV1 = "io.containerd.runc.v1"
|
||||||
|
// RuntimeRuncV2 is the runc runtime that supports multiple containers per shim
|
||||||
|
RuntimeRuncV2 = "io.containerd.runc.v2"
|
||||||
|
)
|
||||||
|
|
||||||
// Registration contains information for registering a plugin
|
// Registration contains information for registering a plugin
|
||||||
type Registration struct {
|
type Registration struct {
|
||||||
// Type of the plugin
|
// Type of the plugin
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/containerd/containerd/remotes"
|
||||||
|
"github.com/containerd/containerd/remotes/docker"
|
||||||
|
"github.com/containerd/containerd/remotes/docker/schema1"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/semaphore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pull downloads the provided content into containerd's content store
|
||||||
|
// and returns a platform specific image object
|
||||||
|
func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image, error) {
|
||||||
|
pullCtx := defaultRemoteContext()
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(c, pullCtx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pullCtx.PlatformMatcher == nil {
|
||||||
|
if len(pullCtx.Platforms) > 1 {
|
||||||
|
return nil, errors.New("cannot pull multiplatform image locally, try Fetch")
|
||||||
|
} else if len(pullCtx.Platforms) == 0 {
|
||||||
|
pullCtx.PlatformMatcher = platforms.Default()
|
||||||
|
} else {
|
||||||
|
p, err := platforms.Parse(pullCtx.Platforms[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "invalid platform %s", pullCtx.Platforms[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
pullCtx.PlatformMatcher = platforms.Only(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, done, err := c.WithLease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer done(ctx)
|
||||||
|
|
||||||
|
img, err := c.fetch(ctx, pullCtx, ref, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := NewImageWithPlatform(c, img, pullCtx.PlatformMatcher)
|
||||||
|
|
||||||
|
if pullCtx.Unpack {
|
||||||
|
if err := i.Unpack(ctx, pullCtx.Snapshotter); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to unpack image on snapshotter %s", pullCtx.Snapshotter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string, limit int) (images.Image, error) {
|
||||||
|
store := c.ContentStore()
|
||||||
|
name, desc, err := rCtx.Resolver.Resolve(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return images.Image{}, errors.Wrapf(err, "failed to resolve reference %q", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetcher, err := rCtx.Resolver.Fetcher(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return images.Image{}, errors.Wrapf(err, "failed to get fetcher for %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
handler images.Handler
|
||||||
|
|
||||||
|
isConvertible bool
|
||||||
|
converterFunc func(context.Context, ocispec.Descriptor) (ocispec.Descriptor, error)
|
||||||
|
limiter *semaphore.Weighted
|
||||||
|
)
|
||||||
|
|
||||||
|
if desc.MediaType == images.MediaTypeDockerSchema1Manifest && rCtx.ConvertSchema1 {
|
||||||
|
schema1Converter := schema1.NewConverter(store, fetcher)
|
||||||
|
|
||||||
|
handler = images.Handlers(append(rCtx.BaseHandlers, schema1Converter)...)
|
||||||
|
|
||||||
|
isConvertible = true
|
||||||
|
|
||||||
|
converterFunc = func(ctx context.Context, _ ocispec.Descriptor) (ocispec.Descriptor, error) {
|
||||||
|
return schema1Converter.Convert(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get all the children for a descriptor
|
||||||
|
childrenHandler := images.ChildrenHandler(store)
|
||||||
|
// Set any children labels for that content
|
||||||
|
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
|
||||||
|
// Filter manifests by platforms but allow to handle manifest
|
||||||
|
// and configuration for not-target platforms
|
||||||
|
childrenHandler = remotes.FilterManifestByPlatformHandler(childrenHandler, rCtx.PlatformMatcher)
|
||||||
|
// Sort and limit manifests if a finite number is needed
|
||||||
|
if limit > 0 {
|
||||||
|
childrenHandler = images.LimitManifests(childrenHandler, rCtx.PlatformMatcher, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set isConvertible to true if there is application/octet-stream media type
|
||||||
|
convertibleHandler := images.HandlerFunc(
|
||||||
|
func(_ context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
|
if desc.MediaType == docker.LegacyConfigMediaType {
|
||||||
|
isConvertible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ocispec.Descriptor{}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
handlers := append(rCtx.BaseHandlers,
|
||||||
|
remotes.FetchHandler(store, fetcher),
|
||||||
|
convertibleHandler,
|
||||||
|
childrenHandler,
|
||||||
|
)
|
||||||
|
|
||||||
|
// append distribution source label to blob data
|
||||||
|
if rCtx.AppendDistributionSourceLabel {
|
||||||
|
appendDistSrcLabelHandler, err := docker.AppendDistributionSourceLabel(store, ref)
|
||||||
|
if err != nil {
|
||||||
|
return images.Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers = append(handlers, appendDistSrcLabelHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = images.Handlers(handlers...)
|
||||||
|
|
||||||
|
converterFunc = func(ctx context.Context, desc ocispec.Descriptor) (ocispec.Descriptor, error) {
|
||||||
|
return docker.ConvertManifest(ctx, store, desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rCtx.HandlerWrapper != nil {
|
||||||
|
handler = rCtx.HandlerWrapper(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rCtx.MaxConcurrentDownloads > 0 {
|
||||||
|
limiter = semaphore.NewWeighted(int64(rCtx.MaxConcurrentDownloads))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := images.Dispatch(ctx, handler, limiter, desc); err != nil {
|
||||||
|
return images.Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isConvertible {
|
||||||
|
if desc, err = converterFunc(ctx, desc); err != nil {
|
||||||
|
return images.Image{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img := images.Image{
|
||||||
|
Name: name,
|
||||||
|
Target: desc,
|
||||||
|
Labels: rCtx.Labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
is := c.ImageService()
|
||||||
|
for {
|
||||||
|
if created, err := is.Create(ctx, img); err != nil {
|
||||||
|
if !errdefs.IsAlreadyExists(err) {
|
||||||
|
return images.Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := is.Update(ctx, img)
|
||||||
|
if err != nil {
|
||||||
|
// if image was removed, try create again
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return images.Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img = updated
|
||||||
|
} else {
|
||||||
|
img = created
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,7 +81,7 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
|
||||||
// TODO(dmcg): Store challenge, not token
|
// TODO(dmcg): Store challenge, not token
|
||||||
// Move token fetching to authorize
|
// Move token fetching to authorize
|
||||||
return a.setTokenAuth(ctx, host, c.parameters)
|
return a.setTokenAuth(ctx, host, c.parameters)
|
||||||
} else if c.scheme == basicAuth {
|
} else if c.scheme == basicAuth && a.credentials != nil {
|
||||||
// TODO: Resolve credentials on authorize
|
// TODO: Resolve credentials on authorize
|
||||||
username, secret, err := a.credentials(host)
|
username, secret, err := a.credentials(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -194,7 +194,11 @@ func (a *dockerAuthorizer) fetchTokenWithOAuth(ctx context.Context, to tokenOpti
|
||||||
form.Set("password", to.secret)
|
form.Set("password", to.secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := ctxhttp.PostForm(ctx, a.client, to.realm, form)
|
resp, err := ctxhttp.Post(
|
||||||
|
ctx, a.client, to.realm,
|
||||||
|
"application/x-www-form-urlencoded; charset=utf-8",
|
||||||
|
strings.NewReader(form.Encode()),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
112
vendor/github.com/containerd/containerd/remotes/docker/handler.go
generated
vendored
Normal file
112
vendor/github.com/containerd/containerd/remotes/docker/handler.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/labels"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/containerd/containerd/reference"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// labelDistributionSource describes the source blob comes from.
|
||||||
|
labelDistributionSource = "containerd.io/distribution.source"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppendDistributionSourceLabel updates the label of blob with distribution source.
|
||||||
|
func AppendDistributionSourceLabel(manager content.Manager, ref string) (images.HandlerFunc, error) {
|
||||||
|
refspec, err := reference.Parse(ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse("dummy://" + refspec.Locator)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
source, repo := u.Hostname(), strings.TrimPrefix(u.Path, "/")
|
||||||
|
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
|
info, err := manager.Info(ctx, desc.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := distributionSourceLabelKey(source)
|
||||||
|
|
||||||
|
originLabel := ""
|
||||||
|
if info.Labels != nil {
|
||||||
|
originLabel = info.Labels[key]
|
||||||
|
}
|
||||||
|
value := appendDistributionSourceLabel(originLabel, repo)
|
||||||
|
|
||||||
|
// The repo name has been limited under 256 and the distribution
|
||||||
|
// label might hit the limitation of label size, when blob data
|
||||||
|
// is used as the very, very common layer.
|
||||||
|
if err := labels.Validate(key, value); err != nil {
|
||||||
|
log.G(ctx).Warnf("skip to append distribution label: %s", err)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info = content.Info{
|
||||||
|
Digest: desc.Digest,
|
||||||
|
Labels: map[string]string{
|
||||||
|
key: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = manager.Update(ctx, info, fmt.Sprintf("labels.%s", key))
|
||||||
|
return nil, err
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendDistributionSourceLabel(originLabel, repo string) string {
|
||||||
|
repos := []string{}
|
||||||
|
if originLabel != "" {
|
||||||
|
repos = strings.Split(originLabel, ",")
|
||||||
|
}
|
||||||
|
repos = append(repos, repo)
|
||||||
|
|
||||||
|
// use emtpy string to present duplicate items
|
||||||
|
for i := 1; i < len(repos); i++ {
|
||||||
|
tmp, j := repos[i], i-1
|
||||||
|
for ; j >= 0 && repos[j] >= tmp; j-- {
|
||||||
|
if repos[j] == tmp {
|
||||||
|
tmp = ""
|
||||||
|
}
|
||||||
|
repos[j+1] = repos[j]
|
||||||
|
}
|
||||||
|
repos[j+1] = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for ; i < len(repos) && repos[i] == ""; i++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(repos[i:], ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func distributionSourceLabelKey(source string) string {
|
||||||
|
return fmt.Sprintf("%s.%s", labelDistributionSource, source)
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
|
"github.com/containerd/containerd/version"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -75,13 +76,16 @@ type ResolverOptions struct {
|
||||||
|
|
||||||
// Credentials provides username and secret given a host.
|
// Credentials provides username and secret given a host.
|
||||||
// If username is empty but a secret is given, that secret
|
// If username is empty but a secret is given, that secret
|
||||||
// is interpretted as a long lived token.
|
// is interpreted as a long lived token.
|
||||||
// Deprecated: use Authorizer
|
// Deprecated: use Authorizer
|
||||||
Credentials func(string) (string, string, error)
|
Credentials func(string) (string, string, error)
|
||||||
|
|
||||||
// Host provides the hostname given a namespace.
|
// Host provides the hostname given a namespace.
|
||||||
Host func(string) (string, error)
|
Host func(string) (string, error)
|
||||||
|
|
||||||
|
// Headers are the HTTP request header fields sent by the resolver
|
||||||
|
Headers http.Header
|
||||||
|
|
||||||
// PlainHTTP specifies to use plain http and not https
|
// PlainHTTP specifies to use plain http and not https
|
||||||
PlainHTTP bool
|
PlainHTTP bool
|
||||||
|
|
||||||
|
@ -105,6 +109,7 @@ func DefaultHost(ns string) (string, error) {
|
||||||
type dockerResolver struct {
|
type dockerResolver struct {
|
||||||
auth Authorizer
|
auth Authorizer
|
||||||
host func(string) (string, error)
|
host func(string) (string, error)
|
||||||
|
headers http.Header
|
||||||
plainHTTP bool
|
plainHTTP bool
|
||||||
client *http.Client
|
client *http.Client
|
||||||
tracker StatusTracker
|
tracker StatusTracker
|
||||||
|
@ -118,12 +123,27 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
|
||||||
if options.Host == nil {
|
if options.Host == nil {
|
||||||
options.Host = DefaultHost
|
options.Host = DefaultHost
|
||||||
}
|
}
|
||||||
|
if options.Headers == nil {
|
||||||
|
options.Headers = make(http.Header)
|
||||||
|
}
|
||||||
|
if _, ok := options.Headers["Accept"]; !ok {
|
||||||
|
// set headers for all the types we support for resolution.
|
||||||
|
options.Headers.Set("Accept", strings.Join([]string{
|
||||||
|
images.MediaTypeDockerSchema2Manifest,
|
||||||
|
images.MediaTypeDockerSchema2ManifestList,
|
||||||
|
ocispec.MediaTypeImageManifest,
|
||||||
|
ocispec.MediaTypeImageIndex, "*"}, ", "))
|
||||||
|
}
|
||||||
|
if _, ok := options.Headers["User-Agent"]; !ok {
|
||||||
|
options.Headers.Set("User-Agent", "containerd/"+version.Version)
|
||||||
|
}
|
||||||
if options.Authorizer == nil {
|
if options.Authorizer == nil {
|
||||||
options.Authorizer = NewAuthorizer(options.Client, options.Credentials)
|
options.Authorizer = NewAuthorizer(options.Client, options.Credentials)
|
||||||
}
|
}
|
||||||
return &dockerResolver{
|
return &dockerResolver{
|
||||||
auth: options.Authorizer,
|
auth: options.Authorizer,
|
||||||
host: options.Host,
|
host: options.Host,
|
||||||
|
headers: options.Headers,
|
||||||
plainHTTP: options.PlainHTTP,
|
plainHTTP: options.PlainHTTP,
|
||||||
client: options.Client,
|
client: options.Client,
|
||||||
tracker: options.Tracker,
|
tracker: options.Tracker,
|
||||||
|
@ -182,12 +202,7 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
|
||||||
return "", ocispec.Descriptor{}, err
|
return "", ocispec.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set headers for all the types we support for resolution.
|
req.Header = r.headers
|
||||||
req.Header.Set("Accept", strings.Join([]string{
|
|
||||||
images.MediaTypeDockerSchema2Manifest,
|
|
||||||
images.MediaTypeDockerSchema2ManifestList,
|
|
||||||
ocispec.MediaTypeImageManifest,
|
|
||||||
ocispec.MediaTypeImageIndex, "*"}, ", "))
|
|
||||||
|
|
||||||
log.G(ctx).Debug("resolving")
|
log.G(ctx).Debug("resolving")
|
||||||
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
|
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
|
||||||
|
|
|
@ -156,7 +156,7 @@ func push(ctx context.Context, provider content.Provider, pusher Pusher, desc oc
|
||||||
//
|
//
|
||||||
// Base handlers can be provided which will be called before any push specific
|
// Base handlers can be provided which will be called before any push specific
|
||||||
// handlers.
|
// handlers.
|
||||||
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, provider content.Provider, platform platforms.MatchComparer, baseHandlers ...images.Handler) error {
|
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, provider content.Provider, platform platforms.MatchComparer, wrapper func(h images.Handler) images.Handler) error {
|
||||||
var m sync.Mutex
|
var m sync.Mutex
|
||||||
manifestStack := []ocispec.Descriptor{}
|
manifestStack := []ocispec.Descriptor{}
|
||||||
|
|
||||||
|
@ -175,13 +175,16 @@ func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, pr
|
||||||
|
|
||||||
pushHandler := PushHandler(pusher, provider)
|
pushHandler := PushHandler(pusher, provider)
|
||||||
|
|
||||||
handlers := append(baseHandlers,
|
var handler images.Handler = images.Handlers(
|
||||||
images.FilterPlatforms(images.ChildrenHandler(provider), platform),
|
images.FilterPlatforms(images.ChildrenHandler(provider), platform),
|
||||||
filterHandler,
|
filterHandler,
|
||||||
pushHandler,
|
pushHandler,
|
||||||
)
|
)
|
||||||
|
if wrapper != nil {
|
||||||
|
handler = wrapper(handler)
|
||||||
|
}
|
||||||
|
|
||||||
if err := images.Dispatch(ctx, images.Handlers(handlers...), desc); err != nil {
|
if err := images.Dispatch(ctx, handler, nil, desc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,3 +206,38 @@ func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, pr
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterManifestByPlatformHandler allows Handler to handle non-target
|
||||||
|
// platform's manifest and configuration data.
|
||||||
|
func FilterManifestByPlatformHandler(f images.HandlerFunc, m platforms.Matcher) images.HandlerFunc {
|
||||||
|
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
|
children, err := f(ctx, desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// no platform information
|
||||||
|
if desc.Platform == nil || m == nil {
|
||||||
|
return children, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var descs []ocispec.Descriptor
|
||||||
|
switch desc.MediaType {
|
||||||
|
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
||||||
|
if m.Match(*desc.Platform) {
|
||||||
|
descs = children
|
||||||
|
} else {
|
||||||
|
for _, child := range children {
|
||||||
|
if child.MediaType == images.MediaTypeDockerSchema2Config ||
|
||||||
|
child.MediaType == ocispec.MediaTypeImageConfig {
|
||||||
|
|
||||||
|
descs = append(descs, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
descs = children
|
||||||
|
}
|
||||||
|
return descs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -112,6 +112,20 @@ file {
|
||||||
type: TYPE_UINT32
|
type: TYPE_UINT32
|
||||||
json_name: "ioGid"
|
json_name: "ioGid"
|
||||||
}
|
}
|
||||||
|
field {
|
||||||
|
name: "criu_work_path"
|
||||||
|
number: 12
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "criuWorkPath"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "criu_image_path"
|
||||||
|
number: 13
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "criuImagePath"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
message_type {
|
message_type {
|
||||||
name: "CheckpointOptions"
|
name: "CheckpointOptions"
|
||||||
|
@ -164,6 +178,20 @@ file {
|
||||||
type: TYPE_STRING
|
type: TYPE_STRING
|
||||||
json_name: "cgroupsMode"
|
json_name: "cgroupsMode"
|
||||||
}
|
}
|
||||||
|
field {
|
||||||
|
name: "work_path"
|
||||||
|
number: 8
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "workPath"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "image_path"
|
||||||
|
number: 9
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "imagePath"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
message_type {
|
message_type {
|
||||||
name: "ProcessDetails"
|
name: "ProcessDetails"
|
||||||
|
|
|
@ -60,6 +60,8 @@ type CreateOptions struct {
|
||||||
ShimCgroup string `protobuf:"bytes,9,opt,name=shim_cgroup,json=shimCgroup,proto3" json:"shim_cgroup,omitempty"`
|
ShimCgroup string `protobuf:"bytes,9,opt,name=shim_cgroup,json=shimCgroup,proto3" json:"shim_cgroup,omitempty"`
|
||||||
IoUid uint32 `protobuf:"varint,10,opt,name=io_uid,json=ioUid,proto3" json:"io_uid,omitempty"`
|
IoUid uint32 `protobuf:"varint,10,opt,name=io_uid,json=ioUid,proto3" json:"io_uid,omitempty"`
|
||||||
IoGid uint32 `protobuf:"varint,11,opt,name=io_gid,json=ioGid,proto3" json:"io_gid,omitempty"`
|
IoGid uint32 `protobuf:"varint,11,opt,name=io_gid,json=ioGid,proto3" json:"io_gid,omitempty"`
|
||||||
|
CriuWorkPath string `protobuf:"bytes,12,opt,name=criu_work_path,json=criuWorkPath,proto3" json:"criu_work_path,omitempty"`
|
||||||
|
CriuImagePath string `protobuf:"bytes,13,opt,name=criu_image_path,json=criuImagePath,proto3" json:"criu_image_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CreateOptions) Reset() { *m = CreateOptions{} }
|
func (m *CreateOptions) Reset() { *m = CreateOptions{} }
|
||||||
|
@ -74,6 +76,8 @@ type CheckpointOptions struct {
|
||||||
FileLocks bool `protobuf:"varint,5,opt,name=file_locks,json=fileLocks,proto3" json:"file_locks,omitempty"`
|
FileLocks bool `protobuf:"varint,5,opt,name=file_locks,json=fileLocks,proto3" json:"file_locks,omitempty"`
|
||||||
EmptyNamespaces []string `protobuf:"bytes,6,rep,name=empty_namespaces,json=emptyNamespaces" json:"empty_namespaces,omitempty"`
|
EmptyNamespaces []string `protobuf:"bytes,6,rep,name=empty_namespaces,json=emptyNamespaces" json:"empty_namespaces,omitempty"`
|
||||||
CgroupsMode string `protobuf:"bytes,7,opt,name=cgroups_mode,json=cgroupsMode,proto3" json:"cgroups_mode,omitempty"`
|
CgroupsMode string `protobuf:"bytes,7,opt,name=cgroups_mode,json=cgroupsMode,proto3" json:"cgroups_mode,omitempty"`
|
||||||
|
WorkPath string `protobuf:"bytes,8,opt,name=work_path,json=workPath,proto3" json:"work_path,omitempty"`
|
||||||
|
ImagePath string `protobuf:"bytes,9,opt,name=image_path,json=imagePath,proto3" json:"image_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CheckpointOptions) Reset() { *m = CheckpointOptions{} }
|
func (m *CheckpointOptions) Reset() { *m = CheckpointOptions{} }
|
||||||
|
@ -252,6 +256,18 @@ func (m *CreateOptions) MarshalTo(dAtA []byte) (int, error) {
|
||||||
i++
|
i++
|
||||||
i = encodeVarintRunc(dAtA, i, uint64(m.IoGid))
|
i = encodeVarintRunc(dAtA, i, uint64(m.IoGid))
|
||||||
}
|
}
|
||||||
|
if len(m.CriuWorkPath) > 0 {
|
||||||
|
dAtA[i] = 0x62
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.CriuWorkPath)))
|
||||||
|
i += copy(dAtA[i:], m.CriuWorkPath)
|
||||||
|
}
|
||||||
|
if len(m.CriuImagePath) > 0 {
|
||||||
|
dAtA[i] = 0x6a
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.CriuImagePath)))
|
||||||
|
i += copy(dAtA[i:], m.CriuImagePath)
|
||||||
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,6 +357,18 @@ func (m *CheckpointOptions) MarshalTo(dAtA []byte) (int, error) {
|
||||||
i = encodeVarintRunc(dAtA, i, uint64(len(m.CgroupsMode)))
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.CgroupsMode)))
|
||||||
i += copy(dAtA[i:], m.CgroupsMode)
|
i += copy(dAtA[i:], m.CgroupsMode)
|
||||||
}
|
}
|
||||||
|
if len(m.WorkPath) > 0 {
|
||||||
|
dAtA[i] = 0x42
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.WorkPath)))
|
||||||
|
i += copy(dAtA[i:], m.WorkPath)
|
||||||
|
}
|
||||||
|
if len(m.ImagePath) > 0 {
|
||||||
|
dAtA[i] = 0x4a
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.ImagePath)))
|
||||||
|
i += copy(dAtA[i:], m.ImagePath)
|
||||||
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,6 +467,14 @@ func (m *CreateOptions) Size() (n int) {
|
||||||
if m.IoGid != 0 {
|
if m.IoGid != 0 {
|
||||||
n += 1 + sovRunc(uint64(m.IoGid))
|
n += 1 + sovRunc(uint64(m.IoGid))
|
||||||
}
|
}
|
||||||
|
l = len(m.CriuWorkPath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
|
l = len(m.CriuImagePath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,6 +506,14 @@ func (m *CheckpointOptions) Size() (n int) {
|
||||||
if l > 0 {
|
if l > 0 {
|
||||||
n += 1 + l + sovRunc(uint64(l))
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
}
|
}
|
||||||
|
l = len(m.WorkPath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
|
l = len(m.ImagePath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,6 +569,8 @@ func (this *CreateOptions) String() string {
|
||||||
`ShimCgroup:` + fmt.Sprintf("%v", this.ShimCgroup) + `,`,
|
`ShimCgroup:` + fmt.Sprintf("%v", this.ShimCgroup) + `,`,
|
||||||
`IoUid:` + fmt.Sprintf("%v", this.IoUid) + `,`,
|
`IoUid:` + fmt.Sprintf("%v", this.IoUid) + `,`,
|
||||||
`IoGid:` + fmt.Sprintf("%v", this.IoGid) + `,`,
|
`IoGid:` + fmt.Sprintf("%v", this.IoGid) + `,`,
|
||||||
|
`CriuWorkPath:` + fmt.Sprintf("%v", this.CriuWorkPath) + `,`,
|
||||||
|
`CriuImagePath:` + fmt.Sprintf("%v", this.CriuImagePath) + `,`,
|
||||||
`}`,
|
`}`,
|
||||||
}, "")
|
}, "")
|
||||||
return s
|
return s
|
||||||
|
@ -541,6 +587,8 @@ func (this *CheckpointOptions) String() string {
|
||||||
`FileLocks:` + fmt.Sprintf("%v", this.FileLocks) + `,`,
|
`FileLocks:` + fmt.Sprintf("%v", this.FileLocks) + `,`,
|
||||||
`EmptyNamespaces:` + fmt.Sprintf("%v", this.EmptyNamespaces) + `,`,
|
`EmptyNamespaces:` + fmt.Sprintf("%v", this.EmptyNamespaces) + `,`,
|
||||||
`CgroupsMode:` + fmt.Sprintf("%v", this.CgroupsMode) + `,`,
|
`CgroupsMode:` + fmt.Sprintf("%v", this.CgroupsMode) + `,`,
|
||||||
|
`WorkPath:` + fmt.Sprintf("%v", this.WorkPath) + `,`,
|
||||||
|
`ImagePath:` + fmt.Sprintf("%v", this.ImagePath) + `,`,
|
||||||
`}`,
|
`}`,
|
||||||
}, "")
|
}, "")
|
||||||
return s
|
return s
|
||||||
|
@ -994,6 +1042,64 @@ func (m *CreateOptions) Unmarshal(dAtA []byte) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 12:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field CriuWorkPath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.CriuWorkPath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 13:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field CriuImagePath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.CriuImagePath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
default:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipRunc(dAtA[iNdEx:])
|
skippy, err := skipRunc(dAtA[iNdEx:])
|
||||||
|
@ -1202,6 +1308,64 @@ func (m *CheckpointOptions) Unmarshal(dAtA []byte) error {
|
||||||
}
|
}
|
||||||
m.CgroupsMode = string(dAtA[iNdEx:postIndex])
|
m.CgroupsMode = string(dAtA[iNdEx:postIndex])
|
||||||
iNdEx = postIndex
|
iNdEx = postIndex
|
||||||
|
case 8:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field WorkPath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.WorkPath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 9:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field ImagePath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.ImagePath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
default:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipRunc(dAtA[iNdEx:])
|
skippy, err := skipRunc(dAtA[iNdEx:])
|
||||||
|
@ -1412,39 +1576,43 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptorRunc = []byte{
|
var fileDescriptorRunc = []byte{
|
||||||
// 541 bytes of a gzipped FileDescriptorProto
|
// 604 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x93, 0xc1, 0x6e, 0xd3, 0x40,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x94, 0xcf, 0x6e, 0xd3, 0x40,
|
||||||
0x10, 0x86, 0x6b, 0xda, 0x26, 0xce, 0xa4, 0x29, 0xb0, 0x50, 0xc9, 0x14, 0x91, 0x86, 0x00, 0x52,
|
0x10, 0xc6, 0xeb, 0xfe, 0x49, 0x9c, 0x49, 0xd2, 0xc2, 0x42, 0x25, 0xd3, 0xaa, 0x69, 0x08, 0x7f,
|
||||||
0xb8, 0xa4, 0x12, 0x88, 0x13, 0xb7, 0xa6, 0x08, 0x55, 0x40, 0xa9, 0x0c, 0x95, 0x10, 0x42, 0x5a,
|
0x14, 0x2e, 0xa9, 0x04, 0xe2, 0xc4, 0xad, 0x29, 0x42, 0x15, 0x50, 0x2a, 0x43, 0x05, 0x42, 0x48,
|
||||||
0xb9, 0xeb, 0x21, 0x59, 0xc5, 0xde, 0x59, 0x79, 0xd7, 0xd4, 0xb9, 0xf5, 0x09, 0x78, 0xae, 0x1e,
|
0x2b, 0x77, 0x3d, 0x24, 0xab, 0xc4, 0x3b, 0x96, 0x77, 0x4d, 0x92, 0x1b, 0x4f, 0xc0, 0x0b, 0xf1,
|
||||||
0x39, 0x72, 0x42, 0x34, 0x2f, 0x02, 0xf2, 0xda, 0x0e, 0x9c, 0x39, 0x72, 0xfb, 0xe7, 0xfb, 0xc7,
|
0x02, 0x3d, 0x21, 0x8e, 0x9c, 0x10, 0xcd, 0x93, 0xa0, 0x5d, 0xc7, 0x69, 0xcf, 0x1c, 0xb9, 0xcd,
|
||||||
0x9e, 0xd1, 0xbf, 0x1a, 0x98, 0x4c, 0xa5, 0x9d, 0xe5, 0x67, 0x63, 0x41, 0xe9, 0xbe, 0x20, 0x65,
|
0xfc, 0xe6, 0xb3, 0x67, 0xf4, 0x7d, 0xb2, 0xa1, 0x3f, 0x90, 0x66, 0x98, 0x9f, 0xf7, 0x04, 0x25,
|
||||||
0x23, 0xa9, 0x30, 0x8b, 0xff, 0x96, 0x59, 0xae, 0xac, 0x4c, 0x71, 0x3f, 0x91, 0x2a, 0x2f, 0xca,
|
0x07, 0x82, 0x94, 0x89, 0xa4, 0xc2, 0x2c, 0xbe, 0x5e, 0x66, 0xb9, 0x32, 0x32, 0xc1, 0x83, 0xb1,
|
||||||
0x4a, 0xd8, 0x85, 0x46, 0xe3, 0xd4, 0x58, 0x67, 0x64, 0x89, 0xed, 0xfc, 0x69, 0x1f, 0xbb, 0xb6,
|
0x54, 0xf9, 0xd4, 0x76, 0xc2, 0xcc, 0x52, 0xd4, 0xae, 0xea, 0xa5, 0x19, 0x19, 0x62, 0xdb, 0x57,
|
||||||
0x71, 0x69, 0xee, 0xde, 0x9e, 0xd2, 0x94, 0x5c, 0xc7, 0x7e, 0xa9, 0xaa, 0xe6, 0xe1, 0x57, 0x0f,
|
0xf2, 0x9e, 0x93, 0xf5, 0xec, 0x70, 0xe7, 0xf6, 0x80, 0x06, 0xe4, 0x14, 0x07, 0xb6, 0x2a, 0xc4,
|
||||||
0xba, 0x61, 0xae, 0xc4, 0x5b, 0x6d, 0x25, 0x29, 0xc3, 0x02, 0x68, 0xd7, 0x23, 0x02, 0x6f, 0xe0,
|
0x9d, 0x6f, 0x1e, 0xd4, 0xc3, 0x5c, 0x89, 0x37, 0xa9, 0x91, 0xa4, 0x34, 0x0b, 0xa0, 0xba, 0x58,
|
||||||
0x8d, 0x3a, 0x61, 0x53, 0xb2, 0xfb, 0xb0, 0x55, 0x4b, 0x9e, 0x11, 0xd9, 0xe0, 0x9a, 0xb3, 0xbb,
|
0x11, 0x78, 0x6d, 0xaf, 0x5b, 0x0b, 0xcb, 0x96, 0xdd, 0x85, 0xc6, 0xa2, 0xe4, 0x19, 0x91, 0x09,
|
||||||
0x35, 0x0b, 0x89, 0x2c, 0xbb, 0x0b, 0x1d, 0x91, 0xc9, 0x9c, 0xeb, 0xc8, 0xce, 0x82, 0x75, 0xe7,
|
0x56, 0xdd, 0xb8, 0xbe, 0x60, 0x21, 0x91, 0x61, 0xbb, 0x50, 0x13, 0x99, 0xcc, 0x79, 0x1a, 0x99,
|
||||||
0xfb, 0x25, 0x38, 0x89, 0xec, 0x8c, 0x3d, 0x82, 0x6d, 0xb3, 0x30, 0x16, 0xd3, 0x98, 0x8b, 0x69,
|
0x61, 0xb0, 0xe6, 0xe6, 0xbe, 0x05, 0xa7, 0x91, 0x19, 0xb2, 0x07, 0xb0, 0xa9, 0x67, 0xda, 0x60,
|
||||||
0x46, 0xb9, 0x0e, 0x36, 0x06, 0xde, 0xc8, 0x0f, 0x7b, 0x35, 0x9d, 0x38, 0x38, 0xbc, 0x58, 0x87,
|
0x12, 0x73, 0x31, 0xc8, 0x28, 0x4f, 0x83, 0xf5, 0xb6, 0xd7, 0xf5, 0xc3, 0xe6, 0x82, 0xf6, 0x1d,
|
||||||
0xde, 0x24, 0xc3, 0xc8, 0x62, 0xb3, 0xd2, 0x10, 0x7a, 0x8a, 0xb8, 0x96, 0x5f, 0xc8, 0x56, 0x93,
|
0xec, 0xfc, 0x58, 0x83, 0x66, 0x3f, 0xc3, 0xc8, 0x60, 0x79, 0x52, 0x07, 0x9a, 0x8a, 0x78, 0x2a,
|
||||||
0x3d, 0xf7, 0x5d, 0x57, 0xd1, 0x49, 0xc9, 0xdc, 0xe4, 0x3b, 0xe0, 0x93, 0x46, 0xc5, 0xad, 0xd0,
|
0xbf, 0x90, 0x29, 0x36, 0x7b, 0xee, 0xb9, 0xba, 0xa2, 0x53, 0xcb, 0xdc, 0xe6, 0x3b, 0xe0, 0x53,
|
||||||
0x6e, 0x31, 0x3f, 0x6c, 0x97, 0xf5, 0x7b, 0xa1, 0xd9, 0x13, 0xd8, 0xc1, 0xc2, 0x62, 0xa6, 0xa2,
|
0x8a, 0x8a, 0x1b, 0x91, 0xba, 0xc3, 0xfc, 0xb0, 0x6a, 0xfb, 0x77, 0x22, 0x65, 0x8f, 0x61, 0x1b,
|
||||||
0x84, 0xe7, 0x4a, 0x16, 0xdc, 0x90, 0x98, 0xa3, 0x35, 0x6e, 0x41, 0x3f, 0xbc, 0xd5, 0x98, 0xa7,
|
0xa7, 0x06, 0x33, 0x15, 0x8d, 0x79, 0xae, 0xe4, 0x94, 0x6b, 0x12, 0x23, 0x34, 0xda, 0x1d, 0xe8,
|
||||||
0x4a, 0x16, 0xef, 0x2a, 0x8b, 0xed, 0x82, 0x6f, 0x31, 0x4b, 0xa5, 0x8a, 0x92, 0x7a, 0xcb, 0x55,
|
0x87, 0xb7, 0xca, 0xe1, 0x99, 0x92, 0xd3, 0xb7, 0xc5, 0x88, 0xed, 0x80, 0x6f, 0x30, 0x4b, 0xa4,
|
||||||
0xcd, 0xee, 0x01, 0x7c, 0x96, 0x09, 0xf2, 0x84, 0xc4, 0xdc, 0x04, 0x9b, 0xce, 0xed, 0x94, 0xe4,
|
0x8a, 0xc6, 0x8b, 0x2b, 0x97, 0x3d, 0xdb, 0x03, 0xf8, 0x2c, 0xc7, 0xc8, 0xc7, 0x24, 0x46, 0x3a,
|
||||||
0x75, 0x09, 0xd8, 0x63, 0xb8, 0x81, 0xa9, 0xb6, 0x0b, 0xae, 0xa2, 0x14, 0x8d, 0x8e, 0x04, 0x9a,
|
0xd8, 0x70, 0xd3, 0x9a, 0x25, 0xaf, 0x2c, 0x60, 0x8f, 0xe0, 0x06, 0x26, 0xa9, 0x99, 0x71, 0x15,
|
||||||
0xa0, 0x35, 0x58, 0x1f, 0x75, 0xc2, 0xeb, 0x8e, 0x1f, 0xaf, 0x70, 0x99, 0x68, 0x95, 0x84, 0xe1,
|
0x25, 0xa8, 0xd3, 0x48, 0xa0, 0x0e, 0x2a, 0xed, 0xb5, 0x6e, 0x2d, 0xdc, 0x72, 0xfc, 0x64, 0x89,
|
||||||
0x29, 0xc5, 0x18, 0xb4, 0xab, 0x44, 0x6b, 0xf6, 0x86, 0x62, 0x64, 0x0f, 0x61, 0x5b, 0x11, 0x57,
|
0xad, 0xa3, 0x85, 0x13, 0x9a, 0x27, 0x14, 0x63, 0x50, 0x2d, 0x1c, 0x5d, 0xb0, 0xd7, 0x14, 0x23,
|
||||||
0x78, 0xce, 0xe7, 0xb8, 0xc8, 0xa4, 0x9a, 0x06, 0xbe, 0x1b, 0xb8, 0xa5, 0xe8, 0x18, 0xcf, 0x5f,
|
0xbb, 0x0f, 0x9b, 0x8a, 0xb8, 0xc2, 0x09, 0x1f, 0xe1, 0x2c, 0x93, 0x6a, 0x10, 0xf8, 0x6e, 0x61,
|
||||||
0x55, 0x8c, 0xed, 0x41, 0xd7, 0xcc, 0x64, 0xda, 0xe4, 0xda, 0x71, 0xff, 0x81, 0x12, 0x55, 0xa1,
|
0x43, 0xd1, 0x09, 0x4e, 0x5e, 0x16, 0x8c, 0xed, 0x43, 0x5d, 0x0f, 0x65, 0x52, 0xfa, 0x5a, 0x73,
|
||||||
0xb2, 0x1d, 0x68, 0x49, 0xe2, 0xb9, 0x8c, 0x03, 0x18, 0x78, 0xa3, 0x5e, 0xb8, 0x29, 0xe9, 0x54,
|
0xef, 0x01, 0x8b, 0x0a, 0x53, 0xd9, 0x36, 0x54, 0x24, 0xf1, 0x5c, 0xc6, 0x01, 0xb4, 0xbd, 0x6e,
|
||||||
0xc6, 0x35, 0x9e, 0xca, 0x38, 0xe8, 0x36, 0xf8, 0xa5, 0x8c, 0x87, 0xbf, 0x3c, 0xb8, 0x39, 0x99,
|
0x33, 0xdc, 0x90, 0x74, 0x26, 0xe3, 0x05, 0x1e, 0xc8, 0x38, 0xa8, 0x97, 0xf8, 0x85, 0x8c, 0xed,
|
||||||
0xa1, 0x98, 0x6b, 0x92, 0xca, 0x36, 0xcf, 0xc0, 0x60, 0x03, 0x0b, 0xd9, 0xa4, 0xef, 0xf4, 0xff,
|
0x52, 0x17, 0xe3, 0x84, 0xb2, 0x51, 0x91, 0x65, 0xc3, 0xbd, 0xb1, 0x61, 0xe9, 0x7b, 0xca, 0x46,
|
||||||
0x1a, 0xfb, 0xf0, 0x19, 0x6c, 0x9f, 0x64, 0x24, 0xd0, 0x98, 0x43, 0xb4, 0x91, 0x4c, 0x0c, 0x7b,
|
0x2e, 0xcf, 0x87, 0xb0, 0xe5, 0x54, 0x32, 0x89, 0x06, 0x58, 0xc8, 0x9a, 0x4e, 0xd6, 0xb4, 0xf8,
|
||||||
0x00, 0x6d, 0x2c, 0x50, 0x70, 0x19, 0x57, 0x77, 0x71, 0x00, 0xcb, 0x1f, 0x7b, 0xad, 0x17, 0x05,
|
0xd8, 0x52, 0xab, 0xeb, 0x7c, 0x5f, 0x85, 0x9b, 0xfd, 0x21, 0x8a, 0x51, 0x4a, 0x52, 0x99, 0x32,
|
||||||
0x8a, 0xa3, 0xc3, 0xb0, 0x55, 0x5a, 0x47, 0xf1, 0xc1, 0xa7, 0xcb, 0xab, 0xfe, 0xda, 0xf7, 0xab,
|
0x54, 0x06, 0xeb, 0x38, 0x95, 0x65, 0x96, 0xae, 0xfe, 0x6f, 0x43, 0xdc, 0x85, 0xda, 0x95, 0x95,
|
||||||
0xfe, 0xda, 0xc5, 0xb2, 0xef, 0x5d, 0x2e, 0xfb, 0xde, 0xb7, 0x65, 0xdf, 0xfb, 0xb9, 0xec, 0x7b,
|
0x7e, 0xf1, 0x59, 0x4c, 0x4a, 0x1b, 0xf7, 0x00, 0xae, 0x39, 0x58, 0x44, 0x57, 0x93, 0x4b, 0xf7,
|
||||||
0x1f, 0x0f, 0xfe, 0xf5, 0xb0, 0x9f, 0xaf, 0xd4, 0x87, 0xb5, 0xb3, 0x96, 0xbb, 0xd9, 0xa7, 0xbf,
|
0x9e, 0xc2, 0xe6, 0x69, 0x46, 0x02, 0xb5, 0x3e, 0x42, 0x13, 0xc9, 0xb1, 0x66, 0xf7, 0xa0, 0x8a,
|
||||||
0x03, 0x00, 0x00, 0xff, 0xff, 0x18, 0xa1, 0x4b, 0x5b, 0x27, 0x04, 0x00, 0x00,
|
0x53, 0x14, 0x5c, 0xc6, 0xc5, 0x17, 0x7a, 0x08, 0xf3, 0xdf, 0xfb, 0x95, 0xe7, 0x53, 0x14, 0xc7,
|
||||||
|
0x47, 0x61, 0xc5, 0x8e, 0x8e, 0xe3, 0xc3, 0x4f, 0x17, 0x97, 0xad, 0x95, 0x5f, 0x97, 0xad, 0x95,
|
||||||
|
0xaf, 0xf3, 0x96, 0x77, 0x31, 0x6f, 0x79, 0x3f, 0xe7, 0x2d, 0xef, 0xcf, 0xbc, 0xe5, 0x7d, 0x3c,
|
||||||
|
0xfc, 0xd7, 0x5f, 0xcc, 0xb3, 0x65, 0xf5, 0x61, 0xe5, 0xbc, 0xe2, 0xfe, 0x1e, 0x4f, 0xfe, 0x06,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x24, 0x6f, 0x2e, 0xb1, 0x04, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ message CreateOptions {
|
||||||
string shim_cgroup = 9;
|
string shim_cgroup = 9;
|
||||||
uint32 io_uid = 10;
|
uint32 io_uid = 10;
|
||||||
uint32 io_gid = 11;
|
uint32 io_gid = 11;
|
||||||
|
string criu_work_path = 12;
|
||||||
|
string criu_image_path = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CheckpointOptions {
|
message CheckpointOptions {
|
||||||
|
@ -35,6 +37,8 @@ message CheckpointOptions {
|
||||||
bool file_locks = 5;
|
bool file_locks = 5;
|
||||||
repeated string empty_namespaces = 6;
|
repeated string empty_namespaces = 6;
|
||||||
string cgroups_mode = 7;
|
string cgroups_mode = 7;
|
||||||
|
string work_path = 8;
|
||||||
|
string image_path = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProcessDetails {
|
message ProcessDetails {
|
||||||
|
|
17
vendor/github.com/containerd/containerd/runtime/v2/runc/options/doc.go
generated
vendored
Normal file
17
vendor/github.com/containerd/containerd/runtime/v2/runc/options/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package options
|
166
vendor/github.com/containerd/containerd/runtime/v2/runc/options/next.pb.txt
generated
vendored
Normal file
166
vendor/github.com/containerd/containerd/runtime/v2/runc/options/next.pb.txt
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
file {
|
||||||
|
name: "github.com/containerd/containerd/runtime/v2/runc/options/oci.proto"
|
||||||
|
package: "containerd.runc.v1"
|
||||||
|
dependency: "gogoproto/gogo.proto"
|
||||||
|
message_type {
|
||||||
|
name: "Options"
|
||||||
|
field {
|
||||||
|
name: "no_pivot_root"
|
||||||
|
number: 1
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "noPivotRoot"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "no_new_keyring"
|
||||||
|
number: 2
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "noNewKeyring"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "shim_cgroup"
|
||||||
|
number: 3
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "shimCgroup"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "io_uid"
|
||||||
|
number: 4
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_UINT32
|
||||||
|
json_name: "ioUid"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "io_gid"
|
||||||
|
number: 5
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_UINT32
|
||||||
|
json_name: "ioGid"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "binary_name"
|
||||||
|
number: 6
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "binaryName"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "root"
|
||||||
|
number: 7
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "root"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "criu_path"
|
||||||
|
number: 8
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "criuPath"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "systemd_cgroup"
|
||||||
|
number: 9
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "systemdCgroup"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "criu_image_path"
|
||||||
|
number: 10
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "criuImagePath"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "criu_work_path"
|
||||||
|
number: 11
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "criuWorkPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message_type {
|
||||||
|
name: "CheckpointOptions"
|
||||||
|
field {
|
||||||
|
name: "exit"
|
||||||
|
number: 1
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "exit"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "open_tcp"
|
||||||
|
number: 2
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "openTcp"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "external_unix_sockets"
|
||||||
|
number: 3
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "externalUnixSockets"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "terminal"
|
||||||
|
number: 4
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "terminal"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "file_locks"
|
||||||
|
number: 5
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "fileLocks"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "empty_namespaces"
|
||||||
|
number: 6
|
||||||
|
label: LABEL_REPEATED
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "emptyNamespaces"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "cgroups_mode"
|
||||||
|
number: 7
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "cgroupsMode"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "image_path"
|
||||||
|
number: 8
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "imagePath"
|
||||||
|
}
|
||||||
|
field {
|
||||||
|
name: "work_path"
|
||||||
|
number: 9
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "workPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message_type {
|
||||||
|
name: "ProcessDetails"
|
||||||
|
field {
|
||||||
|
name: "exec_id"
|
||||||
|
number: 1
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_STRING
|
||||||
|
json_name: "execId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options {
|
||||||
|
go_package: "github.com/containerd/containerd/runtime/v2/runc/options;options"
|
||||||
|
}
|
||||||
|
weak_dependency: 0
|
||||||
|
syntax: "proto3"
|
||||||
|
}
|
1313
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.pb.go
generated
vendored
Normal file
1313
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
58
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.proto
generated
vendored
Normal file
58
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.proto
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package containerd.runc.v1;
|
||||||
|
|
||||||
|
import weak "gogoproto/gogo.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/containerd/containerd/runtime/v2/runc/options;options";
|
||||||
|
|
||||||
|
message Options {
|
||||||
|
// disable pivot root when creating a container
|
||||||
|
bool no_pivot_root = 1;
|
||||||
|
// create a new keyring for the container
|
||||||
|
bool no_new_keyring = 2;
|
||||||
|
// place the shim in a cgroup
|
||||||
|
string shim_cgroup = 3;
|
||||||
|
// set the I/O's pipes uid
|
||||||
|
uint32 io_uid = 4;
|
||||||
|
// set the I/O's pipes gid
|
||||||
|
uint32 io_gid = 5;
|
||||||
|
// binary name of the runc binary
|
||||||
|
string binary_name = 6;
|
||||||
|
// runc root directory
|
||||||
|
string root = 7;
|
||||||
|
// criu binary path
|
||||||
|
string criu_path = 8;
|
||||||
|
// enable systemd cgroups
|
||||||
|
bool systemd_cgroup = 9;
|
||||||
|
// criu image path
|
||||||
|
string criu_image_path = 10;
|
||||||
|
// criu work path
|
||||||
|
string criu_work_path = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckpointOptions {
|
||||||
|
// exit the container after a checkpoint
|
||||||
|
bool exit = 1;
|
||||||
|
// checkpoint open tcp connections
|
||||||
|
bool open_tcp = 2;
|
||||||
|
// checkpoint external unix sockets
|
||||||
|
bool external_unix_sockets = 3;
|
||||||
|
// checkpoint terminals (ptys)
|
||||||
|
bool terminal = 4;
|
||||||
|
// allow checkpointing of file locks
|
||||||
|
bool file_locks = 5;
|
||||||
|
// restore provided namespaces as empty namespaces
|
||||||
|
repeated string empty_namespaces = 6;
|
||||||
|
// set the cgroups mode, soft, full, strict
|
||||||
|
string cgroups_mode = 7;
|
||||||
|
// checkpoint image path
|
||||||
|
string image_path = 8;
|
||||||
|
// checkpoint work path
|
||||||
|
string work_path = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProcessDetails {
|
||||||
|
// exec process id if the process is managed by a shim
|
||||||
|
string exec_id = 1;
|
||||||
|
}
|
463
vendor/github.com/containerd/containerd/services/content/contentserver/contentserver.go
generated
vendored
Normal file
463
vendor/github.com/containerd/containerd/services/content/contentserver/contentserver.go
generated
vendored
Normal file
|
@ -0,0 +1,463 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package contentserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
api "github.com/containerd/containerd/api/services/content/v1"
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
ptypes "github.com/gogo/protobuf/types"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
store content.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
buffer := make([]byte, 1<<20)
|
||||||
|
return &buffer
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns the content GRPC server
|
||||||
|
func New(cs content.Store) api.ContentServer {
|
||||||
|
return &service{store: cs}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Register(server *grpc.Server) error {
|
||||||
|
api.RegisterContentServer(server, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Info(ctx context.Context, req *api.InfoRequest) (*api.InfoResponse, error) {
|
||||||
|
if err := req.Digest.Validate(); err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "%q failed validation", req.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
bi, err := s.store.Info(ctx, req.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.InfoResponse{
|
||||||
|
Info: infoToGRPC(bi),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Update(ctx context.Context, req *api.UpdateRequest) (*api.UpdateResponse, error) {
|
||||||
|
if err := req.Info.Digest.Validate(); err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "%q failed validation", req.Info.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := s.store.Update(ctx, infoFromGRPC(req.Info), req.UpdateMask.GetPaths()...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.UpdateResponse{
|
||||||
|
Info: infoToGRPC(info),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) List(req *api.ListContentRequest, session api.Content_ListServer) error {
|
||||||
|
var (
|
||||||
|
buffer []api.Info
|
||||||
|
sendBlock = func(block []api.Info) error {
|
||||||
|
// send last block
|
||||||
|
return session.Send(&api.ListContentResponse{
|
||||||
|
Info: block,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := s.store.Walk(session.Context(), func(info content.Info) error {
|
||||||
|
buffer = append(buffer, api.Info{
|
||||||
|
Digest: info.Digest,
|
||||||
|
Size_: info.Size,
|
||||||
|
CreatedAt: info.CreatedAt,
|
||||||
|
Labels: info.Labels,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(buffer) >= 100 {
|
||||||
|
if err := sendBlock(buffer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = buffer[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, req.Filters...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buffer) > 0 {
|
||||||
|
// send last block
|
||||||
|
if err := sendBlock(buffer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Delete(ctx context.Context, req *api.DeleteContentRequest) (*ptypes.Empty, error) {
|
||||||
|
log.G(ctx).WithField("digest", req.Digest).Debugf("delete content")
|
||||||
|
if err := req.Digest.Validate(); err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.store.Delete(ctx, req.Digest); err != nil {
|
||||||
|
return nil, errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ptypes.Empty{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Read(req *api.ReadContentRequest, session api.Content_ReadServer) error {
|
||||||
|
if err := req.Digest.Validate(); err != nil {
|
||||||
|
return status.Errorf(codes.InvalidArgument, "%v: %v", req.Digest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oi, err := s.store.Info(session.Context(), req.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ra, err := s.store.ReaderAt(session.Context(), ocispec.Descriptor{Digest: req.Digest})
|
||||||
|
if err != nil {
|
||||||
|
return errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
defer ra.Close()
|
||||||
|
|
||||||
|
var (
|
||||||
|
offset = req.Offset
|
||||||
|
// size is read size, not the expected size of the blob (oi.Size), which the caller might not be aware of.
|
||||||
|
// offset+size can be larger than oi.Size.
|
||||||
|
size = req.Size_
|
||||||
|
|
||||||
|
// TODO(stevvooe): Using the global buffer pool. At 32KB, it is probably
|
||||||
|
// little inefficient for work over a fast network. We can tune this later.
|
||||||
|
p = bufPool.Get().(*[]byte)
|
||||||
|
)
|
||||||
|
defer bufPool.Put(p)
|
||||||
|
|
||||||
|
if offset < 0 {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset > oi.Size {
|
||||||
|
return status.Errorf(codes.OutOfRange, "read past object length %v bytes", oi.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 || offset+size > oi.Size {
|
||||||
|
size = oi.Size - offset
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyBuffer(
|
||||||
|
&readResponseWriter{session: session},
|
||||||
|
io.NewSectionReader(ra, offset, size), *p)
|
||||||
|
return errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readResponseWriter is a writer that places the output into ReadContentRequest messages.
|
||||||
|
//
|
||||||
|
// This allows io.CopyBuffer to do the heavy lifting of chunking the responses
|
||||||
|
// into the buffer size.
|
||||||
|
type readResponseWriter struct {
|
||||||
|
offset int64
|
||||||
|
session api.Content_ReadServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *readResponseWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if err := rw.session.Send(&api.ReadContentResponse{
|
||||||
|
Offset: rw.offset,
|
||||||
|
Data: p,
|
||||||
|
}); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.offset += int64(len(p))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Status(ctx context.Context, req *api.StatusRequest) (*api.StatusResponse, error) {
|
||||||
|
status, err := s.store.Status(ctx, req.Ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errdefs.ToGRPCf(err, "could not get status for ref %q", req.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp api.StatusResponse
|
||||||
|
resp.Status = &api.Status{
|
||||||
|
StartedAt: status.StartedAt,
|
||||||
|
UpdatedAt: status.UpdatedAt,
|
||||||
|
Ref: status.Ref,
|
||||||
|
Offset: status.Offset,
|
||||||
|
Total: status.Total,
|
||||||
|
Expected: status.Expected,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) ListStatuses(ctx context.Context, req *api.ListStatusesRequest) (*api.ListStatusesResponse, error) {
|
||||||
|
statuses, err := s.store.ListStatuses(ctx, req.Filters...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp api.ListStatusesResponse
|
||||||
|
for _, status := range statuses {
|
||||||
|
resp.Statuses = append(resp.Statuses, api.Status{
|
||||||
|
StartedAt: status.StartedAt,
|
||||||
|
UpdatedAt: status.UpdatedAt,
|
||||||
|
Ref: status.Ref,
|
||||||
|
Offset: status.Offset,
|
||||||
|
Total: status.Total,
|
||||||
|
Expected: status.Expected,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Write(session api.Content_WriteServer) (err error) {
|
||||||
|
var (
|
||||||
|
ctx = session.Context()
|
||||||
|
msg api.WriteContentResponse
|
||||||
|
req *api.WriteContentRequest
|
||||||
|
ref string
|
||||||
|
total int64
|
||||||
|
expected digest.Digest
|
||||||
|
)
|
||||||
|
|
||||||
|
defer func(msg *api.WriteContentResponse) {
|
||||||
|
// pump through the last message if no error was encountered
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok && s.Code() != codes.AlreadyExists {
|
||||||
|
// TODO(stevvooe): Really need a log line here to track which
|
||||||
|
// errors are actually causing failure on the server side. May want
|
||||||
|
// to configure the service with an interceptor to make this work
|
||||||
|
// identically across all GRPC methods.
|
||||||
|
//
|
||||||
|
// This is pretty noisy, so we can remove it but leave it for now.
|
||||||
|
log.G(ctx).WithError(err).Error("(*service).Write failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.Send(msg)
|
||||||
|
}(&msg)
|
||||||
|
|
||||||
|
// handle the very first request!
|
||||||
|
req, err = session.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref = req.Ref
|
||||||
|
|
||||||
|
if ref == "" {
|
||||||
|
return status.Errorf(codes.InvalidArgument, "first message must have a reference")
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := logrus.Fields{
|
||||||
|
"ref": ref,
|
||||||
|
}
|
||||||
|
total = req.Total
|
||||||
|
expected = req.Expected
|
||||||
|
if total > 0 {
|
||||||
|
fields["total"] = total
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected != "" {
|
||||||
|
fields["expected"] = expected
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(fields))
|
||||||
|
|
||||||
|
log.G(ctx).Debug("(*service).Write started")
|
||||||
|
// this action locks the writer for the session.
|
||||||
|
wr, err := s.store.Writer(ctx,
|
||||||
|
content.WithRef(ref),
|
||||||
|
content.WithDescriptor(ocispec.Descriptor{Size: total, Digest: expected}))
|
||||||
|
if err != nil {
|
||||||
|
return errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
defer wr.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
msg.Action = req.Action
|
||||||
|
ws, err := wr.Status()
|
||||||
|
if err != nil {
|
||||||
|
return errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Offset = ws.Offset // always set the offset.
|
||||||
|
|
||||||
|
// NOTE(stevvooe): In general, there are two cases underwhich a remote
|
||||||
|
// writer is used.
|
||||||
|
//
|
||||||
|
// For pull, we almost always have this before fetching large content,
|
||||||
|
// through descriptors. We allow predeclaration of the expected size
|
||||||
|
// and digest.
|
||||||
|
//
|
||||||
|
// For push, it is more complex. If we want to cut through content into
|
||||||
|
// storage, we may have no expectation until we are done processing the
|
||||||
|
// content. The case here is the following:
|
||||||
|
//
|
||||||
|
// 1. Start writing content.
|
||||||
|
// 2. Compress inline.
|
||||||
|
// 3. Validate digest and size (maybe).
|
||||||
|
//
|
||||||
|
// Supporting these two paths is quite awkward but it lets both API
|
||||||
|
// users use the same writer style for each with a minimum of overhead.
|
||||||
|
if req.Expected != "" {
|
||||||
|
if expected != "" && expected != req.Expected {
|
||||||
|
log.G(ctx).Debugf("commit digest differs from writer digest: %v != %v", req.Expected, expected)
|
||||||
|
}
|
||||||
|
expected = req.Expected
|
||||||
|
|
||||||
|
if _, err := s.store.Info(session.Context(), req.Expected); err == nil {
|
||||||
|
if err := wr.Close(); err != nil {
|
||||||
|
log.G(ctx).WithError(err).Error("failed to close writer")
|
||||||
|
}
|
||||||
|
if err := s.store.Abort(session.Context(), ref); err != nil {
|
||||||
|
log.G(ctx).WithError(err).Error("failed to abort write")
|
||||||
|
}
|
||||||
|
|
||||||
|
return status.Errorf(codes.AlreadyExists, "blob with expected digest %v exists", req.Expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Total > 0 {
|
||||||
|
// Update the expected total. Typically, this could be seen at
|
||||||
|
// negotiation time or on a commit message.
|
||||||
|
if total > 0 && req.Total != total {
|
||||||
|
log.G(ctx).Debugf("commit size differs from writer size: %v != %v", req.Total, total)
|
||||||
|
}
|
||||||
|
total = req.Total
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.Action {
|
||||||
|
case api.WriteActionStat:
|
||||||
|
msg.Digest = wr.Digest()
|
||||||
|
msg.StartedAt = ws.StartedAt
|
||||||
|
msg.UpdatedAt = ws.UpdatedAt
|
||||||
|
msg.Total = total
|
||||||
|
case api.WriteActionWrite, api.WriteActionCommit:
|
||||||
|
if req.Offset > 0 {
|
||||||
|
// validate the offset if provided
|
||||||
|
if req.Offset != ws.Offset {
|
||||||
|
return status.Errorf(codes.OutOfRange, "write @%v must occur at current offset %v", req.Offset, ws.Offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Offset == 0 && ws.Offset > 0 {
|
||||||
|
if err := wr.Truncate(req.Offset); err != nil {
|
||||||
|
return errors.Wrapf(err, "truncate failed")
|
||||||
|
}
|
||||||
|
msg.Offset = req.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue the write if we actually have data.
|
||||||
|
if len(req.Data) > 0 {
|
||||||
|
// While this looks like we could use io.WriterAt here, because we
|
||||||
|
// maintain the offset as append only, we just issue the write.
|
||||||
|
n, err := wr.Write(req.Data)
|
||||||
|
if err != nil {
|
||||||
|
return errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(req.Data) {
|
||||||
|
// TODO(stevvooe): Perhaps, we can recover this by including it
|
||||||
|
// in the offset on the write return.
|
||||||
|
return status.Errorf(codes.DataLoss, "wrote %v of %v bytes", n, len(req.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Offset += int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Action == api.WriteActionCommit {
|
||||||
|
var opts []content.Opt
|
||||||
|
if req.Labels != nil {
|
||||||
|
opts = append(opts, content.WithLabels(req.Labels))
|
||||||
|
}
|
||||||
|
if err := wr.Commit(ctx, total, expected, opts...); err != nil {
|
||||||
|
return errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Digest = wr.Digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := session.Send(&msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err = session.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Abort(ctx context.Context, req *api.AbortRequest) (*ptypes.Empty, error) {
|
||||||
|
if err := s.store.Abort(ctx, req.Ref); err != nil {
|
||||||
|
return nil, errdefs.ToGRPC(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ptypes.Empty{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func infoToGRPC(info content.Info) api.Info {
|
||||||
|
return api.Info{
|
||||||
|
Digest: info.Digest,
|
||||||
|
Size_: info.Size,
|
||||||
|
CreatedAt: info.CreatedAt,
|
||||||
|
UpdatedAt: info.UpdatedAt,
|
||||||
|
Labels: info.Labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func infoFromGRPC(info api.Info) content.Info {
|
||||||
|
return content.Info{
|
||||||
|
Digest: info.Digest,
|
||||||
|
Size: info.Size_,
|
||||||
|
CreatedAt: info.CreatedAt,
|
||||||
|
UpdatedAt: info.UpdatedAt,
|
||||||
|
Labels: info.Labels,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright The containerd Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
var signalMap = map[string]syscall.Signal{
|
|
||||||
"ABRT": unix.SIGABRT,
|
|
||||||
"ALRM": unix.SIGALRM,
|
|
||||||
"BUS": unix.SIGBUS,
|
|
||||||
"CHLD": unix.SIGCHLD,
|
|
||||||
"CLD": unix.SIGCLD,
|
|
||||||
"CONT": unix.SIGCONT,
|
|
||||||
"FPE": unix.SIGFPE,
|
|
||||||
"HUP": unix.SIGHUP,
|
|
||||||
"ILL": unix.SIGILL,
|
|
||||||
"INT": unix.SIGINT,
|
|
||||||
"IO": unix.SIGIO,
|
|
||||||
"IOT": unix.SIGIOT,
|
|
||||||
"KILL": unix.SIGKILL,
|
|
||||||
"PIPE": unix.SIGPIPE,
|
|
||||||
"POLL": unix.SIGPOLL,
|
|
||||||
"PROF": unix.SIGPROF,
|
|
||||||
"PWR": unix.SIGPWR,
|
|
||||||
"QUIT": unix.SIGQUIT,
|
|
||||||
"SEGV": unix.SIGSEGV,
|
|
||||||
"STKFLT": unix.SIGSTKFLT,
|
|
||||||
"STOP": unix.SIGSTOP,
|
|
||||||
"SYS": unix.SIGSYS,
|
|
||||||
"TERM": unix.SIGTERM,
|
|
||||||
"TRAP": unix.SIGTRAP,
|
|
||||||
"TSTP": unix.SIGTSTP,
|
|
||||||
"TTIN": unix.SIGTTIN,
|
|
||||||
"TTOU": unix.SIGTTOU,
|
|
||||||
"URG": unix.SIGURG,
|
|
||||||
"USR1": unix.SIGUSR1,
|
|
||||||
"USR2": unix.SIGUSR2,
|
|
||||||
"VTALRM": unix.SIGVTALRM,
|
|
||||||
"WINCH": unix.SIGWINCH,
|
|
||||||
"XCPU": unix.SIGXCPU,
|
|
||||||
"XFSZ": unix.SIGXFSZ,
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
// +build darwin freebsd solaris
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright The containerd Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containerd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
var signalMap = map[string]syscall.Signal{
|
|
||||||
"ABRT": unix.SIGABRT,
|
|
||||||
"ALRM": unix.SIGALRM,
|
|
||||||
"BUS": unix.SIGBUS,
|
|
||||||
"CHLD": unix.SIGCHLD,
|
|
||||||
"CONT": unix.SIGCONT,
|
|
||||||
"FPE": unix.SIGFPE,
|
|
||||||
"HUP": unix.SIGHUP,
|
|
||||||
"ILL": unix.SIGILL,
|
|
||||||
"INT": unix.SIGINT,
|
|
||||||
"IO": unix.SIGIO,
|
|
||||||
"IOT": unix.SIGIOT,
|
|
||||||
"KILL": unix.SIGKILL,
|
|
||||||
"PIPE": unix.SIGPIPE,
|
|
||||||
"PROF": unix.SIGPROF,
|
|
||||||
"QUIT": unix.SIGQUIT,
|
|
||||||
"SEGV": unix.SIGSEGV,
|
|
||||||
"STOP": unix.SIGSTOP,
|
|
||||||
"SYS": unix.SIGSYS,
|
|
||||||
"TERM": unix.SIGTERM,
|
|
||||||
"TRAP": unix.SIGTRAP,
|
|
||||||
"TSTP": unix.SIGTSTP,
|
|
||||||
"TTIN": unix.SIGTTIN,
|
|
||||||
"TTOU": unix.SIGTTOU,
|
|
||||||
"URG": unix.SIGURG,
|
|
||||||
"USR1": unix.SIGUSR1,
|
|
||||||
"USR2": unix.SIGUSR2,
|
|
||||||
"VTALRM": unix.SIGVTALRM,
|
|
||||||
"WINCH": unix.SIGWINCH,
|
|
||||||
"XCPU": unix.SIGXCPU,
|
|
||||||
"XFSZ": unix.SIGXFSZ,
|
|
||||||
}
|
|
|
@ -20,13 +20,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StopSignalLabel is a well-known containerd label for storing the stop
|
// StopSignalLabel is a well-known containerd label for storing the stop
|
||||||
|
@ -83,23 +81,3 @@ func GetOCIStopSignal(ctx context.Context, image Image, defaultSignal string) (s
|
||||||
|
|
||||||
return config.StopSignal, nil
|
return config.StopSignal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSignal parses a given string into a syscall.Signal
|
|
||||||
// it checks that the signal exists in the platform-appropriate signalMap
|
|
||||||
func ParseSignal(rawSignal string) (syscall.Signal, error) {
|
|
||||||
s, err := strconv.Atoi(rawSignal)
|
|
||||||
if err == nil {
|
|
||||||
sig := syscall.Signal(s)
|
|
||||||
for _, msig := range signalMap {
|
|
||||||
if sig == msig {
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
|
||||||
}
|
|
||||||
signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
|
|
||||||
if !ok {
|
|
||||||
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
|
||||||
}
|
|
||||||
return signal, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseSignal parses a given string into a syscall.Signal
|
||||||
|
// the rawSignal can be a string with "SIG" prefix,
|
||||||
|
// or a signal number in string format.
|
||||||
|
func ParseSignal(rawSignal string) (syscall.Signal, error) {
|
||||||
|
s, err := strconv.Atoi(rawSignal)
|
||||||
|
if err == nil {
|
||||||
|
signal := syscall.Signal(s)
|
||||||
|
if unix.SignalName(signal) != "" {
|
||||||
|
return signal, nil
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
||||||
|
}
|
||||||
|
signal := unix.SignalNum(strings.ToUpper(rawSignal))
|
||||||
|
if signal == 0 {
|
||||||
|
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
||||||
|
}
|
||||||
|
return signal, nil
|
||||||
|
}
|
|
@ -17,6 +17,9 @@
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
|
@ -37,3 +40,24 @@ var signalMap = map[string]syscall.Signal{
|
||||||
"ALRM": syscall.Signal(windows.SIGALRM),
|
"ALRM": syscall.Signal(windows.SIGALRM),
|
||||||
"TERM": syscall.Signal(windows.SIGTERM),
|
"TERM": syscall.Signal(windows.SIGTERM),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseSignal parses a given string into a syscall.Signal
|
||||||
|
// the rawSignal can be a string with "SIG" prefix,
|
||||||
|
// or a signal number in string format.
|
||||||
|
func ParseSignal(rawSignal string) (syscall.Signal, error) {
|
||||||
|
s, err := strconv.Atoi(rawSignal)
|
||||||
|
if err == nil {
|
||||||
|
sig := syscall.Signal(s)
|
||||||
|
for _, msig := range signalMap {
|
||||||
|
if sig == msig {
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
||||||
|
}
|
||||||
|
signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
|
||||||
|
if !ok {
|
||||||
|
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
||||||
|
}
|
||||||
|
return signal, nil
|
||||||
|
}
|
|
@ -160,9 +160,13 @@ func (u *Usage) Add(other Usage) {
|
||||||
// layerPath, tmpDir := getLayerPath(), mkTmpDir() // just a path to layer tar file.
|
// layerPath, tmpDir := getLayerPath(), mkTmpDir() // just a path to layer tar file.
|
||||||
//
|
//
|
||||||
// We start by using a Snapshotter to Prepare a new snapshot transaction, using a
|
// We start by using a Snapshotter to Prepare a new snapshot transaction, using a
|
||||||
// key and descending from the empty parent "":
|
// key and descending from the empty parent "". To prevent our layer from being
|
||||||
|
// garbage collected during unpacking, we add the `containerd.io/gc.root` label:
|
||||||
//
|
//
|
||||||
// mounts, err := snapshotter.Prepare(ctx, key, "")
|
// noGcOpt := snapshots.WithLabels(map[string]string{
|
||||||
|
// "containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
|
||||||
|
// })
|
||||||
|
// mounts, err := snapshotter.Prepare(ctx, key, "", noGcOpt)
|
||||||
// if err != nil { ... }
|
// if err != nil { ... }
|
||||||
//
|
//
|
||||||
// We get back a list of mounts from Snapshotter.Prepare, with the key identifying
|
// We get back a list of mounts from Snapshotter.Prepare, with the key identifying
|
||||||
|
@ -191,15 +195,13 @@ func (u *Usage) Add(other Usage) {
|
||||||
//
|
//
|
||||||
// Now that we've verified and unpacked our layer, we commit the active
|
// Now that we've verified and unpacked our layer, we commit the active
|
||||||
// snapshot to a name. For this example, we are just going to use the layer
|
// snapshot to a name. For this example, we are just going to use the layer
|
||||||
// digest, but in practice, this will probably be the ChainID:
|
// digest, but in practice, this will probably be the ChainID. This also removes
|
||||||
|
// the active snapshot:
|
||||||
//
|
//
|
||||||
// if err := snapshotter.Commit(ctx, digest.String(), key); err != nil { ... }
|
// if err := snapshotter.Commit(ctx, digest.String(), key, noGcOpt); err != nil { ... }
|
||||||
//
|
//
|
||||||
// Now, we have a layer in the Snapshotter that can be accessed with the digest
|
// Now, we have a layer in the Snapshotter that can be accessed with the digest
|
||||||
// provided during commit. Once you have committed the snapshot, the active
|
// provided during commit.
|
||||||
// snapshot can be removed with the following:
|
|
||||||
//
|
|
||||||
// snapshotter.Remove(ctx, key)
|
|
||||||
//
|
//
|
||||||
// Importing the Next Layer
|
// Importing the Next Layer
|
||||||
//
|
//
|
||||||
|
@ -207,7 +209,7 @@ func (u *Usage) Add(other Usage) {
|
||||||
// above except that the parent is provided as parent when calling
|
// above except that the parent is provided as parent when calling
|
||||||
// Manager.Prepare, assuming a clean, unique key identifier:
|
// Manager.Prepare, assuming a clean, unique key identifier:
|
||||||
//
|
//
|
||||||
// mounts, err := snapshotter.Prepare(ctx, key, parentDigest)
|
// mounts, err := snapshotter.Prepare(ctx, key, parentDigest, noGcOpt)
|
||||||
//
|
//
|
||||||
// We then mount, apply and commit, as we did above. The new snapshot will be
|
// We then mount, apply and commit, as we did above. The new snapshot will be
|
||||||
// based on the content of the previous one.
|
// based on the content of the previous one.
|
||||||
|
|
|
@ -37,11 +37,13 @@ import (
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/rootfs"
|
"github.com/containerd/containerd/rootfs"
|
||||||
|
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
google_protobuf "github.com/gogo/protobuf/types"
|
google_protobuf "github.com/gogo/protobuf/types"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
is "github.com/opencontainers/image-spec/specs-go"
|
is "github.com/opencontainers/image-spec/specs-go"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -115,6 +117,13 @@ type CheckpointTaskInfo struct {
|
||||||
ParentCheckpoint digest.Digest
|
ParentCheckpoint digest.Digest
|
||||||
// Options hold runtime specific settings for checkpointing a task
|
// Options hold runtime specific settings for checkpointing a task
|
||||||
Options interface{}
|
Options interface{}
|
||||||
|
|
||||||
|
runtime string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runtime name for the container
|
||||||
|
func (i *CheckpointTaskInfo) Runtime() string {
|
||||||
|
return i.runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckpointTaskOpts allows the caller to set checkpoint options
|
// CheckpointTaskOpts allows the caller to set checkpoint options
|
||||||
|
@ -129,6 +138,12 @@ type TaskInfo struct {
|
||||||
RootFS []mount.Mount
|
RootFS []mount.Mount
|
||||||
// Options hold runtime specific settings for task creation
|
// Options hold runtime specific settings for task creation
|
||||||
Options interface{}
|
Options interface{}
|
||||||
|
runtime string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runtime name for the container
|
||||||
|
func (i *TaskInfo) Runtime() string {
|
||||||
|
return i.runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task is the executable object within containerd
|
// Task is the executable object within containerd
|
||||||
|
@ -147,6 +162,8 @@ type Task interface {
|
||||||
// OCI Index that can be push and pulled from a remote resource.
|
// OCI Index that can be push and pulled from a remote resource.
|
||||||
//
|
//
|
||||||
// Additional software like CRIU maybe required to checkpoint and restore tasks
|
// Additional software like CRIU maybe required to checkpoint and restore tasks
|
||||||
|
// NOTE: Checkpoint supports to dump task information to a directory, in this way,
|
||||||
|
// an empty OCI Index will be returned.
|
||||||
Checkpoint(context.Context, ...CheckpointTaskOpts) (Image, error)
|
Checkpoint(context.Context, ...CheckpointTaskOpts) (Image, error)
|
||||||
// Update modifies executing tasks with updated settings
|
// Update modifies executing tasks with updated settings
|
||||||
Update(context.Context, ...UpdateTaskOpts) error
|
Update(context.Context, ...UpdateTaskOpts) error
|
||||||
|
@ -389,17 +406,25 @@ func (t *task) Resize(ctx context.Context, w, h uint32) error {
|
||||||
return errdefs.FromGRPC(err)
|
return errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Checkpoint supports to dump task information to a directory, in this way, an empty
|
||||||
|
// OCI Index will be returned.
|
||||||
func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Image, error) {
|
func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Image, error) {
|
||||||
ctx, done, err := t.client.WithLease(ctx)
|
ctx, done, err := t.client.WithLease(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer done(ctx)
|
defer done(ctx)
|
||||||
|
cr, err := t.client.ContainerService().Get(ctx, t.id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
request := &tasks.CheckpointTaskRequest{
|
request := &tasks.CheckpointTaskRequest{
|
||||||
ContainerID: t.id,
|
ContainerID: t.id,
|
||||||
}
|
}
|
||||||
var i CheckpointTaskInfo
|
i := CheckpointTaskInfo{
|
||||||
|
runtime: cr.Runtime.Name,
|
||||||
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
if err := o(&i); err != nil {
|
if err := o(&i); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -422,10 +447,6 @@ func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Imag
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer t.Resume(ctx)
|
defer t.Resume(ctx)
|
||||||
cr, err := t.client.ContainerService().Get(ctx, t.id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
index := v1.Index{
|
index := v1.Index{
|
||||||
Versioned: is.Versioned{
|
Versioned: is.Versioned{
|
||||||
SchemaVersion: 2,
|
SchemaVersion: 2,
|
||||||
|
@ -435,6 +456,12 @@ func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Imag
|
||||||
if err := t.checkpointTask(ctx, &index, request); err != nil {
|
if err := t.checkpointTask(ctx, &index, request); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// if checkpoint image path passed, jump checkpoint image,
|
||||||
|
// return an empty image
|
||||||
|
if isCheckpointPathExist(cr.Runtime.Name, i.Options) {
|
||||||
|
return NewImage(t.client, images.Image{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
if cr.Image != "" {
|
if cr.Image != "" {
|
||||||
if err := t.checkpointImage(ctx, &index, cr.Image); err != nil {
|
if err := t.checkpointImage(ctx, &index, cr.Image); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -544,6 +571,7 @@ func (t *task) checkpointTask(ctx context.Context, index *v1.Index, request *tas
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errdefs.FromGRPC(err)
|
return errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
|
// NOTE: response.Descriptors can be an empty slice if checkpoint image is jumped
|
||||||
// add the checkpoint descriptors to the index
|
// add the checkpoint descriptors to the index
|
||||||
for _, d := range response.Descriptors {
|
for _, d := range response.Descriptors {
|
||||||
index.Manifests = append(index.Manifests, v1.Descriptor{
|
index.Manifests = append(index.Manifests, v1.Descriptor{
|
||||||
|
@ -621,3 +649,24 @@ func writeContent(ctx context.Context, store content.Ingester, mediaType, ref st
|
||||||
Size: size,
|
Size: size,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isCheckpointPathExist only suitable for runc runtime now
|
||||||
|
func isCheckpointPathExist(runtime string, v interface{}) bool {
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime {
|
||||||
|
case plugin.RuntimeRuncV1, plugin.RuntimeRuncV2:
|
||||||
|
if opts, ok := v.(*options.CheckpointOptions); ok && opts.ImagePath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case plugin.RuntimeLinuxV1:
|
||||||
|
if opts, ok := v.(*runctypes.CheckpointOptions); ok && opts.ImagePath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ import (
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
|
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -89,6 +91,58 @@ func WithCheckpointName(name string) CheckpointTaskOpts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCheckpointImagePath sets image path for checkpoint option
|
||||||
|
func WithCheckpointImagePath(path string) CheckpointTaskOpts {
|
||||||
|
return func(r *CheckpointTaskInfo) error {
|
||||||
|
if CheckRuntime(r.Runtime(), "io.containerd.runc") {
|
||||||
|
if r.Options == nil {
|
||||||
|
r.Options = &options.CheckpointOptions{}
|
||||||
|
}
|
||||||
|
opts, ok := r.Options.(*options.CheckpointOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v2 shim checkpoint options format")
|
||||||
|
}
|
||||||
|
opts.ImagePath = path
|
||||||
|
} else {
|
||||||
|
if r.Options == nil {
|
||||||
|
r.Options = &runctypes.CheckpointOptions{}
|
||||||
|
}
|
||||||
|
opts, ok := r.Options.(*runctypes.CheckpointOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v1 shim checkpoint options format")
|
||||||
|
}
|
||||||
|
opts.ImagePath = path
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreImagePath sets image path for create option
|
||||||
|
func WithRestoreImagePath(path string) NewTaskOpts {
|
||||||
|
return func(ctx context.Context, c *Client, ti *TaskInfo) error {
|
||||||
|
if CheckRuntime(ti.Runtime(), "io.containerd.runc") {
|
||||||
|
if ti.Options == nil {
|
||||||
|
ti.Options = &options.Options{}
|
||||||
|
}
|
||||||
|
opts, ok := ti.Options.(*options.Options)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v2 shim create options format")
|
||||||
|
}
|
||||||
|
opts.CriuImagePath = path
|
||||||
|
} else {
|
||||||
|
if ti.Options == nil {
|
||||||
|
ti.Options = &runctypes.CreateOptions{}
|
||||||
|
}
|
||||||
|
opts, ok := ti.Options.(*runctypes.CreateOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v1 shim create options format")
|
||||||
|
}
|
||||||
|
opts.CriuImagePath = path
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessDeleteOpts allows the caller to set options for the deletion of a task
|
// ProcessDeleteOpts allows the caller to set options for the deletion of a task
|
||||||
type ProcessDeleteOpts func(context.Context, Process) error
|
type ProcessDeleteOpts func(context.Context, Process) error
|
||||||
|
|
||||||
|
|
|
@ -22,36 +22,58 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/containerd/containerd/runtime/linux/runctypes"
|
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage.
|
// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage.
|
||||||
// There is an upper limit on the number of keyrings in a linux system
|
// There is an upper limit on the number of keyrings in a linux system
|
||||||
func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
|
func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
|
||||||
if ti.Options == nil {
|
if CheckRuntime(ti.Runtime(), "io.containerd.runc") {
|
||||||
ti.Options = &runctypes.CreateOptions{}
|
if ti.Options == nil {
|
||||||
|
ti.Options = &options.Options{}
|
||||||
|
}
|
||||||
|
opts, ok := ti.Options.(*options.Options)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v2 shim create options format")
|
||||||
|
}
|
||||||
|
opts.NoNewKeyring = true
|
||||||
|
} else {
|
||||||
|
if ti.Options == nil {
|
||||||
|
ti.Options = &runctypes.CreateOptions{}
|
||||||
|
}
|
||||||
|
opts, ok := ti.Options.(*runctypes.CreateOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("could not cast TaskInfo Options to CreateOptions")
|
||||||
|
}
|
||||||
|
opts.NoNewKeyring = true
|
||||||
}
|
}
|
||||||
opts, ok := ti.Options.(*runctypes.CreateOptions)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("could not cast TaskInfo Options to CreateOptions")
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.NoNewKeyring = true
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithNoPivotRoot instructs the runtime not to you pivot_root
|
// WithNoPivotRoot instructs the runtime not to you pivot_root
|
||||||
func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
|
func WithNoPivotRoot(_ context.Context, _ *Client, ti *TaskInfo) error {
|
||||||
if info.Options == nil {
|
if CheckRuntime(ti.Runtime(), "io.containerd.runc") {
|
||||||
info.Options = &runctypes.CreateOptions{
|
if ti.Options == nil {
|
||||||
NoPivotRoot: true,
|
ti.Options = &options.Options{}
|
||||||
}
|
}
|
||||||
return nil
|
opts, ok := ti.Options.(*options.Options)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v2 shim create options format")
|
||||||
|
}
|
||||||
|
opts.NoPivotRoot = true
|
||||||
|
} else {
|
||||||
|
if ti.Options == nil {
|
||||||
|
ti.Options = &runctypes.CreateOptions{
|
||||||
|
NoPivotRoot: true,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
opts, ok := ti.Options.(*runctypes.CreateOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid options type, expected runctypes.CreateOptions")
|
||||||
|
}
|
||||||
|
opts.NoPivotRoot = true
|
||||||
}
|
}
|
||||||
opts, ok := info.Options.(*runctypes.CreateOptions)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("invalid options type, expected runctypes.CreateOptions")
|
|
||||||
}
|
|
||||||
opts.NoPivotRoot = true
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,31 +19,32 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0
|
||||||
github.com/gogo/protobuf v1.0.0
|
github.com/gogo/protobuf v1.0.0
|
||||||
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
|
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
|
||||||
github.com/golang/protobuf v1.1.0
|
github.com/golang/protobuf v1.1.0
|
||||||
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
|
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
|
||||||
github.com/opencontainers/runc 2b18fe1d885ee5083ef9f0838fee39b62d653e30
|
github.com/opencontainers/runc 2b18fe1d885ee5083ef9f0838fee39b62d653e30
|
||||||
github.com/sirupsen/logrus v1.0.0
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1
|
||||||
|
github.com/sirupsen/logrus v1.3.0
|
||||||
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
||||||
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
|
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
|
||||||
google.golang.org/grpc v1.12.0
|
google.golang.org/grpc v1.12.0
|
||||||
github.com/pkg/errors v0.8.0
|
github.com/pkg/errors v0.8.0
|
||||||
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
|
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
|
||||||
golang.org/x/sys 41f3e6584952bb034a481797859f6ab34b6803bd https://github.com/golang/sys
|
golang.org/x/sys d455e41777fca6e8a5a79e34a14b8368bc11d9ba https://github.com/golang/sys
|
||||||
github.com/opencontainers/image-spec v1.0.1
|
github.com/opencontainers/image-spec v1.0.1
|
||||||
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
|
||||||
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
||||||
github.com/Microsoft/go-winio v0.4.11
|
github.com/Microsoft/go-winio v0.4.12
|
||||||
github.com/Microsoft/hcsshim v0.8.1
|
github.com/Microsoft/hcsshim v0.8.5
|
||||||
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
||||||
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
|
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
|
||||||
github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a
|
github.com/containerd/ttrpc f02858b1457c5ca3aaec3a0803eb0d59f96e41d6
|
||||||
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
|
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
|
||||||
gotest.tools v2.1.0
|
gotest.tools v2.1.0
|
||||||
github.com/google/go-cmp v0.1.0
|
github.com/google/go-cmp v0.1.0
|
||||||
go.etcd.io/bbolt v1.3.1-etcd.8
|
go.etcd.io/bbolt v1.3.2
|
||||||
|
|
||||||
# cri dependencies
|
# cri dependencies
|
||||||
github.com/containerd/cri a92c40017473cbe0239ce180125f12669757e44f # release/1.2 branch
|
github.com/containerd/cri 4dd6735020f5596dd41738f8c4f5cb07fa804c5e # master
|
||||||
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
|
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
|
||||||
github.com/blang/semver v3.1.0
|
github.com/blang/semver v3.1.0
|
||||||
github.com/containernetworking/cni v0.6.0
|
github.com/containernetworking/cni v0.6.0
|
||||||
|
@ -53,8 +54,6 @@ github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
|
||||||
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
|
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
|
||||||
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
|
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
|
||||||
github.com/emicklei/go-restful v2.2.1
|
github.com/emicklei/go-restful v2.2.1
|
||||||
github.com/ghodss/yaml v1.0.0
|
|
||||||
github.com/golang/glog 44145f04b68cf362d9c4df2182967c2275eaefed
|
|
||||||
github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
|
github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
|
||||||
github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
|
github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
|
||||||
github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
|
github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
|
||||||
|
@ -73,17 +72,19 @@ golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
|
||||||
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
|
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
|
||||||
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
|
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
|
||||||
gopkg.in/yaml.v2 v2.2.1
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
k8s.io/api kubernetes-1.12.0
|
k8s.io/api kubernetes-1.13.0
|
||||||
k8s.io/apimachinery kubernetes-1.12.0
|
k8s.io/apimachinery kubernetes-1.13.0
|
||||||
k8s.io/apiserver kubernetes-1.12.0
|
k8s.io/apiserver kubernetes-1.13.0
|
||||||
k8s.io/client-go kubernetes-1.12.0
|
k8s.io/client-go kubernetes-1.13.0
|
||||||
k8s.io/kubernetes v1.12.0
|
k8s.io/klog 8139d8cb77af419532b33dfa7dd09fbc5f1d344f
|
||||||
k8s.io/utils cd34563cd63c2bd7c6fe88a73c4dcf34ed8a67cb
|
k8s.io/kubernetes v1.13.0
|
||||||
|
k8s.io/utils 0d26856f57b32ec3398579285e5c8a2bfe8c5243
|
||||||
|
sigs.k8s.io/yaml v1.1.0
|
||||||
|
|
||||||
# zfs dependencies
|
# zfs dependencies
|
||||||
github.com/containerd/zfs 9a0b8b8b5982014b729cd34eb7cd7a11062aa6ec
|
github.com/containerd/zfs 9f6ef3b1fe5144bd91fe5855b4eba81bc0d17d03
|
||||||
github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
||||||
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
||||||
|
|
||||||
# aufs dependencies
|
# aufs dependencies
|
||||||
github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92
|
github.com/containerd/aufs da3cf16bfbe68ba8f114f1536a05c01528a25434
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Package is filled at linking time
|
||||||
|
Package = "github.com/containerd/containerd"
|
||||||
|
|
||||||
|
// Version holds the complete version number. Filled in at linking time.
|
||||||
|
Version = "1.2.0+unknown"
|
||||||
|
|
||||||
|
// Revision is filled with the VCS (e.g. git) revision being used to build
|
||||||
|
// the program at linking time.
|
||||||
|
Revision = ""
|
||||||
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
bin
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
|
@ -0,0 +1 @@
|
||||||
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
|
@ -0,0 +1,33 @@
|
||||||
|
language: go
|
||||||
|
sudo: required
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
go_import_path: github.com/containerd/continuity
|
||||||
|
|
||||||
|
env:
|
||||||
|
# NOTE: we cannot set GOOS directly (because gimme overrides the value)
|
||||||
|
- TRAVIS_GOOS=windows
|
||||||
|
- TRAVIS_GOOS=linux
|
||||||
|
- TRAVIS_GOOS=darwin
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -u github.com/vbatts/git-validation
|
||||||
|
- go get -u github.com/kunalkushwaha/ltag
|
||||||
|
- go get -u github.com/LK4D4/vndr
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- pushd ..; git clone https://github.com/containerd/project; popd
|
||||||
|
|
||||||
|
script:
|
||||||
|
- export GOOS=${TRAVIS_GOOS}
|
||||||
|
- DCO_VERBOSITY=-q ../project/script/validate/dco
|
||||||
|
- ../project/script/validate/fileheader ../project/
|
||||||
|
- ../project/script/validate/vendor
|
||||||
|
- make build binaries
|
||||||
|
- if [ "$GOOS" = "linux" ]; then make vet test; fi
|
||||||
|
- if [ "$GOOS" != "linux" ]; then make test-compile; fi
|
|
@ -0,0 +1,82 @@
|
||||||
|
# Copyright The containerd Authors.
|
||||||
|
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Set an output prefix, which is the local directory if not specified
|
||||||
|
PREFIX?=$(shell pwd)
|
||||||
|
|
||||||
|
# Used to populate version variable in main package.
|
||||||
|
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
|
||||||
|
|
||||||
|
GO_LDFLAGS=-ldflags "-X `go list ./version`.Version=$(VERSION)"
|
||||||
|
|
||||||
|
PACKAGES=$(shell go list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
|
.PHONY: clean all fmt vet lint build test binaries setup
|
||||||
|
.DEFAULT: default
|
||||||
|
# skip lint at the moment
|
||||||
|
all: AUTHORS clean fmt vet fmt build test binaries
|
||||||
|
|
||||||
|
AUTHORS: .mailmap .git/HEAD
|
||||||
|
git log --format='%aN <%aE>' | sort -fu > $@
|
||||||
|
|
||||||
|
# This only needs to be generated by hand when cutting full releases.
|
||||||
|
version/version.go:
|
||||||
|
./version/version.sh > $@
|
||||||
|
|
||||||
|
${PREFIX}/bin/continuity: version/version.go $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/continuity
|
||||||
|
|
||||||
|
setup:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go get -u github.com/golang/lint/golint
|
||||||
|
|
||||||
|
generate:
|
||||||
|
go generate $(PACKAGES)
|
||||||
|
|
||||||
|
# Depends on binaries because vet will silently fail if it can't load compiled
|
||||||
|
# imports
|
||||||
|
vet: binaries
|
||||||
|
@echo "+ $@"
|
||||||
|
@go vet $(PACKAGES)
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@echo "+ $@"
|
||||||
|
@test -z "$$(gofmt -s -l . | grep -v Godeps/_workspace/src/ | grep -v vendor/ | tee /dev/stderr)" || \
|
||||||
|
echo "+ please format Go code with 'gofmt -s'"
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@echo "+ $@"
|
||||||
|
@test -z "$$(golint $(PACKAGES) | grep -v Godeps/_workspace/src/ | grep -v vendor/ |tee /dev/stderr)"
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -v ${GO_LDFLAGS} $(PACKAGES)
|
||||||
|
|
||||||
|
test:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go test $(PACKAGES)
|
||||||
|
|
||||||
|
test-compile:
|
||||||
|
@echo "+ $@"
|
||||||
|
@for pkg in $(PACKAGES); do go test -c $$pkg; done
|
||||||
|
|
||||||
|
binaries: ${PREFIX}/bin/continuity
|
||||||
|
@echo "+ $@"
|
||||||
|
@if [ x$$GOOS = xwindows ]; then echo "+ continuity -> continuity.exe"; mv ${PREFIX}/bin/continuity ${PREFIX}/bin/continuity.exe; fi
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "+ $@"
|
||||||
|
@rm -rf "${PREFIX}/bin/continuity" "${PREFIX}/bin/continuity.exe"
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
# continuity
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/containerd/continuity?status.svg)](https://godoc.org/github.com/containerd/continuity)
|
||||||
|
[![Build Status](https://travis-ci.org/containerd/continuity.svg?branch=master)](https://travis-ci.org/containerd/continuity)
|
||||||
|
|
||||||
|
A transport-agnostic, filesystem metadata manifest system
|
||||||
|
|
||||||
|
This project is a staging area for experiments in providing transport agnostic
|
||||||
|
metadata storage.
|
||||||
|
|
||||||
|
Please see https://github.com/opencontainers/specs/issues/11 for more details.
|
||||||
|
|
||||||
|
## Manifest Format
|
||||||
|
|
||||||
|
A continuity manifest encodes filesystem metadata in Protocol Buffers.
|
||||||
|
Please refer to [proto/manifest.proto](proto/manifest.proto).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a manifest (of this repo itself):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./bin/continuity build . > /tmp/a.pb
|
||||||
|
```
|
||||||
|
|
||||||
|
Dump a manifest:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./bin/continuity ls /tmp/a.pb
|
||||||
|
...
|
||||||
|
-rw-rw-r-- 270 B /.gitignore
|
||||||
|
-rw-rw-r-- 88 B /.mailmap
|
||||||
|
-rw-rw-r-- 187 B /.travis.yml
|
||||||
|
-rw-rw-r-- 359 B /AUTHORS
|
||||||
|
-rw-rw-r-- 11 kB /LICENSE
|
||||||
|
-rw-rw-r-- 1.5 kB /Makefile
|
||||||
|
...
|
||||||
|
-rw-rw-r-- 986 B /testutil_test.go
|
||||||
|
drwxrwxr-x 0 B /version
|
||||||
|
-rw-rw-r-- 478 B /version/version.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify a manifest:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./bin/continuity verify . /tmp/a.pb
|
||||||
|
```
|
||||||
|
|
||||||
|
Break the directory and restore using the manifest:
|
||||||
|
```console
|
||||||
|
$ chmod 777 Makefile
|
||||||
|
$ ./bin/continuity verify . /tmp/a.pb
|
||||||
|
2017/06/23 08:00:34 error verifying manifest: resource "/Makefile" has incorrect mode: -rwxrwxrwx != -rw-rw-r--
|
||||||
|
$ ./bin/continuity apply . /tmp/a.pb
|
||||||
|
$ stat -c %a Makefile
|
||||||
|
664
|
||||||
|
$ ./bin/continuity verify . /tmp/a.pb
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Contribution Guide
|
||||||
|
### Building Proto Package
|
||||||
|
|
||||||
|
If you change the proto file you will need to rebuild the generated Go with `go generate`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go generate ./proto
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project details
|
||||||
|
|
||||||
|
continuity is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
|
||||||
|
As a containerd sub-project, you will find the:
|
||||||
|
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
|
||||||
|
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
|
||||||
|
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
|
@ -0,0 +1,673 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package continuity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/devices"
|
||||||
|
driverpkg "github.com/containerd/continuity/driver"
|
||||||
|
"github.com/containerd/continuity/pathdriver"
|
||||||
|
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNotFound represents the resource not found
|
||||||
|
ErrNotFound = fmt.Errorf("not found")
|
||||||
|
// ErrNotSupported represents the resource not supported
|
||||||
|
ErrNotSupported = fmt.Errorf("not supported")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context represents a file system context for accessing resources. The
|
||||||
|
// responsibility of the context is to convert system specific resources to
|
||||||
|
// generic Resource objects. Most of this is safe path manipulation, as well
|
||||||
|
// as extraction of resource details.
|
||||||
|
type Context interface {
|
||||||
|
Apply(Resource) error
|
||||||
|
Verify(Resource) error
|
||||||
|
Resource(string, os.FileInfo) (Resource, error)
|
||||||
|
Walk(filepath.WalkFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SymlinkPath is intended to give the symlink target value
|
||||||
|
// in a root context. Target and linkname are absolute paths
|
||||||
|
// not under the given root.
|
||||||
|
type SymlinkPath func(root, linkname, target string) (string, error)
|
||||||
|
|
||||||
|
// ContextOptions represents options to create a new context.
|
||||||
|
type ContextOptions struct {
|
||||||
|
Digester Digester
|
||||||
|
Driver driverpkg.Driver
|
||||||
|
PathDriver pathdriver.PathDriver
|
||||||
|
Provider ContentProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// context represents a file system context for accessing resources.
|
||||||
|
// Generally, all path qualified access and system considerations should land
|
||||||
|
// here.
|
||||||
|
type context struct {
|
||||||
|
driver driverpkg.Driver
|
||||||
|
pathDriver pathdriver.PathDriver
|
||||||
|
root string
|
||||||
|
digester Digester
|
||||||
|
provider ContentProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext returns a Context associated with root. The default driver will
|
||||||
|
// be used, as returned by NewDriver.
|
||||||
|
func NewContext(root string) (Context, error) {
|
||||||
|
return NewContextWithOptions(root, ContextOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextWithOptions returns a Context associate with the root.
|
||||||
|
func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
|
||||||
|
// normalize to absolute path
|
||||||
|
pathDriver := options.PathDriver
|
||||||
|
if pathDriver == nil {
|
||||||
|
pathDriver = pathdriver.LocalPathDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
root = pathDriver.FromSlash(root)
|
||||||
|
root, err := pathDriver.Abs(pathDriver.Clean(root))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := options.Driver
|
||||||
|
if driver == nil {
|
||||||
|
driver, err = driverpkg.NewSystemDriver()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
digester := options.Digester
|
||||||
|
if digester == nil {
|
||||||
|
digester = simpleDigester{digest.Canonical}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the root directory. Need to be a little careful here. We are
|
||||||
|
// allowing a link for now, but this may have odd behavior when
|
||||||
|
// canonicalizing paths. As long as all files are opened through the link
|
||||||
|
// path, this should be okay.
|
||||||
|
fi, err := driver.Stat(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &context{
|
||||||
|
root: root,
|
||||||
|
driver: driver,
|
||||||
|
pathDriver: pathDriver,
|
||||||
|
digester: digester,
|
||||||
|
provider: options.Provider,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource returns the resource as path p, populating the entry with info
|
||||||
|
// from fi. The path p should be the path of the resource in the context,
|
||||||
|
// typically obtained through Walk or from the value of Resource.Path(). If fi
|
||||||
|
// is nil, it will be resolved.
|
||||||
|
func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
|
||||||
|
fp, err := c.fullpath(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi == nil {
|
||||||
|
fi, err = c.driver.Lstat(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base, err := newBaseResource(p, fi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
base.xattrs, err = c.resolveXAttrs(fp, fi, base)
|
||||||
|
if err == ErrNotSupported {
|
||||||
|
log.Printf("resolving xattrs on %s not supported", fp)
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): Handle windows alternate data streams.
|
||||||
|
|
||||||
|
if fi.Mode().IsRegular() {
|
||||||
|
dgst, err := c.digest(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRegularFile(*base, base.paths, fi.Size(), dgst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode().IsDir() {
|
||||||
|
return newDirectory(*base)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
// We handle relative links vs absolute links by including a
|
||||||
|
// beginning slash for absolute links. Effectively, the bundle's
|
||||||
|
// root is treated as the absolute link anchor.
|
||||||
|
target, err := c.driver.Readlink(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSymLink(*base, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeNamedPipe != 0 {
|
||||||
|
return newNamedPipe(*base, base.paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeDevice != 0 {
|
||||||
|
deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("device extraction not supported %s", fp)
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// character and block devices merely need to recover the
|
||||||
|
// major/minor device number.
|
||||||
|
major, minor, err := deviceDriver.DeviceInfo(fi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDevice(*base, base.paths, major, minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%q (%v) is not supported", fp, fi.Mode())
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) verifyMetadata(resource, target Resource) error {
|
||||||
|
if target.Mode() != resource.Mode() {
|
||||||
|
return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.UID() != resource.UID() {
|
||||||
|
return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.GID() != resource.GID() {
|
||||||
|
return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
|
||||||
|
}
|
||||||
|
|
||||||
|
if xattrer, ok := resource.(XAttrer); ok {
|
||||||
|
txattrer, tok := target.(XAttrer)
|
||||||
|
if !tok {
|
||||||
|
return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// For xattrs, only ensure that we have those defined in the resource
|
||||||
|
// and their values match. We can ignore other xattrs. In other words,
|
||||||
|
// we only verify that target has the subset defined by resource.
|
||||||
|
txattrs := txattrer.XAttrs()
|
||||||
|
for attr, value := range xattrer.XAttrs() {
|
||||||
|
tvalue, ok := txattrs[attr]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(value, tvalue) {
|
||||||
|
return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r := resource.(type) {
|
||||||
|
case RegularFile:
|
||||||
|
// TODO(stevvooe): Another reason to use a record-based approach. We
|
||||||
|
// have to do another type switch to get this to work. This could be
|
||||||
|
// fixed with an Equal function, but let's study this a little more to
|
||||||
|
// be sure.
|
||||||
|
t, ok := target.(RegularFile)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a regular file", r.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Size() != r.Size() {
|
||||||
|
return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
|
||||||
|
}
|
||||||
|
case Directory:
|
||||||
|
t, ok := target.(Directory)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a directory", t.Path())
|
||||||
|
}
|
||||||
|
case SymLink:
|
||||||
|
t, ok := target.(SymLink)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a symlink", t.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Target() != r.Target() {
|
||||||
|
return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
|
||||||
|
}
|
||||||
|
case Device:
|
||||||
|
t, ok := target.(Device)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q is not a device", t.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Major() != r.Major() || t.Minor() != r.Minor() {
|
||||||
|
return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
|
||||||
|
}
|
||||||
|
case NamedPipe:
|
||||||
|
t, ok := target.(NamedPipe)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q is not a named pipe", t.Path())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot verify resource: %v", resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the resource in the context. An error will be returned a discrepancy
|
||||||
|
// is found.
|
||||||
|
func (c *context) Verify(resource Resource) error {
|
||||||
|
fp, err := c.fullpath(resource.Path())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := c.driver.Lstat(fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := c.Resource(resource.Path(), fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Path() != resource.Path() {
|
||||||
|
return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.verifyMetadata(resource, target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
||||||
|
hardlinkKey, err := newHardlinkKey(fi)
|
||||||
|
if err == errNotAHardLink {
|
||||||
|
if len(h.Paths()) > 1 {
|
||||||
|
return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range h.Paths()[1:] {
|
||||||
|
fpLink, err := c.fullpath(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fiLink, err := c.driver.Lstat(fpLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetLink, err := c.Resource(path, fiLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hardlinkKeyLink, err := newHardlinkKey(fiLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hardlinkKeyLink != hardlinkKey {
|
||||||
|
return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.verifyMetadata(resource, targetLink); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r := resource.(type) {
|
||||||
|
case RegularFile:
|
||||||
|
t, ok := target.(RegularFile)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a regular file", r.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): This may need to get a little more sophisticated
|
||||||
|
// for digest comparison. We may want to actually calculate the
|
||||||
|
// provided digests, rather than the implementations having an
|
||||||
|
// overlap.
|
||||||
|
if !digestsMatch(t.Digests(), r.Digests()) {
|
||||||
|
return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) checkoutFile(fp string, rf RegularFile) error {
|
||||||
|
if c.provider == nil {
|
||||||
|
return fmt.Errorf("no file provider")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
r io.ReadCloser
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, dgst := range rf.Digests() {
|
||||||
|
r, err = c.provider.Reader(dgst)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("file content could not be provided: %v", err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
return atomicWriteFile(fp, r, rf.Size(), rf.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the resource to the contexts. An error will be returned if the
|
||||||
|
// operation fails. Depending on the resource type, the resource may be
|
||||||
|
// created. For resource that cannot be resolved, an error will be returned.
|
||||||
|
func (c *context) Apply(resource Resource) error {
|
||||||
|
fp, err := c.fullpath(resource.Path())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(fp, c.root) {
|
||||||
|
return fmt.Errorf("resource %v escapes root", resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chmod = true
|
||||||
|
fi, err := c.driver.Lstat(fp)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r := resource.(type) {
|
||||||
|
case RegularFile:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.checkoutFile(fp, r); err != nil {
|
||||||
|
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
chmod = false
|
||||||
|
} else {
|
||||||
|
if !fi.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
|
||||||
|
}
|
||||||
|
if fi.Size() != r.Size() {
|
||||||
|
if err := c.checkoutFile(fp, r); err != nil {
|
||||||
|
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, dgst := range r.Digests() {
|
||||||
|
f, err := os.Open(fp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failure opening file for read %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
compared, err := dgst.Algorithm().FromReader(f)
|
||||||
|
if err == nil && dgst != compared {
|
||||||
|
if err := c.checkoutFile(fp, r); err != nil {
|
||||||
|
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err1 := f.Close(); err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error checking digest for %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Directory:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if !fi.Mode().IsDir() {
|
||||||
|
return fmt.Errorf("%q should be a directory, but is not", resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
case SymLink:
|
||||||
|
var target string // only possibly set if target resource is a symlink
|
||||||
|
|
||||||
|
if fi != nil {
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
target, err = c.driver.Readlink(fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target != r.Target() {
|
||||||
|
if fi != nil {
|
||||||
|
if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.driver.Symlink(r.Target(), fp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case Device:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if (fi.Mode() & os.ModeDevice) == 0 {
|
||||||
|
return fmt.Errorf("%q should be a device, but is not", resource.Path())
|
||||||
|
} else {
|
||||||
|
major, minor, err := devices.DeviceInfo(fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if major != r.Major() || minor != r.Minor() {
|
||||||
|
if err := c.driver.Remove(fp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case NamedPipe:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if (fi.Mode() & os.ModeNamedPipe) == 0 {
|
||||||
|
return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
||||||
|
for _, path := range h.Paths() {
|
||||||
|
if path == resource.Path() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lp, err := c.fullpath(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, fi := c.driver.Lstat(lp); fi == nil {
|
||||||
|
c.driver.Remove(lp)
|
||||||
|
}
|
||||||
|
if err := c.driver.Link(fp, lp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filemode if file was not created
|
||||||
|
if chmod {
|
||||||
|
if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if xattrer, ok := resource.(XAttrer); ok {
|
||||||
|
// For xattrs, only ensure that we have those defined in the resource
|
||||||
|
// and their values are set. We can ignore other xattrs. In other words,
|
||||||
|
// we only set xattres defined by resource but never remove.
|
||||||
|
|
||||||
|
if _, ok := resource.(SymLink); ok {
|
||||||
|
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
|
||||||
|
}
|
||||||
|
if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
|
||||||
|
}
|
||||||
|
if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk provides a convenience function to call filepath.Walk correctly for
|
||||||
|
// the context. Otherwise identical to filepath.Walk, the path argument is
|
||||||
|
// corrected to be contained within the context.
|
||||||
|
func (c *context) Walk(fn filepath.WalkFunc) error {
|
||||||
|
root := c.root
|
||||||
|
fi, err := c.driver.Lstat(c.root)
|
||||||
|
if err == nil && fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
root, err = c.driver.Readlink(c.root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, err error) error {
|
||||||
|
contained, err := c.containWithRoot(p, root)
|
||||||
|
return fn(contained, fi, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullpath returns the system path for the resource, joined with the context
|
||||||
|
// root. The path p must be a part of the context.
|
||||||
|
func (c *context) fullpath(p string) (string, error) {
|
||||||
|
p = c.pathDriver.Join(c.root, p)
|
||||||
|
if !strings.HasPrefix(p, c.root) {
|
||||||
|
return "", fmt.Errorf("invalid context path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contain cleans and santizes the filesystem path p to be an absolute path,
|
||||||
|
// effectively relative to the context root.
|
||||||
|
func (c *context) contain(p string) (string, error) {
|
||||||
|
return c.containWithRoot(p, c.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// containWithRoot cleans and santizes the filesystem path p to be an absolute path,
|
||||||
|
// effectively relative to the passed root. Extra care should be used when calling this
|
||||||
|
// instead of contain. This is needed for Walk, as if context root is a symlink,
|
||||||
|
// it must be evaluated prior to the Walk
|
||||||
|
func (c *context) containWithRoot(p string, root string) (string, error) {
|
||||||
|
sanitized, err := c.pathDriver.Rel(root, p)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZOMBIES(stevvooe): In certain cases, we may want to remap these to a
|
||||||
|
// "containment error", so the caller can decide what to do.
|
||||||
|
return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// digest returns the digest of the file at path p, relative to the root.
|
||||||
|
func (c *context) digest(p string) (digest.Digest, error) {
|
||||||
|
f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return c.digester.Digest(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveXAttrs attempts to resolve the extended attributes for the resource
|
||||||
|
// at the path fp, which is the full path to the resource. If the resource
|
||||||
|
// cannot have xattrs, nil will be returned.
|
||||||
|
func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
|
||||||
|
if fi.Mode().IsRegular() || fi.Mode().IsDir() {
|
||||||
|
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
log.Println("xattr extraction not supported")
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
return xattrDriver.Getxattr(fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
log.Println("xattr extraction for symlinks not supported")
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
return lxattrDriver.LGetxattr(fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package devices
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var ErrNotSupported = fmt.Errorf("not supported")
|
|
@ -0,0 +1,74 @@
|
||||||
|
// +build linux darwin freebsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package devices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||||
|
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, fmt.Errorf("cannot extract device from os.FileInfo")
|
||||||
|
}
|
||||||
|
|
||||||
|
dev := uint64(sys.Rdev)
|
||||||
|
return uint64(unix.Major(dev)), uint64(unix.Minor(dev)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mknod provides a shortcut for syscall.Mknod
|
||||||
|
func Mknod(p string, mode os.FileMode, maj, min int) error {
|
||||||
|
var (
|
||||||
|
m = syscallMode(mode.Perm())
|
||||||
|
dev uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
if mode&os.ModeDevice != 0 {
|
||||||
|
dev = unix.Mkdev(uint32(maj), uint32(min))
|
||||||
|
|
||||||
|
if mode&os.ModeCharDevice != 0 {
|
||||||
|
m |= unix.S_IFCHR
|
||||||
|
} else {
|
||||||
|
m |= unix.S_IFBLK
|
||||||
|
}
|
||||||
|
} else if mode&os.ModeNamedPipe != 0 {
|
||||||
|
m |= unix.S_IFIFO
|
||||||
|
}
|
||||||
|
|
||||||
|
return unix.Mknod(p, m, int(dev))
|
||||||
|
}
|
||||||
|
|
||||||
|
// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
|
||||||
|
func syscallMode(i os.FileMode) (o uint32) {
|
||||||
|
o |= uint32(i.Perm())
|
||||||
|
if i&os.ModeSetuid != 0 {
|
||||||
|
o |= unix.S_ISUID
|
||||||
|
}
|
||||||
|
if i&os.ModeSetgid != 0 {
|
||||||
|
o |= unix.S_ISGID
|
||||||
|
}
|
||||||
|
if i&os.ModeSticky != 0 {
|
||||||
|
o |= unix.S_ISVTX
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -14,17 +14,14 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package archive
|
package devices
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"os"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// as at MacOS 10.12 there is apparently no way to set timestamps
|
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||||
// with nanosecond precision. We could fall back to utimes/lutimes
|
return 0, 0, errors.Wrap(ErrNotSupported, "cannot get device info on windows")
|
||||||
// and lose the precision as a temporary workaround.
|
|
||||||
func chtimes(path string, atime, mtime time.Time) error {
|
|
||||||
return errors.New("OSX missing UtimesNanoAt")
|
|
||||||
}
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package continuity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Digester produces a digest for a given read stream
|
||||||
|
type Digester interface {
|
||||||
|
Digest(io.Reader) (digest.Digest, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentProvider produces a read stream for a given digest
|
||||||
|
type ContentProvider interface {
|
||||||
|
Reader(digest.Digest) (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleDigester struct {
|
||||||
|
algorithm digest.Algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd simpleDigester) Digest(r io.Reader) (digest.Digest, error) {
|
||||||
|
digester := sd.algorithm.Digester()
|
||||||
|
|
||||||
|
if _, err := io.Copy(digester.Hash(), r); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return digester.Digest(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniqifyDigests sorts and uniqifies the provided digest, ensuring that the
|
||||||
|
// digests are not repeated and no two digests with the same algorithm have
|
||||||
|
// different values. Because a stable sort is used, this has the effect of
|
||||||
|
// "zipping" digest collections from multiple resources.
|
||||||
|
func uniqifyDigests(digests ...digest.Digest) ([]digest.Digest, error) {
|
||||||
|
sort.Stable(digestSlice(digests)) // stable sort is important for the behavior here.
|
||||||
|
seen := map[digest.Digest]struct{}{}
|
||||||
|
algs := map[digest.Algorithm][]digest.Digest{} // detect different digests.
|
||||||
|
|
||||||
|
var out []digest.Digest
|
||||||
|
// uniqify the digests
|
||||||
|
for _, d := range digests {
|
||||||
|
if _, ok := seen[d]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
seen[d] = struct{}{}
|
||||||
|
algs[d.Algorithm()] = append(algs[d.Algorithm()], d)
|
||||||
|
|
||||||
|
if len(algs[d.Algorithm()]) > 1 {
|
||||||
|
return nil, fmt.Errorf("conflicting digests for %v found", d.Algorithm())
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// digestsMatch compares the two sets of digests to see if they match.
|
||||||
|
func digestsMatch(as, bs []digest.Digest) bool {
|
||||||
|
all := append(as, bs...)
|
||||||
|
|
||||||
|
uniqified, err := uniqifyDigests(all...)
|
||||||
|
if err != nil {
|
||||||
|
// the only error uniqifyDigests returns is when the digests disagree.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
disjoint := len(as) + len(bs)
|
||||||
|
if len(uniqified) == disjoint {
|
||||||
|
// if these two sets have the same cardinality, we know both sides
|
||||||
|
// didn't share any digests.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type digestSlice []digest.Digest
|
||||||
|
|
||||||
|
func (p digestSlice) Len() int { return len(p) }
|
||||||
|
func (p digestSlice) Less(i, j int) bool { return p[i] < p[j] }
|
||||||
|
func (p digestSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotSupported = fmt.Errorf("not supported")
|
||||||
|
|
||||||
|
// Driver provides all of the system-level functions in a common interface.
|
||||||
|
// The context should call these with full paths and should never use the `os`
|
||||||
|
// package or any other package to access resources on the filesystem. This
|
||||||
|
// mechanism let's us carefully control access to the context and maintain
|
||||||
|
// path and resource integrity. It also gives us an interface to reason about
|
||||||
|
// direct resource access.
|
||||||
|
//
|
||||||
|
// Implementations don't need to do much other than meet the interface. For
|
||||||
|
// example, it is not required to wrap os.FileInfo to return correct paths for
|
||||||
|
// the call to Name().
|
||||||
|
type Driver interface {
|
||||||
|
// Note that Open() returns a File interface instead of *os.File. This
|
||||||
|
// is because os.File is a struct, so if Open was to return *os.File,
|
||||||
|
// the only way to fulfill the interface would be to call os.Open()
|
||||||
|
Open(path string) (File, error)
|
||||||
|
OpenFile(path string, flag int, perm os.FileMode) (File, error)
|
||||||
|
|
||||||
|
Stat(path string) (os.FileInfo, error)
|
||||||
|
Lstat(path string) (os.FileInfo, error)
|
||||||
|
Readlink(p string) (string, error)
|
||||||
|
Mkdir(path string, mode os.FileMode) error
|
||||||
|
Remove(path string) error
|
||||||
|
|
||||||
|
Link(oldname, newname string) error
|
||||||
|
Lchmod(path string, mode os.FileMode) error
|
||||||
|
Lchown(path string, uid, gid int64) error
|
||||||
|
Symlink(oldname, newname string) error
|
||||||
|
|
||||||
|
MkdirAll(path string, perm os.FileMode) error
|
||||||
|
RemoveAll(path string) error
|
||||||
|
|
||||||
|
// TODO(aaronl): These methods might move outside the main Driver
|
||||||
|
// interface in the future as more platforms are added.
|
||||||
|
Mknod(path string, mode os.FileMode, major int, minor int) error
|
||||||
|
Mkfifo(path string, mode os.FileMode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// File is the interface for interacting with files returned by continuity's Open
|
||||||
|
// This is needed since os.File is a struct, instead of an interface, so it can't
|
||||||
|
// be used.
|
||||||
|
type File interface {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
io.Seeker
|
||||||
|
Readdir(n int) ([]os.FileInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSystemDriver() (Driver, error) {
|
||||||
|
// TODO(stevvooe): Consider having this take a "hint" path argument, which
|
||||||
|
// would be the context root. The hint could be used to resolve required
|
||||||
|
// filesystem support when assembling the driver to use.
|
||||||
|
return &driver{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XAttrDriver should be implemented on operation systems and filesystems that
|
||||||
|
// have xattr support for regular files and directories.
|
||||||
|
type XAttrDriver interface {
|
||||||
|
// Getxattr returns all of the extended attributes for the file at path.
|
||||||
|
// Typically, this takes a syscall call to Listxattr and Getxattr.
|
||||||
|
Getxattr(path string) (map[string][]byte, error)
|
||||||
|
|
||||||
|
// Setxattr sets all of the extended attributes on file at path, following
|
||||||
|
// any symbolic links, if necessary. All attributes on the target are
|
||||||
|
// replaced by the values from attr. If the operation fails to set any
|
||||||
|
// attribute, those already applied will not be rolled back.
|
||||||
|
Setxattr(path string, attr map[string][]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LXAttrDriver should be implemented by drivers on operating systems and
|
||||||
|
// filesystems that support setting and getting extended attributes on
|
||||||
|
// symbolic links. If this is not implemented, extended attributes will be
|
||||||
|
// ignored on symbolic links.
|
||||||
|
type LXAttrDriver interface {
|
||||||
|
// LGetxattr returns all of the extended attributes for the file at path
|
||||||
|
// and does not follow symlinks. Typically, this takes a syscall call to
|
||||||
|
// Llistxattr and Lgetxattr.
|
||||||
|
LGetxattr(path string) (map[string][]byte, error)
|
||||||
|
|
||||||
|
// LSetxattr sets all of the extended attributes on file at path, without
|
||||||
|
// following symbolic links. All attributes on the target are replaced by
|
||||||
|
// the values from attr. If the operation fails to set any attribute,
|
||||||
|
// those already applied will not be rolled back.
|
||||||
|
LSetxattr(path string, attr map[string][]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceInfoDriver interface {
|
||||||
|
DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// driver is a simple default implementation that sends calls out to the "os"
|
||||||
|
// package. Extend the "driver" type in system-specific files to add support,
|
||||||
|
// such as xattrs, which can add support at compile time.
|
||||||
|
type driver struct{}
|
||||||
|
|
||||||
|
var _ File = &os.File{}
|
||||||
|
|
||||||
|
// LocalDriver is the exported Driver struct for convenience.
|
||||||
|
var LocalDriver Driver = &driver{}
|
||||||
|
|
||||||
|
func (d *driver) Open(p string) (File, error) {
|
||||||
|
return os.Open(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) OpenFile(path string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
return os.OpenFile(path, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Stat(p string) (os.FileInfo, error) {
|
||||||
|
return os.Stat(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Lstat(p string) (os.FileInfo, error) {
|
||||||
|
return os.Lstat(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Mkdir(p string, mode os.FileMode) error {
|
||||||
|
return os.Mkdir(p, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove is used to unlink files and remove directories.
|
||||||
|
// This is following the golang os package api which
|
||||||
|
// combines the operations into a higher level Remove
|
||||||
|
// function. If explicit unlinking or directory removal
|
||||||
|
// to mirror system call is required, they should be
|
||||||
|
// split up at that time.
|
||||||
|
func (d *driver) Remove(path string) error {
|
||||||
|
return os.Remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Link(oldname, newname string) error {
|
||||||
|
return os.Link(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Lchown(name string, uid, gid int64) error {
|
||||||
|
// TODO: error out if uid excesses int bit width?
|
||||||
|
return os.Lchown(name, int(uid), int(gid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Symlink(oldname, newname string) error {
|
||||||
|
return os.Symlink(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return os.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) RemoveAll(path string) error {
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
// +build linux darwin freebsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/devices"
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||||
|
err := devices.Mknod(path, mode, major, minor)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "mknod", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||||
|
if mode&os.ModeNamedPipe == 0 {
|
||||||
|
return errors.New("mode passed to Mkfifo does not have the named pipe bit set")
|
||||||
|
}
|
||||||
|
// mknod with a mode that has ModeNamedPipe set creates a fifo, not a
|
||||||
|
// device.
|
||||||
|
err := devices.Mknod(path, mode, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "mkfifo", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getxattr returns all of the extended attributes for the file at path p.
|
||||||
|
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
|
||||||
|
xattrs, err := sysx.Listxattr(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(xattrs)
|
||||||
|
m := make(map[string][]byte, len(xattrs))
|
||||||
|
|
||||||
|
for _, attr := range xattrs {
|
||||||
|
value, err := sysx.Getxattr(p, attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||||
|
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||||
|
// longer unique.
|
||||||
|
m[attr] = append(m[attr], value...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setxattr sets all of the extended attributes on file at path, following
|
||||||
|
// any symbolic links, if necessary. All attributes on the target are
|
||||||
|
// replaced by the values from attr. If the operation fails to set any
|
||||||
|
// attribute, those already applied will not be rolled back.
|
||||||
|
func (d *driver) Setxattr(path string, attrMap map[string][]byte) error {
|
||||||
|
for attr, value := range attrMap {
|
||||||
|
if err := sysx.Setxattr(path, attr, value, 0); err != nil {
|
||||||
|
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LGetxattr returns all of the extended attributes for the file at path p
|
||||||
|
// not following symbolic links.
|
||||||
|
func (d *driver) LGetxattr(p string) (map[string][]byte, error) {
|
||||||
|
xattrs, err := sysx.LListxattr(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(xattrs)
|
||||||
|
m := make(map[string][]byte, len(xattrs))
|
||||||
|
|
||||||
|
for _, attr := range xattrs {
|
||||||
|
value, err := sysx.LGetxattr(p, attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||||
|
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||||
|
// longer unique.
|
||||||
|
m[attr] = append(m[attr], value...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LSetxattr sets all of the extended attributes on file at path, not
|
||||||
|
// following any symbolic links. All attributes on the target are
|
||||||
|
// replaced by the values from attr. If the operation fails to set any
|
||||||
|
// attribute, those already applied will not be rolled back.
|
||||||
|
func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
|
||||||
|
for attr, value := range attrMap {
|
||||||
|
if err := sysx.LSetxattr(path, attr, value, 0); err != nil {
|
||||||
|
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) {
|
||||||
|
return devices.DeviceInfo(fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readlink was forked on Windows to fix a Golang bug, use the "os" package here
|
||||||
|
func (d *driver) Readlink(p string) (string, error) {
|
||||||
|
return os.Readlink(p)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||||
|
return &os.PathError{Op: "mknod", Path: path, Err: ErrNotSupported}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||||
|
return &os.PathError{Op: "mkfifo", Path: path, Err: ErrNotSupported}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lchmod changes the mode of an file not following symlinks.
|
||||||
|
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
||||||
|
// TODO: Use Window's equivalent
|
||||||
|
return os.Chmod(path, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readlink is forked in order to support Volume paths which are used
|
||||||
|
// in container layers.
|
||||||
|
func (d *driver) Readlink(p string) (string, error) {
|
||||||
|
return sysx.Readlink(p)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lchmod changes the mode of a file not following symlinks.
|
||||||
|
func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
||||||
|
// On Linux, file mode is not supported for symlinks,
|
||||||
|
// and fchmodat() does not support AT_SYMLINK_NOFOLLOW,
|
||||||
|
// so symlinks need to be skipped entirely.
|
||||||
|
if st, err := os.Stat(path); err == nil && st.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), 0)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "lchmod", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// +build darwin freebsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lchmod changes the mode of a file not following symlinks.
|
||||||
|
func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
||||||
|
err := unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), unix.AT_SYMLINK_NOFOLLOW)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "lchmod", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadFile works the same as ioutil.ReadFile with the Driver abstraction
|
||||||
|
func ReadFile(r Driver, filename string) ([]byte, error) {
|
||||||
|
f, err := r.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile works the same as ioutil.WriteFile with the Driver abstraction
|
||||||
|
func WriteFile(r Driver, filename string, data []byte, perm os.FileMode) error {
|
||||||
|
f, err := r.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
n, err := f.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if n != len(data) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir works the same as ioutil.ReadDir with the Driver abstraction
|
||||||
|
func ReadDir(r Driver, dirname string) ([]os.FileInfo, error) {
|
||||||
|
f, err := r.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
dirs, err := f.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(fileInfos(dirs))
|
||||||
|
return dirs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple implementation of the sort.Interface for os.FileInfo
|
||||||
|
type fileInfos []os.FileInfo
|
||||||
|
|
||||||
|
func (fis fileInfos) Len() int {
|
||||||
|
return len(fis)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fis fileInfos) Less(i, j int) bool {
|
||||||
|
return fis[i].Name() < fis[j].Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fis fileInfos) Swap(i, j int) {
|
||||||
|
fis[i], fis[j] = fis[j], fis[i]
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package continuity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(stevvooe): This needs a lot of work before we can call it useful.
|
||||||
|
|
||||||
|
type groupIndex struct {
|
||||||
|
byName map[string]*group
|
||||||
|
byGID map[int]*group
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGroupIndex() (*groupIndex, error) {
|
||||||
|
f, err := os.Open("/etc/group")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
groups, err := parseGroups(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newGroupIndex(groups), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGroupIndex(groups []group) *groupIndex {
|
||||||
|
gi := &groupIndex{
|
||||||
|
byName: make(map[string]*group),
|
||||||
|
byGID: make(map[int]*group),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, group := range groups {
|
||||||
|
gi.byGID[group.gid] = &groups[i]
|
||||||
|
gi.byName[group.name] = &groups[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return gi
|
||||||
|
}
|
||||||
|
|
||||||
|
type group struct {
|
||||||
|
name string
|
||||||
|
gid int
|
||||||
|
members []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGroupName(gid int) (string, error) {
|
||||||
|
f, err := os.Open("/etc/group")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
groups, err := parseGroups(f)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
if group.gid == gid {
|
||||||
|
return group.name, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("no group for gid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGroups parses an /etc/group file for group names, ids and membership.
|
||||||
|
// This is unix specific.
|
||||||
|
func parseGroups(rd io.Reader) ([]group, error) {
|
||||||
|
var groups []group
|
||||||
|
scanner := bufio.NewScanner(rd)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
if strings.HasPrefix(scanner.Text(), "#") {
|
||||||
|
continue // skip comment
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(scanner.Text(), ":", 4)
|
||||||
|
|
||||||
|
if len(parts) != 4 {
|
||||||
|
return nil, fmt.Errorf("bad entry: %q", scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
name, _, sgid, smembers := parts[0], parts[1], parts[2], parts[3]
|
||||||
|
|
||||||
|
gid, err := strconv.Atoi(sgid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bad gid: %q", gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
members := strings.Split(smembers, ",")
|
||||||
|
|
||||||
|
groups = append(groups, group{
|
||||||
|
name: name,
|
||||||
|
gid: gid,
|
||||||
|
members: members,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
return nil, scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue