new libimage package
The new `libimage` package is an attempt to consolidate the code for managing container images and performing operations on them such as pulling, pushing, saving, searching, local lookups, removing etc. Over time, Buildah, CRI-O and Podman diverged with respect to managing container images resulting in a high amount of code duplication rendering the tools harder to maintain (e.g., bug fixes) and harder to extend (e.g., adding new features) than necessary. The desire to share all that code in a common library grew and this is an attempt to address the it. The changes as they are now pass Buildah CI [1]. Once merged into Buildah, I expect follow up changes when migrating Podman over to `libimage`. Miscellaneous changes: * Copy `podman/pkg/signal` to `pkg/signal`. * Copy `buildah/manifests` to `image/manifests`. Note that the unit tests require root privileges. Skip()'s are added when running rootless. Currently excluded from linting. * Copy `buildah/pkg/manifests` to `pkg/manifests`. Currently excluded from linting. * Copy `buildah/pkg/supplemented` to `pkg/supplemented`. Currently excluded from linting. [1] github.com/containers/buildah/pull/3148 Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
parent
4c782ab603
commit
32a28aee73
|
|
@ -1,10 +1,15 @@
|
|||
---
|
||||
run:
|
||||
skip-dirs:
|
||||
- libimage/manifests
|
||||
- pkg/manifests
|
||||
- pkg/supplemented
|
||||
build-tags:
|
||||
- apparmor
|
||||
- seccomp
|
||||
- selinux
|
||||
- exclude_graphdriver_btrfs
|
||||
- containers_image_openpgp
|
||||
concurrency: 6
|
||||
deadline: 5m
|
||||
linters:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ GO_BUILD=$(GO) build
|
|||
ifeq ($(shell go help mod >/dev/null 2>&1 && echo true), true)
|
||||
GO_BUILD=GO111MODULE=on $(GO) build -mod=vendor
|
||||
endif
|
||||
BUILDTAGS :=
|
||||
BUILDTAGS := containers_image_openpgp
|
||||
DESTDIR ?=
|
||||
PREFIX := /usr/local
|
||||
CONFIGDIR := ${PREFIX}/share/containers
|
||||
|
|
@ -54,12 +54,12 @@ build: build-amd64 build-386
|
|||
|
||||
.PHONY: build-amd64
|
||||
build-amd64:
|
||||
GOARCH=amd64 $(GO_BUILD) ./...
|
||||
GOARCH=amd64 $(GO_BUILD) -tags $(BUILDTAGS) ./...
|
||||
|
||||
.PHONY: build-386
|
||||
build-386:
|
||||
ifneq ($(shell uname -s), Darwin)
|
||||
GOARCH=386 $(GO_BUILD) ./...
|
||||
GOARCH=386 $(GO_BUILD) -tags $(BUILDTAGS) ./...
|
||||
endif
|
||||
|
||||
.PHONY: docs
|
||||
|
|
@ -108,8 +108,8 @@ test: test-unit
|
|||
|
||||
.PHONY: test-unit
|
||||
test-unit:
|
||||
go test -v $(PROJECT)/pkg/...
|
||||
go test --tags remote,seccomp -v $(PROJECT)/pkg/...
|
||||
go test --tags $(BUILDTAGS) -v $(PROJECT)/pkg/...
|
||||
go test --tags remote,seccomp,$(BUILDTAGS) -v $(PROJECT)/pkg/...
|
||||
|
||||
clean: ## Clean artifacts
|
||||
$(MAKE) -C docs clean
|
||||
|
|
|
|||
|
|
@ -4,16 +4,21 @@ go 1.15
|
|||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/containers/image/v5 v5.11.0
|
||||
github.com/containers/image/v5 v5.11.1
|
||||
github.com/containers/ocicrypt v1.1.0
|
||||
github.com/containers/storage v1.30.0
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible
|
||||
github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/onsi/ginkgo v1.16.1
|
||||
github.com/onsi/gomega v1.11.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6
|
||||
github.com/opencontainers/runc v1.0.0-rc93
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d
|
||||
github.com/opencontainers/runtime-tools v0.9.0
|
||||
|
|
@ -25,6 +30,7 @@ require (
|
|||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
|
|||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg=
|
||||
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
|
|
@ -52,6 +54,7 @@ github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEY
|
|||
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
|
||||
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
|
||||
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
|
||||
github.com/Microsoft/hcsshim v0.8.16 h1:8/auA4LFIZFTGrqfKhGBSXwM6/4X1fHa/xniyEHu8ac=
|
||||
github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
|
||||
|
|
@ -60,7 +63,9 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
|||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
|
|
@ -95,8 +100,11 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
|||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
|
||||
|
|
@ -114,6 +122,7 @@ github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqh
|
|||
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
|
||||
github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
||||
github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
||||
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68 h1:hkGVFjz+plgr5UfxZUTPFbUFIF/Km6/s+RVRIRHLrrY=
|
||||
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
|
|
@ -129,6 +138,7 @@ github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
|
|||
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
|
||||
github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
|
||||
github.com/containerd/containerd v1.5.0-beta.4 h1:zjz4MOAOFgdBlwid2nNUlJ3YLpVi/97L36lfMYJex60=
|
||||
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
|
|
@ -165,14 +175,14 @@ github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1Dv
|
|||
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=
|
||||
github.com/containers/image/v5 v5.11.0 h1:SwxGucW1AZ8H/5KH9jW70lo9WyuOrtxafutyQ9RPPLw=
|
||||
github.com/containers/image/v5 v5.11.0/go.mod h1:dCbUB4w6gmxIEOCsE0tZQppr8iBoXb4Evr74ZKlmwoI=
|
||||
github.com/containers/image/v5 v5.11.1 h1:mNybUvU6zXUwcMsQaa3n+Idsru5pV+GE7k4oRuPzYi0=
|
||||
github.com/containers/image/v5 v5.11.1/go.mod h1:HC9lhJ/Nz5v3w/5Co7H431kLlgzlVlOC+auD/er3OqE=
|
||||
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE=
|
||||
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
|
||||
github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
|
||||
github.com/containers/ocicrypt v1.1.0 h1:A6UzSUFMla92uxO43O6lm86i7evMGjTY7wTKB2DyGPY=
|
||||
github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
|
||||
github.com/containers/storage v1.28.1/go.mod h1:5bwiMh2LkrN3AWIfDFMH7A/xbVNLcve+oeXYvHvW8cc=
|
||||
github.com/containers/storage v1.29.0/go.mod h1:u84RU4CCufGeJBNTRNwMB+FoE+AiFeFw4SsMoqAOeCM=
|
||||
github.com/containers/storage v1.30.0 h1:KS6zmoPyy0Qcx1HCCiseQ0ysSckRvtiuoVpIGh9iwQA=
|
||||
github.com/containers/storage v1.30.0/go.mod h1:M/xn0pg6ReYFrLtWl5YELI/a4Xjq+Z3e5GJxQrJCcDI=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
|
|
@ -193,6 +203,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
|
|
@ -212,8 +224,8 @@ github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
|
|||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible h1:+0LETFJcCLdIqdtEbVWF1JIxATqM15Y4sLiMcWOYq2U=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible h1:Yu2uGErhwEoOT/OxAFe+/SiJCqRLs+pgcS5XKrDXnG4=
|
||||
github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
|
||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
|
|
@ -282,12 +294,14 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
|||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
|
|
@ -320,8 +334,10 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=
|
||||
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
|
@ -334,6 +350,7 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
|
|
@ -382,6 +399,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
|
|
@ -395,6 +413,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
|
|||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
|
|
@ -402,7 +421,6 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
|
|||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.12.1 h1:/+xsCsk06wE38cyiqOR/o7U2fSftcH72xD+BQXmja/g=
|
||||
github.com/klauspost/compress v1.12.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
|
|
@ -419,26 +437,34 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=
|
||||
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
|
||||
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible h1:aKW/4cBs+yK6gpqU3K/oIwk9Q/XICqd3zOX/UFuvqmk=
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
|
|
@ -454,12 +480,16 @@ github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh
|
|||
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/mtrmac/gpgme v0.1.2 h1:dNOmvYmsrakgW7LcgiprD0yfRuQQe8/C8F6Z+zogO3s=
|
||||
github.com/mtrmac/gpgme v0.1.2/go.mod h1:GYYHnGSuS7HK3zVS2n3y73y0okK/BeKzwnn5jgiVFNI=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
|
|
@ -515,6 +545,7 @@ github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pK
|
|||
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
||||
github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM=
|
||||
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
||||
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw=
|
||||
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
|
|
@ -565,6 +596,7 @@ github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3x
|
|||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
|
@ -606,6 +638,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
@ -625,6 +658,7 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG
|
|||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
|
||||
github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs=
|
||||
github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
|
|
@ -636,6 +670,7 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
|||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE=
|
||||
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
|
||||
github.com/vbauerster/mpb/v6 v6.0.3 h1:j+twHHhSUe8aXWaT/27E98G5cSBeqEuJSVCMjmLg0PI=
|
||||
github.com/vbauerster/mpb/v6 v6.0.3/go.mod h1:5luBx4rDLWxpA4t6I5sdeeQuZhqDxc+wr5Nqf35+tnM=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
|
|
@ -660,12 +695,15 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS
|
|||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
|
|
@ -765,6 +803,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -816,6 +855,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -828,6 +868,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -840,6 +881,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
@ -923,6 +965,7 @@ google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfG
|
|||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
|
@ -937,6 +980,7 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
|||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
|
@ -966,6 +1010,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
|
|||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
|
|
@ -983,6 +1028,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,403 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/common/pkg/retry"
|
||||
"github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/signature"
|
||||
storageTransport "github.com/containers/image/v5/storage"
|
||||
"github.com/containers/image/v5/types"
|
||||
encconfig "github.com/containers/ocicrypt/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMaxRetries = 3
|
||||
defaultRetryDelay = time.Second
|
||||
)
|
||||
|
||||
// LookupReferenceFunc return an image reference based on the specified one.
|
||||
// This can be used to pass custom blob caches to the copy operation.
|
||||
type LookupReferenceFunc func(ref types.ImageReference) (types.ImageReference, error)
|
||||
|
||||
// CopyOptions allow for customizing image-copy operations.
|
||||
type CopyOptions struct {
|
||||
// If set, will be used for copying the image. Fields below may
|
||||
// override certain settings.
|
||||
SystemContext *types.SystemContext
|
||||
// Allows for customizing the source reference lookup. This can be
|
||||
// used to use custom blob caches.
|
||||
SourceLookupReferenceFunc LookupReferenceFunc
|
||||
// Allows for customizing the destination reference lookup. This can
|
||||
// be used to use custom blob caches.
|
||||
DestinationLookupReferenceFunc LookupReferenceFunc
|
||||
|
||||
// containers-auth.json(5) file to use when authenticating against
|
||||
// container registries.
|
||||
AuthFilePath string
|
||||
// Custom path to a blob-info cache.
|
||||
BlobInfoCacheDirPath string
|
||||
// Path to the certificates directory.
|
||||
CertDirPath string
|
||||
// Allow contacting registries over HTTP, or HTTPS with failed TLS
|
||||
// verification. Note that this does not affect other TLS connections.
|
||||
InsecureSkipTLSVerify types.OptionalBool
|
||||
// Maximum number of retries with exponential backoff when facing
|
||||
// transient network errors. A reasonable default is used if not set.
|
||||
// Default 3.
|
||||
MaxRetries *uint
|
||||
// RetryDelay used for the exponential back off of MaxRetries.
|
||||
// Default 1 time.Scond.
|
||||
RetryDelay *time.Duration
|
||||
// ManifestMIMEType is the desired media type the image will be
|
||||
// converted to if needed. Note that it must contain the exact MIME
|
||||
// types. Short forms (e.g., oci, v2s2) used by some tools are not
|
||||
// supported.
|
||||
ManifestMIMEType string
|
||||
// If OciEncryptConfig is non-nil, it indicates that an image should be
|
||||
// encrypted. The encryption options is derived from the construction
|
||||
// of EncryptConfig object. Note: During initial encryption process of
|
||||
// a layer, the resultant digest is not known during creation, so
|
||||
// newDigestingReader has to be set with validateDigest = false
|
||||
OciEncryptConfig *encconfig.EncryptConfig
|
||||
// OciEncryptLayers represents the list of layers to encrypt. If nil,
|
||||
// don't encrypt any layers. If non-nil and len==0, denotes encrypt
|
||||
// all layers. integers in the slice represent 0-indexed layer
|
||||
// indices, with support for negative indexing. i.e. 0 is the first
|
||||
// layer, -1 is the last (top-most) layer.
|
||||
OciEncryptLayers *[]int
|
||||
// OciDecryptConfig contains the config that can be used to decrypt an
|
||||
// image if it is encrypted if non-nil. If nil, it does not attempt to
|
||||
// decrypt an image.
|
||||
OciDecryptConfig *encconfig.DecryptConfig
|
||||
// Reported to when ProgressInterval has arrived for a single
|
||||
// artifact+offset.
|
||||
Progress chan types.ProgressProperties
|
||||
// If set, allow using the storage transport even if it's disabled by
|
||||
// the specified SignaturePolicyPath.
|
||||
PolicyAllowStorage bool
|
||||
// SignaturePolicyPath to overwrite the default one.
|
||||
SignaturePolicyPath string
|
||||
// If non-empty, asks for a signature to be added during the copy, and
|
||||
// specifies a key ID.
|
||||
SignBy string
|
||||
// Remove any pre-existing signatures. SignBy will still add a new
|
||||
// signature.
|
||||
RemoveSignatures bool
|
||||
// Writer is used to display copy information including progress bars.
|
||||
Writer io.Writer
|
||||
|
||||
// ----- platform -----------------------------------------------------
|
||||
|
||||
// Architecture to use for choosing images.
|
||||
Architecture string
|
||||
// OS to use for choosing images.
|
||||
OS string
|
||||
// Variant to use when choosing images.
|
||||
Variant string
|
||||
|
||||
// ----- credentials --------------------------------------------------
|
||||
|
||||
// Username to use when authenticating at a container registry.
|
||||
Username string
|
||||
// Password to use when authenticating at a container registry.
|
||||
Password string
|
||||
// Credentials is an alternative way to specify credentials in format
|
||||
// "username[:password]". Cannot be used in combination with
|
||||
// Username/Password.
|
||||
Credentials string
|
||||
|
||||
// ----- internal -----------------------------------------------------
|
||||
|
||||
// Additional tags when creating or copying a docker-archive.
|
||||
dockerArchiveAdditionalTags []reference.NamedTagged
|
||||
}
|
||||
|
||||
// copier is an internal helper to conveniently copy images.
|
||||
type copier struct {
|
||||
imageCopyOptions copy.Options
|
||||
retryOptions retry.RetryOptions
|
||||
systemContext *types.SystemContext
|
||||
policyContext *signature.PolicyContext
|
||||
|
||||
sourceLookup LookupReferenceFunc
|
||||
destinationLookup LookupReferenceFunc
|
||||
}
|
||||
|
||||
var (
|
||||
// storageAllowedPolicyScopes overrides the policy for local storage
|
||||
// to ensure that we can read images from it.
|
||||
storageAllowedPolicyScopes = signature.PolicyTransportScopes{
|
||||
"": []signature.PolicyRequirement{
|
||||
signature.NewPRInsecureAcceptAnything(),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// getDockerAuthConfig extracts a docker auth config from the CopyOptions. Returns
|
||||
// nil if no credentials are set.
|
||||
func (options *CopyOptions) getDockerAuthConfig() (*types.DockerAuthConfig, error) {
|
||||
if options.Username != "" {
|
||||
if options.Credentials != "" {
|
||||
return nil, errors.New("username/password cannot be used with credentials")
|
||||
}
|
||||
return &types.DockerAuthConfig{
|
||||
Username: options.Username,
|
||||
Password: options.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if options.Credentials != "" {
|
||||
var username, password string
|
||||
split := strings.SplitN(options.Credentials, ":", 2)
|
||||
switch len(split) {
|
||||
case 1:
|
||||
username = split[0]
|
||||
default:
|
||||
username = split[0]
|
||||
password = split[1]
|
||||
}
|
||||
return &types.DockerAuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// newCopier creates a copier. Note that fields in options *may* overwrite the
|
||||
// counterparts of the specified system context. Please make sure to call
|
||||
// `(*copier).close()`.
|
||||
func newCopier(sys *types.SystemContext, options *CopyOptions) (*copier, error) {
|
||||
c := copier{}
|
||||
|
||||
if options.SourceLookupReferenceFunc != nil {
|
||||
c.sourceLookup = options.SourceLookupReferenceFunc
|
||||
}
|
||||
|
||||
if options.DestinationLookupReferenceFunc != nil {
|
||||
c.destinationLookup = options.DestinationLookupReferenceFunc
|
||||
}
|
||||
|
||||
c.systemContext = sys
|
||||
if c.systemContext == nil {
|
||||
c.systemContext = &types.SystemContext{}
|
||||
}
|
||||
|
||||
if options.AuthFilePath != "" {
|
||||
c.systemContext.AuthFilePath = options.AuthFilePath
|
||||
}
|
||||
|
||||
c.systemContext.DockerArchiveAdditionalTags = options.dockerArchiveAdditionalTags
|
||||
|
||||
if options.Architecture != "" {
|
||||
c.systemContext.ArchitectureChoice = options.Architecture
|
||||
}
|
||||
if options.OS != "" {
|
||||
c.systemContext.OSChoice = options.OS
|
||||
}
|
||||
if options.Variant != "" {
|
||||
c.systemContext.VariantChoice = options.Variant
|
||||
}
|
||||
|
||||
if options.SignaturePolicyPath != "" {
|
||||
c.systemContext.SignaturePolicyPath = options.SignaturePolicyPath
|
||||
}
|
||||
|
||||
dockerAuthConfig, err := options.getDockerAuthConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dockerAuthConfig != nil {
|
||||
c.systemContext.DockerAuthConfig = dockerAuthConfig
|
||||
}
|
||||
|
||||
if options.BlobInfoCacheDirPath != "" {
|
||||
c.systemContext.BlobInfoCacheDir = options.BlobInfoCacheDirPath
|
||||
}
|
||||
|
||||
policy, err := signature.DefaultPolicy(sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Buildah compatibility: even if the policy denies _all_ transports,
|
||||
// Buildah still wants the storage to be accessible.
|
||||
if options.PolicyAllowStorage {
|
||||
policy.Transports[storageTransport.Transport.Name()] = storageAllowedPolicyScopes
|
||||
}
|
||||
|
||||
policyContext, err := signature.NewPolicyContext(policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.policyContext = policyContext
|
||||
|
||||
c.retryOptions.MaxRetry = defaultMaxRetries
|
||||
if options.MaxRetries != nil {
|
||||
c.retryOptions.MaxRetry = int(*options.MaxRetries)
|
||||
}
|
||||
c.retryOptions.Delay = defaultRetryDelay
|
||||
if options.RetryDelay != nil {
|
||||
c.retryOptions.Delay = *options.RetryDelay
|
||||
}
|
||||
|
||||
c.imageCopyOptions.Progress = options.Progress
|
||||
if c.imageCopyOptions.Progress != nil {
|
||||
c.imageCopyOptions.ProgressInterval = time.Second
|
||||
}
|
||||
|
||||
c.imageCopyOptions.ForceManifestMIMEType = options.ManifestMIMEType
|
||||
c.imageCopyOptions.SourceCtx = c.systemContext
|
||||
c.imageCopyOptions.DestinationCtx = c.systemContext
|
||||
c.imageCopyOptions.OciEncryptConfig = options.OciEncryptConfig
|
||||
c.imageCopyOptions.OciEncryptLayers = options.OciEncryptLayers
|
||||
c.imageCopyOptions.OciDecryptConfig = options.OciDecryptConfig
|
||||
c.imageCopyOptions.RemoveSignatures = options.RemoveSignatures
|
||||
c.imageCopyOptions.SignBy = options.SignBy
|
||||
c.imageCopyOptions.ReportWriter = options.Writer
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// close open resources.
|
||||
func (c *copier) close() error {
|
||||
return c.policyContext.Destroy()
|
||||
}
|
||||
|
||||
// copy the source to the destination. Returns the bytes of the copied
|
||||
// manifest which may be used for digest computation.
|
||||
func (c *copier) copy(ctx context.Context, source, destination types.ImageReference) ([]byte, error) {
|
||||
logrus.Debugf("Copying source image %s to destination image %s", source.StringWithinTransport(), destination.StringWithinTransport())
|
||||
|
||||
var err error
|
||||
|
||||
if c.sourceLookup != nil {
|
||||
source, err = c.sourceLookup(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if c.destinationLookup != nil {
|
||||
destination, err = c.destinationLookup(destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Buildah compat: used when running in OpenShift.
|
||||
sourceInsecure, err := checkRegistrySourcesAllows(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
destinationInsecure, err := checkRegistrySourcesAllows(destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sanity checks for Buildah.
|
||||
if sourceInsecure != nil && *sourceInsecure {
|
||||
if c.systemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse {
|
||||
return nil, errors.Errorf("can't require tls verification on an insecured registry")
|
||||
}
|
||||
}
|
||||
if destinationInsecure != nil && *destinationInsecure {
|
||||
if c.systemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse {
|
||||
return nil, errors.Errorf("can't require tls verification on an insecured registry")
|
||||
}
|
||||
}
|
||||
|
||||
var copiedManifest []byte
|
||||
f := func() error {
|
||||
opts := c.imageCopyOptions
|
||||
if sourceInsecure != nil {
|
||||
value := types.NewOptionalBool(*sourceInsecure)
|
||||
opts.SourceCtx.DockerInsecureSkipTLSVerify = value
|
||||
}
|
||||
if destinationInsecure != nil {
|
||||
value := types.NewOptionalBool(*destinationInsecure)
|
||||
opts.DestinationCtx.DockerInsecureSkipTLSVerify = value
|
||||
}
|
||||
|
||||
var err error
|
||||
copiedManifest, err = copy.Image(ctx, c.policyContext, destination, source, &opts)
|
||||
return err
|
||||
}
|
||||
return copiedManifest, retry.RetryIfNecessary(ctx, f, &c.retryOptions)
|
||||
}
|
||||
|
||||
// checkRegistrySourcesAllows checks the $BUILD_REGISTRY_SOURCES environment
|
||||
// variable, if it's set. The contents are expected to be a JSON-encoded
|
||||
// github.com/openshift/api/config/v1.Image, set by an OpenShift build
|
||||
// controller that arranged for us to be run in a container.
|
||||
//
|
||||
// If set, the insecure return value indicates whether the registry is set to
|
||||
// be insecure.
|
||||
//
|
||||
// NOTE: this functionality is required by Buildah.
|
||||
func checkRegistrySourcesAllows(dest types.ImageReference) (insecure *bool, err error) {
|
||||
registrySources, ok := os.LookupEnv("BUILD_REGISTRY_SOURCES")
|
||||
if !ok || registrySources == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("BUILD_REGISTRY_SOURCES set %q", registrySources)
|
||||
|
||||
dref := dest.DockerReference()
|
||||
if dref == nil || reference.Domain(dref) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Use local struct instead of github.com/openshift/api/config/v1 RegistrySources
|
||||
var sources struct {
|
||||
InsecureRegistries []string `json:"insecureRegistries,omitempty"`
|
||||
BlockedRegistries []string `json:"blockedRegistries,omitempty"`
|
||||
AllowedRegistries []string `json:"allowedRegistries,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(registrySources), &sources); err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing $BUILD_REGISTRY_SOURCES (%q) as JSON", registrySources)
|
||||
}
|
||||
blocked := false
|
||||
if len(sources.BlockedRegistries) > 0 {
|
||||
for _, blockedDomain := range sources.BlockedRegistries {
|
||||
if blockedDomain == reference.Domain(dref) {
|
||||
blocked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if blocked {
|
||||
return nil, errors.Errorf("registry %q denied by policy: it is in the blocked registries list (%s)", reference.Domain(dref), registrySources)
|
||||
}
|
||||
allowed := true
|
||||
if len(sources.AllowedRegistries) > 0 {
|
||||
allowed = false
|
||||
for _, allowedDomain := range sources.AllowedRegistries {
|
||||
if allowedDomain == reference.Domain(dref) {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return nil, errors.Errorf("registry %q denied by policy: not in allowed registries list (%s)", reference.Domain(dref), registrySources)
|
||||
}
|
||||
|
||||
for _, inseureDomain := range sources.InsecureRegistries {
|
||||
if inseureDomain == reference.Domain(dref) {
|
||||
insecure := true
|
||||
return &insecure, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// tmpdir returns a path to a temporary directory.
|
||||
func (r *Runtime) tmpdir() string {
|
||||
tmpdir := os.Getenv("TMPDIR")
|
||||
if tmpdir == "" {
|
||||
tmpdir = "/var/tmp"
|
||||
}
|
||||
|
||||
return tmpdir
|
||||
}
|
||||
|
||||
// downloadFromURL downloads an image in the format "https:/example.com/myimage.tar"
|
||||
// and temporarily saves in it $TMPDIR/importxyz, which is deleted after the image is imported
|
||||
func (r *Runtime) downloadFromURL(source string) (string, error) {
|
||||
fmt.Printf("Downloading from %q\n", source)
|
||||
|
||||
outFile, err := ioutil.TempFile(r.tmpdir(), "import")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error creating file")
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
response, err := http.Get(source) // nolint:noctx
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error downloading %q", source)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
_, err = io.Copy(outFile, response.Body)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name())
|
||||
}
|
||||
|
||||
return outFile.Name(), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package libimage
|
||||
|
||||
import "time"
|
||||
|
||||
// EventType indicates the type of an event. Currrently, there is only one
|
||||
// supported type for container image but we may add more (e.g., for manifest
|
||||
// lists) in the future.
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
// EventTypeUnknow is an unitialized EventType.
|
||||
EventTypeUnknown EventType = iota
|
||||
// EventTypeImagePull represents an image pull.
|
||||
EventTypeImagePull
|
||||
// EventTypeImagePush represents an image push.
|
||||
EventTypeImagePush
|
||||
// EventTypeImageRemove represents an image removal.
|
||||
EventTypeImageRemove
|
||||
// EventTypeImageLoad represents an image being loaded.
|
||||
EventTypeImageLoad
|
||||
// EventTypeImageSave represents an image being saved.
|
||||
EventTypeImageSave
|
||||
// EventTypeImageTag represents an image being tagged.
|
||||
EventTypeImageTag
|
||||
// EventTypeImageUntag represents an image being untagged.
|
||||
EventTypeImageUntag
|
||||
// EventTypeImageMount represents an image being mounted.
|
||||
EventTypeImageMount
|
||||
// EventTypeImageUnmounted represents an image being unmounted.
|
||||
EventTypeImageUnmount
|
||||
)
|
||||
|
||||
// Event represents an event such an image pull or image tag.
|
||||
type Event struct {
|
||||
// ID of the object (e.g., image ID).
|
||||
ID string
|
||||
// Name of the object (e.g., image name "quay.io/containers/podman:latest")
|
||||
Name string
|
||||
// Time of the event.
|
||||
Time time.Time
|
||||
// Type of the event.
|
||||
Type EventType
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
filtersPkg "github.com/containers/common/pkg/filters"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// filterFunc is a prototype for a positive image filter. Returning `true`
|
||||
// indicates that the image matches the criteria.
|
||||
type filterFunc func(*Image) (bool, error)
|
||||
|
||||
// filterImages returns a slice of images which are passing all specified
|
||||
// filters.
|
||||
func filterImages(images []*Image, filters []filterFunc) ([]*Image, error) {
|
||||
if len(filters) == 0 {
|
||||
return images, nil
|
||||
}
|
||||
result := []*Image{}
|
||||
for i := range images {
|
||||
include := true
|
||||
var err error
|
||||
for _, filter := range filters {
|
||||
include, err = filter(images[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !include {
|
||||
break
|
||||
}
|
||||
}
|
||||
if include {
|
||||
result = append(result, images[i])
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// compileImageFilters creates `filterFunc`s for the specified filters. The
|
||||
// required format is `key=value` with the following supported keys:
|
||||
// after, since, before, dangling, id, label, readonly, reference, intermediate
|
||||
func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]filterFunc, error) {
|
||||
logrus.Tracef("Parsing image filters %s", filters)
|
||||
|
||||
filterFuncs := []filterFunc{}
|
||||
visitedKeys := make(map[string]bool)
|
||||
|
||||
for _, filter := range filters {
|
||||
// First, parse the filter.
|
||||
var key, value string
|
||||
split := strings.SplitN(filter, "=", 2)
|
||||
if len(split) != 2 {
|
||||
return nil, errors.Errorf("invalid image filter %q: must be in the format %q", filter, "filter=value")
|
||||
}
|
||||
|
||||
key = split[0]
|
||||
value = split[1]
|
||||
|
||||
if _, exists := visitedKeys[key]; exists {
|
||||
return nil, errors.Errorf("image filter %q specified multiple times", key)
|
||||
}
|
||||
visitedKeys[key] = true
|
||||
|
||||
// Second, dispatch the filters.
|
||||
switch key {
|
||||
|
||||
case "after", "since":
|
||||
img, _, err := r.LookupImage(value, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not find local image for filter %q", filter)
|
||||
}
|
||||
filterFuncs = append(filterFuncs, filterAfter(img.Created()))
|
||||
|
||||
case "before":
|
||||
img, _, err := r.LookupImage(value, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not find local image for filter %q", filter)
|
||||
}
|
||||
filterFuncs = append(filterFuncs, filterBefore(img.Created()))
|
||||
|
||||
case "dangling":
|
||||
dangling, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "non-boolean value %q for dangling filter", value)
|
||||
}
|
||||
filterFuncs = append(filterFuncs, filterDangling(dangling))
|
||||
|
||||
case "id":
|
||||
filterFuncs = append(filterFuncs, filterID(value))
|
||||
|
||||
case "intermediate":
|
||||
intermediate, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "non-boolean value %q for intermediate filter", value)
|
||||
}
|
||||
filterFuncs = append(filterFuncs, filterIntermediate(ctx, intermediate))
|
||||
|
||||
case "label":
|
||||
filterFuncs = append(filterFuncs, filterLabel(ctx, value))
|
||||
|
||||
case "readonly":
|
||||
readOnly, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "non-boolean value %q for readonly filter", value)
|
||||
}
|
||||
filterFuncs = append(filterFuncs, filterReadOnly(readOnly))
|
||||
|
||||
case "reference":
|
||||
filterFuncs = append(filterFuncs, filterReference(value))
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported image filter %q", key)
|
||||
}
|
||||
}
|
||||
|
||||
return filterFuncs, nil
|
||||
}
|
||||
|
||||
// filterReference creates a reference filter for matching the specified value.
|
||||
func filterReference(value string) filterFunc {
|
||||
// Replacing all '/' with '|' so that filepath.Match() can work '|'
|
||||
// character is not valid in image name, so this is safe.
|
||||
//
|
||||
// TODO: this has been copied from Podman and requires some more review
|
||||
// and especially tests.
|
||||
filter := fmt.Sprintf("*%s*", value)
|
||||
filter = strings.ReplaceAll(filter, "/", "|")
|
||||
return func(img *Image) (bool, error) {
|
||||
if len(value) < 1 {
|
||||
return true, nil
|
||||
}
|
||||
for _, name := range img.Names() {
|
||||
newName := strings.ReplaceAll(name, "/", "|")
|
||||
match, _ := filepath.Match(filter, newName)
|
||||
if match {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterLabel creates a label for matching the specified value.
|
||||
func filterLabel(ctx context.Context, value string) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
labels, err := img.Labels(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return filtersPkg.MatchLabelFilters([]string{value}, labels), nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterAfter creates an after filter for matching the specified value.
|
||||
func filterAfter(value time.Time) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return img.Created().After(value), nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterBefore creates a before filter for matching the specified value.
|
||||
func filterBefore(value time.Time) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return img.Created().Before(value), nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterReadOnly creates a readonly filter for matching the specified value.
|
||||
func filterReadOnly(value bool) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return img.IsReadOnly() == value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterDangling creates a dangling filter for matching the specified value.
|
||||
func filterDangling(value bool) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return img.IsDangling() == value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterID creates an image-ID filter for matching the specified value.
|
||||
func filterID(value string) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return img.ID() == value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterIntermediate creates an intermediate filter for images. An image is
|
||||
// considered to be an intermediate image if it is dangling (i.e., no tags) and
|
||||
// has no children (i.e., no other image depends on it).
|
||||
func filterIntermediate(ctx context.Context, value bool) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
isIntermediate, err := img.IsIntermediate(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return isIntermediate == value, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
libimageTypes "github.com/containers/common/libimage/types"
|
||||
"github.com/containers/storage"
|
||||
)
|
||||
|
||||
// History computes the image history of the image including all of its parents.
|
||||
func (i *Image) History(ctx context.Context) ([]libimageTypes.ImageHistory, error) {
|
||||
ociImage, err := i.toOCI(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layerTree, err := i.runtime.layerTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var allHistory []libimageTypes.ImageHistory
|
||||
var layer *storage.Layer
|
||||
if i.TopLayer() != "" {
|
||||
layer, err = i.runtime.store.Layer(i.TopLayer())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate in reverse order over the history entries, and lookup the
|
||||
// corresponding image ID, size and get the next later if needed.
|
||||
numHistories := len(ociImage.History) - 1
|
||||
usedIDs := make(map[string]bool) // prevents assigning images IDs more than once
|
||||
for x := numHistories; x >= 0; x-- {
|
||||
history := libimageTypes.ImageHistory{
|
||||
ID: "<missing>", // may be overridden below
|
||||
Created: ociImage.History[x].Created,
|
||||
CreatedBy: ociImage.History[x].CreatedBy,
|
||||
Comment: ociImage.History[x].Comment,
|
||||
}
|
||||
|
||||
if layer != nil {
|
||||
history.Tags = layer.Names
|
||||
if !ociImage.History[x].EmptyLayer {
|
||||
history.Size = layer.UncompressedSize
|
||||
}
|
||||
// Query the layer tree if it's the top layer of an
|
||||
// image.
|
||||
node := layerTree.node(layer.ID)
|
||||
if len(node.images) > 0 {
|
||||
id := node.images[0].ID() // always use the first one
|
||||
if _, used := usedIDs[id]; !used {
|
||||
history.ID = id
|
||||
usedIDs[id] = true
|
||||
}
|
||||
}
|
||||
if layer.Parent != "" && !ociImage.History[x].EmptyLayer {
|
||||
layer, err = i.runtime.store.Layer(layer.Parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allHistory = append(allHistory, history)
|
||||
}
|
||||
|
||||
return allHistory, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,582 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
libimageTypes "github.com/containers/common/libimage/types"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
storageTransport "github.com/containers/image/v5/storage"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Image represents an image in the containers storage and allows for further
|
||||
// operations and data manipulation.
|
||||
type Image struct {
|
||||
// Backwards pointer to the runtime.
|
||||
runtime *Runtime
|
||||
|
||||
// Counterpart in the local containers storage.
|
||||
storageImage *storage.Image
|
||||
|
||||
// Image reference to the containers storage.
|
||||
storageReference types.ImageReference
|
||||
|
||||
// All fields in the below structure are cached. They may be cleared
|
||||
// at any time. When adding a new field, please make sure to clear
|
||||
// it in `(*Image).reload()`.
|
||||
cached struct {
|
||||
// Image source. Cached for performance reasons.
|
||||
imageSource types.ImageSource
|
||||
// Inspect data we get from containers/image.
|
||||
partialInspectData *types.ImageInspectInfo
|
||||
// Fully assembled image data.
|
||||
completeInspectData *libimageTypes.ImageData
|
||||
// Corresponding OCI image.
|
||||
ociv1Image *ociv1.Image
|
||||
}
|
||||
}
|
||||
|
||||
// reload the image and pessimitically clear all cached data.
|
||||
func (i *Image) reload() error {
|
||||
logrus.Tracef("Reloading image %s", i.ID())
|
||||
img, err := i.runtime.store.Image(i.ID())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error reloading image")
|
||||
}
|
||||
i.storageImage = img
|
||||
i.cached.imageSource = nil
|
||||
i.cached.partialInspectData = nil
|
||||
i.cached.completeInspectData = nil
|
||||
i.cached.ociv1Image = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Names returns associated names with the image which may be a mix of tags and
|
||||
// digests.
|
||||
func (i *Image) Names() []string {
|
||||
return i.storageImage.Names
|
||||
}
|
||||
|
||||
// StorageImage returns the underlying storage.Image.
|
||||
func (i *Image) StorageImage() *storage.Image {
|
||||
return i.storageImage
|
||||
}
|
||||
|
||||
// NamesHistory returns a string array of names previously associated with the
|
||||
// image, which may be a mixture of tags and digests.
|
||||
func (i *Image) NamesHistory() []string {
|
||||
return i.storageImage.NamesHistory
|
||||
}
|
||||
|
||||
// ID returns the ID of the image.
|
||||
func (i *Image) ID() string {
|
||||
return i.storageImage.ID
|
||||
}
|
||||
|
||||
// Digest is a digest value that we can use to locate the image, if one was
|
||||
// specified at creation-time.
|
||||
func (i *Image) Digest() digest.Digest {
|
||||
return i.storageImage.Digest
|
||||
}
|
||||
|
||||
// Digests is a list of digest values of the image's manifests, and possibly a
|
||||
// manually-specified value, that we can use to locate the image. If Digest is
|
||||
// set, its value is also in this list.
|
||||
func (i *Image) Digests() []digest.Digest {
|
||||
return i.storageImage.Digests
|
||||
}
|
||||
|
||||
// IsReadOnly returns whether the image is set read only.
|
||||
func (i *Image) IsReadOnly() bool {
|
||||
return i.storageImage.ReadOnly
|
||||
}
|
||||
|
||||
// IsDangling returns true if the image is dangling. An image is considered
|
||||
// dangling if no names are associated with it in the containers storage.
|
||||
func (i *Image) IsDangling() bool {
|
||||
return len(i.Names()) == 0
|
||||
}
|
||||
|
||||
// IsIntermediate returns true if the image is an intermediate image, that is
|
||||
// a dangling image without children.
|
||||
func (i *Image) IsIntermediate(ctx context.Context) (bool, error) {
|
||||
// If the image has tags, it's not an intermediate one.
|
||||
if !i.IsDangling() {
|
||||
return false, nil
|
||||
}
|
||||
children, err := i.getChildren(ctx, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// No tags, no children -> intermediate!
|
||||
return len(children) != 0, nil
|
||||
}
|
||||
|
||||
// Created returns the time the image was created.
|
||||
func (i *Image) Created() time.Time {
|
||||
return i.storageImage.Created
|
||||
}
|
||||
|
||||
// Labels returns the label of the image.
|
||||
func (i *Image) Labels(ctx context.Context) (map[string]string, error) {
|
||||
data, err := i.inspectInfo(ctx)
|
||||
if err != nil {
|
||||
isManifestList, listErr := i.isManifestList(ctx)
|
||||
if listErr != nil {
|
||||
err = errors.Wrapf(err, "fallback error checking whether image is a manifest list: %v", err)
|
||||
} else if isManifestList {
|
||||
logrus.Debugf("Ignoring error: cannot return labels for manifest list or image index %s", i.ID())
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data.Labels, nil
|
||||
}
|
||||
|
||||
// TopLayer returns the top layer id as a string
|
||||
func (i *Image) TopLayer() string {
|
||||
return i.storageImage.TopLayer
|
||||
}
|
||||
|
||||
// Parent returns the parent image or nil if there is none
|
||||
func (i *Image) Parent(ctx context.Context) (*Image, error) {
|
||||
tree, err := i.runtime.layerTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tree.parent(ctx, i)
|
||||
}
|
||||
|
||||
// HasChildren returns indicates if the image has children.
|
||||
func (i *Image) HasChildren(ctx context.Context) (bool, error) {
|
||||
children, err := i.getChildren(ctx, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(children) > 0, nil
|
||||
}
|
||||
|
||||
// Children returns the image's children.
|
||||
func (i *Image) Children(ctx context.Context) ([]*Image, error) {
|
||||
children, err := i.getChildren(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
|
||||
// getChildren returns a list of imageIDs that depend on the image. If all is
|
||||
// false, only the first child image is returned.
|
||||
func (i *Image) getChildren(ctx context.Context, all bool) ([]*Image, error) {
|
||||
tree, err := i.runtime.layerTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tree.children(ctx, i, all)
|
||||
}
|
||||
|
||||
// Containers returns a list of containers using the image.
|
||||
func (i *Image) Containers() ([]string, error) {
|
||||
var containerIDs []string
|
||||
containers, err := i.runtime.store.Containers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageID := i.ID()
|
||||
for i := range containers {
|
||||
if containers[i].ImageID == imageID {
|
||||
containerIDs = append(containerIDs, containers[i].ID)
|
||||
}
|
||||
}
|
||||
return containerIDs, nil
|
||||
}
|
||||
|
||||
// removeContainers removes all containers using the image.
|
||||
func (i *Image) removeContainers(fn RemoveContainerFunc) error {
|
||||
// Execute the custom removal func if specified.
|
||||
if fn != nil {
|
||||
logrus.Debugf("Removing containers of image %s with custom removal function", i.ID())
|
||||
return fn(i.ID())
|
||||
}
|
||||
|
||||
containers, err := i.Containers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Removing containers of image %s from the local containers storage", i.ID())
|
||||
var multiE error
|
||||
for _, cID := range containers {
|
||||
if err := i.runtime.store.DeleteContainer(cID); err != nil {
|
||||
// If the container does not exist anymore, we're good.
|
||||
if errors.Cause(err) != storage.ErrContainerUnknown {
|
||||
multiE = multierror.Append(multiE, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multiE
|
||||
}
|
||||
|
||||
// RemoveContainerFunc allows for customizing the removal of containers using
|
||||
// an image specified by imageID.
|
||||
type RemoveContainerFunc func(imageID string) error
|
||||
|
||||
// RemoveImageOptions allow for customizing image removal.
|
||||
type RemoveImageOptions struct {
|
||||
// Force will remove all containers from the local storage that are
|
||||
// using a removed image. Use RemoveContainerFunc for a custom logic.
|
||||
// If set, all child images will be removed as well.
|
||||
Force bool
|
||||
// RemoveContainerFunc allows for a custom logic for removing
|
||||
// containers using a specific image. By default, all containers in
|
||||
// the local containers storage will be removed (if Force is set).
|
||||
RemoveContainerFunc RemoveContainerFunc
|
||||
}
|
||||
|
||||
// Remove removes the image along with all dangling parent images that no other
|
||||
// image depends on. The image must not be set read-only and not be used by
|
||||
// containers. Callers must make sure to remove containers before image
|
||||
// removal and may use `(*Image).Containers()` to get a list of containers
|
||||
// using the image.
|
||||
//
|
||||
// If the image is used by containers return storage.ErrImageUsedByContainer.
|
||||
// Use force to remove these containers.
|
||||
func (i *Image) Remove(ctx context.Context, options *RemoveImageOptions) error {
|
||||
logrus.Debugf("Removing image %s", i.ID())
|
||||
if i.IsReadOnly() {
|
||||
return errors.Errorf("cannot remove read-only image %q", i.ID())
|
||||
}
|
||||
|
||||
if options == nil {
|
||||
options = &RemoveImageOptions{}
|
||||
}
|
||||
|
||||
if options.Force {
|
||||
if err := i.removeContainers(options.RemoveContainerFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a dangling parent that no other image depends on, remove
|
||||
// it recursively.
|
||||
parent, err := i.Parent(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := i.runtime.store.DeleteImage(i.ID(), true); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(i.runtime.imageIDmap, i.ID())
|
||||
|
||||
if parent == nil || !parent.IsDangling() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return parent.Remove(ctx, options)
|
||||
}
|
||||
|
||||
// Tag the image with the specified name and store it in the local containers
|
||||
// storage. The name is normalized according to the rules of NormalizeName.
|
||||
func (i *Image) Tag(name string) error {
|
||||
ref, err := NormalizeName(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error normalizing name %q", name)
|
||||
}
|
||||
|
||||
logrus.Debugf("Tagging image %s with %q", i.ID(), ref.String())
|
||||
|
||||
newNames := append(i.Names(), ref.String())
|
||||
if err := i.runtime.store.SetNames(i.ID(), newNames); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.reload()
|
||||
}
|
||||
|
||||
// Untag the image with the specified name and make the change persistent in
|
||||
// the local containers storage. The name is normalized according to the rules
|
||||
// of NormalizeName.
|
||||
func (i *Image) Untag(name string) error {
|
||||
ref, err := NormalizeName(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error normalizing name %q", name)
|
||||
}
|
||||
name = ref.String()
|
||||
|
||||
removedName := false
|
||||
newNames := []string{}
|
||||
for _, n := range i.Names() {
|
||||
if n == name {
|
||||
removedName = true
|
||||
continue
|
||||
}
|
||||
newNames = append(newNames, n)
|
||||
}
|
||||
|
||||
if !removedName {
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Untagging %q from image %s", ref.String(), i.ID())
|
||||
|
||||
if err := i.runtime.store.SetNames(i.ID(), newNames); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.reload()
|
||||
}
|
||||
|
||||
// RepoTags returns a string slice of repotags associated with the image.
|
||||
func (i *Image) RepoTags() ([]string, error) {
|
||||
namedTagged, err := i.NamedTaggedRepoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoTags := make([]string, len(namedTagged))
|
||||
for i := range namedTagged {
|
||||
repoTags[i] = namedTagged[i].String()
|
||||
}
|
||||
return repoTags, nil
|
||||
}
|
||||
|
||||
// NammedTaggedRepoTags returns the repotags associated with the image as a
|
||||
// slice of reference.NamedTagged.
|
||||
func (i *Image) NamedTaggedRepoTags() ([]reference.NamedTagged, error) {
|
||||
var repoTags []reference.NamedTagged
|
||||
for _, name := range i.Names() {
|
||||
named, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tagged, isTagged := named.(reference.NamedTagged); isTagged {
|
||||
repoTags = append(repoTags, tagged)
|
||||
}
|
||||
}
|
||||
return repoTags, nil
|
||||
}
|
||||
|
||||
// RepoDigests returns a string array of repodigests associated with the image
|
||||
func (i *Image) RepoDigests() ([]string, error) {
|
||||
var repoDigests []string
|
||||
added := make(map[string]struct{})
|
||||
|
||||
for _, name := range i.Names() {
|
||||
for _, imageDigest := range append(i.Digests(), i.Digest()) {
|
||||
if imageDigest == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
named, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
canonical, err := reference.WithDigest(reference.TrimNamed(named), imageDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, alreadyInList := added[canonical.String()]; !alreadyInList {
|
||||
repoDigests = append(repoDigests, canonical.String())
|
||||
added[canonical.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(repoDigests)
|
||||
return repoDigests, nil
|
||||
}
|
||||
|
||||
// Mount the image with the specified mount options and label, both of which
|
||||
// are directly passed down to the containers storage. Returns the fully
|
||||
// evaluated path to the mount point.
|
||||
func (i *Image) Mount(ctx context.Context, mountOptions []string, mountLabel string) (string, error) {
|
||||
mountPoint, err := i.runtime.store.MountImage(i.ID(), mountOptions, mountLabel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
mountPoint, err = filepath.EvalSymlinks(mountPoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
logrus.Debugf("Mounted image %s at %q", i.ID(), mountPoint)
|
||||
return mountPoint, nil
|
||||
}
|
||||
|
||||
// Unmount the image. Use force to ignore the reference counter and forcefully
|
||||
// unmount.
|
||||
func (i *Image) Unmount(force bool) error {
|
||||
logrus.Debugf("Unmounted image %s", i.ID())
|
||||
_, err := i.runtime.store.UnmountImage(i.ID(), force)
|
||||
return err
|
||||
}
|
||||
|
||||
// MountPoint returns the fully-evaluated mount point of the image. If the
|
||||
// image isn't mounted, an empty string is returned.
|
||||
func (i *Image) MountPoint() (string, error) {
|
||||
counter, err := i.runtime.store.Mounted(i.TopLayer())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if counter == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
layer, err := i.runtime.store.Layer(i.TopLayer())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.EvalSymlinks(layer.MountPoint)
|
||||
}
|
||||
|
||||
// Size computes the size of the image layers and associated data.
|
||||
func (i *Image) Size() (int64, error) {
|
||||
return i.runtime.store.ImageSize(i.ID())
|
||||
}
|
||||
|
||||
// HasDifferentDigest returns true if the image specified by `remoteRef` has a
|
||||
// different digest than the local one. This check can be useful to check for
|
||||
// updates on remote registries.
|
||||
func (i *Image) HasDifferentDigest(ctx context.Context, remoteRef types.ImageReference) (bool, error) {
|
||||
// We need to account for the arch that the image uses. It seems
|
||||
// common on ARM to tweak this option to pull the correct image. See
|
||||
// github.com/containers/podman/issues/6613.
|
||||
inspectInfo, err := i.inspectInfo(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
sys := i.runtime.systemContext
|
||||
sys.ArchitectureChoice = inspectInfo.Architecture
|
||||
// OS and variant may not be set, so let's check to avoid accidental
|
||||
// overrides of the runtime settings.
|
||||
if inspectInfo.Os != "" {
|
||||
sys.OSChoice = inspectInfo.Os
|
||||
}
|
||||
if inspectInfo.Variant != "" {
|
||||
sys.VariantChoice = inspectInfo.Variant
|
||||
}
|
||||
|
||||
remoteImg, err := remoteRef.NewImage(ctx, &sys)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rawManifest, _, err := remoteImg.Manifest(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
remoteDigest, err := manifest.Digest(rawManifest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return i.Digest().String() != remoteDigest.String(), nil
|
||||
}
|
||||
|
||||
// driverData gets the driver data from the store on a layer
|
||||
func (i *Image) driverData() (*libimageTypes.DriverData, error) {
|
||||
store := i.runtime.store
|
||||
layerID := i.TopLayer()
|
||||
driver, err := store.GraphDriver()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metaData, err := driver.Metadata(layerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mountTimes, err := store.Mounted(layerID); mountTimes == 0 || err != nil {
|
||||
delete(metaData, "MergedDir")
|
||||
}
|
||||
return &libimageTypes.DriverData{
|
||||
Name: driver.String(),
|
||||
Data: metaData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StorageReference returns the image's reference to the containers storage
|
||||
// using the image ID.
|
||||
func (i *Image) StorageReference() (types.ImageReference, error) {
|
||||
if i.storageReference != nil {
|
||||
return i.storageReference, nil
|
||||
}
|
||||
ref, err := storageTransport.Transport.ParseStoreReference(i.runtime.store, "@"+i.ID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.storageReference = ref
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// isManifestList returns true if the image is a manifest list (Docker) or an
|
||||
// image index (OCI). This information may be useful to make certain execution
|
||||
// paths more robust.
|
||||
// NOTE: please use this function only to optimize specific execution paths.
|
||||
// In general, errors should only be suppressed when necessary.
|
||||
func (i *Image) isManifestList(ctx context.Context) (bool, error) {
|
||||
ref, err := i.StorageReference()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
imgRef, err := ref.NewImageSource(ctx, &i.runtime.systemContext)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, manifestType, err := imgRef.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return manifest.MIMETypeIsMultiImage(manifestType), nil
|
||||
}
|
||||
|
||||
// source returns the possibly cached image reference.
|
||||
func (i *Image) source(ctx context.Context) (types.ImageSource, error) {
|
||||
if i.cached.imageSource != nil {
|
||||
return i.cached.imageSource, nil
|
||||
}
|
||||
ref, err := i.StorageReference()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src, err := ref.NewImageSource(ctx, &i.runtime.systemContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.cached.imageSource = src
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// getImageDigest creates an image object and uses the hex value of the digest as the image ID
|
||||
// for parsing the store reference
|
||||
func getImageDigest(ctx context.Context, src types.ImageReference, sys *types.SystemContext) (string, error) {
|
||||
newImg, err := src.NewImage(ctx, sys)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
if err := newImg.Close(); err != nil {
|
||||
logrus.Errorf("failed to close image: %q", err)
|
||||
}
|
||||
}()
|
||||
imageDigest := newImg.ConfigInfo().Digest
|
||||
if err = imageDigest.Validate(); err != nil {
|
||||
return "", errors.Wrapf(err, "error getting config info")
|
||||
}
|
||||
return "@" + imageDigest.Hex(), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/signal"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ImageConfig is a wrapper around the OCIv1 Image Configuration struct exported
|
||||
// by containers/image, but containing additional fields that are not supported
|
||||
// by OCIv1 (but are by Docker v2) - notably OnBuild.
|
||||
type ImageConfig struct {
|
||||
ociv1.ImageConfig
|
||||
OnBuild []string
|
||||
}
|
||||
|
||||
// ImageConfigFromChanges produces a v1.ImageConfig from the --change flag that
|
||||
// is accepted by several Podman commands. It accepts a (limited subset) of
|
||||
// Dockerfile instructions.
|
||||
// Valid changes are:
|
||||
// * USER
|
||||
// * EXPOSE
|
||||
// * ENV
|
||||
// * ENTRYPOINT
|
||||
// * CMD
|
||||
// * VOLUME
|
||||
// * WORKDIR
|
||||
// * LABEL
|
||||
// * STOPSIGNAL
|
||||
// * ONBUILD
|
||||
func ImageConfigFromChanges(changes []string) (*ImageConfig, error) { // nolint:gocyclo
|
||||
config := &ImageConfig{}
|
||||
|
||||
for _, change := range changes {
|
||||
// First, let's assume proper Dockerfile format - space
|
||||
// separator between instruction and value
|
||||
split := strings.SplitN(change, " ", 2)
|
||||
|
||||
if len(split) != 2 {
|
||||
split = strings.SplitN(change, "=", 2)
|
||||
if len(split) != 2 {
|
||||
return nil, errors.Errorf("invalid change %q - must be formatted as KEY VALUE", change)
|
||||
}
|
||||
}
|
||||
|
||||
outerKey := strings.ToUpper(strings.TrimSpace(split[0]))
|
||||
value := strings.TrimSpace(split[1])
|
||||
switch outerKey {
|
||||
case "USER":
|
||||
// Assume literal contents are the user.
|
||||
if value == "" {
|
||||
return nil, errors.Errorf("invalid change %q - must provide a value to USER", change)
|
||||
}
|
||||
config.User = value
|
||||
case "EXPOSE":
|
||||
// EXPOSE is either [portnum] or
|
||||
// [portnum]/[proto]
|
||||
// Protocol must be "tcp" or "udp"
|
||||
splitPort := strings.Split(value, "/")
|
||||
if len(splitPort) > 2 {
|
||||
return nil, errors.Errorf("invalid change %q - EXPOSE port must be formatted as PORT[/PROTO]", change)
|
||||
}
|
||||
portNum, err := strconv.Atoi(splitPort[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid change %q - EXPOSE port must be an integer", change)
|
||||
}
|
||||
if portNum > 65535 || portNum <= 0 {
|
||||
return nil, errors.Errorf("invalid change %q - EXPOSE port must be a valid port number", change)
|
||||
}
|
||||
proto := "tcp"
|
||||
if len(splitPort) > 1 {
|
||||
testProto := strings.ToLower(splitPort[1])
|
||||
switch testProto {
|
||||
case "tcp", "udp":
|
||||
proto = testProto
|
||||
default:
|
||||
return nil, errors.Errorf("invalid change %q - EXPOSE protocol must be TCP or UDP", change)
|
||||
}
|
||||
}
|
||||
if config.ExposedPorts == nil {
|
||||
config.ExposedPorts = make(map[string]struct{})
|
||||
}
|
||||
config.ExposedPorts[fmt.Sprintf("%d/%s", portNum, proto)] = struct{}{}
|
||||
case "ENV":
|
||||
// Format is either:
|
||||
// ENV key=value
|
||||
// ENV key=value key=value ...
|
||||
// ENV key value
|
||||
// Both keys and values can be surrounded by quotes to group them.
|
||||
// For now: we only support key=value
|
||||
// We will attempt to strip quotation marks if present.
|
||||
|
||||
var (
|
||||
key, val string
|
||||
)
|
||||
|
||||
splitEnv := strings.SplitN(value, "=", 2)
|
||||
key = splitEnv[0]
|
||||
// We do need a key
|
||||
if key == "" {
|
||||
return nil, errors.Errorf("invalid change %q - ENV must have at least one argument", change)
|
||||
}
|
||||
// Perfectly valid to not have a value
|
||||
if len(splitEnv) == 2 {
|
||||
val = splitEnv[1]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) {
|
||||
key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`)
|
||||
}
|
||||
if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) {
|
||||
val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`)
|
||||
}
|
||||
config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, val))
|
||||
case "ENTRYPOINT":
|
||||
// Two valid forms.
|
||||
// First, JSON array.
|
||||
// Second, not a JSON array - we interpret this as an
|
||||
// argument to `sh -c`, unless empty, in which case we
|
||||
// just use a blank entrypoint.
|
||||
testUnmarshal := []string{}
|
||||
if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil {
|
||||
// It ain't valid JSON, so assume it's an
|
||||
// argument to sh -c if not empty.
|
||||
if value != "" {
|
||||
config.Entrypoint = []string{"/bin/sh", "-c", value}
|
||||
} else {
|
||||
config.Entrypoint = []string{}
|
||||
}
|
||||
} else {
|
||||
// Valid JSON
|
||||
config.Entrypoint = testUnmarshal
|
||||
}
|
||||
case "CMD":
|
||||
// Same valid forms as entrypoint.
|
||||
// However, where ENTRYPOINT assumes that 'ENTRYPOINT '
|
||||
// means no entrypoint, CMD assumes it is 'sh -c' with
|
||||
// no third argument.
|
||||
testUnmarshal := []string{}
|
||||
if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil {
|
||||
// It ain't valid JSON, so assume it's an
|
||||
// argument to sh -c.
|
||||
// Only include volume if it's not ""
|
||||
config.Cmd = []string{"/bin/sh", "-c"}
|
||||
if value != "" {
|
||||
config.Cmd = append(config.Cmd, value)
|
||||
}
|
||||
} else {
|
||||
// Valid JSON
|
||||
config.Cmd = testUnmarshal
|
||||
}
|
||||
case "VOLUME":
|
||||
// Either a JSON array or a set of space-separated
|
||||
// paths.
|
||||
// Acts rather similar to ENTRYPOINT and CMD, but always
|
||||
// appends rather than replacing, and no sh -c prepend.
|
||||
testUnmarshal := []string{}
|
||||
if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil {
|
||||
// Not valid JSON, so split on spaces
|
||||
testUnmarshal = strings.Split(value, " ")
|
||||
}
|
||||
if len(testUnmarshal) == 0 {
|
||||
return nil, errors.Errorf("invalid change %q - must provide at least one argument to VOLUME", change)
|
||||
}
|
||||
for _, vol := range testUnmarshal {
|
||||
if vol == "" {
|
||||
return nil, errors.Errorf("invalid change %q - VOLUME paths must not be empty", change)
|
||||
}
|
||||
if config.Volumes == nil {
|
||||
config.Volumes = make(map[string]struct{})
|
||||
}
|
||||
config.Volumes[vol] = struct{}{}
|
||||
}
|
||||
case "WORKDIR":
|
||||
// This can be passed multiple times.
|
||||
// Each successive invocation is treated as relative to
|
||||
// the previous one - so WORKDIR /A, WORKDIR b,
|
||||
// WORKDIR c results in /A/b/c
|
||||
// Just need to check it's not empty...
|
||||
if value == "" {
|
||||
return nil, errors.Errorf("invalid change %q - must provide a non-empty WORKDIR", change)
|
||||
}
|
||||
config.WorkingDir = filepath.Join(config.WorkingDir, value)
|
||||
case "LABEL":
|
||||
// Same general idea as ENV, but we no longer allow " "
|
||||
// as a separator.
|
||||
// We didn't do that for ENV either, so nice and easy.
|
||||
// Potentially problematic: LABEL might theoretically
|
||||
// allow an = in the key? If people really do this, we
|
||||
// may need to investigate more advanced parsing.
|
||||
var (
|
||||
key, val string
|
||||
)
|
||||
|
||||
splitLabel := strings.SplitN(value, "=", 2)
|
||||
// Unlike ENV, LABEL must have a value
|
||||
if len(splitLabel) != 2 {
|
||||
return nil, errors.Errorf("invalid change %q - LABEL must be formatted key=value", change)
|
||||
}
|
||||
key = splitLabel[0]
|
||||
val = splitLabel[1]
|
||||
|
||||
if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) {
|
||||
key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`)
|
||||
}
|
||||
if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) {
|
||||
val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`)
|
||||
}
|
||||
// Check key after we strip quotations
|
||||
if key == "" {
|
||||
return nil, errors.Errorf("invalid change %q - LABEL must have a non-empty key", change)
|
||||
}
|
||||
if config.Labels == nil {
|
||||
config.Labels = make(map[string]string)
|
||||
}
|
||||
config.Labels[key] = val
|
||||
case "STOPSIGNAL":
|
||||
// Check the provided signal for validity.
|
||||
killSignal, err := signal.ParseSignal(value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid change %q - KILLSIGNAL must be given a valid signal", change)
|
||||
}
|
||||
config.StopSignal = fmt.Sprintf("%d", killSignal)
|
||||
case "ONBUILD":
|
||||
// Onbuild always appends.
|
||||
if value == "" {
|
||||
return nil, errors.Errorf("invalid change %q - ONBUILD must be given an argument", change)
|
||||
}
|
||||
config.OnBuild = append(config.OnBuild, value)
|
||||
default:
|
||||
return nil, errors.Errorf("invalid change %q - invalid instruction %s", change, outerKey)
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
imageTreeMiddleItem = "├── "
|
||||
imageTreeContinueItem = "│ "
|
||||
imageTreeLastItem = "└── "
|
||||
)
|
||||
|
||||
// Tree generates a tree for the specified image and its layers. Use
|
||||
// `traverseChildren` to traverse the layers of all children. By default, only
|
||||
// layers of the image are printed.
|
||||
func (i *Image) Tree(traverseChildren bool) (*strings.Builder, error) {
|
||||
// NOTE: a string builder prevents us from copying to much data around
|
||||
// and compile the string when and where needed.
|
||||
sb := &strings.Builder{}
|
||||
|
||||
// First print the pretty header for the target image.
|
||||
size, err := i.Size()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoTags, err := i.RepoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Fprintf(sb, "Image ID: %s\n", i.ID()[:12])
|
||||
fmt.Fprintf(sb, "Tags: %s\n", repoTags)
|
||||
fmt.Fprintf(sb, "Size: %v\n", units.HumanSizeWithPrecision(float64(size), 4))
|
||||
if i.TopLayer() != "" {
|
||||
fmt.Fprintf(sb, "Image Layers\n")
|
||||
} else {
|
||||
fmt.Fprintf(sb, "No Image Layers\n")
|
||||
}
|
||||
|
||||
layerTree, err := i.runtime.layerTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageNode := layerTree.node(i.TopLayer())
|
||||
// Traverse the entire tree down to all children.
|
||||
if traverseChildren {
|
||||
return imageTreeTraverseChildren(sb, imageNode, "", true)
|
||||
}
|
||||
|
||||
// Walk all layers of the image and assemlbe their data.
|
||||
for parentNode := imageNode.parent; parentNode != nil; parentNode = parentNode.parent {
|
||||
indent := imageTreeMiddleItem
|
||||
if parentNode.parent == nil {
|
||||
indent = imageTreeLastItem
|
||||
}
|
||||
|
||||
var tags string
|
||||
repoTags, err := parentNode.repoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(repoTags) > 0 {
|
||||
tags = fmt.Sprintf(" Top Layer of: %s", repoTags)
|
||||
}
|
||||
fmt.Fprintf(sb, "%s ID: %s Size: %7v%s\n", indent, parentNode.layer.ID[:12], units.HumanSizeWithPrecision(float64(parentNode.layer.UncompressedSize), 4), tags)
|
||||
}
|
||||
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
func imageTreeTraverseChildren(sb *strings.Builder, node *layerNode, prefix string, last bool) (*strings.Builder, error) {
|
||||
numChildren := len(node.children)
|
||||
if numChildren == 0 {
|
||||
return sb, nil
|
||||
}
|
||||
sb.WriteString(prefix)
|
||||
|
||||
intend := imageTreeMiddleItem
|
||||
if !last {
|
||||
prefix += imageTreeContinueItem
|
||||
} else {
|
||||
intend = imageTreeLastItem
|
||||
prefix += " "
|
||||
}
|
||||
|
||||
for i := range node.children {
|
||||
child := node.children[i]
|
||||
var tags string
|
||||
repoTags, err := child.repoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(repoTags) > 0 {
|
||||
tags = fmt.Sprintf(" Top Layer of: %s", repoTags)
|
||||
}
|
||||
fmt.Fprintf(sb, "%sID: %s Size: %7v%s\n", intend, child.layer.ID[:12], units.HumanSizeWithPrecision(float64(child.layer.UncompressedSize), 4), tags)
|
||||
sb, err = imageTreeTraverseChildren(sb, child, prefix, i == numChildren-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sb, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
storageTransport "github.com/containers/image/v5/storage"
|
||||
tarballTransport "github.com/containers/image/v5/tarball"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ImportOptions allow for customizing image imports.
|
||||
type ImportOptions struct {
|
||||
CopyOptions
|
||||
|
||||
// Apply the specified changes to the created image. Please refer to
|
||||
// `ImageConfigFromChanges` for supported change instructions.
|
||||
Changes []string
|
||||
// Set the commit message as a comment to created image's history.
|
||||
CommitMessage string
|
||||
// Tag the imported image with this value.
|
||||
Tag string
|
||||
}
|
||||
|
||||
// Import imports a custom tarball at the specified path. Returns the name of
|
||||
// the imported image.
|
||||
func (r *Runtime) Import(ctx context.Context, path string, options *ImportOptions) (string, error) {
|
||||
logrus.Debugf("Importing image from %q", path)
|
||||
|
||||
if options == nil {
|
||||
options = &ImportOptions{}
|
||||
}
|
||||
|
||||
ic := v1.ImageConfig{}
|
||||
if len(options.Changes) > 0 {
|
||||
config, err := ImageConfigFromChanges(options.Changes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ic = config.ImageConfig
|
||||
}
|
||||
|
||||
hist := []v1.History{
|
||||
{Comment: options.CommitMessage},
|
||||
}
|
||||
|
||||
config := v1.Image{
|
||||
Config: ic,
|
||||
History: hist,
|
||||
}
|
||||
|
||||
u, err := url.ParseRequestURI(path)
|
||||
if err == nil && u.Scheme != "" {
|
||||
// If source is a URL, download the file.
|
||||
file, err := r.downloadFromURL(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Remove(file)
|
||||
path = file
|
||||
} else if path == "-" {
|
||||
// "-" special cases stdin
|
||||
path = os.Stdin.Name()
|
||||
}
|
||||
|
||||
srcRef, err := tarballTransport.Transport.ParseReference(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
updater, ok := srcRef.(tarballTransport.ConfigUpdater)
|
||||
if !ok {
|
||||
return "", errors.New("unexpected type, a tarball reference should implement tarball.ConfigUpdater")
|
||||
}
|
||||
annotations := make(map[string]string)
|
||||
if err := updater.ConfigUpdate(config, annotations); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
name := options.Tag
|
||||
if name == "" {
|
||||
name, err = getImageDigest(ctx, srcRef, &r.systemContext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
destRef, err := storageTransport.Transport.ParseStoreReference(r.store, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
c, err := newCopier(&r.systemContext, &options.CopyOptions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
libimageTypes "github.com/containers/common/libimage/types"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Inspect inspects the image. Use `withSize` to also perform the
|
||||
// comparatively expensive size computation of the image.
|
||||
func (i *Image) Inspect(ctx context.Context, withSize bool) (*libimageTypes.ImageData, error) {
|
||||
logrus.Debugf("Inspecting image %s", i.ID())
|
||||
|
||||
if i.cached.completeInspectData != nil {
|
||||
return i.cached.completeInspectData, nil
|
||||
}
|
||||
|
||||
// First assemble data that does not depend on the format of the image.
|
||||
info, err := i.inspectInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ociImage, err := i.toOCI(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentImage, err := i.Parent(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoTags, err := i.RepoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoDigests, err := i.RepoDigests()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driverData, err := i.driverData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size := int64(-1)
|
||||
if withSize {
|
||||
size, err = i.Size()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
data := &libimageTypes.ImageData{
|
||||
ID: i.ID(),
|
||||
RepoTags: repoTags,
|
||||
RepoDigests: repoDigests,
|
||||
Created: ociImage.Created,
|
||||
Author: ociImage.Author,
|
||||
Architecture: ociImage.Architecture,
|
||||
Os: ociImage.OS,
|
||||
Config: &ociImage.Config,
|
||||
Version: info.DockerVersion,
|
||||
Size: size,
|
||||
VirtualSize: size, // TODO: they should be different (inherited from Podman)
|
||||
Digest: i.Digest(),
|
||||
Labels: info.Labels,
|
||||
RootFS: &libimageTypes.RootFS{
|
||||
Type: ociImage.RootFS.Type,
|
||||
Layers: ociImage.RootFS.DiffIDs,
|
||||
},
|
||||
GraphDriver: driverData,
|
||||
User: ociImage.Config.User,
|
||||
History: ociImage.History,
|
||||
NamesHistory: i.NamesHistory(),
|
||||
}
|
||||
|
||||
if parentImage != nil {
|
||||
data.Parent = parentImage.ID()
|
||||
}
|
||||
|
||||
// Determine the format of the image. How we determine certain data
|
||||
// depends on the format (e.g., Docker v2s2, OCI v1).
|
||||
src, err := i.source(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifestRaw, manifestType, err := src.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data.ManifestType = manifestType
|
||||
|
||||
switch manifestType {
|
||||
// OCI image
|
||||
case ociv1.MediaTypeImageManifest:
|
||||
var ociManifest ociv1.Manifest
|
||||
if err := json.Unmarshal(manifestRaw, &ociManifest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Annotations = ociManifest.Annotations
|
||||
if len(ociImage.History) > 0 {
|
||||
data.Comment = ociImage.History[0].Comment
|
||||
}
|
||||
|
||||
// Docker image
|
||||
case manifest.DockerV2Schema2MediaType:
|
||||
var dockerManifest manifest.Schema2Image
|
||||
if err := json.Unmarshal(manifestRaw, &dockerManifest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Comment = dockerManifest.Comment
|
||||
data.HealthCheck = dockerManifest.ContainerConfig.Healthcheck
|
||||
}
|
||||
|
||||
i.cached.completeInspectData = data
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// inspectInfo returns the image inspect info.
|
||||
func (i *Image) inspectInfo(ctx context.Context) (*types.ImageInspectInfo, error) {
|
||||
if i.cached.partialInspectData != nil {
|
||||
return i.cached.partialInspectData, nil
|
||||
}
|
||||
|
||||
ref, err := i.StorageReference()
|
||||
if err != nil {
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
img, err := ref.NewImage(ctx, &i.runtime.systemContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer img.Close()
|
||||
|
||||
data, err := img.Inspect(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.cached.partialInspectData = data
|
||||
return data, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/storage"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// layerTree is an internal representation of local layers.
|
||||
type layerTree struct {
|
||||
// nodes is the actual layer tree with layer IDs being keys.
|
||||
nodes map[string]*layerNode
|
||||
// ociCache is a cache for Image.ID -> OCI Image. Translations are done
|
||||
// on-demand.
|
||||
ociCache map[string]*ociv1.Image
|
||||
}
|
||||
|
||||
// node returns a layerNode for the specified layerID.
|
||||
func (t *layerTree) node(layerID string) *layerNode {
|
||||
node, exists := t.nodes[layerID]
|
||||
if !exists {
|
||||
node = &layerNode{}
|
||||
t.nodes[layerID] = node
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// toOCI returns an OCI image for the specified image.
|
||||
func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) {
|
||||
var err error
|
||||
oci, exists := t.ociCache[i.ID()]
|
||||
if !exists {
|
||||
oci, err = i.toOCI(ctx)
|
||||
if err == nil {
|
||||
t.ociCache[i.ID()] = oci
|
||||
}
|
||||
}
|
||||
return oci, err
|
||||
}
|
||||
|
||||
// layerNode is a node in a layerTree. It's ID is the key in a layerTree.
|
||||
type layerNode struct {
|
||||
children []*layerNode
|
||||
images []*Image
|
||||
parent *layerNode
|
||||
layer *storage.Layer
|
||||
}
|
||||
|
||||
// repoTags assemble all repo tags all of images of the layer node.
|
||||
func (l *layerNode) repoTags() ([]string, error) {
|
||||
orderedTags := []string{}
|
||||
visitedTags := make(map[string]bool)
|
||||
|
||||
for _, image := range l.images {
|
||||
repoTags, err := image.RepoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, tag := range repoTags {
|
||||
if _, visted := visitedTags[tag]; visted {
|
||||
continue
|
||||
}
|
||||
visitedTags[tag] = true
|
||||
orderedTags = append(orderedTags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
return orderedTags, nil
|
||||
}
|
||||
|
||||
// layerTree extracts a layerTree from the layers in the local storage and
|
||||
// relates them to the specified images.
|
||||
func (r *Runtime) layerTree() (*layerTree, error) {
|
||||
layers, err := r.store.Layers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
images, err := r.ListImages(context.Background(), nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tree := layerTree{
|
||||
nodes: make(map[string]*layerNode),
|
||||
ociCache: make(map[string]*ociv1.Image),
|
||||
}
|
||||
|
||||
// First build a tree purely based on layer information.
|
||||
for i := range layers {
|
||||
node := tree.node(layers[i].ID)
|
||||
node.layer = &layers[i]
|
||||
if layers[i].Parent == "" {
|
||||
continue
|
||||
}
|
||||
parent := tree.node(layers[i].Parent)
|
||||
node.parent = parent
|
||||
parent.children = append(parent.children, node)
|
||||
}
|
||||
|
||||
// Now assign the images to each (top) layer.
|
||||
for i := range images {
|
||||
img := images[i] // do not leak loop variable outside the scope
|
||||
topLayer := img.TopLayer()
|
||||
if topLayer == "" {
|
||||
continue
|
||||
}
|
||||
node, exists := tree.nodes[topLayer]
|
||||
if !exists {
|
||||
// Note: erroring out in this case has turned out having been a
|
||||
// mistake. Users may not be able to recover, so we're now
|
||||
// throwing a warning to guide them to resolve the issue and
|
||||
// turn the errors non-fatal.
|
||||
logrus.Warnf("Top layer %s of image %s not found in layer tree. The storage may be corrupted, consider running `podman system reset`.", topLayer, img.ID())
|
||||
continue
|
||||
}
|
||||
node.images = append(node.images, img)
|
||||
}
|
||||
|
||||
return &tree, nil
|
||||
}
|
||||
|
||||
// children returns the child images of parent. Child images are images with
|
||||
// either the same top layer as parent or parent being the true parent layer.
|
||||
// Furthermore, the history of the parent and child images must match with the
|
||||
// parent having one history item less. If all is true, all images are
|
||||
// returned. Otherwise, the first image is returned.
|
||||
func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]*Image, error) {
|
||||
if parent.TopLayer() == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var children []*Image
|
||||
|
||||
parentNode, exists := t.nodes[parent.TopLayer()]
|
||||
if !exists {
|
||||
// Note: erroring out in this case has turned out having been a
|
||||
// mistake. Users may not be able to recover, so we're now
|
||||
// throwing a warning to guide them to resolve the issue and
|
||||
// turn the errors non-fatal.
|
||||
logrus.Warnf("Layer %s not found in layer tree. The storage may be corrupted, consider running `podman system reset`.", parent.TopLayer())
|
||||
return children, nil
|
||||
}
|
||||
|
||||
parentID := parent.ID()
|
||||
parentOCI, err := t.toOCI(ctx, parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// checkParent returns true if child and parent are in such a relation.
|
||||
checkParent := func(child *Image) (bool, error) {
|
||||
if parentID == child.ID() {
|
||||
return false, nil
|
||||
}
|
||||
childOCI, err := t.toOCI(ctx, child)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// History check.
|
||||
return areParentAndChild(parentOCI, childOCI), nil
|
||||
}
|
||||
|
||||
// addChildrenFrom adds child images of parent to children. Returns
|
||||
// true if any image is a child of parent.
|
||||
addChildrenFromNode := func(node *layerNode) (bool, error) {
|
||||
foundChildren := false
|
||||
for i, childImage := range node.images {
|
||||
isChild, err := checkParent(childImage)
|
||||
if err != nil {
|
||||
return foundChildren, err
|
||||
}
|
||||
if isChild {
|
||||
foundChildren = true
|
||||
children = append(children, node.images[i])
|
||||
if all {
|
||||
return foundChildren, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundChildren, nil
|
||||
}
|
||||
|
||||
// First check images where parent's top layer is also the parent
|
||||
// layer.
|
||||
for _, childNode := range parentNode.children {
|
||||
found, err := addChildrenFromNode(childNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if found && all {
|
||||
return children, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Now check images with the same top layer.
|
||||
if _, err := addChildrenFromNode(parentNode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return children, nil
|
||||
}
|
||||
|
||||
// parent returns the parent image or nil if no parent image could be found.
|
||||
func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) {
|
||||
if child.TopLayer() == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
node, exists := t.nodes[child.TopLayer()]
|
||||
if !exists {
|
||||
// Note: erroring out in this case has turned out having been a
|
||||
// mistake. Users may not be able to recover, so we're now
|
||||
// throwing a warning to guide them to resolve the issue and
|
||||
// turn the errors non-fatal.
|
||||
logrus.Warnf("Layer %s not found in layer tree. The storage may be corrupted, consider running `podman system reset`.", child.TopLayer())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
childOCI, err := t.toOCI(ctx, child)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check images from the parent node (i.e., parent layer) and images
|
||||
// with the same layer (i.e., same top layer).
|
||||
childID := child.ID()
|
||||
images := node.images
|
||||
if node.parent != nil {
|
||||
images = append(images, node.parent.images...)
|
||||
}
|
||||
for _, parent := range images {
|
||||
if parent.ID() == childID {
|
||||
continue
|
||||
}
|
||||
parentOCI, err := t.toOCI(ctx, parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// History check.
|
||||
if areParentAndChild(parentOCI, childOCI) {
|
||||
return parent, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
dirTransport "github.com/containers/image/v5/directory"
|
||||
dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
|
||||
ociArchiveTransport "github.com/containers/image/v5/oci/archive"
|
||||
ociTransport "github.com/containers/image/v5/oci/layout"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type LoadOptions struct {
|
||||
CopyOptions
|
||||
}
|
||||
|
||||
// Load loads one or more images (depending on the transport) from the
|
||||
// specified path. The path may point to an image the following transports:
|
||||
// oci, oci-archive, dir, docker-archive.
|
||||
func (r *Runtime) Load(ctx context.Context, path string, options *LoadOptions) ([]string, error) {
|
||||
logrus.Debugf("Loading image from %q", path)
|
||||
|
||||
var (
|
||||
loadedImages []string
|
||||
loadError error
|
||||
)
|
||||
|
||||
if options == nil {
|
||||
options = &LoadOptions{}
|
||||
}
|
||||
|
||||
for _, f := range []func() ([]string, error){
|
||||
// OCI
|
||||
func() ([]string, error) {
|
||||
ref, err := ociTransport.NewReference(path, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.copyFromDefault(ctx, ref, &options.CopyOptions)
|
||||
},
|
||||
|
||||
// OCI-ARCHIVE
|
||||
func() ([]string, error) {
|
||||
ref, err := ociArchiveTransport.NewReference(path, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.copyFromDefault(ctx, ref, &options.CopyOptions)
|
||||
},
|
||||
|
||||
// DIR
|
||||
func() ([]string, error) {
|
||||
ref, err := dirTransport.NewReference(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.copyFromDefault(ctx, ref, &options.CopyOptions)
|
||||
},
|
||||
|
||||
// DOCKER-ARCHIVE
|
||||
func() ([]string, error) {
|
||||
ref, err := dockerArchiveTransport.ParseReference(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.copyFromDockerArchive(ctx, ref, &options.CopyOptions)
|
||||
},
|
||||
|
||||
// Give a decent error message if nothing above worked.
|
||||
func() ([]string, error) {
|
||||
return nil, errors.New("payload does not match any of the supported image formats (oci, oci-archive, dir, docker-archive)")
|
||||
},
|
||||
} {
|
||||
loadedImages, loadError = f()
|
||||
if loadError == nil {
|
||||
return loadedImages, loadError
|
||||
}
|
||||
logrus.Debugf("Error loading %s: %v", path, loadError)
|
||||
}
|
||||
|
||||
return nil, loadError
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package manifests
|
||||
|
||||
import (
|
||||
"github.com/containers/image/v5/signature"
|
||||
)
|
||||
|
||||
var (
|
||||
// storageAllowedPolicyScopes overrides the policy for local storage
|
||||
// to ensure that we can read images from it.
|
||||
storageAllowedPolicyScopes = signature.PolicyTransportScopes{
|
||||
"": []signature.PolicyRequirement{
|
||||
signature.NewPRInsecureAcceptAnything(),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,397 @@
|
|||
package manifests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"io"
|
||||
|
||||
"github.com/containers/common/pkg/manifests"
|
||||
"github.com/containers/common/pkg/supplemented"
|
||||
cp "github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/signature"
|
||||
is "github.com/containers/image/v5/storage"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const instancesData = "instances.json"
|
||||
|
||||
// ErrListImageUnknown is returned when we attempt to create an image reference
|
||||
// for a List that has not yet been saved to an image.
|
||||
var ErrListImageUnknown = stderrors.New("unable to determine which image holds the manifest list")
|
||||
|
||||
type list struct {
|
||||
manifests.List
|
||||
instances map[digest.Digest]string
|
||||
}
|
||||
|
||||
// List is a manifest list or image index, either created using Create(), or
|
||||
// loaded from local storage using LoadFromImage().
|
||||
type List interface {
|
||||
manifests.List
|
||||
SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error)
|
||||
Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error)
|
||||
Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error)
|
||||
Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error)
|
||||
}
|
||||
|
||||
// PushOptions includes various settings which are needed for pushing the
|
||||
// manifest list and its instances.
|
||||
type PushOptions struct {
|
||||
Store storage.Store
|
||||
SystemContext *types.SystemContext // github.com/containers/image/types.SystemContext
|
||||
ImageListSelection cp.ImageListSelection // set to either CopySystemImage, CopyAllImages, or CopySpecificImages
|
||||
Instances []digest.Digest // instances to copy if ImageListSelection == CopySpecificImages
|
||||
ReportWriter io.Writer // will be used to log the writing of the list and any blobs
|
||||
SignBy string // fingerprint of GPG key to use to sign images
|
||||
RemoveSignatures bool // true to discard signatures in images
|
||||
ManifestType string // the format to use when saving the list - possible options are oci, v2s1, and v2s2
|
||||
}
|
||||
|
||||
// Create creates a new list containing information about the specified image,
|
||||
// computing its manifest's digest, and retrieving OS and architecture
|
||||
// information from its configuration blob. Returns the new list, and the
|
||||
// instanceDigest for the initial image.
|
||||
func Create() List {
|
||||
return &list{
|
||||
List: manifests.Create(),
|
||||
instances: make(map[digest.Digest]string),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFromImage reads the manifest list or image index, and additional
|
||||
// information about where the various instances that it contains live, from an
|
||||
// image record with the specified ID in local storage.
|
||||
func LoadFromImage(store storage.Store, image string) (string, List, error) {
|
||||
img, err := store.Image(image)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image)
|
||||
}
|
||||
manifestBytes, err := store.ImageBigData(img.ID, storage.ImageDigestManifestBigDataNamePrefix)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image)
|
||||
}
|
||||
manifestList, err := manifests.FromBlob(manifestBytes)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
list := &list{
|
||||
List: manifestList,
|
||||
instances: make(map[digest.Digest]string),
|
||||
}
|
||||
instancesBytes, err := store.ImageBigData(img.ID, instancesData)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error locating image %q for loading instance list", image)
|
||||
}
|
||||
if err := json.Unmarshal(instancesBytes, &list.instances); err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error decoding instance list for image %q", image)
|
||||
}
|
||||
list.instances[""] = img.ID
|
||||
return img.ID, list, err
|
||||
}
|
||||
|
||||
// SaveToImage saves the manifest list or image index as the manifest of an
|
||||
// Image record with the specified names in local storage, generating a random
|
||||
// image ID if none is specified. It also stores information about where the
|
||||
// images whose manifests are included in the list can be found.
|
||||
func (l *list) SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error) {
|
||||
manifestBytes, err := l.List.Serialize(mimeType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
instancesBytes, err := json.Marshal(&l.instances)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
img, err := store.CreateImage(imageID, names, "", "", &storage.ImageOptions{})
|
||||
if err == nil || errors.Cause(err) == storage.ErrDuplicateID {
|
||||
created := (err == nil)
|
||||
if created {
|
||||
imageID = img.ID
|
||||
l.instances[""] = img.ID
|
||||
}
|
||||
err := store.SetImageBigData(imageID, storage.ImageDigestManifestBigDataNamePrefix, manifestBytes, manifest.Digest)
|
||||
if err != nil {
|
||||
if created {
|
||||
if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
|
||||
logrus.Errorf("error deleting image %q after failing to save manifest for it", img.ID)
|
||||
}
|
||||
}
|
||||
return "", errors.Wrapf(err, "error saving manifest list to image %q", imageID)
|
||||
}
|
||||
err = store.SetImageBigData(imageID, instancesData, instancesBytes, nil)
|
||||
if err != nil {
|
||||
if created {
|
||||
if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
|
||||
logrus.Errorf("error deleting image %q after failing to save instance locations for it", img.ID)
|
||||
}
|
||||
}
|
||||
return "", errors.Wrapf(err, "error saving instance list to image %q", imageID)
|
||||
}
|
||||
return imageID, nil
|
||||
}
|
||||
return "", errors.Wrapf(err, "error creating image to hold manifest list")
|
||||
}
|
||||
|
||||
// Reference returns an image reference for the composite image being built
|
||||
// in the list, or an error if the list has never been saved to a local image.
|
||||
func (l *list) Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error) {
|
||||
if l.instances[""] == "" {
|
||||
return nil, errors.Wrap(ErrListImageUnknown, "error building reference to list")
|
||||
}
|
||||
s, err := is.Transport.ParseStoreReference(store, l.instances[""])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating ImageReference from image %q", l.instances[""])
|
||||
}
|
||||
references := make([]types.ImageReference, 0, len(l.instances))
|
||||
whichInstances := make([]digest.Digest, 0, len(l.instances))
|
||||
switch multiple {
|
||||
case cp.CopyAllImages, cp.CopySystemImage:
|
||||
for instance := range l.instances {
|
||||
if instance != "" {
|
||||
whichInstances = append(whichInstances, instance)
|
||||
}
|
||||
}
|
||||
case cp.CopySpecificImages:
|
||||
for instance := range l.instances {
|
||||
for _, allowed := range instances {
|
||||
if instance == allowed {
|
||||
whichInstances = append(whichInstances, instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, instance := range whichInstances {
|
||||
imageName := l.instances[instance]
|
||||
ref, err := alltransports.ParseImageName(imageName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating ImageReference from image %q", imageName)
|
||||
}
|
||||
references = append(references, ref)
|
||||
}
|
||||
return supplemented.Reference(s, references, multiple, instances), nil
|
||||
}
|
||||
|
||||
// Push saves the manifest list and whichever blobs are needed to a destination location.
|
||||
func (l *list) Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) {
|
||||
// Load the system signing policy.
|
||||
pushPolicy, err := signature.DefaultPolicy(options.SystemContext)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrapf(err, "error obtaining default signature policy")
|
||||
}
|
||||
|
||||
// Override the settings for local storage to make sure that we can always read the source "image".
|
||||
pushPolicy.Transports[is.Transport.Name()] = storageAllowedPolicyScopes
|
||||
|
||||
policyContext, err := signature.NewPolicyContext(pushPolicy)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrapf(err, "error creating new signature policy context")
|
||||
}
|
||||
defer func() {
|
||||
if err2 := policyContext.Destroy(); err2 != nil {
|
||||
logrus.Errorf("error destroying signature policy context: %v", err2)
|
||||
}
|
||||
}()
|
||||
|
||||
// If we were given a media type that corresponds to a multiple-images
|
||||
// type, reset it to a valid corresponding single-image type, since we
|
||||
// already expect the image library to infer the list type from the
|
||||
// image type that we're telling it to force.
|
||||
singleImageManifestType := options.ManifestType
|
||||
switch singleImageManifestType {
|
||||
case v1.MediaTypeImageIndex:
|
||||
singleImageManifestType = v1.MediaTypeImageManifest
|
||||
case manifest.DockerV2ListMediaType:
|
||||
singleImageManifestType = manifest.DockerV2Schema2MediaType
|
||||
}
|
||||
|
||||
// Build a source reference for our list and grab bag full of blobs.
|
||||
src, err := l.Reference(options.Store, options.ImageListSelection, options.Instances)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
copyOptions := &cp.Options{
|
||||
ImageListSelection: options.ImageListSelection,
|
||||
Instances: options.Instances,
|
||||
SourceCtx: options.SystemContext,
|
||||
DestinationCtx: options.SystemContext,
|
||||
ReportWriter: options.ReportWriter,
|
||||
RemoveSignatures: options.RemoveSignatures,
|
||||
SignBy: options.SignBy,
|
||||
ForceManifestMIMEType: singleImageManifestType,
|
||||
}
|
||||
|
||||
// Copy whatever we were asked to copy.
|
||||
manifestBytes, err := cp.Image(ctx, policyContext, dest, src, copyOptions)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return nil, manifestDigest, nil
|
||||
}
|
||||
|
||||
// Add adds information about the specified image to the list, computing the
|
||||
// image's manifest's digest, retrieving OS and architecture information from
|
||||
// the image's configuration, and recording the image's reference so that it
|
||||
// can be found at push-time. Returns the instanceDigest for the image. If
|
||||
// the reference points to an image list, either all instances are added (if
|
||||
// "all" is true), or the instance which matches "sys" (if "all" is false) will
|
||||
// be added.
|
||||
func (l *list) Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error) {
|
||||
src, err := ref.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error setting up to read manifest and configuration from %q", transports.ImageName(ref))
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
type instanceInfo struct {
|
||||
instanceDigest *digest.Digest
|
||||
OS, Architecture, OSVersion, Variant string
|
||||
Features, OSFeatures, Annotations []string
|
||||
Size int64
|
||||
}
|
||||
var instanceInfos []instanceInfo
|
||||
var manifestDigest digest.Digest
|
||||
|
||||
primaryManifestBytes, primaryManifestType, err := src.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading manifest from %q", transports.ImageName(ref))
|
||||
}
|
||||
|
||||
if manifest.MIMETypeIsMultiImage(primaryManifestType) {
|
||||
lists, err := manifests.FromBlob(primaryManifestBytes)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref))
|
||||
}
|
||||
if all {
|
||||
for i, instance := range lists.OCIv1().Manifests {
|
||||
platform := instance.Platform
|
||||
if platform == nil {
|
||||
platform = &v1.Platform{}
|
||||
}
|
||||
instanceDigest := instance.Digest
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: &instanceDigest,
|
||||
OS: platform.OS,
|
||||
Architecture: platform.Architecture,
|
||||
OSVersion: platform.OSVersion,
|
||||
Variant: platform.Variant,
|
||||
Features: append([]string{}, lists.Docker().Manifests[i].Platform.Features...),
|
||||
OSFeatures: append([]string{}, platform.OSFeatures...),
|
||||
Size: instance.Size,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
}
|
||||
} else {
|
||||
list, err := manifest.ListFromBlob(primaryManifestBytes, primaryManifestType)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref))
|
||||
}
|
||||
instanceDigest, err := list.ChooseInstance(sys)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error selecting image from manifest list in %q", transports.ImageName(ref))
|
||||
}
|
||||
added := false
|
||||
for i, instance := range lists.OCIv1().Manifests {
|
||||
if instance.Digest != instanceDigest {
|
||||
continue
|
||||
}
|
||||
platform := instance.Platform
|
||||
if platform == nil {
|
||||
platform = &v1.Platform{}
|
||||
}
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: &instanceDigest,
|
||||
OS: platform.OS,
|
||||
Architecture: platform.Architecture,
|
||||
OSVersion: platform.OSVersion,
|
||||
Variant: platform.Variant,
|
||||
Features: append([]string{}, lists.Docker().Manifests[i].Platform.Features...),
|
||||
OSFeatures: append([]string{}, platform.OSFeatures...),
|
||||
Size: instance.Size,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
added = true
|
||||
}
|
||||
if !added {
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: &instanceDigest,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: nil,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
}
|
||||
|
||||
for _, instanceInfo := range instanceInfos {
|
||||
if instanceInfo.OS == "" || instanceInfo.Architecture == "" {
|
||||
img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, instanceInfo.instanceDigest))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading configuration blob from %q", transports.ImageName(ref))
|
||||
}
|
||||
config, err := img.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading info about config blob from %q", transports.ImageName(ref))
|
||||
}
|
||||
if instanceInfo.OS == "" {
|
||||
instanceInfo.OS = config.OS
|
||||
}
|
||||
if instanceInfo.Architecture == "" {
|
||||
instanceInfo.Architecture = config.Architecture
|
||||
}
|
||||
}
|
||||
manifestBytes, manifestType, err := src.GetManifest(ctx, instanceInfo.instanceDigest)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading manifest from %q, instance %q", transports.ImageName(ref), instanceInfo.instanceDigest)
|
||||
}
|
||||
if instanceInfo.instanceDigest == nil {
|
||||
manifestDigest, err = manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error computing digest of manifest from %q", transports.ImageName(ref))
|
||||
}
|
||||
instanceInfo.instanceDigest = &manifestDigest
|
||||
instanceInfo.Size = int64(len(manifestBytes))
|
||||
} else {
|
||||
if manifestDigest == "" {
|
||||
manifestDigest = *instanceInfo.instanceDigest
|
||||
}
|
||||
}
|
||||
err = l.List.AddInstance(*instanceInfo.instanceDigest, instanceInfo.Size, manifestType, instanceInfo.OS, instanceInfo.Architecture, instanceInfo.OSVersion, instanceInfo.OSFeatures, instanceInfo.Variant, instanceInfo.Features, instanceInfo.Annotations)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error adding instance with digest %q", *instanceInfo.instanceDigest)
|
||||
}
|
||||
if _, ok := l.instances[*instanceInfo.instanceDigest]; !ok {
|
||||
l.instances[*instanceInfo.instanceDigest] = transports.ImageName(ref)
|
||||
}
|
||||
}
|
||||
|
||||
return manifestDigest, nil
|
||||
}
|
||||
|
||||
// Remove filters out any instances in the list which match the specified digest.
|
||||
func (l *list) Remove(instanceDigest digest.Digest) error {
|
||||
err := l.List.Remove(instanceDigest)
|
||||
if err == nil {
|
||||
if _, needToDelete := l.instances[instanceDigest]; needToDelete {
|
||||
delete(l.instances, instanceDigest)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
package manifests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/common/pkg/manifests"
|
||||
cp "github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
_ List = &list{}
|
||||
|
||||
sys = &types.SystemContext{
|
||||
SystemRegistriesConfPath: "../../tests/registries.conf",
|
||||
SignaturePolicyPath: "../../tests/policy.json",
|
||||
}
|
||||
amd64sys = &types.SystemContext{ArchitectureChoice: "amd64"}
|
||||
arm64sys = &types.SystemContext{ArchitectureChoice: "arm64"}
|
||||
ppc64sys = &types.SystemContext{ArchitectureChoice: "ppc64le"}
|
||||
)
|
||||
|
||||
const (
|
||||
listImageName = "foo"
|
||||
|
||||
otherListImage = "docker://k8s.gcr.io/pause:3.1"
|
||||
otherListDigest = "sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea"
|
||||
otherListAmd64Digest = "sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610"
|
||||
otherListArm64Digest = "sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
|
||||
otherListPpc64Digest = "sha256:bcf9771c0b505e68c65440474179592ffdfa98790eb54ffbf129969c5e429990"
|
||||
otherListInstanceDigest = "docker://k8s.gcr.io/pause@sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
|
||||
)
|
||||
|
||||
func TestSaveLoad(t *testing.T) {
|
||||
if unshare.IsRootless() {
|
||||
t.Skip("Test can only run as root")
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "manifests")
|
||||
assert.Nilf(t, err, "error creating temporary directory")
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
storeOptions := storage.StoreOptions{
|
||||
GraphRoot: filepath.Join(dir, "root"),
|
||||
RunRoot: filepath.Join(dir, "runroot"),
|
||||
GraphDriverName: "vfs",
|
||||
}
|
||||
store, err := storage.GetStore(storeOptions)
|
||||
assert.Nilf(t, err, "error opening store")
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if _, err := store.Shutdown(true); err != nil {
|
||||
assert.Nilf(t, err, "error closing store")
|
||||
}
|
||||
}()
|
||||
|
||||
list := Create()
|
||||
assert.NotNil(t, list, "Create() returned nil?")
|
||||
|
||||
image, err := list.SaveToImage(store, "", []string{listImageName}, manifest.DockerV2ListMediaType)
|
||||
assert.Nilf(t, err, "SaveToImage(1)")
|
||||
imageReused, err := list.SaveToImage(store, image, nil, manifest.DockerV2ListMediaType)
|
||||
assert.Nilf(t, err, "SaveToImage(2)")
|
||||
|
||||
_, list, err = LoadFromImage(store, image)
|
||||
assert.Nilf(t, err, "LoadFromImage(1)")
|
||||
assert.NotNilf(t, list, "LoadFromImage(1)")
|
||||
_, list, err = LoadFromImage(store, imageReused)
|
||||
assert.Nilf(t, err, "LoadFromImage(2)")
|
||||
assert.NotNilf(t, list, "LoadFromImage(2)")
|
||||
_, list, err = LoadFromImage(store, listImageName)
|
||||
assert.Nilf(t, err, "LoadFromImage(3)")
|
||||
assert.NotNilf(t, list, "LoadFromImage(3)")
|
||||
}
|
||||
|
||||
func TestAddRemove(t *testing.T) {
|
||||
if unshare.IsRootless() {
|
||||
t.Skip("Test can only run as root")
|
||||
}
|
||||
ctx := context.TODO()
|
||||
|
||||
ref, err := alltransports.ParseImageName(otherListImage)
|
||||
assert.Nilf(t, err, "ParseImageName(%q)", otherListImage)
|
||||
src, err := ref.NewImageSource(ctx, sys)
|
||||
assert.Nilf(t, err, "NewImageSource(%q)", otherListImage)
|
||||
defer assert.Nilf(t, src.Close(), "ImageSource.Close()")
|
||||
m, _, err := src.GetManifest(ctx, nil)
|
||||
assert.Nilf(t, err, "ImageSource.GetManifest()")
|
||||
assert.Nilf(t, src.Close(), "ImageSource.GetManifest()")
|
||||
listDigest, err := manifest.Digest(m)
|
||||
assert.Nilf(t, err, "manifest.Digest()")
|
||||
assert.Equalf(t, listDigest.String(), otherListDigest, "digest for image %q changed?", otherListImage)
|
||||
|
||||
l, err := manifests.FromBlob(m)
|
||||
assert.Nilf(t, err, "manifests.FromBlob()")
|
||||
assert.NotNilf(t, l, "manifests.FromBlob()")
|
||||
assert.Equalf(t, len(l.Instances()), 5, "image %q had an arch added?", otherListImage)
|
||||
|
||||
list := Create()
|
||||
instanceDigest, err := list.Add(ctx, amd64sys, ref, false)
|
||||
assert.Nilf(t, err, "list.Add(all=false)")
|
||||
assert.Equal(t, instanceDigest.String(), otherListAmd64Digest)
|
||||
assert.Equalf(t, len(list.Instances()), 1, "too many instances added")
|
||||
|
||||
list = Create()
|
||||
instanceDigest, err = list.Add(ctx, arm64sys, ref, false)
|
||||
assert.Nilf(t, err, "list.Add(all=false)")
|
||||
assert.Equal(t, instanceDigest.String(), otherListArm64Digest)
|
||||
assert.Equalf(t, len(list.Instances()), 1, "too many instances added")
|
||||
|
||||
list = Create()
|
||||
instanceDigest, err = list.Add(ctx, ppc64sys, ref, false)
|
||||
assert.Nilf(t, err, "list.Add(all=false)")
|
||||
assert.Equal(t, instanceDigest.String(), otherListPpc64Digest)
|
||||
assert.Equalf(t, len(list.Instances()), 1, "too many instances added")
|
||||
|
||||
_, err = list.Add(ctx, sys, ref, true)
|
||||
assert.Nilf(t, err, "list.Add(all=true)")
|
||||
assert.Equalf(t, len(list.Instances()), 5, "too many instances added")
|
||||
|
||||
list = Create()
|
||||
_, err = list.Add(ctx, sys, ref, true)
|
||||
assert.Nilf(t, err, "list.Add(all=true)")
|
||||
assert.Equalf(t, len(list.Instances()), 5, "too many instances added", otherListImage)
|
||||
|
||||
for _, instance := range list.Instances() {
|
||||
assert.Nilf(t, list.Remove(instance), "error removing instance %q", instance)
|
||||
}
|
||||
assert.Equalf(t, len(list.Instances()), 0, "should have removed all instances")
|
||||
|
||||
ref, err = alltransports.ParseImageName(otherListInstanceDigest)
|
||||
assert.Nilf(t, err, "ParseImageName(%q)", otherListInstanceDigest)
|
||||
|
||||
list = Create()
|
||||
_, err = list.Add(ctx, sys, ref, false)
|
||||
assert.Nilf(t, err, "list.Add(all=false)")
|
||||
assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest)
|
||||
|
||||
list = Create()
|
||||
_, err = list.Add(ctx, sys, ref, true)
|
||||
assert.Nilf(t, err, "list.Add(all=true)")
|
||||
assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest)
|
||||
}
|
||||
|
||||
func TestReference(t *testing.T) {
|
||||
if unshare.IsRootless() {
|
||||
t.Skip("Test can only run as root")
|
||||
}
|
||||
ctx := context.TODO()
|
||||
|
||||
dir, err := ioutil.TempDir("", "manifests")
|
||||
assert.Nilf(t, err, "error creating temporary directory")
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
storeOptions := storage.StoreOptions{
|
||||
GraphRoot: filepath.Join(dir, "root"),
|
||||
RunRoot: filepath.Join(dir, "runroot"),
|
||||
GraphDriverName: "vfs",
|
||||
}
|
||||
store, err := storage.GetStore(storeOptions)
|
||||
assert.Nilf(t, err, "error opening store")
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if _, err := store.Shutdown(true); err != nil {
|
||||
assert.Nilf(t, err, "error closing store")
|
||||
}
|
||||
}()
|
||||
|
||||
ref, err := alltransports.ParseImageName(otherListImage)
|
||||
assert.Nilf(t, err, "ParseImageName(%q)", otherListImage)
|
||||
|
||||
list := Create()
|
||||
_, err = list.Add(ctx, ppc64sys, ref, false)
|
||||
assert.Nilf(t, err, "list.Add(all=false)")
|
||||
|
||||
listRef, err := list.Reference(store, cp.CopyAllImages, nil)
|
||||
assert.NotNilf(t, err, "list.Reference(never saved)")
|
||||
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopyAllImages, nil)
|
||||
assert.NotNilf(t, err, "list.Reference(never saved)")
|
||||
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySystemImage, nil)
|
||||
assert.NotNilf(t, err, "list.Reference(never saved)")
|
||||
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest})
|
||||
assert.NotNilf(t, err, "list.Reference(never saved)")
|
||||
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest})
|
||||
assert.NotNilf(t, err, "list.Reference(never saved)")
|
||||
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
||||
|
||||
_, err = list.SaveToImage(store, "", []string{listImageName}, "")
|
||||
assert.Nilf(t, err, "SaveToImage")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopyAllImages, nil)
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySystemImage, nil)
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, nil)
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest})
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest})
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
_, err = list.Add(ctx, sys, ref, true)
|
||||
assert.Nilf(t, err, "list.Add(all=true)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopyAllImages, nil)
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySystemImage, nil)
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, nil)
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest})
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
|
||||
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest})
|
||||
assert.Nilf(t, err, "list.Reference(saved)")
|
||||
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
||||
}
|
||||
|
||||
func TestPush(t *testing.T) {
|
||||
if unshare.IsRootless() {
|
||||
t.Skip("Test can only run as root")
|
||||
}
|
||||
ctx := context.TODO()
|
||||
|
||||
dir, err := ioutil.TempDir("", "manifests")
|
||||
assert.Nilf(t, err, "error creating temporary directory")
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
storeOptions := storage.StoreOptions{
|
||||
GraphRoot: filepath.Join(dir, "root"),
|
||||
RunRoot: filepath.Join(dir, "runroot"),
|
||||
GraphDriverName: "vfs",
|
||||
}
|
||||
store, err := storage.GetStore(storeOptions)
|
||||
assert.Nilf(t, err, "error opening store")
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if _, err := store.Shutdown(true); err != nil {
|
||||
assert.Nilf(t, err, "error closing store")
|
||||
}
|
||||
}()
|
||||
|
||||
dest, err := ioutil.TempDir("", "manifests")
|
||||
assert.Nilf(t, err, "error creating temporary directory")
|
||||
defer os.RemoveAll(dest)
|
||||
|
||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", dest))
|
||||
assert.Nilf(t, err, "ParseImageName()")
|
||||
|
||||
ref, err := alltransports.ParseImageName(otherListImage)
|
||||
assert.Nilf(t, err, "ParseImageName(%q)", otherListImage)
|
||||
|
||||
list := Create()
|
||||
_, err = list.Add(ctx, sys, ref, true)
|
||||
assert.Nilf(t, err, "list.Add(all=true)")
|
||||
|
||||
_, err = list.SaveToImage(store, "", []string{listImageName}, "")
|
||||
assert.Nilf(t, err, "SaveToImage")
|
||||
|
||||
options := PushOptions{
|
||||
Store: store,
|
||||
SystemContext: sys,
|
||||
ImageListSelection: cp.CopyAllImages,
|
||||
Instances: nil,
|
||||
}
|
||||
_, _, err = list.Push(ctx, destRef, options)
|
||||
assert.Nilf(t, err, "list.Push(all)")
|
||||
|
||||
options.ImageListSelection = cp.CopySystemImage
|
||||
_, _, err = list.Push(ctx, destRef, options)
|
||||
assert.Nilf(t, err, "list.Push(local)")
|
||||
|
||||
options.ImageListSelection = cp.CopySpecificImages
|
||||
_, _, err = list.Push(ctx, destRef, options)
|
||||
assert.Nilf(t, err, "list.Push(none specified)")
|
||||
|
||||
options.Instances = []digest.Digest{otherListAmd64Digest}
|
||||
_, _, err = list.Push(ctx, destRef, options)
|
||||
assert.Nilf(t, err, "list.Push(one specified)")
|
||||
|
||||
options.Instances = append(options.Instances, otherListArm64Digest)
|
||||
_, _, err = list.Push(ctx, destRef, options)
|
||||
assert.Nilf(t, err, "list.Push(two specified)")
|
||||
|
||||
options.Instances = append(options.Instances, otherListPpc64Digest)
|
||||
_, _, err = list.Push(ctx, destRef, options)
|
||||
assert.Nilf(t, err, "list.Push(three specified)")
|
||||
|
||||
options.Instances = append(options.Instances, otherListDigest)
|
||||
_, _, err = list.Push(ctx, destRef, options)
|
||||
assert.Nilf(t, err, "list.Push(four specified)")
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// NormalizeName normalizes the provided name according to the conventions by
|
||||
// Podman and Buildah. If tag and digest are missing, the "latest" tag will be
|
||||
// used. If it's a short name, it will be prefixed with "localhost/".
|
||||
//
|
||||
// References to docker.io are normalized according to the Docker conventions.
|
||||
// For instance, "docker.io/foo" turns into "docker.io/library/foo".
|
||||
func NormalizeName(name string) (reference.Named, error) {
|
||||
// NOTE: this code is in symmetrie with containers/image/pkg/shortnames.
|
||||
ref, err := reference.Parse(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
named, ok := ref.(reference.Named)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%q is not a named reference", name)
|
||||
}
|
||||
|
||||
// Enforce "localhost" if needed.
|
||||
registry := reference.Domain(named)
|
||||
if !(strings.ContainsAny(registry, ".:") || registry == "localhost") {
|
||||
name = toLocalImageName(ref.String())
|
||||
}
|
||||
|
||||
// Another parse which also makes sure that docker.io references are
|
||||
// correctly normalized (e.g., docker.io/alpine to
|
||||
// docker.io/library/alpine).
|
||||
named, err = reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, hasTag := named.(reference.NamedTagged); hasTag {
|
||||
return named, nil
|
||||
}
|
||||
if _, hasDigest := named.(reference.Digested); hasDigest {
|
||||
return named, nil
|
||||
}
|
||||
|
||||
// Make sure to tag "latest".
|
||||
return reference.TagNameOnly(named), nil
|
||||
}
|
||||
|
||||
// prefix the specified name with "localhost/".
|
||||
func toLocalImageName(name string) string {
|
||||
return "localhost/" + strings.TrimLeft(name, "/")
|
||||
}
|
||||
|
||||
// NameTagPair represents a RepoTag of an image.
|
||||
type NameTagPair struct {
|
||||
// Name of the RepoTag. Maybe "<none>".
|
||||
Name string
|
||||
// Tag of the RepoTag. Maybe "<none>".
|
||||
Tag string
|
||||
}
|
||||
|
||||
// ToNameTagsPairs splits repoTags into name&tag pairs.
|
||||
// Guaranteed to return at least one pair.
|
||||
func ToNameTagPairs(repoTags []reference.NamedTagged) ([]NameTagPair, error) {
|
||||
none := "<none>"
|
||||
|
||||
var pairs []NameTagPair
|
||||
for _, named := range repoTags {
|
||||
pair := NameTagPair{Name: named.Name(), Tag: none}
|
||||
if tagged, isTagged := named.(reference.NamedTagged); isTagged {
|
||||
pair.Tag = tagged.Tag()
|
||||
}
|
||||
pairs = append(pairs, pair)
|
||||
}
|
||||
|
||||
if len(pairs) == 0 {
|
||||
pairs = append(pairs, NameTagPair{Name: none, Tag: none})
|
||||
}
|
||||
return pairs, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNormalizeName(t *testing.T) {
|
||||
const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
|
||||
for _, c := range []struct{ input, expected string }{
|
||||
{"#", ""}, // Clearly invalid
|
||||
{"example.com/busybox", "example.com/busybox:latest"}, // Qualified name-only
|
||||
{"example.com/busybox:notlatest", "example.com/busybox:notlatest"}, // Qualified name:tag
|
||||
{"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix}, // Qualified name@digest; FIXME? Should we allow tagging with a digest at all?
|
||||
{"example.com/busybox:notlatest" + digestSuffix, "example.com/busybox:notlatest" + digestSuffix}, // Qualified name:tag@digest
|
||||
{"busybox:latest", "localhost/busybox:latest"}, // Unqualified name-only
|
||||
{"localhost/busybox", "localhost/busybox:latest"}, // Qualified with localhost
|
||||
{"ns/busybox:latest", "localhost/ns/busybox:latest"}, // Unqualified with a dot-less namespace
|
||||
{"docker.io/busybox:latest", "docker.io/library/busybox:latest"}, // docker.io without /library/
|
||||
} {
|
||||
res, err := NormalizeName(c.input)
|
||||
if c.expected == "" {
|
||||
assert.Error(t, err, c.input)
|
||||
} else {
|
||||
require.NoError(t, err, c.input)
|
||||
assert.Equal(t, c.expected, res.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// toOCI returns the image as OCI v1 image.
|
||||
func (i *Image) toOCI(ctx context.Context) (*ociv1.Image, error) {
|
||||
if i.cached.ociv1Image != nil {
|
||||
return i.cached.ociv1Image, nil
|
||||
}
|
||||
ref, err := i.StorageReference()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
img, err := ref.NewImage(ctx, &i.runtime.systemContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer img.Close()
|
||||
|
||||
return img.OCIConfig(ctx)
|
||||
}
|
||||
|
||||
// historiesMatch returns the number of entries in the histories which have the
|
||||
// same contents
|
||||
func historiesMatch(a, b []ociv1.History) int {
|
||||
i := 0
|
||||
for i < len(a) && i < len(b) {
|
||||
if a[i].Created != nil && b[i].Created == nil {
|
||||
return i
|
||||
}
|
||||
if a[i].Created == nil && b[i].Created != nil {
|
||||
return i
|
||||
}
|
||||
if a[i].Created != nil && b[i].Created != nil {
|
||||
if !a[i].Created.Equal(*(b[i].Created)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
if a[i].CreatedBy != b[i].CreatedBy {
|
||||
return i
|
||||
}
|
||||
if a[i].Author != b[i].Author {
|
||||
return i
|
||||
}
|
||||
if a[i].Comment != b[i].Comment {
|
||||
return i
|
||||
}
|
||||
if a[i].EmptyLayer != b[i].EmptyLayer {
|
||||
return i
|
||||
}
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// areParentAndChild checks diff ID and history in the two images and return
|
||||
// true if the second should be considered to be directly based on the first
|
||||
func areParentAndChild(parent, child *ociv1.Image) bool {
|
||||
// the child and candidate parent should share all of the
|
||||
// candidate parent's diff IDs, which together would have
|
||||
// controlled which layers were used
|
||||
|
||||
// Both, child and parent, may be nil when the storage is left in an
|
||||
// incoherent state. Issue #7444 describes such a case when a build
|
||||
// has been killed.
|
||||
if child == nil || parent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(parent.RootFS.DiffIDs) > len(child.RootFS.DiffIDs) {
|
||||
return false
|
||||
}
|
||||
childUsesCandidateDiffs := true
|
||||
for i := range parent.RootFS.DiffIDs {
|
||||
if child.RootFS.DiffIDs[i] != parent.RootFS.DiffIDs[i] {
|
||||
childUsesCandidateDiffs = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !childUsesCandidateDiffs {
|
||||
return false
|
||||
}
|
||||
// the child should have the same history as the parent, plus
|
||||
// one more entry
|
||||
if len(parent.History)+1 != len(child.History) {
|
||||
return false
|
||||
}
|
||||
if historiesMatch(parent.History, child.History) != len(parent.History) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
libimageTypes "github.com/containers/common/libimage/types"
|
||||
dirTransport "github.com/containers/image/v5/directory"
|
||||
dockerTransport "github.com/containers/image/v5/docker"
|
||||
dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
ociArchiveTransport "github.com/containers/image/v5/oci/archive"
|
||||
ociTransport "github.com/containers/image/v5/oci/layout"
|
||||
"github.com/containers/image/v5/pkg/shortnames"
|
||||
storageTransport "github.com/containers/image/v5/storage"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PullOptions allows for custommizing image pulls.
|
||||
type PullOptions struct {
|
||||
CopyOptions
|
||||
|
||||
// If true, all tags of the image will be pulled from the container
|
||||
// registry. Only supported for the docker transport.
|
||||
AllTags bool
|
||||
}
|
||||
|
||||
// Pull pulls the specified name. Name may refer to any of the supported
|
||||
// transports from github.com/containers/image. If no transport is encoded,
|
||||
// name will be treated as a reference to a registry (i.e., docker transport).
|
||||
//
|
||||
// Note that pullPolicy is only used when pulling from a container registry but
|
||||
// it *must* be different than the default value `PullPolicyUnsupported`. This
|
||||
// way, callers are forced to decide on the pull behaviour. The reasoning
|
||||
// behind is that some (commands of some) tools have different default pull
|
||||
// policies (e.g., buildah-bud versus podman-build). Making the pull-policy
|
||||
// choice explicit is an attempt to prevent silent regressions.
|
||||
//
|
||||
// The errror is storage.ErrImageUnknown iff the pull policy is set to "never"
|
||||
// and no local image has been found. This allows for an easier integration
|
||||
// into some users of this package (e.g., Buildah).
|
||||
func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy libimageTypes.PullPolicy, options *PullOptions) ([]*Image, error) {
|
||||
logrus.Debugf("Pulling image %s", name)
|
||||
|
||||
if options == nil {
|
||||
options = &PullOptions{}
|
||||
}
|
||||
|
||||
ref, err := alltransports.ParseImageName(name)
|
||||
if err != nil {
|
||||
// If the image clearly refers to a local one, we can look it up directly.
|
||||
// In fact, we need to since they are not parseable.
|
||||
if strings.HasPrefix(name, "sha256:") || (len(name) == 64 && !strings.Contains(name, "/.:@")) {
|
||||
if pullPolicy == libimageTypes.PullPolicyAlways {
|
||||
return nil, errors.Errorf("pull policy is always but image has been referred to by ID (%s)", name)
|
||||
}
|
||||
local, _, err := r.LookupImage(name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if local == nil {
|
||||
return nil, errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
return []*Image{local}, err
|
||||
}
|
||||
|
||||
// If the input does not include a transport assume it refers
|
||||
// to a registry.
|
||||
dockerRef, dockerErr := alltransports.ParseImageName("docker://" + name)
|
||||
if dockerErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref = dockerRef
|
||||
}
|
||||
|
||||
if options.AllTags && ref.Transport().Name() != dockerTransport.Transport.Name() {
|
||||
return nil, errors.Errorf("pulling all tags is not supported for %s transport", ref.Transport().Name())
|
||||
}
|
||||
|
||||
var (
|
||||
pulledImages []string
|
||||
pullError error
|
||||
)
|
||||
|
||||
// Dispatch the copy operation.
|
||||
switch ref.Transport().Name() {
|
||||
|
||||
// DOCKER/REGISTRY
|
||||
case dockerTransport.Transport.Name():
|
||||
pulledImages, pullError = r.copyFromRegistry(ctx, ref, strings.TrimPrefix(name, "docker://"), pullPolicy, options)
|
||||
|
||||
// DOCKER ARCHIVE
|
||||
case dockerArchiveTransport.Transport.Name():
|
||||
pulledImages, pullError = r.copyFromDockerArchive(ctx, ref, &options.CopyOptions)
|
||||
|
||||
// OCI
|
||||
case ociTransport.Transport.Name():
|
||||
pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions)
|
||||
|
||||
// OCI ARCHIVE
|
||||
case ociArchiveTransport.Transport.Name():
|
||||
pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions)
|
||||
|
||||
// DIR
|
||||
case dirTransport.Transport.Name():
|
||||
pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions)
|
||||
|
||||
// UNSUPPORTED
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported transport %q for pulling", ref.Transport().Name())
|
||||
}
|
||||
|
||||
if pullError != nil {
|
||||
return nil, pullError
|
||||
}
|
||||
|
||||
localImages := []*Image{}
|
||||
for _, name := range pulledImages {
|
||||
local, _, err := r.LookupImage(name, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error locating pulled image %q name in containers storage", name)
|
||||
}
|
||||
if local == nil {
|
||||
return nil, errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
localImages = append(localImages, local)
|
||||
}
|
||||
|
||||
return localImages, pullError
|
||||
}
|
||||
|
||||
// copyFromDefault is the default copier for a number of transports. Other
|
||||
// transports require some specific dancing, sometimes Yoga.
|
||||
func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) {
|
||||
c, err := newCopier(&r.systemContext, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
// Figure out a name for the storage destination.
|
||||
var storageName, imageName string
|
||||
switch ref.Transport().Name() {
|
||||
|
||||
case ociTransport.Transport.Name():
|
||||
split := strings.SplitN(ref.StringWithinTransport(), ":", 2)
|
||||
storageName = toLocalImageName(split[0])
|
||||
imageName = storageName
|
||||
|
||||
case ociArchiveTransport.Transport.Name():
|
||||
manifest, err := ociArchiveTransport.LoadManifestDescriptor(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if index.json has no reference name, compute the image digest instead
|
||||
if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" {
|
||||
storageName, err = getImageDigest(ctx, ref, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageName = "sha256:" + storageName[1:]
|
||||
} else {
|
||||
storageName = manifest.Annotations["org.opencontainers.image.ref.name"]
|
||||
imageName = storageName
|
||||
}
|
||||
|
||||
default:
|
||||
storageName = toLocalImageName(ref.StringWithinTransport())
|
||||
imageName = storageName
|
||||
}
|
||||
|
||||
// Create a storage reference.
|
||||
destRef, err := storageTransport.Transport.ParseStoreReference(r.store, storageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = c.copy(ctx, ref, destRef)
|
||||
return []string{imageName}, err
|
||||
}
|
||||
|
||||
// storageReferencesFromArchiveReader returns a slice of image references inside the
|
||||
// archive reader. A docker archive may include more than one image and this
|
||||
// method allows for extracting them into containers storage references which
|
||||
// can later be used from copying.
|
||||
func (r *Runtime) storageReferencesReferencesFromArchiveReader(ctx context.Context, readerRef types.ImageReference, reader *dockerArchiveTransport.Reader) ([]types.ImageReference, []string, error) {
|
||||
destNames, err := reader.ManifestTagsForReference(readerRef)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var imageNames []string
|
||||
if len(destNames) == 0 {
|
||||
destName, err := getImageDigest(ctx, readerRef, &r.systemContext)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
destNames = append(destNames, destName)
|
||||
// Make sure the image can be loaded after the pull by
|
||||
// replacing the @ with sha256:.
|
||||
imageNames = append(imageNames, "sha256:"+destName[1:])
|
||||
} else {
|
||||
for i := range destNames {
|
||||
ref, err := NormalizeName(destNames[i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
destNames[i] = ref.String()
|
||||
}
|
||||
imageNames = destNames
|
||||
}
|
||||
|
||||
references := []types.ImageReference{}
|
||||
for _, destName := range destNames {
|
||||
destRef, err := storageTransport.Transport.ParseStoreReference(r.store, destName)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
|
||||
}
|
||||
references = append(references, destRef)
|
||||
}
|
||||
|
||||
return references, imageNames, nil
|
||||
}
|
||||
|
||||
// copyFromDockerArchive copies one or more images from the specified
|
||||
// reference.
|
||||
func (r *Runtime) copyFromDockerArchive(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) {
|
||||
c, err := newCopier(&r.systemContext, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
// There may be more than one image inside the docker archive, so we
|
||||
// need a quick glimpse inside.
|
||||
reader, readerRef, err := dockerArchiveTransport.NewReaderForReference(&r.systemContext, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get a slice of storage references we can copy.
|
||||
references, destNames, err := r.storageReferencesReferencesFromArchiveReader(ctx, readerRef, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now copy all of the images. Use readerRef for performance.
|
||||
for _, destRef := range references {
|
||||
if _, err := c.copy(ctx, readerRef, destRef); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return destNames, nil
|
||||
}
|
||||
|
||||
// copyFromRegistry pulls the specified, possibly unqualified, name from a
|
||||
// registry. On successful pull it returns the used fully-qualified name that
|
||||
// can later be used to look up the image in the local containers storage.
|
||||
//
|
||||
// If options.All is set, all tags from the specified registry will be pulled.
|
||||
func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference, inputName string, pullPolicy libimageTypes.PullPolicy, options *PullOptions) ([]string, error) {
|
||||
// Sanity check.
|
||||
if err := pullPolicy.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !options.AllTags {
|
||||
return r.copySingleImageFromRegistry(ctx, inputName, pullPolicy, options)
|
||||
}
|
||||
|
||||
named := reference.TrimNamed(ref.DockerReference())
|
||||
tags, err := dockerTransport.GetRepositoryTags(ctx, &r.systemContext, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pulledTags := []string{}
|
||||
for _, tag := range tags {
|
||||
tagged, err := reference.WithTag(named, tag)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating tagged reference (name %s, tag %s)", named.String(), tag)
|
||||
}
|
||||
pulled, err := r.copySingleImageFromRegistry(ctx, tagged.String(), pullPolicy, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pulledTags = append(pulledTags, pulled...)
|
||||
}
|
||||
|
||||
return pulledTags, nil
|
||||
}
|
||||
|
||||
// copySingleImageFromRegistry pulls the specified, possibly unqualified, name
|
||||
// from a registry. On successful pull it returns the used fully-qualified
|
||||
// name that can later be used to look up the image in the local containers
|
||||
// storage.
|
||||
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy libimageTypes.PullPolicy, options *PullOptions) ([]string, error) {
|
||||
// Sanity check.
|
||||
if err := pullPolicy.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
localImage *Image
|
||||
resolvedImageName string
|
||||
err error
|
||||
)
|
||||
|
||||
// Always check if there's a local image. If, we should use it's
|
||||
// resolved name for pulling. Assume we're doing a `pull foo`.
|
||||
// If there's already a local image "localhost/foo", then we should
|
||||
// attempt pulling that instead of doing the full short-name dance.
|
||||
localImage, resolvedImageName, err = r.LookupImage(imageName, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error looking up local image")
|
||||
}
|
||||
|
||||
if pullPolicy == libimageTypes.PullPolicyNever {
|
||||
if localImage != nil {
|
||||
logrus.Debugf("Pull policy %q but no local image has been found for %s", pullPolicy, imageName)
|
||||
return []string{resolvedImageName}, nil
|
||||
}
|
||||
logrus.Debugf("Pull policy %q and %s resolved to local image %s", pullPolicy, imageName, resolvedImageName)
|
||||
return nil, errors.Wrap(storage.ErrImageUnknown, imageName)
|
||||
}
|
||||
|
||||
if pullPolicy == libimageTypes.PullPolicyMissing && localImage != nil {
|
||||
return []string{resolvedImageName}, nil
|
||||
}
|
||||
|
||||
// If we looked up the image by ID, we cannot really pull from anywhere.
|
||||
if localImage != nil && strings.HasPrefix(localImage.ID(), imageName) {
|
||||
switch pullPolicy {
|
||||
case libimageTypes.PullPolicyAlways:
|
||||
return nil, errors.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName)
|
||||
default:
|
||||
return []string{resolvedImageName}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a local image, we should use it's locally resolved name
|
||||
// (see containers/buildah #2904).
|
||||
if localImage != nil {
|
||||
if imageName != resolvedImageName {
|
||||
logrus.Debugf("Image %s resolved to local image %s which will be used for pulling", imageName, resolvedImageName)
|
||||
}
|
||||
imageName = resolvedImageName
|
||||
}
|
||||
resolved, err := shortnames.Resolve(&r.systemContext, imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE: Below we print the description from the short-name resolution.
|
||||
// In theory we could print it here. In practice, however, this is
|
||||
// causing a hard time for Buildah uses who are doing a `buildah from
|
||||
// image` and expect just the container name to be printed if the image
|
||||
// is present locally.
|
||||
// The pragmatic solution is to only print the description when we found
|
||||
// a _newer_ image that we're about to pull.
|
||||
wroteDesc := false
|
||||
writeDesc := func() error {
|
||||
if wroteDesc {
|
||||
return nil
|
||||
}
|
||||
wroteDesc = true
|
||||
if desc := resolved.Description(); len(desc) > 0 {
|
||||
logrus.Debug(desc)
|
||||
if options.Writer != nil {
|
||||
if _, err := options.Writer.Write([]byte(desc + "\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := newCopier(&r.systemContext, &options.CopyOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
var pullErrors []error
|
||||
for _, candidate := range resolved.PullCandidates {
|
||||
candidateString := candidate.Value.String()
|
||||
logrus.Debugf("Attempting to pull candidate %s for %s", candidateString, imageName)
|
||||
srcRef, err := dockerTransport.NewReference(candidate.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pullPolicy == libimageTypes.PullPolicyNewer && localImage != nil {
|
||||
isNewer, err := localImage.HasDifferentDigest(ctx, srcRef)
|
||||
if err != nil {
|
||||
pullErrors = append(pullErrors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !isNewer {
|
||||
logrus.Debugf("Skipping pull candidate %s as the image is not newer (pull policy %s)", candidateString, pullPolicy)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
destRef, err := storageTransport.Transport.ParseStoreReference(r.store, candidate.Value.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := writeDesc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.Writer != nil {
|
||||
if _, err := io.WriteString(options.Writer, fmt.Sprintf("Trying to pull %s...\n", candidateString)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
|
||||
logrus.Debugf("Error pulling candidate %s: %v", candidateString, err)
|
||||
pullErrors = append(pullErrors, err)
|
||||
continue
|
||||
}
|
||||
if err := candidate.Record(); err != nil {
|
||||
// Only log the recording errors. Podman has seen
|
||||
// reports where users set most of the system to
|
||||
// read-only which can cause issues.
|
||||
logrus.Errorf("Error recording short-name alias %q: %v", candidateString, err)
|
||||
}
|
||||
|
||||
logrus.Debugf("Pulled candidate %s successfully", candidateString)
|
||||
return []string{candidate.Value.String()}, nil
|
||||
}
|
||||
|
||||
if localImage != nil && pullPolicy == libimageTypes.PullPolicyNewer {
|
||||
return []string{resolvedImageName}, nil
|
||||
}
|
||||
|
||||
if len(pullErrors) == 0 {
|
||||
return nil, errors.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy)
|
||||
}
|
||||
|
||||
return nil, resolved.FormatPullErrors(pullErrors)
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PullPolicy determines how and which images are being pulled from a container
|
||||
// registry (i.e., docker transport only).
|
||||
//
|
||||
// Supported string values are:
|
||||
// * "always" <-> PullPolicyAlways
|
||||
// * "missing" <-> PullPolicyMissing
|
||||
// * "newer" <-> PullPolicyNewer
|
||||
// * "never" <-> PullPolicyNever
|
||||
type PullPolicy int
|
||||
|
||||
const (
|
||||
// This default value forces callers to setup a custom default policy.
|
||||
// Some tools use different policies (e.g., buildah-bud versus
|
||||
// podman-build).
|
||||
PullPolicyUnsupported PullPolicy = iota
|
||||
// Always pull the image.
|
||||
PullPolicyAlways
|
||||
// Pull the image only if it could not be found in the local containers
|
||||
// storage.
|
||||
PullPolicyMissing
|
||||
// Pull if the image on the registry is new than the one in the local
|
||||
// containers storage. An image is considered to be newer when the
|
||||
// digests are different. Comparing the time stamps is prone to
|
||||
// errors.
|
||||
PullPolicyNewer
|
||||
// Never pull the image but use the one from the local containers
|
||||
// storage.
|
||||
PullPolicyNever
|
||||
)
|
||||
|
||||
// String converts a PullPolicy into a string.
|
||||
//
|
||||
// Supported string values are:
|
||||
// * "always" <-> PullPolicyAlways
|
||||
// * "missing" <-> PullPolicyMissing
|
||||
// * "newer" <-> PullPolicyNewer
|
||||
// * "never" <-> PullPolicyNever
|
||||
func (p PullPolicy) String() string {
|
||||
switch p {
|
||||
case PullPolicyAlways:
|
||||
return "always"
|
||||
case PullPolicyMissing:
|
||||
return "missing"
|
||||
case PullPolicyNewer:
|
||||
return "newer"
|
||||
case PullPolicyNever:
|
||||
return "never"
|
||||
}
|
||||
return fmt.Sprintf("unrecognized policy %d", p)
|
||||
}
|
||||
|
||||
// Validate returns if the pull policy is not supported.
|
||||
func (p PullPolicy) Validate() error {
|
||||
switch p {
|
||||
case PullPolicyAlways, PullPolicyMissing, PullPolicyNewer, PullPolicyNever:
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("unsupported pull policy %d", p)
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePullPolicy parses the string into a pull policy.
|
||||
//
|
||||
// Supported string values are:
|
||||
// * "always" <-> PullPolicyAlways
|
||||
// * "missing" <-> PullPolicyMissing
|
||||
// * "newer" <-> PullPolicyNewer
|
||||
// * "never" <-> PullPolicyNever
|
||||
func ParsePullPolicy(s string) (PullPolicy, error) {
|
||||
switch s {
|
||||
case "always":
|
||||
return PullPolicyAlways, nil
|
||||
case "missing":
|
||||
return PullPolicyMissing, nil
|
||||
case "newer":
|
||||
return PullPolicyNewer, nil
|
||||
case "never":
|
||||
return PullPolicyMissing, nil
|
||||
default:
|
||||
return PullPolicyUnsupported, errors.Errorf("unsupported pull policy %q", s)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/storage"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PushOptions allows for custommizing image pushes.
|
||||
type PushOptions struct {
|
||||
CopyOptions
|
||||
}
|
||||
|
||||
// Push pushes the specified source which must refer to an image in the local
|
||||
// containers storage. It may or may not have the `containers-storage:`
|
||||
// prefix. Use destination to push to a custom destination. The destination
|
||||
// can refer to any supported transport. If not transport is specified, the
|
||||
// docker transport (i.e., a registry) is implied. If destination is left
|
||||
// empty, the docker destination will be extrapolated from the source.
|
||||
//
|
||||
// Return storage.ErrImageUnknown if source could not be found in the local
|
||||
// containers storage.
|
||||
func (r *Runtime) Push(ctx context.Context, source, destination string, options *PushOptions) ([]byte, error) {
|
||||
if options == nil {
|
||||
options = &PushOptions{}
|
||||
}
|
||||
|
||||
// Look up the local image.
|
||||
image, resolvedSource, err := r.LookupImage(source, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if image == nil {
|
||||
return nil, errors.Wrap(storage.ErrImageUnknown, source)
|
||||
}
|
||||
|
||||
srcRef, err := image.StorageReference()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure we have a proper destination, and parse it into an image
|
||||
// reference for copying.
|
||||
if destination == "" {
|
||||
// Doing an ID check here is tempting but false positives (due
|
||||
// to a short partial IDs) are more painful than false
|
||||
// negatives.
|
||||
destination = resolvedSource
|
||||
}
|
||||
|
||||
logrus.Debugf("Pushing image %s to %s", source, destination)
|
||||
|
||||
destRef, err := alltransports.ParseImageName(destination)
|
||||
if err != nil {
|
||||
// If the input does not include a transport assume it refers
|
||||
// to a registry.
|
||||
dockerRef, dockerErr := alltransports.ParseImageName("docker://" + destination)
|
||||
if dockerErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
destRef = dockerRef
|
||||
}
|
||||
|
||||
// Buildah compat: Make sure to tag the destination image if it's a
|
||||
// Docker archive. This way, we preseve the image name.
|
||||
if destRef.Transport().Name() == dockerArchiveTransport.Transport.Name() {
|
||||
if named, err := reference.ParseNamed(resolvedSource); err == nil {
|
||||
tagged, isTagged := named.(reference.NamedTagged)
|
||||
if isTagged {
|
||||
options.dockerArchiveAdditionalTags = []reference.NamedTagged{tagged}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c, err := newCopier(&r.systemContext, &options.CopyOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer c.close()
|
||||
|
||||
return c.copy(ctx, srcRef, destRef)
|
||||
}
|
||||
|
|
@ -0,0 +1,448 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/pkg/shortnames"
|
||||
storageTransport "github.com/containers/image/v5/storage"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// RuntimeOptions allow for creating a customized Runtime.
|
||||
type RuntimeOptions struct {
|
||||
SystemContext *types.SystemContext
|
||||
}
|
||||
|
||||
// setRegistriesConfPath sets the registries.conf path for the specified context.
|
||||
func setRegistriesConfPath(systemContext *types.SystemContext) {
|
||||
if systemContext.SystemRegistriesConfPath != "" {
|
||||
return
|
||||
}
|
||||
if envOverride, ok := os.LookupEnv("CONTAINERS_REGISTRIES_CONF"); ok {
|
||||
systemContext.SystemRegistriesConfPath = envOverride
|
||||
return
|
||||
}
|
||||
if envOverride, ok := os.LookupEnv("REGISTRIES_CONFIG_PATH"); ok {
|
||||
systemContext.SystemRegistriesConfPath = envOverride
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime is responsible for image management and storing them in a containers
|
||||
// storage.
|
||||
type Runtime struct {
|
||||
// Underlying storage store.
|
||||
store storage.Store
|
||||
// Global system context. No pointer to simplify copying and modifying
|
||||
// it.
|
||||
systemContext types.SystemContext
|
||||
// maps an image ID to an Image pointer. Allows for aggressive
|
||||
// caching.
|
||||
imageIDmap map[string]*Image
|
||||
}
|
||||
|
||||
// RuntimeFromStore returns a Runtime for the specified store.
|
||||
func RuntimeFromStore(store storage.Store, options *RuntimeOptions) (*Runtime, error) {
|
||||
if options == nil {
|
||||
options = &RuntimeOptions{}
|
||||
}
|
||||
|
||||
var systemContext types.SystemContext
|
||||
if options.SystemContext != nil {
|
||||
systemContext = *options.SystemContext
|
||||
} else {
|
||||
systemContext = types.SystemContext{}
|
||||
}
|
||||
|
||||
setRegistriesConfPath(&systemContext)
|
||||
|
||||
if systemContext.BlobInfoCacheDir == "" {
|
||||
systemContext.BlobInfoCacheDir = filepath.Join(store.GraphRoot(), "cache")
|
||||
}
|
||||
|
||||
return &Runtime{
|
||||
store: store,
|
||||
systemContext: systemContext,
|
||||
imageIDmap: make(map[string]*Image),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RuntimeFromStoreOptions returns a return for the specified store options.
|
||||
func RuntimeFromStoreOptions(runtimeOptions *RuntimeOptions, storeOptions *storage.StoreOptions) (*Runtime, error) {
|
||||
if storeOptions == nil {
|
||||
storeOptions = &storage.StoreOptions{}
|
||||
}
|
||||
store, err := storage.GetStore(*storeOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storageTransport.Transport.SetStore(store)
|
||||
return RuntimeFromStore(store, runtimeOptions)
|
||||
}
|
||||
|
||||
// Shutdown attempts to free any kernel resources which are being used by the
|
||||
// underlying driver. If "force" is true, any mounted (i.e., in use) layers
|
||||
// are unmounted beforehand. If "force" is not true, then layers being in use
|
||||
// is considered to be an error condition.
|
||||
func (r *Runtime) Shutdown(force bool) error {
|
||||
_, err := r.store.Shutdown(force)
|
||||
return err
|
||||
}
|
||||
|
||||
// storageToImage transforms a storage.Image to an Image.
|
||||
func (r *Runtime) storageToImage(storageImage *storage.Image, ref types.ImageReference) *Image {
|
||||
image, exists := r.imageIDmap[storageImage.ID]
|
||||
if exists {
|
||||
return image
|
||||
}
|
||||
image = &Image{
|
||||
runtime: r,
|
||||
storageImage: storageImage,
|
||||
storageReference: ref,
|
||||
}
|
||||
r.imageIDmap[storageImage.ID] = image
|
||||
return image
|
||||
}
|
||||
|
||||
// Exists returns true if the specicifed image exists in the local containers
|
||||
// storage.
|
||||
func (r *Runtime) Exists(name string) (bool, error) {
|
||||
image, _, err := r.LookupImage(name, nil)
|
||||
return image != nil, err
|
||||
}
|
||||
|
||||
// LookupImageOptions allow for customizing local image lookups.
|
||||
type LookupImageOptions struct {
|
||||
// If set, the image will be purely looked up by name. No matching to
|
||||
// the current platform will be performed. This can be helpful when
|
||||
// the platform does not matter, for instance, for image removal.
|
||||
IgnorePlatform bool
|
||||
}
|
||||
|
||||
// Lookup Image looks up `name` in the local container storage matching the
|
||||
// specified SystemContext. Returns the image and the name it has been found
|
||||
// with. Returns nil if no image has been found. Note that name may also use
|
||||
// the `containers-storage:` prefix used to refer to the containers-storage
|
||||
// transport.
|
||||
//
|
||||
// If the specified name uses the `containers-storage` transport, the resolved
|
||||
// name is empty.
|
||||
func (r *Runtime) LookupImage(name string, options *LookupImageOptions) (*Image, string, error) {
|
||||
logrus.Debugf("Looking up image %q in local containers storage", name)
|
||||
|
||||
if options == nil {
|
||||
options = &LookupImageOptions{}
|
||||
}
|
||||
|
||||
// If needed extract the name sans transport.
|
||||
storageRef, err := alltransports.ParseImageName(name)
|
||||
if err == nil {
|
||||
if storageRef.Transport().Name() != storageTransport.Transport.Name() {
|
||||
return nil, "", errors.Errorf("unsupported transport %q for looking up local images", storageRef.Transport().Name())
|
||||
}
|
||||
img, err := storageTransport.Transport.GetStoreImage(r.store, storageRef)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
logrus.Debugf("Found image %q in local containers storage (%s)", name, storageRef.StringWithinTransport())
|
||||
return r.storageToImage(img, storageRef), "", nil
|
||||
}
|
||||
|
||||
byDigest := false
|
||||
if strings.HasPrefix(name, "sha256:") {
|
||||
byDigest = true
|
||||
name = strings.TrimPrefix(name, "sha256:")
|
||||
}
|
||||
|
||||
// Anonymouns function to lookup the provided image in the storage and
|
||||
// check whether it's matching the system context.
|
||||
findImage := func(input string) (*Image, error) {
|
||||
img, err := r.store.Image(input)
|
||||
if err != nil && errors.Cause(err) != storage.ErrImageUnknown {
|
||||
return nil, err
|
||||
}
|
||||
if img == nil {
|
||||
return nil, nil
|
||||
}
|
||||
ref, err := storageTransport.Transport.ParseStoreReference(r.store, img.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.IgnorePlatform {
|
||||
logrus.Debugf("Found image %q as %q in local containers storage", name, input)
|
||||
return r.storageToImage(img, ref), nil
|
||||
}
|
||||
|
||||
matches, err := imageReferenceMatchesContext(context.Background(), ref, &r.systemContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !matches {
|
||||
return nil, nil
|
||||
}
|
||||
// Also print the string within the storage transport. That
|
||||
// may aid in debugging when using additional stores since we
|
||||
// see explicitly where the store is and which driver (options)
|
||||
// are used.
|
||||
logrus.Debugf("Found image %q as %q in local containers storage (%s)", name, input, ref.StringWithinTransport())
|
||||
return r.storageToImage(img, ref), nil
|
||||
}
|
||||
|
||||
// First, check if we have an exact match in the storage. Maybe an ID
|
||||
// or a fully-qualified image name.
|
||||
img, err := findImage(name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if img != nil {
|
||||
return img, name, nil
|
||||
}
|
||||
|
||||
// If the name clearly referred to a local image, there's nothing we can
|
||||
// do anymore.
|
||||
if storageRef != nil || byDigest {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
// Second, try out the candidates as resolved by shortnames. This takes
|
||||
// "localhost/" prefixed images into account as well.
|
||||
candidates, err := shortnames.ResolveLocally(&r.systemContext, name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// Backwards compat: normalize to docker.io as some users may very well
|
||||
// rely on that.
|
||||
dockerNamed, err := reference.ParseDockerRef(name)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "error normalizing to docker.io")
|
||||
}
|
||||
|
||||
candidates = append(candidates, dockerNamed)
|
||||
for _, candidate := range candidates {
|
||||
img, err := findImage(candidate.String())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if img != nil {
|
||||
return img, candidate.String(), err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
// imageReferenceMatchesContext return true if the specified reference matches
|
||||
// the platform (os, arch, variant) as specified by the system context.
|
||||
func imageReferenceMatchesContext(ctx context.Context, ref types.ImageReference, sys *types.SystemContext) (bool, error) {
|
||||
if sys == nil {
|
||||
return true, nil
|
||||
}
|
||||
img, err := ref.NewImage(ctx, sys)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer img.Close()
|
||||
data, err := img.Inspect(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
osChoice := sys.OSChoice
|
||||
if osChoice == "" {
|
||||
osChoice = runtime.GOOS
|
||||
}
|
||||
arch := sys.ArchitectureChoice
|
||||
if arch == "" {
|
||||
arch = runtime.GOARCH
|
||||
}
|
||||
if osChoice == data.Os && arch == data.Architecture {
|
||||
if sys.VariantChoice == "" || sys.VariantChoice == data.Variant {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ListImagesOptions allow for customizing listing images.
|
||||
type ListImagesOptions struct {
|
||||
// Filters to filter the listed images. Supported filters are
|
||||
// * after,before,since=image
|
||||
// * dangling=true,false
|
||||
// * intermediate=true,false (useful for pruning images)
|
||||
// * id=id
|
||||
// * label=key[=value]
|
||||
// * readonly=true,false
|
||||
// * reference=name[:tag] (wildcards allowed)
|
||||
Filters []string
|
||||
}
|
||||
|
||||
// ListImages lists images in the local container storage. If names are
|
||||
// specified, only images with the specified names are looked up and filtered.
|
||||
func (r *Runtime) ListImages(ctx context.Context, names []string, options *ListImagesOptions) ([]*Image, error) {
|
||||
if options == nil {
|
||||
options = &ListImagesOptions{}
|
||||
}
|
||||
|
||||
var images []*Image
|
||||
if len(names) > 0 {
|
||||
lookupOpts := LookupImageOptions{IgnorePlatform: true}
|
||||
for _, name := range names {
|
||||
image, _, err := r.LookupImage(name, &lookupOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if image == nil {
|
||||
return nil, errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
images = append(images, image)
|
||||
}
|
||||
} else {
|
||||
storageImages, err := r.store.Images()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range storageImages {
|
||||
images = append(images, r.storageToImage(&storageImages[i], nil))
|
||||
}
|
||||
}
|
||||
|
||||
var filters []filterFunc
|
||||
if len(options.Filters) > 0 {
|
||||
compiledFilters, err := r.compileImageFilters(ctx, options.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filters = append(filters, compiledFilters...)
|
||||
}
|
||||
|
||||
return filterImages(images, filters)
|
||||
}
|
||||
|
||||
// RemoveImagesOptions allow for customizing image removal.
|
||||
type RemoveImagesOptions struct {
|
||||
RemoveImageOptions
|
||||
|
||||
// Filters to filter the removed images. Supported filters are
|
||||
// * after,before,since=image
|
||||
// * dangling=true,false
|
||||
// * intermediate=true,false (useful for pruning images)
|
||||
// * id=id
|
||||
// * label=key[=value]
|
||||
// * readonly=true,false
|
||||
// * reference=name[:tag] (wildcards allowed)
|
||||
Filters []string
|
||||
}
|
||||
|
||||
// RemoveImages removes images specified by names. All images are expected to
|
||||
// exist in the local containers storage.
|
||||
//
|
||||
// If an image has more names than one name, the image will be untagged with
|
||||
// the specified name. RemoveImages returns a slice of untagged and removed
|
||||
// images.
|
||||
func (r *Runtime) RemoveImages(ctx context.Context, names []string, options *RemoveImagesOptions) (untagged, removed []string, rmError error) {
|
||||
if options == nil {
|
||||
options = &RemoveImagesOptions{}
|
||||
}
|
||||
|
||||
// deleteMe bundles an image with a possibly empty string value it has
|
||||
// been looked up with. The string value is required to implement the
|
||||
// untagging logic.
|
||||
type deleteMe struct {
|
||||
image *Image
|
||||
name string
|
||||
}
|
||||
|
||||
var images []*deleteMe
|
||||
switch {
|
||||
case len(names) > 0:
|
||||
lookupOptions := LookupImageOptions{IgnorePlatform: true}
|
||||
for _, name := range names {
|
||||
img, resolvedName, err := r.LookupImage(name, &lookupOptions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if img == nil {
|
||||
return nil, nil, errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
images = append(images, &deleteMe{image: img, name: resolvedName})
|
||||
}
|
||||
if len(images) == 0 {
|
||||
return nil, nil, errors.New("no images found")
|
||||
}
|
||||
|
||||
case len(options.Filters) > 0:
|
||||
filteredImages, err := r.ListImages(ctx, nil, &ListImagesOptions{Filters: options.Filters})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, img := range filteredImages {
|
||||
images = append(images, &deleteMe{image: img})
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove the images.
|
||||
for _, delete := range images {
|
||||
numNames := len(delete.image.Names())
|
||||
|
||||
skipRemove := false
|
||||
if len(names) > 0 {
|
||||
hasChildren, err := delete.image.HasChildren(ctx)
|
||||
if err != nil {
|
||||
rmError = multierror.Append(rmError, err)
|
||||
continue
|
||||
}
|
||||
skipRemove = hasChildren
|
||||
}
|
||||
|
||||
if delete.name != "" {
|
||||
untagged = append(untagged, delete.name)
|
||||
}
|
||||
|
||||
mustUntag := !options.Force && delete.name != "" && (numNames > 1 || skipRemove)
|
||||
if mustUntag {
|
||||
if err := delete.image.Untag(delete.name); err != nil {
|
||||
rmError = multierror.Append(rmError, err)
|
||||
continue
|
||||
}
|
||||
// If the untag did not reduce the image names, name
|
||||
// must have been an ID in which case we should throw
|
||||
// an error. UNLESS there is only one tag left.
|
||||
newNumNames := len(delete.image.Names())
|
||||
if newNumNames == numNames && newNumNames != 1 {
|
||||
err := errors.Errorf("unable to delete image %q by ID with more than one tag (%s): use force removal", delete.image.ID(), delete.image.Names())
|
||||
rmError = multierror.Append(rmError, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// If we deleted the last tag/name, we can continue
|
||||
// removing the image. Otherwise, we mark it as
|
||||
// untagged and need to continue.
|
||||
if newNumNames >= 1 || skipRemove {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := delete.image.Remove(ctx, &options.RemoveImageOptions); err != nil {
|
||||
// If the image does not exist (anymore) we are good.
|
||||
// We already performed a presence check in the image
|
||||
// look up when `names` are specified.
|
||||
if errors.Cause(err) != storage.ErrImageUnknown {
|
||||
rmError = multierror.Append(rmError, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
removed = append(removed, delete.image.ID())
|
||||
}
|
||||
|
||||
return untagged, removed, rmError
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
dirTransport "github.com/containers/image/v5/directory"
|
||||
dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
ociArchiveTransport "github.com/containers/image/v5/oci/archive"
|
||||
ociTransport "github.com/containers/image/v5/oci/layout"
|
||||
"github.com/containers/image/v5/types"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SaveOptions allow for customizing saving images.
|
||||
type SaveOptions struct {
|
||||
CopyOptions
|
||||
|
||||
// AdditionalTags for the saved image. Incompatible when saving
|
||||
// multiple images.
|
||||
AdditionalTags []string
|
||||
}
|
||||
|
||||
// Save saves one or more images indicated by `names` in the specified `format`
|
||||
// to `path`. Supported formats are oci-archive, docker-archive, oci-dir and
|
||||
// docker-dir. The latter two adhere to the dir transport in the corresponding
|
||||
// oci or docker v2s2 format. Please note that only docker-archive supports
|
||||
// saving more than one images. Other formats will yield an error attempting
|
||||
// to save more than one.
|
||||
func (r *Runtime) Save(ctx context.Context, names []string, format, path string, options *SaveOptions) error {
|
||||
logrus.Debugf("Saving one more images (%s) to %q", names, path)
|
||||
|
||||
if options == nil {
|
||||
options = &SaveOptions{}
|
||||
}
|
||||
|
||||
// First some sanity checks to simplify subsequent code.
|
||||
switch len(names) {
|
||||
case 0:
|
||||
return errors.New("no image specified for saving images")
|
||||
case 1:
|
||||
// All formats support saving 1.
|
||||
default:
|
||||
if format != "docker-archive" {
|
||||
return errors.Errorf("unspported format %q for saving multiple images (only docker-archive)", format)
|
||||
}
|
||||
if len(options.AdditionalTags) > 0 {
|
||||
return errors.Errorf("cannot save multiple images with multiple tags")
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch the save operations.
|
||||
switch format {
|
||||
case "oci-archive", "oci-dir", "docker-dir":
|
||||
return r.saveSingleImage(ctx, names[0], format, path, options)
|
||||
|
||||
case "docker-archive":
|
||||
return r.saveDockerArchive(ctx, names, path, options)
|
||||
}
|
||||
|
||||
return errors.Errorf("unspported format %q for saving images", format)
|
||||
|
||||
}
|
||||
|
||||
// saveSingleImage saves the specified image name to the specified path.
|
||||
// Supported formats are "oci-archive", "oci-dir" and "docker-dir".
|
||||
func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string, options *SaveOptions) error {
|
||||
image, imageName, err := r.LookupImage(name, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unless the image was referenced by ID, use the resolved name as a
|
||||
// tag.
|
||||
var tag string
|
||||
if strings.HasPrefix(image.ID(), imageName) {
|
||||
tag = imageName
|
||||
}
|
||||
|
||||
srcRef, err := image.StorageReference()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare the destination reference.
|
||||
var destRef types.ImageReference
|
||||
switch format {
|
||||
case "oci-archive":
|
||||
destRef, err = ociArchiveTransport.NewReference(path, tag)
|
||||
|
||||
case "oci-dir":
|
||||
destRef, err = ociTransport.NewReference(path, tag)
|
||||
options.ManifestMIMEType = ociv1.MediaTypeImageManifest
|
||||
|
||||
case "docker-dir":
|
||||
destRef, err = dirTransport.NewReference(path)
|
||||
options.ManifestMIMEType = manifest.DockerV2Schema2MediaType
|
||||
|
||||
default:
|
||||
return errors.Errorf("unspported format %q for saving images", format)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sys := r.systemContext
|
||||
c, err := newCopier(&sys, &options.CopyOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
_, err = c.copy(ctx, srcRef, destRef)
|
||||
return err
|
||||
}
|
||||
|
||||
// saveDockerArchive saves the specified images indicated by names to the path.
|
||||
// It loads all images from the local containers storage and assembles the meta
|
||||
// data needed to properly save images. Since multiple names could refer to
|
||||
// the *same* image, we need to dance a bit and store additional "names".
|
||||
// Those can then be used as additional tags when copying.
|
||||
func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path string, options *SaveOptions) error {
|
||||
type localImage struct {
|
||||
image *Image
|
||||
tags []reference.NamedTagged
|
||||
}
|
||||
|
||||
orderedIDs := []string{} // to preserve the relative order
|
||||
localImages := make(map[string]*localImage) // to assemble tags
|
||||
visitedNames := make(map[string]bool) // filters duplicate names
|
||||
for _, name := range names {
|
||||
// Look up local images.
|
||||
image, imageName, err := r.LookupImage(name, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure to filter duplicates purely based on the resolved
|
||||
// name.
|
||||
if _, exists := visitedNames[imageName]; exists {
|
||||
continue
|
||||
}
|
||||
visitedNames[imageName] = true
|
||||
// Extract and assemble the data.
|
||||
local, exists := localImages[image.ID()]
|
||||
if !exists {
|
||||
local = &localImage{image: image}
|
||||
orderedIDs = append(orderedIDs, image.ID())
|
||||
}
|
||||
// Add the tag if the locally resolved name is properly tagged
|
||||
// (which it should unless we looked it up by ID).
|
||||
named, err := reference.ParseNamed(imageName)
|
||||
if err == nil {
|
||||
tagged, withTag := named.(reference.NamedTagged)
|
||||
if withTag {
|
||||
local.tags = append(local.tags, tagged)
|
||||
}
|
||||
}
|
||||
localImages[image.ID()] = local
|
||||
}
|
||||
|
||||
writer, err := dockerArchiveTransport.NewWriter(&r.systemContext, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
for _, id := range orderedIDs {
|
||||
local, exists := localImages[id]
|
||||
if !exists {
|
||||
return errors.Errorf("internal error: saveDockerArchive: ID %s not found in local map", id)
|
||||
}
|
||||
|
||||
copyOpts := options.CopyOptions
|
||||
copyOpts.dockerArchiveAdditionalTags = local.tags
|
||||
sys := r.systemContext // prevent copier from modifying the runtime's context
|
||||
|
||||
c, err := newCopier(&sys, ©Opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
destRef, err := writer.NewReference(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcRef, err := local.image.StorageReference()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
package libimage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
dockerTransport "github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/pkg/sysregistriesv2"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
const (
|
||||
searchTruncLength = 44
|
||||
searchMaxQueries = 25
|
||||
// Let's follow Firefox by limiting parallel downloads to 6. We do the
|
||||
// same when pulling images in c/image.
|
||||
searchMaxParallel = int64(6)
|
||||
)
|
||||
|
||||
// SearchResult is holding image-search related data.
|
||||
type SearchResult struct {
|
||||
// Index is the image index (e.g., "docker.io" or "quay.io")
|
||||
Index string
|
||||
// Name is the canonical name of the image (e.g., "docker.io/library/alpine").
|
||||
Name string
|
||||
// Description of the image.
|
||||
Description string
|
||||
// Stars is the number of stars of the image.
|
||||
Stars int
|
||||
// Official indicates if it's an official image.
|
||||
Official string
|
||||
// Automated indicates if the image was created by an automated build.
|
||||
Automated string
|
||||
// Tag is the image tag
|
||||
Tag string
|
||||
}
|
||||
|
||||
// SearchOptions customize searching images.
|
||||
type SearchOptions struct {
|
||||
// Filter allows to filter the results.
|
||||
Filter SearchFilter
|
||||
// Limit limits the number of queries per index (default: 25). Must be
|
||||
// greater than 0 to overwrite the default value.
|
||||
Limit int
|
||||
// NoTrunc avoids the output to be truncated.
|
||||
NoTrunc bool
|
||||
// Authfile is the path to the authentication file.
|
||||
Authfile string
|
||||
// InsecureSkipTLSVerify allows to skip TLS verification.
|
||||
InsecureSkipTLSVerify types.OptionalBool
|
||||
// ListTags returns the search result with available tags
|
||||
ListTags bool
|
||||
}
|
||||
|
||||
// SearchFilter allows filtering images while searching.
|
||||
type SearchFilter struct {
|
||||
// Stars describes the minimal amount of starts of an image.
|
||||
Stars int
|
||||
// IsAutomated decides if only images from automated builds are displayed.
|
||||
IsAutomated types.OptionalBool
|
||||
// IsOfficial decides if only official images are displayed.
|
||||
IsOfficial types.OptionalBool
|
||||
}
|
||||
|
||||
func (r *Runtime) Search(ctx context.Context, term string, options SearchOptions) ([]SearchResult, error) {
|
||||
searchRegistries, err := sysregistriesv2.UnqualifiedSearchRegistries(&r.systemContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Searching images matching term %s at the following registries %s", term, searchRegistries)
|
||||
|
||||
// Try to extract a registry from the specified search term. We
|
||||
// consider everything before the first slash to be the registry. Note
|
||||
// that we cannot use the reference parser from the containers/image
|
||||
// library as the search term may container arbitrary input such as
|
||||
// wildcards. See bugzilla.redhat.com/show_bug.cgi?id=1846629.
|
||||
if spl := strings.SplitN(term, "/", 2); len(spl) > 1 {
|
||||
searchRegistries = append(searchRegistries, spl[0])
|
||||
term = spl[1]
|
||||
}
|
||||
|
||||
// searchOutputData is used as a return value for searching in parallel.
|
||||
type searchOutputData struct {
|
||||
data []SearchResult
|
||||
err error
|
||||
}
|
||||
|
||||
sem := semaphore.NewWeighted(searchMaxParallel)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(searchRegistries))
|
||||
data := make([]searchOutputData, len(searchRegistries))
|
||||
|
||||
for i := range searchRegistries {
|
||||
if err := sem.Acquire(ctx, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := i
|
||||
go func() {
|
||||
defer sem.Release(1)
|
||||
defer wg.Done()
|
||||
searchOutput, err := r.searchImageInRegistry(ctx, term, searchRegistries[index], options)
|
||||
data[index] = searchOutputData{data: searchOutput, err: err}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
results := []SearchResult{}
|
||||
var multiErr error
|
||||
for _, d := range data {
|
||||
if d.err != nil {
|
||||
multiErr = multierror.Append(multiErr, d.err)
|
||||
continue
|
||||
}
|
||||
results = append(results, d.data...)
|
||||
}
|
||||
|
||||
// Optimistically assume that one successfully searched registry
|
||||
// includes what the user is looking for.
|
||||
if len(results) > 0 {
|
||||
return results, nil
|
||||
}
|
||||
return results, multiErr
|
||||
}
|
||||
|
||||
func (r *Runtime) searchImageInRegistry(ctx context.Context, term, registry string, options SearchOptions) ([]SearchResult, error) {
|
||||
// Max number of queries by default is 25
|
||||
limit := searchMaxQueries
|
||||
if options.Limit > 0 {
|
||||
limit = options.Limit
|
||||
}
|
||||
|
||||
sys := r.systemContext
|
||||
if options.InsecureSkipTLSVerify != types.OptionalBoolUndefined {
|
||||
sys.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify
|
||||
}
|
||||
|
||||
if options.ListTags {
|
||||
results, err := searchRepositoryTags(ctx, &sys, registry, term, options)
|
||||
if err != nil {
|
||||
return []SearchResult{}, err
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
results, err := dockerTransport.SearchRegistry(ctx, &sys, registry, term, limit)
|
||||
if err != nil {
|
||||
return []SearchResult{}, err
|
||||
}
|
||||
index := registry
|
||||
arr := strings.Split(registry, ".")
|
||||
if len(arr) > 2 {
|
||||
index = strings.Join(arr[len(arr)-2:], ".")
|
||||
}
|
||||
|
||||
// limit is the number of results to output
|
||||
// if the total number of results is less than the limit, output all
|
||||
// if the limit has been set by the user, output those number of queries
|
||||
limit = searchMaxQueries
|
||||
if len(results) < limit {
|
||||
limit = len(results)
|
||||
}
|
||||
if options.Limit != 0 {
|
||||
limit = len(results)
|
||||
if options.Limit < len(results) {
|
||||
limit = options.Limit
|
||||
}
|
||||
}
|
||||
|
||||
paramsArr := []SearchResult{}
|
||||
for i := 0; i < limit; i++ {
|
||||
// Check whether query matches filters
|
||||
if !(options.Filter.matchesAutomatedFilter(results[i]) && options.Filter.matchesOfficialFilter(results[i]) && options.Filter.matchesStarFilter(results[i])) {
|
||||
continue
|
||||
}
|
||||
official := ""
|
||||
if results[i].IsOfficial {
|
||||
official = "[OK]"
|
||||
}
|
||||
automated := ""
|
||||
if results[i].IsAutomated {
|
||||
automated = "[OK]"
|
||||
}
|
||||
description := strings.ReplaceAll(results[i].Description, "\n", " ")
|
||||
if len(description) > 44 && !options.NoTrunc {
|
||||
description = description[:searchTruncLength] + "..."
|
||||
}
|
||||
name := registry + "/" + results[i].Name
|
||||
if index == "docker.io" && !strings.Contains(results[i].Name, "/") {
|
||||
name = index + "/library/" + results[i].Name
|
||||
}
|
||||
params := SearchResult{
|
||||
Index: index,
|
||||
Name: name,
|
||||
Description: description,
|
||||
Official: official,
|
||||
Automated: automated,
|
||||
Stars: results[i].StarCount,
|
||||
}
|
||||
paramsArr = append(paramsArr, params)
|
||||
}
|
||||
return paramsArr, nil
|
||||
}
|
||||
|
||||
func searchRepositoryTags(ctx context.Context, sys *types.SystemContext, registry, term string, options SearchOptions) ([]SearchResult, error) {
|
||||
dockerPrefix := "docker://"
|
||||
imageRef, err := alltransports.ParseImageName(fmt.Sprintf("%s/%s", registry, term))
|
||||
if err == nil && imageRef.Transport().Name() != dockerTransport.Transport.Name() {
|
||||
return nil, errors.Errorf("reference %q must be a docker reference", term)
|
||||
} else if err != nil {
|
||||
imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, fmt.Sprintf("%s/%s", registry, term)))
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("reference %q must be a docker reference", term)
|
||||
}
|
||||
}
|
||||
tags, err := dockerTransport.GetRepositoryTags(ctx, sys, imageRef)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error getting repository tags: %v", err)
|
||||
}
|
||||
limit := searchMaxQueries
|
||||
if len(tags) < limit {
|
||||
limit = len(tags)
|
||||
}
|
||||
if options.Limit != 0 {
|
||||
limit = len(tags)
|
||||
if options.Limit < limit {
|
||||
limit = options.Limit
|
||||
}
|
||||
}
|
||||
paramsArr := []SearchResult{}
|
||||
for i := 0; i < limit; i++ {
|
||||
params := SearchResult{
|
||||
Name: imageRef.DockerReference().Name(),
|
||||
Tag: tags[i],
|
||||
}
|
||||
paramsArr = append(paramsArr, params)
|
||||
}
|
||||
return paramsArr, nil
|
||||
}
|
||||
|
||||
func (f *SearchFilter) matchesStarFilter(result dockerTransport.SearchResult) bool {
|
||||
return result.StarCount >= f.Stars
|
||||
}
|
||||
|
||||
func (f *SearchFilter) matchesAutomatedFilter(result dockerTransport.SearchResult) bool {
|
||||
if f.IsAutomated != types.OptionalBoolUndefined {
|
||||
return result.IsAutomated == (f.IsAutomated == types.OptionalBoolTrue)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *SearchFilter) matchesOfficialFilter(result dockerTransport.SearchResult) bool {
|
||||
if f.IsOfficial != types.OptionalBoolUndefined {
|
||||
return result.IsOfficial == (f.IsOfficial == types.OptionalBoolTrue)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PullPolicy determines how and which images are being pulled from a container
|
||||
// registry (i.e., docker transport only).
|
||||
//
|
||||
// Supported string values are:
|
||||
// * "always" <-> PullPolicyAlways
|
||||
// * "missing" <-> PullPolicyMissing
|
||||
// * "newer" <-> PullPolicyNewer
|
||||
// * "never" <-> PullPolicyNever
|
||||
type PullPolicy int
|
||||
|
||||
const (
|
||||
// This default value forces callers to setup a custom default policy.
|
||||
// Some tools use different policies (e.g., buildah-bud versus
|
||||
// podman-build).
|
||||
PullPolicyUnsupported PullPolicy = iota
|
||||
// Always pull the image.
|
||||
PullPolicyAlways
|
||||
// Pull the image only if it could not be found in the local containers
|
||||
// storage.
|
||||
PullPolicyMissing
|
||||
// Pull if the image on the registry is new than the one in the local
|
||||
// containers storage. An image is considered to be newer when the
|
||||
// digests are different. Comparing the time stamps is prone to
|
||||
// errors.
|
||||
PullPolicyNewer
|
||||
// Never pull the image but use the one from the local containers
|
||||
// storage.
|
||||
PullPolicyNever
|
||||
)
|
||||
|
||||
// String converts a PullPolicy into a string.
|
||||
//
|
||||
// Supported string values are:
|
||||
// * "always" <-> PullPolicyAlways
|
||||
// * "missing" <-> PullPolicyMissing
|
||||
// * "newer" <-> PullPolicyNewer
|
||||
// * "never" <-> PullPolicyNever
|
||||
func (p PullPolicy) String() string {
|
||||
switch p {
|
||||
case PullPolicyAlways:
|
||||
return "always"
|
||||
case PullPolicyMissing:
|
||||
return "missing"
|
||||
case PullPolicyNewer:
|
||||
return "newer"
|
||||
case PullPolicyNever:
|
||||
return "never"
|
||||
}
|
||||
return fmt.Sprintf("unrecognized policy %d", p)
|
||||
}
|
||||
|
||||
// Validate returns if the pull policy is not supported.
|
||||
func (p PullPolicy) Validate() error {
|
||||
switch p {
|
||||
case PullPolicyAlways, PullPolicyMissing, PullPolicyNewer, PullPolicyNever:
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("unsupported pull policy %d", p)
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePullPolicy parses the string into a pull policy.
|
||||
//
|
||||
// Supported string values are:
|
||||
// * "always" <-> PullPolicyAlways
|
||||
// * "missing" <-> PullPolicyMissing
|
||||
// * "newer" <-> PullPolicyNewer (also "ifnewer")
|
||||
// * "never" <-> PullPolicyNever
|
||||
func ParsePullPolicy(s string) (PullPolicy, error) {
|
||||
switch s {
|
||||
case "always":
|
||||
return PullPolicyAlways, nil
|
||||
case "missing":
|
||||
return PullPolicyMissing, nil
|
||||
case "newer", "ifnewer":
|
||||
return PullPolicyNewer, nil
|
||||
case "never":
|
||||
return PullPolicyNever, nil
|
||||
default:
|
||||
return PullPolicyUnsupported, errors.Errorf("unsupported pull policy %q", s)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// ImageData contains the inspected data of an image.
|
||||
type ImageData struct {
|
||||
ID string `json:"Id"`
|
||||
Digest digest.Digest `json:"Digest"`
|
||||
RepoTags []string `json:"RepoTags"`
|
||||
RepoDigests []string `json:"RepoDigests"`
|
||||
Parent string `json:"Parent"`
|
||||
Comment string `json:"Comment"`
|
||||
Created *time.Time `json:"Created"`
|
||||
Config *ociv1.ImageConfig `json:"Config"`
|
||||
Version string `json:"Version"`
|
||||
Author string `json:"Author"`
|
||||
Architecture string `json:"Architecture"`
|
||||
Os string `json:"Os"`
|
||||
Size int64 `json:"Size"`
|
||||
VirtualSize int64 `json:"VirtualSize"`
|
||||
GraphDriver *DriverData `json:"GraphDriver"`
|
||||
RootFS *RootFS `json:"RootFS"`
|
||||
Labels map[string]string `json:"Labels"`
|
||||
Annotations map[string]string `json:"Annotations"`
|
||||
ManifestType string `json:"ManifestType"`
|
||||
User string `json:"User"`
|
||||
History []ociv1.History `json:"History"`
|
||||
NamesHistory []string `json:"NamesHistory"`
|
||||
HealthCheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"`
|
||||
}
|
||||
|
||||
// DriverData includes data on the storage driver of the image.
|
||||
type DriverData struct {
|
||||
Name string `json:"Name"`
|
||||
Data map[string]string `json:"Data"`
|
||||
}
|
||||
|
||||
// RootFS includes data on the root filesystem of the image.
|
||||
type RootFS struct {
|
||||
Type string `json:"Type"`
|
||||
Layers []digest.Digest `json:"Layers"`
|
||||
}
|
||||
|
||||
// ImageHistory contains the history information of an image.
|
||||
type ImageHistory struct {
|
||||
ID string `json:"id"`
|
||||
Created *time.Time `json:"created"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Size int64 `json:"size"`
|
||||
Comment string `json:"comment"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
terminal "golang.org/x/term"
|
||||
)
|
||||
|
||||
// GetDefaultAuthFile returns env value REGISTRY_AUTH_FILE as default
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
terminal "golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package manifests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDigestNotFound is returned when we look for an image instance
|
||||
// with a particular digest in a list or index, and fail to find it.
|
||||
ErrDigestNotFound = errors.New("no image instance matching the specified digest was found in the list or index")
|
||||
// ErrManifestTypeNotSupported is returned when we attempt to parse a
|
||||
// manifest with a known MIME type as a list or index, or when we attempt
|
||||
// to serialize a list or index to a manifest with a MIME type that we
|
||||
// don't know how to encode.
|
||||
ErrManifestTypeNotSupported = errors.New("manifest type not supported")
|
||||
)
|
||||
|
|
@ -0,0 +1,493 @@
|
|||
package manifests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// List is a generic interface for manipulating a manifest list or an image
|
||||
// index.
|
||||
type List interface {
|
||||
AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, os, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error
|
||||
Remove(instanceDigest digest.Digest) error
|
||||
|
||||
SetURLs(instanceDigest digest.Digest, urls []string) error
|
||||
URLs(instanceDigest digest.Digest) ([]string, error)
|
||||
|
||||
SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error
|
||||
Annotations(instanceDigest *digest.Digest) (map[string]string, error)
|
||||
|
||||
SetOS(instanceDigest digest.Digest, os string) error
|
||||
OS(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetArchitecture(instanceDigest digest.Digest, arch string) error
|
||||
Architecture(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetOSVersion(instanceDigest digest.Digest, osVersion string) error
|
||||
OSVersion(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetVariant(instanceDigest digest.Digest, variant string) error
|
||||
Variant(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetFeatures(instanceDigest digest.Digest, features []string) error
|
||||
Features(instanceDigest digest.Digest) ([]string, error)
|
||||
|
||||
SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error
|
||||
OSFeatures(instanceDigest digest.Digest) ([]string, error)
|
||||
|
||||
Serialize(mimeType string) ([]byte, error)
|
||||
Instances() []digest.Digest
|
||||
OCIv1() *v1.Index
|
||||
Docker() *manifest.Schema2List
|
||||
|
||||
findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error)
|
||||
findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error)
|
||||
}
|
||||
|
||||
type list struct {
|
||||
docker manifest.Schema2List
|
||||
oci v1.Index
|
||||
}
|
||||
|
||||
// OCIv1 returns the list as a Docker schema 2 list. The returned structure should NOT be modified.
|
||||
func (l *list) Docker() *manifest.Schema2List {
|
||||
return &l.docker
|
||||
}
|
||||
|
||||
// OCIv1 returns the list as an OCI image index. The returned structure should NOT be modified.
|
||||
func (l *list) OCIv1() *v1.Index {
|
||||
return &l.oci
|
||||
}
|
||||
|
||||
// Create creates a new list.
|
||||
func Create() List {
|
||||
return &list{
|
||||
docker: manifest.Schema2List{
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2ListMediaType,
|
||||
},
|
||||
oci: v1.Index{
|
||||
Versioned: imgspec.Versioned{SchemaVersion: 2},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AddInstance adds an entry for the specified manifest digest, with assorted
|
||||
// additional information specified in parameters, to the list or index.
|
||||
func (l *list) AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, osName, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error {
|
||||
if err := l.Remove(manifestDigest); err != nil && !os.IsNotExist(errors.Cause(err)) {
|
||||
return err
|
||||
}
|
||||
|
||||
schema2platform := manifest.Schema2PlatformSpec{
|
||||
Architecture: architecture,
|
||||
OS: osName,
|
||||
OSVersion: osVersion,
|
||||
OSFeatures: osFeatures,
|
||||
Variant: variant,
|
||||
Features: features,
|
||||
}
|
||||
l.docker.Manifests = append(l.docker.Manifests, manifest.Schema2ManifestDescriptor{
|
||||
Schema2Descriptor: manifest.Schema2Descriptor{
|
||||
MediaType: manifestType,
|
||||
Size: manifestSize,
|
||||
Digest: manifestDigest,
|
||||
},
|
||||
Platform: schema2platform,
|
||||
})
|
||||
|
||||
ociv1platform := v1.Platform{
|
||||
Architecture: architecture,
|
||||
OS: osName,
|
||||
OSVersion: osVersion,
|
||||
OSFeatures: osFeatures,
|
||||
Variant: variant,
|
||||
}
|
||||
l.oci.Manifests = append(l.oci.Manifests, v1.Descriptor{
|
||||
MediaType: manifestType,
|
||||
Size: manifestSize,
|
||||
Digest: manifestDigest,
|
||||
Platform: &ociv1platform,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove filters out any instances in the list which match the specified digest.
|
||||
func (l *list) Remove(instanceDigest digest.Digest) error {
|
||||
err := errors.Wrapf(os.ErrNotExist, "no instance matching digest %q found in manifest list", instanceDigest)
|
||||
newDockerManifests := make([]manifest.Schema2ManifestDescriptor, 0, len(l.docker.Manifests))
|
||||
for i := range l.docker.Manifests {
|
||||
if l.docker.Manifests[i].Digest != instanceDigest {
|
||||
newDockerManifests = append(newDockerManifests, l.docker.Manifests[i])
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
l.docker.Manifests = newDockerManifests
|
||||
newOCIv1Manifests := make([]v1.Descriptor, 0, len(l.oci.Manifests))
|
||||
for i := range l.oci.Manifests {
|
||||
if l.oci.Manifests[i].Digest != instanceDigest {
|
||||
newOCIv1Manifests = append(newOCIv1Manifests, l.oci.Manifests[i])
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
l.oci.Manifests = newOCIv1Manifests
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *list) findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error) {
|
||||
for i := range l.docker.Manifests {
|
||||
if l.docker.Manifests[i].Digest == instanceDigest {
|
||||
return &l.docker.Manifests[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "no Docker manifest matching digest %q was found in list", instanceDigest.String())
|
||||
}
|
||||
|
||||
func (l *list) findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error) {
|
||||
for i := range l.oci.Manifests {
|
||||
if l.oci.Manifests[i].Digest == instanceDigest {
|
||||
return &l.oci.Manifests[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "no OCI manifest matching digest %q was found in list", instanceDigest.String())
|
||||
}
|
||||
|
||||
// SetURLs sets the URLs where the manifest might also be found.
|
||||
func (l *list) SetURLs(instanceDigest digest.Digest, urls []string) error {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci.URLs = append([]string{}, urls...)
|
||||
docker.URLs = append([]string{}, urls...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// URLs retrieves the locations from which this object might possibly be downloaded.
|
||||
func (l *list) URLs(instanceDigest digest.Digest) ([]string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]string{}, oci.URLs...), nil
|
||||
}
|
||||
|
||||
// SetAnnotations sets annotations on the image index, or on a specific manifest.
|
||||
// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
|
||||
func (l *list) SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error {
|
||||
a := &l.oci.Annotations
|
||||
if instanceDigest != nil {
|
||||
oci, err := l.findOCIv1(*instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a = &oci.Annotations
|
||||
}
|
||||
(*a) = make(map[string]string)
|
||||
for k, v := range annotations {
|
||||
(*a)[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Annotations retrieves the annotations which have been set on the image index, or on one instance.
|
||||
// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
|
||||
func (l *list) Annotations(instanceDigest *digest.Digest) (map[string]string, error) {
|
||||
a := l.oci.Annotations
|
||||
if instanceDigest != nil {
|
||||
oci, err := l.findOCIv1(*instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a = oci.Annotations
|
||||
}
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range a {
|
||||
annotations[k] = v
|
||||
}
|
||||
return annotations, nil
|
||||
}
|
||||
|
||||
// SetOS sets the OS field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetOS(instanceDigest digest.Digest, os string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.OS = os
|
||||
oci.Platform.OS = os
|
||||
return nil
|
||||
}
|
||||
|
||||
// OS retrieves the OS field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) OS(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.OS, nil
|
||||
}
|
||||
|
||||
// SetArchitecture sets the Architecture field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetArchitecture(instanceDigest digest.Digest, arch string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.Architecture = arch
|
||||
oci.Platform.Architecture = arch
|
||||
return nil
|
||||
}
|
||||
|
||||
// Architecture retrieves the Architecture field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) Architecture(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.Architecture, nil
|
||||
}
|
||||
|
||||
// SetOSVersion sets the OSVersion field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetOSVersion(instanceDigest digest.Digest, osVersion string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.OSVersion = osVersion
|
||||
oci.Platform.OSVersion = osVersion
|
||||
return nil
|
||||
}
|
||||
|
||||
// OSVersion retrieves the OSVersion field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) OSVersion(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.OSVersion, nil
|
||||
}
|
||||
|
||||
// SetVariant sets the Variant field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetVariant(instanceDigest digest.Digest, variant string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.Variant = variant
|
||||
oci.Platform.Variant = variant
|
||||
return nil
|
||||
}
|
||||
|
||||
// Variant retrieves the Variant field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) Variant(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.Variant, nil
|
||||
}
|
||||
|
||||
// SetFeatures sets the features list in the platform information associated with the instance with the specified digest.
|
||||
// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes.
|
||||
func (l *list) SetFeatures(instanceDigest digest.Digest, features []string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.Features = append([]string{}, features...)
|
||||
// no OCI equivalent
|
||||
return nil
|
||||
}
|
||||
|
||||
// Features retrieves the features list from the platform information associated with the instance with the specified digest.
|
||||
// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes.
|
||||
func (l *list) Features(instanceDigest digest.Digest) ([]string, error) {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]string{}, docker.Platform.Features...), nil
|
||||
}
|
||||
|
||||
// SetOSFeatures sets the OS features list in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.OSFeatures = append([]string{}, osFeatures...)
|
||||
oci.Platform.OSFeatures = append([]string{}, osFeatures...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OSFeatures retrieves the OS features list from the platform information associated with the instance with the specified digest.
|
||||
func (l *list) OSFeatures(instanceDigest digest.Digest) ([]string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]string{}, oci.Platform.OSFeatures...), nil
|
||||
}
|
||||
|
||||
// FromBlob builds a list from an encoded manifest list or image index.
|
||||
func FromBlob(manifestBytes []byte) (List, error) {
|
||||
manifestType := manifest.GuessMIMEType(manifestBytes)
|
||||
list := &list{
|
||||
docker: manifest.Schema2List{
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2ListMediaType,
|
||||
},
|
||||
oci: v1.Index{
|
||||
Versioned: imgspec.Versioned{SchemaVersion: 2},
|
||||
},
|
||||
}
|
||||
switch manifestType {
|
||||
default:
|
||||
return nil, errors.Wrapf(ErrManifestTypeNotSupported, "unable to load manifest list: unsupported format %q", manifestType)
|
||||
case manifest.DockerV2ListMediaType:
|
||||
if err := json.Unmarshal(manifestBytes, &list.docker); err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse Docker manifest list from image")
|
||||
}
|
||||
for _, m := range list.docker.Manifests {
|
||||
list.oci.Manifests = append(list.oci.Manifests, v1.Descriptor{
|
||||
MediaType: m.Schema2Descriptor.MediaType,
|
||||
Size: m.Schema2Descriptor.Size,
|
||||
Digest: m.Schema2Descriptor.Digest,
|
||||
Platform: &v1.Platform{
|
||||
Architecture: m.Platform.Architecture,
|
||||
OS: m.Platform.OS,
|
||||
OSVersion: m.Platform.OSVersion,
|
||||
OSFeatures: m.Platform.OSFeatures,
|
||||
Variant: m.Platform.Variant,
|
||||
},
|
||||
})
|
||||
}
|
||||
case v1.MediaTypeImageIndex:
|
||||
if err := json.Unmarshal(manifestBytes, &list.oci); err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse OCIv1 manifest list")
|
||||
}
|
||||
for _, m := range list.oci.Manifests {
|
||||
platform := m.Platform
|
||||
if platform == nil {
|
||||
platform = &v1.Platform{}
|
||||
}
|
||||
list.docker.Manifests = append(list.docker.Manifests, manifest.Schema2ManifestDescriptor{
|
||||
Schema2Descriptor: manifest.Schema2Descriptor{
|
||||
MediaType: m.MediaType,
|
||||
Size: m.Size,
|
||||
Digest: m.Digest,
|
||||
},
|
||||
Platform: manifest.Schema2PlatformSpec{
|
||||
Architecture: platform.Architecture,
|
||||
OS: platform.OS,
|
||||
OSVersion: platform.OSVersion,
|
||||
OSFeatures: platform.OSFeatures,
|
||||
Variant: platform.Variant,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (l *list) preferOCI() bool {
|
||||
// If we have any data that's only in the OCI format, use that.
|
||||
for _, m := range l.oci.Manifests {
|
||||
if len(m.URLs) > 0 {
|
||||
return true
|
||||
}
|
||||
if len(m.Annotations) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// If we have any data that's only in the Docker format, use that.
|
||||
for _, m := range l.docker.Manifests {
|
||||
if len(m.Platform.Features) > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// If we have no manifests, remember that the Docker format is
|
||||
// explicitly typed, so use that. Otherwise, default to using the OCI
|
||||
// format.
|
||||
return len(l.docker.Manifests) != 0
|
||||
}
|
||||
|
||||
// Serialize encodes the list using the specified format, or by selecting one
|
||||
// which it thinks is appropriate.
|
||||
func (l *list) Serialize(mimeType string) ([]byte, error) {
|
||||
var manifestBytes []byte
|
||||
switch mimeType {
|
||||
case "":
|
||||
if l.preferOCI() {
|
||||
manifest, err := json.Marshal(&l.oci)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling OCI image index")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
} else {
|
||||
manifest, err := json.Marshal(&l.docker)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling Docker manifest list")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
}
|
||||
case v1.MediaTypeImageIndex:
|
||||
manifest, err := json.Marshal(&l.oci)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling OCI image index")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
case manifest.DockerV2ListMediaType:
|
||||
manifest, err := json.Marshal(&l.docker)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling Docker manifest list")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
default:
|
||||
return nil, errors.Wrapf(ErrManifestTypeNotSupported, "serializing list to type %q not implemented", mimeType)
|
||||
}
|
||||
return manifestBytes, nil
|
||||
}
|
||||
|
||||
// Instances returns the list of image instances mentioned in this list.
|
||||
func (l *list) Instances() []digest.Digest {
|
||||
instances := make([]digest.Digest, 0, len(l.oci.Manifests))
|
||||
for _, instance := range l.oci.Manifests {
|
||||
instances = append(instances, instance.Digest)
|
||||
}
|
||||
return instances
|
||||
}
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
package manifests
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
expectedInstance = digest.Digest("sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b")
|
||||
ociFixture = "testdata/fedora.index.json"
|
||||
dockerFixture = "testdata/fedora.list.json"
|
||||
)
|
||||
|
||||
var (
|
||||
_ List = &list{}
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
list := Create()
|
||||
if list == nil {
|
||||
t.Fatalf("error creating an empty list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromBlob(t *testing.T) {
|
||||
for _, version := range []string{
|
||||
ociFixture,
|
||||
dockerFixture,
|
||||
} {
|
||||
bytes, err := ioutil.ReadFile(version)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading %s: %v", version, err)
|
||||
}
|
||||
list, err := FromBlob(bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing %s: %v", version, err)
|
||||
}
|
||||
if len(list.Docker().Manifests) != len(list.OCIv1().Manifests) {
|
||||
t.Fatalf("%s: expected the same number of manifests, but %d != %d", version, len(list.Docker().Manifests), len(list.OCIv1().Manifests))
|
||||
}
|
||||
for i := range list.Docker().Manifests {
|
||||
d := list.Docker().Manifests[i]
|
||||
o := list.OCIv1().Manifests[i]
|
||||
if d.Platform.OS != o.Platform.OS {
|
||||
t.Fatalf("%s: expected the same OS", version)
|
||||
}
|
||||
if d.Platform.Architecture != o.Platform.Architecture {
|
||||
t.Fatalf("%s: expected the same Architecture", version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddInstance(t *testing.T) {
|
||||
manifestBytes, err := ioutil.ReadFile("testdata/fedora-minimal.schema2.json")
|
||||
if err != nil {
|
||||
t.Fatalf("error loading testdata/fedora-minimal.schema2.json: %v", err)
|
||||
}
|
||||
manifestType := manifest.GuessMIMEType(manifestBytes)
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error digesting testdata/fedora-minimal.schema2.json: %v", err)
|
||||
}
|
||||
for _, version := range []string{
|
||||
ociFixture,
|
||||
dockerFixture,
|
||||
} {
|
||||
bytes, err := ioutil.ReadFile(version)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading %s: %v", version, err)
|
||||
}
|
||||
list, err := FromBlob(bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing %s: %v", version, err)
|
||||
}
|
||||
if err = list.AddInstance(manifestDigest, int64(len(manifestBytes)), manifestType, "linux", "amd64", "", nil, "", nil, nil); err != nil {
|
||||
t.Fatalf("adding an instance failed in %s: %v", version, err)
|
||||
}
|
||||
if d, err := list.findDocker(manifestDigest); d == nil || err != nil {
|
||||
t.Fatalf("adding an instance failed in %s: %v", version, err)
|
||||
}
|
||||
if o, err := list.findOCIv1(manifestDigest); o == nil || err != nil {
|
||||
t.Fatalf("adding an instance failed in %s: %v", version, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
bytes, err := ioutil.ReadFile(ociFixture)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading blob: %v", err)
|
||||
}
|
||||
list, err := FromBlob(bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing blob: %v", err)
|
||||
}
|
||||
before := len(list.OCIv1().Manifests)
|
||||
instanceDigest := expectedInstance
|
||||
if d, err := list.findDocker(instanceDigest); d == nil || err != nil {
|
||||
t.Fatalf("finding expected instance failed: %v", err)
|
||||
}
|
||||
if o, err := list.findOCIv1(instanceDigest); o == nil || err != nil {
|
||||
t.Fatalf("finding expected instance failed: %v", err)
|
||||
}
|
||||
err = list.Remove(instanceDigest)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing blob: %v", err)
|
||||
}
|
||||
after := len(list.Docker().Manifests)
|
||||
if after != before-1 {
|
||||
t.Fatalf("removing instance should have succeeded")
|
||||
}
|
||||
if d, err := list.findDocker(instanceDigest); d != nil || err == nil {
|
||||
t.Fatalf("finding instance should have failed")
|
||||
}
|
||||
if o, err := list.findOCIv1(instanceDigest); o != nil || err == nil {
|
||||
t.Fatalf("finding instance should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func testString(t *testing.T, values []string, set func(List, digest.Digest, string) error, get func(List, digest.Digest) (string, error)) {
|
||||
bytes, err := ioutil.ReadFile(ociFixture)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading blob: %v", err)
|
||||
}
|
||||
list, err := FromBlob(bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing blob: %v", err)
|
||||
}
|
||||
for _, testString := range values {
|
||||
if err = set(list, expectedInstance, testString); err != nil {
|
||||
t.Fatalf("error setting %q: %v", testString, err)
|
||||
}
|
||||
b, err := list.Serialize("")
|
||||
if err != nil {
|
||||
t.Fatalf("error serializing list: %v", err)
|
||||
}
|
||||
list, err := FromBlob(b)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing list: %v", err)
|
||||
}
|
||||
value, err := get(list, expectedInstance)
|
||||
if err != nil {
|
||||
t.Fatalf("error retrieving value %q: %v", testString, err)
|
||||
}
|
||||
if value != testString {
|
||||
t.Fatalf("expected value %q, got %q: %v", value, testString, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testStringSlice(t *testing.T, values [][]string, set func(List, digest.Digest, []string) error, get func(List, digest.Digest) ([]string, error)) {
|
||||
bytes, err := ioutil.ReadFile(ociFixture)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading blob: %v", err)
|
||||
}
|
||||
list, err := FromBlob(bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing blob: %v", err)
|
||||
}
|
||||
for _, testSlice := range values {
|
||||
if err = set(list, expectedInstance, testSlice); err != nil {
|
||||
t.Fatalf("error setting %v: %v", testSlice, err)
|
||||
}
|
||||
b, err := list.Serialize("")
|
||||
if err != nil {
|
||||
t.Fatalf("error serializing list: %v", err)
|
||||
}
|
||||
list, err := FromBlob(b)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing list: %v", err)
|
||||
}
|
||||
values, err := get(list, expectedInstance)
|
||||
if err != nil {
|
||||
t.Fatalf("error retrieving value %v: %v", testSlice, err)
|
||||
}
|
||||
if !reflect.DeepEqual(values, testSlice) {
|
||||
t.Fatalf("expected values %v, got %v: %v", testSlice, values, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMap(t *testing.T, values []map[string]string, set func(List, *digest.Digest, map[string]string) error, get func(List, *digest.Digest) (map[string]string, error)) {
|
||||
bytes, err := ioutil.ReadFile(ociFixture)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading blob: %v", err)
|
||||
}
|
||||
list, err := FromBlob(bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing blob: %v", err)
|
||||
}
|
||||
instance := expectedInstance
|
||||
for _, instanceDigest := range []*digest.Digest{nil, &instance} {
|
||||
for _, testMap := range values {
|
||||
if err = set(list, instanceDigest, testMap); err != nil {
|
||||
t.Fatalf("error setting %v: %v", testMap, err)
|
||||
}
|
||||
b, err := list.Serialize("")
|
||||
if err != nil {
|
||||
t.Fatalf("error serializing list: %v", err)
|
||||
}
|
||||
list, err := FromBlob(b)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing list: %v", err)
|
||||
}
|
||||
values, err := get(list, instanceDigest)
|
||||
if err != nil {
|
||||
t.Fatalf("error retrieving value %v: %v", testMap, err)
|
||||
}
|
||||
if len(values) != len(testMap) {
|
||||
t.Fatalf("expected %d map entries, got %d", len(testMap), len(values))
|
||||
}
|
||||
for k, v := range testMap {
|
||||
if values[k] != v {
|
||||
t.Fatalf("expected map value %q=%q, got %q", k, v, values[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
testMap(t,
|
||||
[]map[string]string{{"A": "B", "C": "D"}, {"E": "F", "G": "H"}},
|
||||
func(l List, i *digest.Digest, m map[string]string) error {
|
||||
return l.SetAnnotations(i, m)
|
||||
},
|
||||
func(l List, i *digest.Digest) (map[string]string, error) {
|
||||
return l.Annotations(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestArchitecture(t *testing.T) {
|
||||
testString(t,
|
||||
[]string{"abacus", "sliderule"},
|
||||
func(l List, i digest.Digest, s string) error {
|
||||
return l.SetArchitecture(i, s)
|
||||
},
|
||||
func(l List, i digest.Digest) (string, error) {
|
||||
return l.Architecture(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestFeatures(t *testing.T) {
|
||||
testStringSlice(t,
|
||||
[][]string{{"chrome", "hubcaps"}, {"climate", "control"}},
|
||||
func(l List, i digest.Digest, s []string) error {
|
||||
return l.SetFeatures(i, s)
|
||||
},
|
||||
func(l List, i digest.Digest) ([]string, error) {
|
||||
return l.Features(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestOS(t *testing.T) {
|
||||
testString(t,
|
||||
[]string{"linux", "darwin"},
|
||||
func(l List, i digest.Digest, s string) error {
|
||||
return l.SetOS(i, s)
|
||||
},
|
||||
func(l List, i digest.Digest) (string, error) {
|
||||
return l.OS(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestOSFeatures(t *testing.T) {
|
||||
testStringSlice(t,
|
||||
[][]string{{"ipv6", "containers"}, {"nested", "virtualization"}},
|
||||
func(l List, i digest.Digest, s []string) error {
|
||||
return l.SetOSFeatures(i, s)
|
||||
},
|
||||
func(l List, i digest.Digest) ([]string, error) {
|
||||
return l.OSFeatures(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestOSVersion(t *testing.T) {
|
||||
testString(t,
|
||||
[]string{"el7", "el8"},
|
||||
func(l List, i digest.Digest, s string) error {
|
||||
return l.SetOSVersion(i, s)
|
||||
},
|
||||
func(l List, i digest.Digest) (string, error) {
|
||||
return l.OSVersion(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestURLs(t *testing.T) {
|
||||
testStringSlice(t,
|
||||
[][]string{{"https://example.com", "https://example.net"}, {"http://example.com", "http://example.net"}},
|
||||
func(l List, i digest.Digest, s []string) error {
|
||||
return l.SetURLs(i, s)
|
||||
},
|
||||
func(l List, i digest.Digest) ([]string, error) {
|
||||
return l.URLs(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestVariant(t *testing.T) {
|
||||
testString(t,
|
||||
[]string{"workstation", "cloud", "server"},
|
||||
func(l List, i digest.Digest, s string) error {
|
||||
return l.SetVariant(i, s)
|
||||
},
|
||||
func(l List, i digest.Digest) (string, error) {
|
||||
return l.Variant(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestSerialize(t *testing.T) {
|
||||
for _, version := range []string{
|
||||
ociFixture,
|
||||
dockerFixture,
|
||||
} {
|
||||
bytes, err := ioutil.ReadFile(version)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading %s: %v", version, err)
|
||||
}
|
||||
list, err := FromBlob(bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing %s: %v", version, err)
|
||||
}
|
||||
for _, mimeType := range []string{"", v1.MediaTypeImageIndex, manifest.DockerV2ListMediaType} {
|
||||
b, err := list.Serialize(mimeType)
|
||||
if err != nil {
|
||||
t.Fatalf("error serializing %s with type %q: %v", version, mimeType, err)
|
||||
}
|
||||
l, err := FromBlob(b)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing %s re-encoded as %q: %v\n%s", version, mimeType, err, string(b))
|
||||
}
|
||||
if !reflect.DeepEqual(list.Docker().Manifests, l.Docker().Manifests) {
|
||||
t.Fatalf("re-encoded %s as %q was different\n%#v\n%#v", version, mimeType, list, l)
|
||||
}
|
||||
for i := range list.OCIv1().Manifests {
|
||||
manifest := list.OCIv1().Manifests[i]
|
||||
m := l.OCIv1().Manifests[i]
|
||||
if manifest.Digest != m.Digest ||
|
||||
manifest.MediaType != m.MediaType ||
|
||||
manifest.Size != m.Size ||
|
||||
!reflect.DeepEqual(list.OCIv1().Manifests[i].Platform, l.OCIv1().Manifests[i].Platform) {
|
||||
t.Fatalf("re-encoded %s OCI %d as %q was different\n%#v\n%#v", version, i, mimeType, list, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||
"size": 1316,
|
||||
"digest": "sha256:847a6054047619b8908f61e0211e3480ab20ce4b6cf17b03db081322ade301d3"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": 42163967,
|
||||
"digest": "sha256:8fad33c002fa130aceec4fd0cadc8bad0d7561667ab24aee75dea825be933de0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"manifests": [
|
||||
{
|
||||
"digest": "sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7",
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
},
|
||||
"size": 529
|
||||
},
|
||||
{
|
||||
"digest": "sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "arm64",
|
||||
"os": "linux",
|
||||
"variant": "v8"
|
||||
},
|
||||
"size": 529
|
||||
},
|
||||
{
|
||||
"digest": "sha256:68b26da78d8790df143479ec2e3174c57cedb1c2e84ce1b2675d942d6848f2da",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "ppc64le",
|
||||
"os": "linux"
|
||||
},
|
||||
"size": 529
|
||||
},
|
||||
{
|
||||
"digest": "sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "s390x",
|
||||
"os": "linux"
|
||||
},
|
||||
"size": 529
|
||||
}
|
||||
],
|
||||
"schemaVersion": 2,
|
||||
"annotations": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"manifests": [
|
||||
{
|
||||
"digest": "sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
},
|
||||
"size": 529
|
||||
},
|
||||
{
|
||||
"digest": "sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "arm64",
|
||||
"os": "linux",
|
||||
"variant": "v8"
|
||||
},
|
||||
"size": 529
|
||||
},
|
||||
{
|
||||
"digest": "sha256:68b26da78d8790df143479ec2e3174c57cedb1c2e84ce1b2675d942d6848f2da",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "ppc64le",
|
||||
"os": "linux"
|
||||
},
|
||||
"size": 529
|
||||
},
|
||||
{
|
||||
"digest": "sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"platform": {
|
||||
"architecture": "s390x",
|
||||
"os": "linux"
|
||||
},
|
||||
"size": 529
|
||||
}
|
||||
],
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"schemaVersion": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||
"size": 2037,
|
||||
"digest": "sha256:e9ed59d2baf72308f3a811ebc49ff3f4e0175abf40bf636bea0160759c637999"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": 69532283,
|
||||
"digest": "sha256:5a915a173fbc36dc8e1410afdd9de2b08f71efb226f8eb1ebcdc00a1acbced62"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package signal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ParseSignal translates a string to a valid syscall signal.
|
||||
// It returns an error if the signal map doesn't include the given signal.
|
||||
func ParseSignal(rawSignal string) (syscall.Signal, error) {
|
||||
s, err := strconv.Atoi(rawSignal)
|
||||
if err == nil {
|
||||
if s == 0 {
|
||||
return -1, fmt.Errorf("invalid signal: %s", rawSignal)
|
||||
}
|
||||
return syscall.Signal(s), nil
|
||||
}
|
||||
sig, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("invalid signal: %s", rawSignal)
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input
|
||||
// can be a name or number representation i.e. "KILL" "9"
|
||||
func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
|
||||
basename := strings.TrimPrefix(rawSignal, "-")
|
||||
s, err := ParseSignal(basename)
|
||||
if err == nil {
|
||||
return s, nil
|
||||
}
|
||||
for k, v := range signalMap {
|
||||
if strings.EqualFold(k, basename) {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("invalid signal: %s", basename)
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// +build linux
|
||||
// +build !mips,!mipsle,!mips64,!mips64le
|
||||
|
||||
// Signal handling for Linux only.
|
||||
package signal
|
||||
|
||||
// Copyright 2013-2018 Docker, Inc.
|
||||
|
||||
// NOTE: this package has originally been copied from github.com/docker/docker.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
sigrtmin = 34
|
||||
sigrtmax = 64
|
||||
|
||||
SIGWINCH = syscall.SIGWINCH // For cross-compilation with Windows
|
||||
)
|
||||
|
||||
// signalMap is a map of Linux signals.
|
||||
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,
|
||||
"RTMIN": sigrtmin,
|
||||
"RTMIN+1": sigrtmin + 1,
|
||||
"RTMIN+2": sigrtmin + 2,
|
||||
"RTMIN+3": sigrtmin + 3,
|
||||
"RTMIN+4": sigrtmin + 4,
|
||||
"RTMIN+5": sigrtmin + 5,
|
||||
"RTMIN+6": sigrtmin + 6,
|
||||
"RTMIN+7": sigrtmin + 7,
|
||||
"RTMIN+8": sigrtmin + 8,
|
||||
"RTMIN+9": sigrtmin + 9,
|
||||
"RTMIN+10": sigrtmin + 10,
|
||||
"RTMIN+11": sigrtmin + 11,
|
||||
"RTMIN+12": sigrtmin + 12,
|
||||
"RTMIN+13": sigrtmin + 13,
|
||||
"RTMIN+14": sigrtmin + 14,
|
||||
"RTMIN+15": sigrtmin + 15,
|
||||
"RTMAX-14": sigrtmax - 14,
|
||||
"RTMAX-13": sigrtmax - 13,
|
||||
"RTMAX-12": sigrtmax - 12,
|
||||
"RTMAX-11": sigrtmax - 11,
|
||||
"RTMAX-10": sigrtmax - 10,
|
||||
"RTMAX-9": sigrtmax - 9,
|
||||
"RTMAX-8": sigrtmax - 8,
|
||||
"RTMAX-7": sigrtmax - 7,
|
||||
"RTMAX-6": sigrtmax - 6,
|
||||
"RTMAX-5": sigrtmax - 5,
|
||||
"RTMAX-4": sigrtmax - 4,
|
||||
"RTMAX-3": sigrtmax - 3,
|
||||
"RTMAX-2": sigrtmax - 2,
|
||||
"RTMAX-1": sigrtmax - 1,
|
||||
"RTMAX": sigrtmax,
|
||||
}
|
||||
|
||||
// CatchAll catches all signals and relays them to the specified channel.
|
||||
func CatchAll(sigc chan os.Signal) {
|
||||
handledSigs := make([]os.Signal, 0, len(signalMap))
|
||||
for _, s := range signalMap {
|
||||
handledSigs = append(handledSigs, s)
|
||||
}
|
||||
signal.Notify(sigc, handledSigs...)
|
||||
}
|
||||
|
||||
// StopCatch stops catching the signals and closes the specified channel.
|
||||
func StopCatch(sigc chan os.Signal) {
|
||||
signal.Stop(sigc)
|
||||
close(sigc)
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// +build linux
|
||||
// +build mips mipsle mips64 mips64le
|
||||
|
||||
// Special signal handling for mips architecture
|
||||
package signal
|
||||
|
||||
// Copyright 2013-2018 Docker, Inc.
|
||||
|
||||
// NOTE: this package has originally been copied from github.com/docker/docker.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
sigrtmin = 34
|
||||
sigrtmax = 127
|
||||
|
||||
SIGWINCH = syscall.SIGWINCH
|
||||
)
|
||||
|
||||
// signalMap is a map of Linux signals.
|
||||
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,
|
||||
"EMT": unix.SIGEMT,
|
||||
"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,
|
||||
"RTMIN": sigrtmin,
|
||||
"RTMIN+1": sigrtmin + 1,
|
||||
"RTMIN+2": sigrtmin + 2,
|
||||
"RTMIN+3": sigrtmin + 3,
|
||||
"RTMIN+4": sigrtmin + 4,
|
||||
"RTMIN+5": sigrtmin + 5,
|
||||
"RTMIN+6": sigrtmin + 6,
|
||||
"RTMIN+7": sigrtmin + 7,
|
||||
"RTMIN+8": sigrtmin + 8,
|
||||
"RTMIN+9": sigrtmin + 9,
|
||||
"RTMIN+10": sigrtmin + 10,
|
||||
"RTMIN+11": sigrtmin + 11,
|
||||
"RTMIN+12": sigrtmin + 12,
|
||||
"RTMIN+13": sigrtmin + 13,
|
||||
"RTMIN+14": sigrtmin + 14,
|
||||
"RTMIN+15": sigrtmin + 15,
|
||||
"RTMAX-14": sigrtmax - 14,
|
||||
"RTMAX-13": sigrtmax - 13,
|
||||
"RTMAX-12": sigrtmax - 12,
|
||||
"RTMAX-11": sigrtmax - 11,
|
||||
"RTMAX-10": sigrtmax - 10,
|
||||
"RTMAX-9": sigrtmax - 9,
|
||||
"RTMAX-8": sigrtmax - 8,
|
||||
"RTMAX-7": sigrtmax - 7,
|
||||
"RTMAX-6": sigrtmax - 6,
|
||||
"RTMAX-5": sigrtmax - 5,
|
||||
"RTMAX-4": sigrtmax - 4,
|
||||
"RTMAX-3": sigrtmax - 3,
|
||||
"RTMAX-2": sigrtmax - 2,
|
||||
"RTMAX-1": sigrtmax - 1,
|
||||
"RTMAX": sigrtmax,
|
||||
}
|
||||
|
||||
// CatchAll catches all signals and relays them to the specified channel.
|
||||
func CatchAll(sigc chan os.Signal) {
|
||||
handledSigs := make([]os.Signal, 0, len(signalMap))
|
||||
for _, s := range signalMap {
|
||||
handledSigs = append(handledSigs, s)
|
||||
}
|
||||
signal.Notify(sigc, handledSigs...)
|
||||
}
|
||||
|
||||
// StopCatch stops catching the signals and closes the specified channel.
|
||||
func StopCatch(sigc chan os.Signal) {
|
||||
signal.Stop(sigc)
|
||||
close(sigc)
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// +build !linux
|
||||
|
||||
// Signal handling for Linux only.
|
||||
package signal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
sigrtmin = 34
|
||||
sigrtmax = 64
|
||||
|
||||
SIGWINCH = syscall.Signal(0xff)
|
||||
)
|
||||
|
||||
// signalMap is a map of Linux signals.
|
||||
// These constants are sourced from the Linux version of golang.org/x/sys/unix
|
||||
// (I don't see much risk of this changing).
|
||||
// This should work as long as Podman only runs containers on Linux, which seems
|
||||
// a safe assumption for now.
|
||||
var signalMap = map[string]syscall.Signal{
|
||||
"ABRT": syscall.Signal(0x6),
|
||||
"ALRM": syscall.Signal(0xe),
|
||||
"BUS": syscall.Signal(0x7),
|
||||
"CHLD": syscall.Signal(0x11),
|
||||
"CLD": syscall.Signal(0x11),
|
||||
"CONT": syscall.Signal(0x12),
|
||||
"FPE": syscall.Signal(0x8),
|
||||
"HUP": syscall.Signal(0x1),
|
||||
"ILL": syscall.Signal(0x4),
|
||||
"INT": syscall.Signal(0x2),
|
||||
"IO": syscall.Signal(0x1d),
|
||||
"IOT": syscall.Signal(0x6),
|
||||
"KILL": syscall.Signal(0x9),
|
||||
"PIPE": syscall.Signal(0xd),
|
||||
"POLL": syscall.Signal(0x1d),
|
||||
"PROF": syscall.Signal(0x1b),
|
||||
"PWR": syscall.Signal(0x1e),
|
||||
"QUIT": syscall.Signal(0x3),
|
||||
"SEGV": syscall.Signal(0xb),
|
||||
"STKFLT": syscall.Signal(0x10),
|
||||
"STOP": syscall.Signal(0x13),
|
||||
"SYS": syscall.Signal(0x1f),
|
||||
"TERM": syscall.Signal(0xf),
|
||||
"TRAP": syscall.Signal(0x5),
|
||||
"TSTP": syscall.Signal(0x14),
|
||||
"TTIN": syscall.Signal(0x15),
|
||||
"TTOU": syscall.Signal(0x16),
|
||||
"URG": syscall.Signal(0x17),
|
||||
"USR1": syscall.Signal(0xa),
|
||||
"USR2": syscall.Signal(0xc),
|
||||
"VTALRM": syscall.Signal(0x1a),
|
||||
"WINCH": syscall.Signal(0x1c),
|
||||
"XCPU": syscall.Signal(0x18),
|
||||
"XFSZ": syscall.Signal(0x19),
|
||||
"RTMIN": sigrtmin,
|
||||
"RTMIN+1": sigrtmin + 1,
|
||||
"RTMIN+2": sigrtmin + 2,
|
||||
"RTMIN+3": sigrtmin + 3,
|
||||
"RTMIN+4": sigrtmin + 4,
|
||||
"RTMIN+5": sigrtmin + 5,
|
||||
"RTMIN+6": sigrtmin + 6,
|
||||
"RTMIN+7": sigrtmin + 7,
|
||||
"RTMIN+8": sigrtmin + 8,
|
||||
"RTMIN+9": sigrtmin + 9,
|
||||
"RTMIN+10": sigrtmin + 10,
|
||||
"RTMIN+11": sigrtmin + 11,
|
||||
"RTMIN+12": sigrtmin + 12,
|
||||
"RTMIN+13": sigrtmin + 13,
|
||||
"RTMIN+14": sigrtmin + 14,
|
||||
"RTMIN+15": sigrtmin + 15,
|
||||
"RTMAX-14": sigrtmax - 14,
|
||||
"RTMAX-13": sigrtmax - 13,
|
||||
"RTMAX-12": sigrtmax - 12,
|
||||
"RTMAX-11": sigrtmax - 11,
|
||||
"RTMAX-10": sigrtmax - 10,
|
||||
"RTMAX-9": sigrtmax - 9,
|
||||
"RTMAX-8": sigrtmax - 8,
|
||||
"RTMAX-7": sigrtmax - 7,
|
||||
"RTMAX-6": sigrtmax - 6,
|
||||
"RTMAX-5": sigrtmax - 5,
|
||||
"RTMAX-4": sigrtmax - 4,
|
||||
"RTMAX-3": sigrtmax - 3,
|
||||
"RTMAX-2": sigrtmax - 2,
|
||||
"RTMAX-1": sigrtmax - 1,
|
||||
"RTMAX": sigrtmax,
|
||||
}
|
||||
|
||||
// CatchAll catches all signals and relays them to the specified channel.
|
||||
func CatchAll(sigc chan os.Signal) {
|
||||
panic("Unsupported on non-linux platforms")
|
||||
}
|
||||
|
||||
// StopCatch stops catching the signals and closes the specified channel.
|
||||
func StopCatch(sigc chan os.Signal) {
|
||||
panic("Unsupported on non-linux platforms")
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package supplemented
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/containers/common/pkg/manifests"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDigestNotFound is returned when we look for an image instance
|
||||
// with a particular digest in a list or index, and fail to find it.
|
||||
ErrDigestNotFound = manifests.ErrDigestNotFound
|
||||
// ErrBlobNotFound is returned when try to figure out which supplemental
|
||||
// image we should ask for a blob with the specified characteristics,
|
||||
// based on the information in each of the supplemental images' manifests.
|
||||
ErrBlobNotFound = errors.New("location of blob could not be determined")
|
||||
)
|
||||
|
|
@ -0,0 +1,402 @@
|
|||
package supplemented
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"io"
|
||||
|
||||
cp "github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// supplementedImageReference groups multiple references together.
|
||||
type supplementedImageReference struct {
|
||||
types.ImageReference
|
||||
references []types.ImageReference
|
||||
multiple cp.ImageListSelection
|
||||
instances []digest.Digest
|
||||
}
|
||||
|
||||
// supplementedImageSource represents an image, plus all of the blobs of other images.
|
||||
type supplementedImageSource struct {
|
||||
types.ImageSource
|
||||
reference types.ImageReference
|
||||
manifest []byte // The manifest list or image index.
|
||||
manifestType string // The MIME type of the manifest list or image index.
|
||||
sourceDefaultInstances map[types.ImageSource]digest.Digest // The default manifest instances of open ImageSource objects.
|
||||
sourceInstancesByInstance map[digest.Digest]types.ImageSource // A map from manifest instance digests to open ImageSource objects.
|
||||
instancesByBlobDigest map[digest.Digest]digest.Digest // A map from blob digests to manifest instance digests.
|
||||
}
|
||||
|
||||
// Reference groups one reference and some number of additional references
|
||||
// together as a group. The first reference's default instance will be treated
|
||||
// as the default instance of the resulting reference, with the other
|
||||
// references' instances made available as instances for their respective
|
||||
// digests.
|
||||
func Reference(ref types.ImageReference, supplemental []types.ImageReference, multiple cp.ImageListSelection, instances []digest.Digest) types.ImageReference {
|
||||
if len(instances) > 0 {
|
||||
i := make([]digest.Digest, len(instances))
|
||||
copy(i, instances)
|
||||
instances = i
|
||||
}
|
||||
return &supplementedImageReference{
|
||||
ImageReference: ref,
|
||||
references: append([]types.ImageReference{}, supplemental...),
|
||||
multiple: multiple,
|
||||
instances: instances,
|
||||
}
|
||||
}
|
||||
|
||||
// NewImage returns a new higher-level view of the image.
|
||||
func (s *supplementedImageReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := s.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error building a new Image using an ImageSource")
|
||||
}
|
||||
return image.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource opens the referenced images, scans their manifests for
|
||||
// instances, and builds mappings from each blob mentioned in them to their
|
||||
// instances.
|
||||
func (s *supplementedImageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (iss types.ImageSource, err error) {
|
||||
sources := make(map[digest.Digest]types.ImageSource)
|
||||
defaultInstances := make(map[types.ImageSource]digest.Digest)
|
||||
instances := make(map[digest.Digest]digest.Digest)
|
||||
var sis *supplementedImageSource
|
||||
|
||||
// Open the default instance for reading.
|
||||
top, err := s.ImageReference.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(s.ImageReference))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if iss != nil {
|
||||
// The composite source has been created. Use its Close method.
|
||||
if err2 := iss.Close(); err2 != nil {
|
||||
logrus.Errorf("error opening image: %v", err2)
|
||||
}
|
||||
} else if top != nil {
|
||||
// The composite source has not been created, but the top was already opened. Close it.
|
||||
if err2 := top.Close(); err2 != nil {
|
||||
logrus.Errorf("error opening image: %v", err2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var addSingle, addMulti func(manifestBytes []byte, manifestType string, src types.ImageSource) error
|
||||
type manifestToRead struct {
|
||||
src types.ImageSource
|
||||
instance *digest.Digest
|
||||
}
|
||||
manifestsToRead := list.New()
|
||||
|
||||
addSingle = func(manifestBytes []byte, manifestType string, src types.ImageSource) error {
|
||||
// Mark this instance as being associated with this ImageSource.
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error computing digest over manifest %q", string(manifestBytes))
|
||||
}
|
||||
sources[manifestDigest] = src
|
||||
|
||||
// Parse the manifest as a single image.
|
||||
man, err := manifest.FromBlob(manifestBytes, manifestType)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing manifest %q", string(manifestBytes))
|
||||
}
|
||||
|
||||
// Log the config blob's digest and the blobs of its layers as associated with this manifest.
|
||||
config := man.ConfigInfo()
|
||||
if config.Digest != "" {
|
||||
instances[config.Digest] = manifestDigest
|
||||
logrus.Debugf("blob %q belongs to %q", config.Digest, manifestDigest)
|
||||
}
|
||||
|
||||
layers := man.LayerInfos()
|
||||
for _, layer := range layers {
|
||||
instances[layer.Digest] = manifestDigest
|
||||
logrus.Debugf("layer %q belongs to %q", layer.Digest, manifestDigest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
addMulti = func(manifestBytes []byte, manifestType string, src types.ImageSource) error {
|
||||
// Mark this instance as being associated with this ImageSource.
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error computing manifest digest")
|
||||
}
|
||||
sources[manifestDigest] = src
|
||||
|
||||
// Parse the manifest as a list of images.
|
||||
list, err := manifest.ListFromBlob(manifestBytes, manifestType)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(manifestBytes), manifestType)
|
||||
}
|
||||
|
||||
// Figure out which of its instances we want to look at.
|
||||
var chaseInstances []digest.Digest
|
||||
switch s.multiple {
|
||||
case cp.CopySystemImage:
|
||||
instance, err := list.ChooseInstance(sys)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error selecting appropriate instance from list")
|
||||
}
|
||||
chaseInstances = []digest.Digest{instance}
|
||||
case cp.CopySpecificImages:
|
||||
chaseInstances = s.instances
|
||||
case cp.CopyAllImages:
|
||||
chaseInstances = list.Instances()
|
||||
}
|
||||
|
||||
// Queue these manifest instances for reading from this
|
||||
// ImageSource later, if we don't stumble across them somewhere
|
||||
// else first.
|
||||
for _, instanceIterator := range chaseInstances {
|
||||
instance := instanceIterator
|
||||
next := &manifestToRead{
|
||||
src: src,
|
||||
instance: &instance,
|
||||
}
|
||||
if src == top {
|
||||
// Prefer any other source.
|
||||
manifestsToRead.PushBack(next)
|
||||
} else {
|
||||
// Prefer this source over the first ("main") one.
|
||||
manifestsToRead.PushFront(next)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
visitedReferences := make(map[types.ImageReference]struct{})
|
||||
for i, ref := range append([]types.ImageReference{s.ImageReference}, s.references...) {
|
||||
if _, visited := visitedReferences[ref]; visited {
|
||||
continue
|
||||
}
|
||||
visitedReferences[ref] = struct{}{}
|
||||
|
||||
// Open this image for reading.
|
||||
var src types.ImageSource
|
||||
if ref == s.ImageReference {
|
||||
src = top
|
||||
} else {
|
||||
src, err = ref.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(ref))
|
||||
}
|
||||
}
|
||||
|
||||
// Read the default manifest for the image.
|
||||
manifestBytes, manifestType, err := src.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading default manifest from image %q", transports.ImageName(ref))
|
||||
}
|
||||
|
||||
// If this is the first image, mark it as our starting point.
|
||||
if i == 0 {
|
||||
sources[""] = src
|
||||
|
||||
sis = &supplementedImageSource{
|
||||
ImageSource: top,
|
||||
reference: s,
|
||||
manifest: manifestBytes,
|
||||
manifestType: manifestType,
|
||||
sourceDefaultInstances: defaultInstances,
|
||||
sourceInstancesByInstance: sources,
|
||||
instancesByBlobDigest: instances,
|
||||
}
|
||||
iss = sis
|
||||
}
|
||||
|
||||
// Record the digest of the ImageSource's default instance's manifest.
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error computing digest of manifest from image %q", transports.ImageName(ref))
|
||||
}
|
||||
sis.sourceDefaultInstances[src] = manifestDigest
|
||||
|
||||
// If the ImageSource's default manifest is a list, parse each of its instances.
|
||||
if manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
if err = addMulti(manifestBytes, manifestType, src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding multi-image %q", transports.ImageName(ref))
|
||||
}
|
||||
} else {
|
||||
if err = addSingle(manifestBytes, manifestType, src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding single image %q", transports.ImageName(ref))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the rest of the instances.
|
||||
for manifestsToRead.Front() != nil {
|
||||
front := manifestsToRead.Front()
|
||||
value := front.Value
|
||||
manifestToRead, ok := value.(*manifestToRead)
|
||||
if !ok {
|
||||
panic("bug: wrong type looking for *manifestToRead in list?")
|
||||
}
|
||||
manifestsToRead.Remove(front)
|
||||
|
||||
// If we already read this manifest, no need to read it again.
|
||||
if _, alreadyRead := sources[*manifestToRead.instance]; alreadyRead {
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the instance's manifest.
|
||||
manifestBytes, manifestType, err := manifestToRead.src.GetManifest(ctx, manifestToRead.instance)
|
||||
if err != nil {
|
||||
// if errors.Cause(err) == storage.ErrImageUnknown || os.IsNotExist(errors.Cause(err)) {
|
||||
// Trust that we either don't need it, or that it's in another reference.
|
||||
// continue
|
||||
// }
|
||||
return nil, errors.Wrapf(err, "error reading manifest for instance %q", manifestToRead.instance)
|
||||
}
|
||||
|
||||
if manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
// Add the list's contents.
|
||||
if err = addMulti(manifestBytes, manifestType, manifestToRead.src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance)
|
||||
}
|
||||
} else {
|
||||
// Add the single image's contents.
|
||||
if err = addSingle(manifestBytes, manifestType, manifestToRead.src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iss, nil
|
||||
}
|
||||
|
||||
func (s *supplementedImageReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("deletion of images not implemented")
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) Close() error {
|
||||
var returnErr *multierror.Error
|
||||
closed := make(map[types.ImageSource]struct{})
|
||||
for _, sourceInstance := range s.sourceInstancesByInstance {
|
||||
if _, closed := closed[sourceInstance]; closed {
|
||||
continue
|
||||
}
|
||||
if err := sourceInstance.Close(); err != nil {
|
||||
returnErr = multierror.Append(returnErr, err)
|
||||
}
|
||||
closed[sourceInstance] = struct{}{}
|
||||
}
|
||||
if returnErr == nil {
|
||||
return nil
|
||||
}
|
||||
return returnErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
requestInstanceDigest := instanceDigest
|
||||
if instanceDigest == nil {
|
||||
return s.manifest, s.manifestType, nil
|
||||
}
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
|
||||
if *instanceDigest == s.sourceDefaultInstances[sourceInstance] {
|
||||
requestInstanceDigest = nil
|
||||
}
|
||||
return sourceInstance.GetManifest(ctx, requestInstanceDigest)
|
||||
}
|
||||
return nil, "", errors.Wrapf(ErrDigestNotFound, "error getting manifest for digest %q", *instanceDigest)
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) GetBlob(ctx context.Context, blob types.BlobInfo, bic types.BlobInfoCache) (io.ReadCloser, int64, error) {
|
||||
sourceInstance, ok := s.instancesByBlobDigest[blob.Digest]
|
||||
if !ok {
|
||||
return nil, -1, errors.Wrapf(ErrBlobNotFound, "error blob %q in known instances", blob.Digest)
|
||||
}
|
||||
src, ok := s.sourceInstancesByInstance[sourceInstance]
|
||||
if !ok {
|
||||
return nil, -1, errors.Wrapf(ErrDigestNotFound, "error getting image source for instance %q", sourceInstance)
|
||||
}
|
||||
return src.GetBlob(ctx, blob, bic)
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) HasThreadSafeGetBlob() bool {
|
||||
checked := make(map[types.ImageSource]struct{})
|
||||
for _, sourceInstance := range s.sourceInstancesByInstance {
|
||||
if _, checked := checked[sourceInstance]; checked {
|
||||
continue
|
||||
}
|
||||
if !sourceInstance.HasThreadSafeGetBlob() {
|
||||
return false
|
||||
}
|
||||
checked[sourceInstance] = struct{}{}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
var (
|
||||
src types.ImageSource
|
||||
digest digest.Digest
|
||||
)
|
||||
requestInstanceDigest := instanceDigest
|
||||
if instanceDigest == nil {
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
} else {
|
||||
digest = *instanceDigest
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
if *instanceDigest == s.sourceDefaultInstances[src] {
|
||||
requestInstanceDigest = nil
|
||||
}
|
||||
}
|
||||
if src != nil {
|
||||
return src.GetSignatures(ctx, requestInstanceDigest)
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to read signatures", digest)
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
|
||||
var src types.ImageSource
|
||||
requestInstanceDigest := instanceDigest
|
||||
errMsgDigest := ""
|
||||
if instanceDigest == nil {
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
} else {
|
||||
errMsgDigest = string(*instanceDigest)
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
if *instanceDigest == s.sourceDefaultInstances[src] {
|
||||
requestInstanceDigest = nil
|
||||
}
|
||||
}
|
||||
if src != nil {
|
||||
blobInfos, err := src.LayerInfosForCopy(ctx, requestInstanceDigest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading layer infos for copy from instance %q", instanceDigest)
|
||||
}
|
||||
var manifestDigest digest.Digest
|
||||
if instanceDigest != nil {
|
||||
manifestDigest = *instanceDigest
|
||||
}
|
||||
for _, blobInfo := range blobInfos {
|
||||
s.instancesByBlobDigest[blobInfo.Digest] = manifestDigest
|
||||
}
|
||||
return blobInfos, nil
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to copy layers", errMsgDigest)
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
package supplemented
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cp "github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/pkg/blobinfocache/none"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
_ types.ImageReference = &supplementedImageReference{}
|
||||
_ types.ImageSource = &supplementedImageSource{}
|
||||
now = time.Now()
|
||||
)
|
||||
|
||||
func makeLayer(t *testing.T) []byte {
|
||||
var b bytes.Buffer
|
||||
len := 512
|
||||
randomLen := 8
|
||||
tw := tar.NewWriter(&b)
|
||||
assert.Nilf(t, tw.WriteHeader(&tar.Header{
|
||||
Typeflag: tar.TypeReg,
|
||||
Name: "tmpfile",
|
||||
Size: int64(len),
|
||||
Mode: 0644,
|
||||
Uname: "root",
|
||||
Gname: "root",
|
||||
ModTime: time.Now(),
|
||||
}), "error writing in-memory layer")
|
||||
buf := make([]byte, len)
|
||||
n, err := rand.Read(buf[0:randomLen])
|
||||
assert.Nilf(t, err, "error reading a random byte")
|
||||
assert.Equalf(t, randomLen, n, "error reading random content: wrong length")
|
||||
for i := randomLen; i < len; i++ {
|
||||
buf[i] = (buf[i-1] + 1) & 0xff
|
||||
}
|
||||
n, err = tw.Write(buf)
|
||||
assert.Nilf(t, err, "error writing file content")
|
||||
assert.Equalf(t, n, len, "error writing file content: wrong length")
|
||||
assert.Nilf(t, tw.Close(), "error flushing file content")
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func makeConfig(arch, os string, layer []byte) v1.Image {
|
||||
diffID := digest.Canonical.FromBytes(layer)
|
||||
return v1.Image{
|
||||
Created: &now,
|
||||
Architecture: arch,
|
||||
OS: os,
|
||||
Config: v1.ImageConfig{
|
||||
User: "root",
|
||||
Entrypoint: []string{"/tmpfile"},
|
||||
WorkingDir: "/",
|
||||
},
|
||||
RootFS: v1.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []digest.Digest{diffID},
|
||||
},
|
||||
History: []v1.History{{
|
||||
Created: &now,
|
||||
CreatedBy: "shenanigans",
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func makeManifest(layer, config []byte) v1.Manifest {
|
||||
return v1.Manifest{
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
Config: v1.Descriptor{
|
||||
MediaType: v1.MediaTypeImageConfig,
|
||||
Digest: digest.Canonical.FromBytes(config),
|
||||
Size: int64(len(config)),
|
||||
},
|
||||
Layers: []v1.Descriptor{{
|
||||
MediaType: v1.MediaTypeImageLayer,
|
||||
Digest: digest.Canonical.FromBytes(layer),
|
||||
Size: int64(len(layer)),
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func makeImage(t *testing.T, arch, os string) (ref types.ImageReference, dir string, layer, config, manifest []byte) {
|
||||
ctx := context.TODO()
|
||||
|
||||
dir, err := ioutil.TempDir("", "supplemented")
|
||||
assert.Nilf(t, err, "error creating temporary directory")
|
||||
|
||||
layerBytes := makeLayer(t)
|
||||
cb := makeConfig(arch, os, layer)
|
||||
configBytes, err := json.Marshal(&cb)
|
||||
assert.Nilf(t, err, "error encoding image configuration")
|
||||
m := makeManifest(layerBytes, configBytes)
|
||||
manifestBytes, err := json.Marshal(&m)
|
||||
assert.Nilf(t, err, "error encoding image manifest")
|
||||
|
||||
ref, err = alltransports.ParseImageName(fmt.Sprintf("dir:%s", dir))
|
||||
assert.Nilf(t, err, "error parsing reference 'dir:%s'", dir)
|
||||
sys := &types.SystemContext{}
|
||||
dest, err := ref.NewImageDestination(ctx, sys)
|
||||
assert.Nilf(t, err, "error opening 'dir:%s' as an image destination", dir)
|
||||
bi := types.BlobInfo{
|
||||
MediaType: v1.MediaTypeImageLayer,
|
||||
Digest: digest.Canonical.FromBytes(layerBytes),
|
||||
Size: int64(len(layerBytes)),
|
||||
}
|
||||
_, err = dest.PutBlob(ctx, bytes.NewReader(layerBytes), bi, none.NoCache, false)
|
||||
assert.Nilf(t, err, "error storing layer blob to 'dir:%s'", dir)
|
||||
bi = types.BlobInfo{
|
||||
MediaType: v1.MediaTypeImageConfig,
|
||||
Digest: digest.Canonical.FromBytes(configBytes),
|
||||
Size: int64(len(configBytes)),
|
||||
}
|
||||
_, err = dest.PutBlob(ctx, bytes.NewReader(configBytes), bi, none.NoCache, true)
|
||||
assert.Nilf(t, err, "error storing config blob to 'dir:%s'", dir)
|
||||
err = dest.PutManifest(ctx, manifestBytes, nil)
|
||||
assert.Nilf(t, err, "error storing manifest to 'dir:%s'", dir)
|
||||
err = dest.Commit(ctx, nil)
|
||||
assert.Nilf(t, err, "error committing image to 'dir:%s'", dir)
|
||||
|
||||
return ref, dir, layerBytes, configBytes, manifestBytes
|
||||
}
|
||||
|
||||
func TestSupplemented(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
arch2 := "foo"
|
||||
arch3 := "bar"
|
||||
|
||||
sys := &types.SystemContext{
|
||||
SignaturePolicyPath: "../../tests/policy.json",
|
||||
}
|
||||
defaultPolicy, err := signature.DefaultPolicy(sys)
|
||||
assert.Nilf(t, err, "error obtaining default policy")
|
||||
policyContext, err := signature.NewPolicyContext(defaultPolicy)
|
||||
assert.Nilf(t, err, "error obtaining policy context")
|
||||
|
||||
ref1, dir1, layer1, config1, manifest1 := makeImage(t, runtime.GOARCH, runtime.GOOS)
|
||||
defer os.RemoveAll(dir1)
|
||||
digest1, err := manifest.Digest(manifest1)
|
||||
assert.Nilf(t, err, "error digesting manifest")
|
||||
|
||||
ref2, dir2, layer2, config2, manifest2 := makeImage(t, arch2, runtime.GOOS)
|
||||
defer os.RemoveAll(dir2)
|
||||
digest2, err := manifest.Digest(manifest2)
|
||||
assert.Nilf(t, err, "error digesting manifest")
|
||||
|
||||
ref3, dir3, layer3, config3, manifest3 := makeImage(t, arch3, runtime.GOOS)
|
||||
defer os.RemoveAll(dir3)
|
||||
digest3, err := manifest.Digest(manifest3)
|
||||
assert.Nilf(t, err, "error digesting manifest")
|
||||
|
||||
multidir, err := ioutil.TempDir("", "supplemented")
|
||||
assert.Nilf(t, err, "error creating temporary directory")
|
||||
defer os.RemoveAll(multidir)
|
||||
|
||||
destDir, err := ioutil.TempDir("", "supplemented")
|
||||
assert.Nilf(t, err, "error creating temporary directory")
|
||||
defer os.RemoveAll(destDir)
|
||||
|
||||
index := v1.Index{
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
Manifests: []v1.Descriptor{
|
||||
{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Digest: digest1,
|
||||
Size: int64(len(manifest1)),
|
||||
Platform: &v1.Platform{
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
},
|
||||
},
|
||||
{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Digest: digest2,
|
||||
Size: int64(len(manifest2)),
|
||||
Platform: &v1.Platform{
|
||||
Architecture: arch2,
|
||||
OS: runtime.GOOS,
|
||||
},
|
||||
},
|
||||
{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Digest: digest3,
|
||||
Size: int64(len(manifest3)),
|
||||
Platform: &v1.Platform{
|
||||
Architecture: arch3,
|
||||
OS: runtime.GOOS,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
indexBytes, err := json.Marshal(&index)
|
||||
assert.Nilf(t, err, "error encoding image index")
|
||||
indexDigest, err := manifest.Digest(indexBytes)
|
||||
assert.Nilf(t, err, "error digesting image index")
|
||||
|
||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", destDir))
|
||||
assert.Nilf(t, err, "error parsing reference 'dir:%s'", destDir)
|
||||
|
||||
multiRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", multidir))
|
||||
assert.Nilf(t, err, "error parsing reference 'dir:%s'", multidir)
|
||||
destImg, err := multiRef.NewImageDestination(ctx, sys)
|
||||
assert.Nilf(t, err, "error opening 'dir:%s' as an image destination", multidir)
|
||||
err = destImg.PutManifest(ctx, indexBytes, nil)
|
||||
assert.Nilf(t, err, "error storing index to 'dir:%s'", multidir)
|
||||
err = destImg.Commit(ctx, nil)
|
||||
assert.Nilf(t, err, "error committing image to 'dir:%s'", multidir)
|
||||
|
||||
t.Logf("list: digest=%q,value=%s", indexDigest, string(indexBytes))
|
||||
|
||||
_, err = multiRef.NewImage(ctx, sys)
|
||||
assert.NotNilf(t, err, "unexpected success opening image 'dir:%s': shouldn't have been able to read config", multidir)
|
||||
|
||||
src, err := Reference(multiRef, []types.ImageReference{ref1}, cp.CopyAllImages, nil).NewImageSource(ctx, sys)
|
||||
assert.NotNilf(t, err, "unexpected success opening image 'dir:%s': shouldn't have been able to read all manifests", multidir)
|
||||
assert.Nilf(t, src, "unexpected success opening image 'dir:%s': shouldn't have been able to read all manifests", multidir)
|
||||
src, err = Reference(multiRef, []types.ImageReference{ref1}, cp.CopySpecificImages, []digest.Digest{digest1}).NewImageSource(ctx, sys)
|
||||
assert.Nilf(t, err, "error opening image 'dir:%s' with specific instances", multidir)
|
||||
assert.Nilf(t, src.Close(), "error closing image 'dir:%s' with specific instances", multidir)
|
||||
|
||||
img, err := Reference(multiRef, nil, cp.CopySystemImage, nil).NewImage(ctx, sys)
|
||||
assert.NotNilf(t, err, "unexpected success opening image 'dir:%s': shouldn't have been able to read config", multidir)
|
||||
assert.Nilf(t, img, "unexpected success opening image 'dir:%s': shouldn't have been able to read config", multidir)
|
||||
img, err = Reference(multiRef, []types.ImageReference{ref1}, cp.CopySystemImage, []digest.Digest{digest1}).NewImage(ctx, sys)
|
||||
assert.Nilf(t, err, "error opening image %q+%q", transports.ImageName(multiRef), transports.ImageName(ref1))
|
||||
assert.Nilf(t, img.Close(), "error closing image %q+%q", transports.ImageName(multiRef), transports.ImageName(ref1))
|
||||
|
||||
type testCase struct {
|
||||
label string
|
||||
supplements []types.ImageReference
|
||||
expectToFind [][]byte
|
||||
expectToNotFind [][]byte
|
||||
multiple cp.ImageListSelection
|
||||
instances []digest.Digest
|
||||
}
|
||||
|
||||
for _, test := range []testCase{
|
||||
{
|
||||
label: "no supplements, nil instances",
|
||||
supplements: nil,
|
||||
expectToFind: nil,
|
||||
expectToNotFind: [][]byte{layer1, config1, layer2, config2, layer3, config3},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: nil,
|
||||
},
|
||||
{
|
||||
label: "no supplements, 0 instances",
|
||||
supplements: nil,
|
||||
expectToFind: nil,
|
||||
expectToNotFind: [][]byte{layer1, config1, layer2, config2, layer3, config3},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{},
|
||||
},
|
||||
{
|
||||
label: "just ref1 supplementing",
|
||||
supplements: []types.ImageReference{ref1},
|
||||
expectToFind: [][]byte{layer1, config1},
|
||||
expectToNotFind: [][]byte{layer2, config2, layer3, config3},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{digest1},
|
||||
},
|
||||
{
|
||||
label: "just ref2 supplementing",
|
||||
supplements: []types.ImageReference{ref2},
|
||||
expectToFind: [][]byte{layer2, config2},
|
||||
expectToNotFind: [][]byte{layer1, config1, layer3, config3},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{digest2},
|
||||
},
|
||||
{
|
||||
label: "just ref3 supplementing",
|
||||
supplements: []types.ImageReference{ref3},
|
||||
expectToFind: [][]byte{layer3, config3},
|
||||
expectToNotFind: [][]byte{layer1, config1, layer2, config2},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{digest3},
|
||||
},
|
||||
{
|
||||
label: "refs 1 and 2 supplementing",
|
||||
supplements: []types.ImageReference{ref1, ref2},
|
||||
expectToFind: [][]byte{layer1, config1, layer2, config2},
|
||||
expectToNotFind: [][]byte{layer3, config3},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{digest1, digest2},
|
||||
},
|
||||
{
|
||||
label: "refs 2 and 3 supplementing",
|
||||
supplements: []types.ImageReference{ref2, ref3},
|
||||
expectToFind: [][]byte{layer2, config2, layer3, config3},
|
||||
expectToNotFind: [][]byte{layer1, config1},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{digest2, digest3},
|
||||
},
|
||||
{
|
||||
label: "refs 1 and 3 supplementing",
|
||||
supplements: []types.ImageReference{ref1, ref3},
|
||||
expectToFind: [][]byte{layer1, config1, layer3, config3},
|
||||
expectToNotFind: [][]byte{layer2, config2},
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{digest1, digest3},
|
||||
},
|
||||
{
|
||||
label: "all refs supplementing, all instances",
|
||||
supplements: []types.ImageReference{ref1, ref2, ref3},
|
||||
expectToFind: [][]byte{layer1, config1, layer2, config2, layer3, config3},
|
||||
expectToNotFind: nil,
|
||||
multiple: cp.CopySpecificImages,
|
||||
instances: []digest.Digest{digest1, digest2, digest3},
|
||||
},
|
||||
{
|
||||
label: "all refs supplementing, all images",
|
||||
supplements: []types.ImageReference{ref1, ref2, ref3},
|
||||
expectToFind: [][]byte{layer1, config1, layer2, config2, layer3, config3},
|
||||
expectToNotFind: nil,
|
||||
multiple: cp.CopyAllImages,
|
||||
},
|
||||
} {
|
||||
supplemented := Reference(multiRef, test.supplements, test.multiple, test.instances)
|
||||
src, err := supplemented.NewImageSource(ctx, sys)
|
||||
assert.Nilf(t, err, "error opening image source 'dir:%s'[%s]", multidir, test.label)
|
||||
defer src.Close()
|
||||
for i, expect := range test.expectToFind {
|
||||
bi := types.BlobInfo{
|
||||
Digest: digest.Canonical.FromBytes(expect),
|
||||
Size: int64(len(expect)),
|
||||
}
|
||||
rc, _, err := src.GetBlob(ctx, bi, none.NoCache)
|
||||
assert.Nilf(t, err, "error reading blob 'dir:%s'[%s][%d]", multidir, test.label, i)
|
||||
_, err = io.Copy(ioutil.Discard, rc)
|
||||
assert.Nilf(t, err, "error discarding blob 'dir:%s'[%s][%d]", multidir, test.label, i)
|
||||
rc.Close()
|
||||
}
|
||||
for i, expect := range test.expectToNotFind {
|
||||
bi := types.BlobInfo{
|
||||
Digest: digest.Canonical.FromBytes(expect),
|
||||
Size: int64(len(expect)),
|
||||
}
|
||||
_, _, err := src.GetBlob(ctx, bi, none.NoCache)
|
||||
assert.NotNilf(t, err, "unexpected success reading blob 'dir:%s'[%s][%d]", multidir, test.label, i)
|
||||
}
|
||||
options := cp.Options{
|
||||
ImageListSelection: test.multiple,
|
||||
Instances: test.instances,
|
||||
}
|
||||
_, err = cp.Image(ctx, policyContext, destRef, supplemented, &options)
|
||||
assert.Nilf(t, err, "error copying image 'dir:%s'[%s]", multidir, test.label)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"default": [
|
||||
{
|
||||
"type": "insecureAcceptAnything"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Note that changing the order here may break tests.
|
||||
unqualified-search-registries = ['docker.io', 'quay.io', 'registry.fedoraproject.org']
|
||||
|
||||
[[registry]]
|
||||
# In Nov. 2020, Docker rate-limits image pulling. To avoid hitting these
|
||||
# limits while testing, always use the google mirror for qualified and
|
||||
# unqualified `docker.io` images.
|
||||
# Ref: https://cloud.google.com/container-registry/docs/pulling-cached-images
|
||||
prefix="docker.io"
|
||||
location="mirror.gcr.io"
|
||||
|
||||
# 2020-10-27 a number of images are not present in gcr.io, and podman
|
||||
# barfs spectacularly when trying to fetch them. We've hand-copied
|
||||
# those to quay, using skopeo copy --all ...
|
||||
[[registry]]
|
||||
prefix="docker.io/library"
|
||||
location="quay.io/libpod"
|
||||
|
||||
# 2021-03-23 these are used in buildah system tests, but not (yet?)
|
||||
# listed in the global shortnames.conf.
|
||||
[aliases]
|
||||
busybox="docker.io/library/busybox"
|
||||
ubuntu="docker.io/library/ubuntu"
|
||||
php="docker.io/library/php"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// +build !windows
|
||||
// This file only exists to allow go get on non-Windows platforms.
|
||||
|
||||
package backuptar
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package backuptar
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Functions copied from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
|
||||
// as we need to manage the LIBARCHIVE.creationtime PAXRecord manually.
|
||||
// Idea taken from containerd which did the same thing.
|
||||
|
||||
// parsePAXTime takes a string of the form %d.%d as described in the PAX
|
||||
// specification. Note that this implementation allows for negative timestamps,
|
||||
// which is allowed for by the PAX specification, but not always portable.
|
||||
func parsePAXTime(s string) (time.Time, error) {
|
||||
const maxNanoSecondDigits = 9
|
||||
|
||||
// Split string into seconds and sub-seconds parts.
|
||||
ss, sn := s, ""
|
||||
if pos := strings.IndexByte(s, '.'); pos >= 0 {
|
||||
ss, sn = s[:pos], s[pos+1:]
|
||||
}
|
||||
|
||||
// Parse the seconds.
|
||||
secs, err := strconv.ParseInt(ss, 10, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, tar.ErrHeader
|
||||
}
|
||||
if len(sn) == 0 {
|
||||
return time.Unix(secs, 0), nil // No sub-second values
|
||||
}
|
||||
|
||||
// Parse the nanoseconds.
|
||||
if strings.Trim(sn, "0123456789") != "" {
|
||||
return time.Time{}, tar.ErrHeader
|
||||
}
|
||||
if len(sn) < maxNanoSecondDigits {
|
||||
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
|
||||
} else {
|
||||
sn = sn[:maxNanoSecondDigits] // Right truncate
|
||||
}
|
||||
nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
|
||||
if len(ss) > 0 && ss[0] == '-' {
|
||||
return time.Unix(secs, -1*nsecs), nil // Negative correction
|
||||
}
|
||||
return time.Unix(secs, nsecs), nil
|
||||
}
|
||||
|
||||
// formatPAXTime converts ts into a time of the form %d.%d as described in the
|
||||
// PAX specification. This function is capable of negative timestamps.
|
||||
func formatPAXTime(ts time.Time) (s string) {
|
||||
secs, nsecs := ts.Unix(), ts.Nanosecond()
|
||||
if nsecs == 0 {
|
||||
return strconv.FormatInt(secs, 10)
|
||||
}
|
||||
|
||||
// If seconds is negative, then perform correction.
|
||||
sign := ""
|
||||
if secs < 0 {
|
||||
sign = "-" // Remember sign
|
||||
secs = -(secs + 1) // Add a second to secs
|
||||
nsecs = -(nsecs - 1e9) // Take that second away from nsecs
|
||||
}
|
||||
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
|
||||
}
|
||||
|
|
@ -0,0 +1,452 @@
|
|||
// +build windows
|
||||
|
||||
package backuptar
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
c_ISUID = 04000 // Set uid
|
||||
c_ISGID = 02000 // Set gid
|
||||
c_ISVTX = 01000 // Save text (sticky bit)
|
||||
c_ISDIR = 040000 // Directory
|
||||
c_ISFIFO = 010000 // FIFO
|
||||
c_ISREG = 0100000 // Regular file
|
||||
c_ISLNK = 0120000 // Symbolic link
|
||||
c_ISBLK = 060000 // Block special file
|
||||
c_ISCHR = 020000 // Character special file
|
||||
c_ISSOCK = 0140000 // Socket
|
||||
)
|
||||
|
||||
const (
|
||||
hdrFileAttributes = "MSWINDOWS.fileattr"
|
||||
hdrSecurityDescriptor = "MSWINDOWS.sd"
|
||||
hdrRawSecurityDescriptor = "MSWINDOWS.rawsd"
|
||||
hdrMountPoint = "MSWINDOWS.mountpoint"
|
||||
hdrEaPrefix = "MSWINDOWS.xattr."
|
||||
|
||||
hdrCreationTime = "LIBARCHIVE.creationtime"
|
||||
)
|
||||
|
||||
func writeZeroes(w io.Writer, count int64) error {
|
||||
buf := make([]byte, 8192)
|
||||
c := len(buf)
|
||||
for i := int64(0); i < count; i += int64(c) {
|
||||
if int64(c) > count-i {
|
||||
c = int(count - i)
|
||||
}
|
||||
_, err := w.Write(buf[:c])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
|
||||
curOffset := int64(0)
|
||||
for {
|
||||
bhdr, err := br.Next()
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bhdr.Id != winio.BackupSparseBlock {
|
||||
return fmt.Errorf("unexpected stream %d", bhdr.Id)
|
||||
}
|
||||
|
||||
// archive/tar does not support writing sparse files
|
||||
// so just write zeroes to catch up to the current offset.
|
||||
err = writeZeroes(t, bhdr.Offset-curOffset)
|
||||
if bhdr.Size == 0 {
|
||||
break
|
||||
}
|
||||
n, err := io.Copy(t, br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
curOffset = bhdr.Offset + n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BasicInfoHeader creates a tar header from basic file information.
|
||||
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
|
||||
hdr := &tar.Header{
|
||||
Format: tar.FormatPAX,
|
||||
Name: filepath.ToSlash(name),
|
||||
Size: size,
|
||||
Typeflag: tar.TypeReg,
|
||||
ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
|
||||
ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
|
||||
AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
|
||||
PAXRecords: make(map[string]string),
|
||||
}
|
||||
hdr.PAXRecords[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
|
||||
hdr.PAXRecords[hdrCreationTime] = formatPAXTime(time.Unix(0, fileInfo.CreationTime.Nanoseconds()))
|
||||
|
||||
if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
|
||||
hdr.Mode |= c_ISDIR
|
||||
hdr.Size = 0
|
||||
hdr.Typeflag = tar.TypeDir
|
||||
}
|
||||
return hdr
|
||||
}
|
||||
|
||||
// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
|
||||
//
|
||||
// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
|
||||
//
|
||||
// The additional Win32 metadata is:
|
||||
//
|
||||
// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
|
||||
//
|
||||
// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
|
||||
//
|
||||
// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
|
||||
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
|
||||
name = filepath.ToSlash(name)
|
||||
hdr := BasicInfoHeader(name, size, fileInfo)
|
||||
|
||||
// If r can be seeked, then this function is two-pass: pass 1 collects the
|
||||
// tar header data, and pass 2 copies the data stream. If r cannot be
|
||||
// seeked, then some header data (in particular EAs) will be silently lost.
|
||||
var (
|
||||
restartPos int64
|
||||
err error
|
||||
)
|
||||
sr, readTwice := r.(io.Seeker)
|
||||
if readTwice {
|
||||
if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil {
|
||||
readTwice = false
|
||||
}
|
||||
}
|
||||
|
||||
br := winio.NewBackupStreamReader(r)
|
||||
var dataHdr *winio.BackupHeader
|
||||
for dataHdr == nil {
|
||||
bhdr, err := br.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch bhdr.Id {
|
||||
case winio.BackupData:
|
||||
hdr.Mode |= c_ISREG
|
||||
if !readTwice {
|
||||
dataHdr = bhdr
|
||||
}
|
||||
case winio.BackupSecurity:
|
||||
sd, err := ioutil.ReadAll(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
|
||||
|
||||
case winio.BackupReparseData:
|
||||
hdr.Mode |= c_ISLNK
|
||||
hdr.Typeflag = tar.TypeSymlink
|
||||
reparseBuffer, err := ioutil.ReadAll(br)
|
||||
rp, err := winio.DecodeReparsePoint(reparseBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rp.IsMountPoint {
|
||||
hdr.PAXRecords[hdrMountPoint] = "1"
|
||||
}
|
||||
hdr.Linkname = rp.Target
|
||||
|
||||
case winio.BackupEaData:
|
||||
eab, err := ioutil.ReadAll(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eas, err := winio.DecodeExtendedAttributes(eab)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ea := range eas {
|
||||
// Use base64 encoding for the binary value. Note that there
|
||||
// is no way to encode the EA's flags, since their use doesn't
|
||||
// make any sense for persisted EAs.
|
||||
hdr.PAXRecords[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value)
|
||||
}
|
||||
|
||||
case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||
// ignore these streams
|
||||
default:
|
||||
return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
|
||||
}
|
||||
}
|
||||
|
||||
err = t.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if readTwice {
|
||||
// Get back to the data stream.
|
||||
if _, err = sr.Seek(restartPos, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
for dataHdr == nil {
|
||||
bhdr, err := br.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bhdr.Id == winio.BackupData {
|
||||
dataHdr = bhdr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dataHdr != nil {
|
||||
// A data stream was found. Copy the data.
|
||||
if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||
if size != dataHdr.Size {
|
||||
return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
|
||||
}
|
||||
_, err = io.Copy(t, br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = copySparse(t, br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for streams after the data stream. The only ones we handle are alternate data streams.
|
||||
// Other streams may have metadata that could be serialized, but the tar header has already
|
||||
// been written. In practice, this means that we don't get EA or TXF metadata.
|
||||
for {
|
||||
bhdr, err := br.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch bhdr.Id {
|
||||
case winio.BackupAlternateData:
|
||||
altName := bhdr.Name
|
||||
if strings.HasSuffix(altName, ":$DATA") {
|
||||
altName = altName[:len(altName)-len(":$DATA")]
|
||||
}
|
||||
if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||
hdr = &tar.Header{
|
||||
Format: hdr.Format,
|
||||
Name: name + altName,
|
||||
Mode: hdr.Mode,
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: bhdr.Size,
|
||||
ModTime: hdr.ModTime,
|
||||
AccessTime: hdr.AccessTime,
|
||||
ChangeTime: hdr.ChangeTime,
|
||||
}
|
||||
err = t.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(t, br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
// Unsupported for now, since the size of the alternate stream is not present
|
||||
// in the backup stream until after the data has been read.
|
||||
return errors.New("tar of sparse alternate data streams is unsupported")
|
||||
}
|
||||
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||
// ignore these streams
|
||||
default:
|
||||
return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
|
||||
// WriteTarFileFromBackupStream.
|
||||
func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
|
||||
name = hdr.Name
|
||||
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||
size = hdr.Size
|
||||
}
|
||||
fileInfo = &winio.FileBasicInfo{
|
||||
LastAccessTime: windows.NsecToFiletime(hdr.AccessTime.UnixNano()),
|
||||
LastWriteTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||
ChangeTime: windows.NsecToFiletime(hdr.ChangeTime.UnixNano()),
|
||||
// Default to ModTime, we'll pull hdrCreationTime below if present
|
||||
CreationTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||
}
|
||||
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
|
||||
attr, err := strconv.ParseUint(attrStr, 10, 32)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
}
|
||||
fileInfo.FileAttributes = uint32(attr)
|
||||
} else {
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
|
||||
}
|
||||
}
|
||||
if creationTimeStr, ok := hdr.PAXRecords[hdrCreationTime]; ok {
|
||||
creationTime, err := parsePAXTime(creationTimeStr)
|
||||
if err != nil {
|
||||
return "", 0, nil, err
|
||||
}
|
||||
fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
|
||||
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
|
||||
// tar file that was not processed, or io.EOF is there are no more.
|
||||
func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
|
||||
bw := winio.NewBackupStreamWriter(w)
|
||||
var sd []byte
|
||||
var err error
|
||||
// Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
|
||||
// by this library will have raw binary for the security descriptor.
|
||||
if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
|
||||
sd, err = winio.SddlToSecurityDescriptor(sddl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
|
||||
sd, err = base64.StdEncoding.DecodeString(sdraw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(sd) != 0 {
|
||||
bhdr := winio.BackupHeader{
|
||||
Id: winio.BackupSecurity,
|
||||
Size: int64(len(sd)),
|
||||
}
|
||||
err := bw.WriteHeader(&bhdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = bw.Write(sd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var eas []winio.ExtendedAttribute
|
||||
for k, v := range hdr.PAXRecords {
|
||||
if !strings.HasPrefix(k, hdrEaPrefix) {
|
||||
continue
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eas = append(eas, winio.ExtendedAttribute{
|
||||
Name: k[len(hdrEaPrefix):],
|
||||
Value: data,
|
||||
})
|
||||
}
|
||||
if len(eas) != 0 {
|
||||
eadata, err := winio.EncodeExtendedAttributes(eas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bhdr := winio.BackupHeader{
|
||||
Id: winio.BackupEaData,
|
||||
Size: int64(len(eadata)),
|
||||
}
|
||||
err = bw.WriteHeader(&bhdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = bw.Write(eadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hdr.Typeflag == tar.TypeSymlink {
|
||||
_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
|
||||
rp := winio.ReparsePoint{
|
||||
Target: filepath.FromSlash(hdr.Linkname),
|
||||
IsMountPoint: isMountPoint,
|
||||
}
|
||||
reparse := winio.EncodeReparsePoint(&rp)
|
||||
bhdr := winio.BackupHeader{
|
||||
Id: winio.BackupReparseData,
|
||||
Size: int64(len(reparse)),
|
||||
}
|
||||
err := bw.WriteHeader(&bhdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = bw.Write(reparse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||
bhdr := winio.BackupHeader{
|
||||
Id: winio.BackupData,
|
||||
Size: hdr.Size,
|
||||
}
|
||||
err := bw.WriteHeader(&bhdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(bw, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Copy all the alternate data streams and return the next non-ADS header.
|
||||
for {
|
||||
ahdr, err := t.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
|
||||
return ahdr, nil
|
||||
}
|
||||
bhdr := winio.BackupHeader{
|
||||
Id: winio.BackupAlternateData,
|
||||
Size: ahdr.Size,
|
||||
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
|
||||
}
|
||||
err = bw.WriteHeader(&bhdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(bw, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
161
common/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go
generated
vendored
Normal file
161
common/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go
generated
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// +build windows
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
accessMask uint32
|
||||
accessMode uint32
|
||||
desiredAccess uint32
|
||||
inheritMode uint32
|
||||
objectType uint32
|
||||
shareMode uint32
|
||||
securityInformation uint32
|
||||
trusteeForm uint32
|
||||
trusteeType uint32
|
||||
|
||||
explicitAccess struct {
|
||||
accessPermissions accessMask
|
||||
accessMode accessMode
|
||||
inheritance inheritMode
|
||||
trustee trustee
|
||||
}
|
||||
|
||||
trustee struct {
|
||||
multipleTrustee *trustee
|
||||
multipleTrusteeOperation int32
|
||||
trusteeForm trusteeForm
|
||||
trusteeType trusteeType
|
||||
name uintptr
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ
|
||||
|
||||
accessModeGrant accessMode = 1
|
||||
|
||||
desiredAccessReadControl desiredAccess = 0x20000
|
||||
desiredAccessWriteDac desiredAccess = 0x40000
|
||||
|
||||
gvmga = "GrantVmGroupAccess:"
|
||||
|
||||
inheritModeNoInheritance inheritMode = 0x0
|
||||
inheritModeSubContainersAndObjectsInherit inheritMode = 0x3
|
||||
|
||||
objectTypeFileObject objectType = 0x1
|
||||
|
||||
securityInformationDACL securityInformation = 0x4
|
||||
|
||||
shareModeRead shareMode = 0x1
|
||||
shareModeWrite shareMode = 0x2
|
||||
|
||||
sidVmGroup = "S-1-5-83-0"
|
||||
|
||||
trusteeFormIsSid trusteeForm = 0
|
||||
|
||||
trusteeTypeWellKnownGroup trusteeType = 5
|
||||
)
|
||||
|
||||
// GrantVMGroupAccess sets the DACL for a specified file or directory to
|
||||
// include Grant ACE entries for the VM Group SID. This is a golang re-
|
||||
// implementation of the same function in vmcompute, just not exported in
|
||||
// RS5. Which kind of sucks. Sucks a lot :/
|
||||
func GrantVmGroupAccess(name string) error {
|
||||
// Stat (to determine if `name` is a directory).
|
||||
s, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s os.Stat %s", gvmga, name)
|
||||
}
|
||||
|
||||
// Get a handle to the file/directory. Must defer Close on success.
|
||||
fd, err := createFile(name, s.IsDir())
|
||||
if err != nil {
|
||||
return err // Already wrapped
|
||||
}
|
||||
defer syscall.CloseHandle(fd)
|
||||
|
||||
// Get the current DACL and Security Descriptor. Must defer LocalFree on success.
|
||||
ot := objectTypeFileObject
|
||||
si := securityInformationDACL
|
||||
sd := uintptr(0)
|
||||
origDACL := uintptr(0)
|
||||
if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil {
|
||||
return errors.Wrapf(err, "%s GetSecurityInfo %s", gvmga, name)
|
||||
}
|
||||
defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd)))
|
||||
|
||||
// Generate a new DACL which is the current DACL with the required ACEs added.
|
||||
// Must defer LocalFree on success.
|
||||
newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL)
|
||||
if err != nil {
|
||||
return err // Already wrapped
|
||||
}
|
||||
defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL)))
|
||||
|
||||
// And finally use SetSecurityInfo to apply the updated DACL.
|
||||
if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil {
|
||||
return errors.Wrapf(err, "%s SetSecurityInfo %s", gvmga, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createFile is a helper function to call [Nt]CreateFile to get a handle to
|
||||
// the file or directory.
|
||||
func createFile(name string, isDir bool) (syscall.Handle, error) {
|
||||
namep := syscall.StringToUTF16(name)
|
||||
da := uint32(desiredAccessReadControl | desiredAccessWriteDac)
|
||||
sm := uint32(shareModeRead | shareModeWrite)
|
||||
fa := uint32(syscall.FILE_ATTRIBUTE_NORMAL)
|
||||
if isDir {
|
||||
fa = uint32(fa | syscall.FILE_FLAG_BACKUP_SEMANTICS)
|
||||
}
|
||||
fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "%s syscall.CreateFile %s", gvmga, name)
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added.
|
||||
// The caller is responsible for LocalFree of the returned DACL on success.
|
||||
func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) {
|
||||
// Generate pointers to the SIDs based on the string SIDs
|
||||
sid, err := syscall.StringToSid(sidVmGroup)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "%s syscall.StringToSid %s %s", gvmga, name, sidVmGroup)
|
||||
}
|
||||
|
||||
inheritance := inheritModeNoInheritance
|
||||
if isDir {
|
||||
inheritance = inheritModeSubContainersAndObjectsInherit
|
||||
}
|
||||
|
||||
eaArray := []explicitAccess{
|
||||
explicitAccess{
|
||||
accessPermissions: accessMaskDesiredPermission,
|
||||
accessMode: accessModeGrant,
|
||||
inheritance: inheritance,
|
||||
trustee: trustee{
|
||||
trusteeForm: trusteeFormIsSid,
|
||||
trusteeType: trusteeTypeWellKnownGroup,
|
||||
name: uintptr(unsafe.Pointer(sid)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
modifiedDACL := uintptr(0)
|
||||
if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil {
|
||||
return 0, errors.Wrapf(err, "%s SetEntriesInAcl %s", gvmga, name)
|
||||
}
|
||||
|
||||
return modifiedDACL, nil
|
||||
}
|
||||
7
common/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go
generated
vendored
Normal file
7
common/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package security
|
||||
|
||||
//go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
|
||||
|
||||
//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) [failretval!=0] = advapi32.GetSecurityInfo
|
||||
//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) [failretval!=0] = advapi32.SetSecurityInfo
|
||||
//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) [failretval!=0] = advapi32.SetEntriesInAclW
|
||||
70
common/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go
generated
vendored
Normal file
70
common/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
|
||||
procGetSecurityInfo = modadvapi32.NewProc("GetSecurityInfo")
|
||||
procSetEntriesInAclW = modadvapi32.NewProc("SetEntriesInAclW")
|
||||
procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo")
|
||||
)
|
||||
|
||||
func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0)
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0)
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0)
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
// +build windows
|
||||
|
||||
package vhd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/go-winio/pkg/guid"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//go:generate go run mksyscall_windows.go -output zvhd_windows.go vhd.go
|
||||
|
||||
//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.CreateVirtualDisk
|
||||
//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) [failretval != 0] = virtdisk.OpenVirtualDisk
|
||||
//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) [failretval != 0] = virtdisk.AttachVirtualDisk
|
||||
//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = virtdisk.DetachVirtualDisk
|
||||
//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) [failretval != 0] = virtdisk.GetVirtualDiskPhysicalPath
|
||||
|
||||
type (
|
||||
CreateVirtualDiskFlag uint32
|
||||
VirtualDiskFlag uint32
|
||||
AttachVirtualDiskFlag uint32
|
||||
DetachVirtualDiskFlag uint32
|
||||
VirtualDiskAccessMask uint32
|
||||
)
|
||||
|
||||
type VirtualStorageType struct {
|
||||
DeviceID uint32
|
||||
VendorID guid.GUID
|
||||
}
|
||||
|
||||
type CreateVersion2 struct {
|
||||
UniqueID guid.GUID
|
||||
MaximumSize uint64
|
||||
BlockSizeInBytes uint32
|
||||
SectorSizeInBytes uint32
|
||||
PhysicalSectorSizeInByte uint32
|
||||
ParentPath *uint16 // string
|
||||
SourcePath *uint16 // string
|
||||
OpenFlags uint32
|
||||
ParentVirtualStorageType VirtualStorageType
|
||||
SourceVirtualStorageType VirtualStorageType
|
||||
ResiliencyGUID guid.GUID
|
||||
}
|
||||
|
||||
type CreateVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 CreateVersion2
|
||||
}
|
||||
|
||||
type OpenVersion2 struct {
|
||||
GetInfoOnly bool
|
||||
ReadOnly bool
|
||||
ResiliencyGUID guid.GUID
|
||||
}
|
||||
|
||||
type OpenVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 OpenVersion2
|
||||
}
|
||||
|
||||
type AttachVersion2 struct {
|
||||
RestrictedOffset uint64
|
||||
RestrictedLength uint64
|
||||
}
|
||||
|
||||
type AttachVirtualDiskParameters struct {
|
||||
Version uint32 // Must always be set to 2
|
||||
Version2 AttachVersion2
|
||||
}
|
||||
|
||||
const (
|
||||
VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3
|
||||
|
||||
// Access Mask for opening a VHD
|
||||
VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000
|
||||
VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000
|
||||
VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000
|
||||
VirtualDiskAccessDetach VirtualDiskAccessMask = 0x00040000
|
||||
VirtualDiskAccessGetInfo VirtualDiskAccessMask = 0x00080000
|
||||
VirtualDiskAccessCreate VirtualDiskAccessMask = 0x00100000
|
||||
VirtualDiskAccessMetaOps VirtualDiskAccessMask = 0x00200000
|
||||
VirtualDiskAccessRead VirtualDiskAccessMask = 0x000d0000
|
||||
VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000
|
||||
VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000
|
||||
|
||||
// Flags for creating a VHD
|
||||
CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0
|
||||
CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1
|
||||
CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2
|
||||
CreateVirtualDiskFlagDoNotCopyMetadataFromParent CreateVirtualDiskFlag = 0x4
|
||||
CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8
|
||||
CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10
|
||||
CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20
|
||||
CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40
|
||||
CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80
|
||||
CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100
|
||||
CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200
|
||||
|
||||
// Flags for opening a VHD
|
||||
OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000
|
||||
OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001
|
||||
OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002
|
||||
OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x00000004
|
||||
OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x00000008
|
||||
OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x00000010
|
||||
OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x00000020
|
||||
OpenVirtualDiskFlagVhdsetFileOnly VirtualDiskFlag = 0x00000040
|
||||
OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x00000080
|
||||
OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100
|
||||
OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200
|
||||
|
||||
// Flags for attaching a VHD
|
||||
AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000
|
||||
AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001
|
||||
AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002
|
||||
AttachVirtualDiskFlagPermanentLifetime AttachVirtualDiskFlag = 0x00000004
|
||||
AttachVirtualDiskFlagNoLocalHost AttachVirtualDiskFlag = 0x00000008
|
||||
AttachVirtualDiskFlagNoSecurityDescriptor AttachVirtualDiskFlag = 0x00000010
|
||||
AttachVirtualDiskFlagBypassDefaultEncryptionPolicy AttachVirtualDiskFlag = 0x00000020
|
||||
AttachVirtualDiskFlagNonPnp AttachVirtualDiskFlag = 0x00000040
|
||||
AttachVirtualDiskFlagRestrictedRange AttachVirtualDiskFlag = 0x00000080
|
||||
AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100
|
||||
AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200
|
||||
|
||||
// Flags for detaching a VHD
|
||||
DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0
|
||||
)
|
||||
|
||||
// CreateVhdx is a helper function to create a simple vhdx file at the given path using
|
||||
// default values.
|
||||
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
|
||||
params := CreateVirtualDiskParameters{
|
||||
Version: 2,
|
||||
Version2: CreateVersion2{
|
||||
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
|
||||
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
|
||||
},
|
||||
}
|
||||
|
||||
handle, err := CreateVirtualDisk(path, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, ¶ms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syscall.CloseHandle(handle); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetachVirtualDisk detaches a virtual hard disk by handle.
|
||||
func DetachVirtualDisk(handle syscall.Handle) (err error) {
|
||||
if err := detachVirtualDisk(handle, 0, 0); err != nil {
|
||||
return errors.Wrap(err, "failed to detach virtual disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetachVhd detaches a vhd found at `path`.
|
||||
func DetachVhd(path string) error {
|
||||
handle, err := OpenVirtualDisk(
|
||||
path,
|
||||
VirtualDiskAccessNone,
|
||||
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.CloseHandle(handle)
|
||||
return DetachVirtualDisk(handle)
|
||||
}
|
||||
|
||||
// AttachVirtualDisk attaches a virtual hard disk for use.
|
||||
func AttachVirtualDisk(handle syscall.Handle, attachVirtualDiskFlag AttachVirtualDiskFlag, parameters *AttachVirtualDiskParameters) (err error) {
|
||||
// Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5.
|
||||
if err := attachVirtualDisk(
|
||||
handle,
|
||||
nil,
|
||||
uint32(attachVirtualDiskFlag),
|
||||
0,
|
||||
parameters,
|
||||
nil,
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "failed to attach virtual disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachVhd attaches a virtual hard disk at `path` for use. Attaches using version 2
|
||||
// of the ATTACH_VIRTUAL_DISK_PARAMETERS.
|
||||
func AttachVhd(path string) (err error) {
|
||||
handle, err := OpenVirtualDisk(
|
||||
path,
|
||||
VirtualDiskAccessNone,
|
||||
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer syscall.CloseHandle(handle)
|
||||
params := AttachVirtualDiskParameters{Version: 2}
|
||||
if err := AttachVirtualDisk(
|
||||
handle,
|
||||
AttachVirtualDiskFlagNone,
|
||||
¶ms,
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "failed to attach virtual disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
|
||||
func OpenVirtualDisk(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag) (syscall.Handle, error) {
|
||||
parameters := OpenVirtualDiskParameters{Version: 2}
|
||||
handle, err := OpenVirtualDiskWithParameters(
|
||||
vhdPath,
|
||||
virtualDiskAccessMask,
|
||||
openVirtualDiskFlags,
|
||||
¶meters,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// OpenVirtualDiskWithParameters obtains a handle to a VHD opened with supplied access mask, flags and parameters.
|
||||
func OpenVirtualDiskWithParameters(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag, parameters *OpenVirtualDiskParameters) (syscall.Handle, error) {
|
||||
var (
|
||||
handle syscall.Handle
|
||||
defaultType VirtualStorageType
|
||||
)
|
||||
if parameters.Version != 2 {
|
||||
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
|
||||
}
|
||||
if err := openVirtualDisk(
|
||||
&defaultType,
|
||||
vhdPath,
|
||||
uint32(virtualDiskAccessMask),
|
||||
uint32(openVirtualDiskFlags),
|
||||
parameters,
|
||||
&handle,
|
||||
); err != nil {
|
||||
return 0, errors.Wrap(err, "failed to open virtual disk")
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// CreateVirtualDisk creates a virtual harddisk and returns a handle to the disk.
|
||||
func CreateVirtualDisk(path string, virtualDiskAccessMask VirtualDiskAccessMask, createVirtualDiskFlags CreateVirtualDiskFlag, parameters *CreateVirtualDiskParameters) (syscall.Handle, error) {
|
||||
var (
|
||||
handle syscall.Handle
|
||||
defaultType VirtualStorageType
|
||||
)
|
||||
if parameters.Version != 2 {
|
||||
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
|
||||
}
|
||||
|
||||
if err := createVirtualDisk(
|
||||
&defaultType,
|
||||
path,
|
||||
uint32(virtualDiskAccessMask),
|
||||
nil,
|
||||
uint32(createVirtualDiskFlags),
|
||||
0,
|
||||
parameters,
|
||||
nil,
|
||||
&handle,
|
||||
); err != nil {
|
||||
return handle, errors.Wrap(err, "failed to create virtual disk")
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// GetVirtualDiskPhysicalPath takes a handle to a virtual hard disk and returns the physical
|
||||
// path of the disk on the machine. This path is in the form \\.\PhysicalDriveX where X is an integer
|
||||
// that represents the particular enumeration of the physical disk on the caller's system.
|
||||
func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) {
|
||||
var (
|
||||
diskPathSizeInBytes uint32 = 256 * 2 // max path length 256 wide chars
|
||||
diskPhysicalPathBuf [256]uint16
|
||||
)
|
||||
if err := getVirtualDiskPhysicalPath(
|
||||
handle,
|
||||
&diskPathSizeInBytes,
|
||||
&diskPhysicalPathBuf[0],
|
||||
); err != nil {
|
||||
return "", errors.Wrap(err, "failed to get disk physical path")
|
||||
}
|
||||
return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil
|
||||
}
|
||||
|
||||
// CreateDiffVhd is a helper function to create a differencing virtual disk.
|
||||
func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error {
|
||||
// Setting `ParentPath` is how to signal to create a differencing disk.
|
||||
createParams := &CreateVirtualDiskParameters{
|
||||
Version: 2,
|
||||
Version2: CreateVersion2{
|
||||
ParentPath: windows.StringToUTF16Ptr(baseVhdPath),
|
||||
BlockSizeInBytes: blockSizeInMB * 1024 * 1024,
|
||||
OpenFlags: uint32(OpenVirtualDiskFlagCachedIO),
|
||||
},
|
||||
}
|
||||
|
||||
vhdHandle, err := CreateVirtualDisk(
|
||||
diffVhdPath,
|
||||
VirtualDiskAccessNone,
|
||||
CreateVirtualDiskFlagNone,
|
||||
createParams,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create differencing vhd: %s", err)
|
||||
}
|
||||
if err := syscall.CloseHandle(vhdHandle); err != nil {
|
||||
return fmt.Errorf("failed to close differencing vhd handle: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package vhd
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modvirtdisk = windows.NewLazySystemDLL("virtdisk.dll")
|
||||
|
||||
procAttachVirtualDisk = modvirtdisk.NewProc("AttachVirtualDisk")
|
||||
procCreateVirtualDisk = modvirtdisk.NewProc("CreateVirtualDisk")
|
||||
procDetachVirtualDisk = modvirtdisk.NewProc("DetachVirtualDisk")
|
||||
procGetVirtualDiskPhysicalPath = modvirtdisk.NewProc("GetVirtualDiskPhysicalPath")
|
||||
procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk")
|
||||
)
|
||||
|
||||
func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procAttachVirtualDisk.Addr(), 6, uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle)
|
||||
}
|
||||
|
||||
func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetVirtualDiskPhysicalPath.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle)
|
||||
}
|
||||
|
||||
func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *OpenVirtualDiskParameters, handle *syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
*.exe
|
||||
.idea
|
||||
.vscode
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
* @microsoft/containerplat
|
||||
|
||||
/hcn/* @nagiesek
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Microsoft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
version = "unstable"
|
||||
generator = "gogoctrd"
|
||||
plugins = ["grpc", "fieldpath"]
|
||||
|
||||
# Control protoc include paths. Below are usually some good defaults, but feel
|
||||
# free to try it without them if it works for your project.
|
||||
[includes]
|
||||
# Include paths that will be added before all others. Typically, you want to
|
||||
# treat the root of the project as an include, but this may not be necessary.
|
||||
before = ["./protobuf"]
|
||||
|
||||
# Paths that should be treated as include roots in relation to the vendor
|
||||
# directory. These will be calculated with the vendor directory nearest the
|
||||
# target package.
|
||||
packages = ["github.com/gogo/protobuf"]
|
||||
|
||||
# Paths that will be added untouched to the end of the includes. We use
|
||||
# `/usr/local/include` to pickup the common install location of protobuf.
|
||||
# This is the default.
|
||||
after = ["/usr/local/include"]
|
||||
|
||||
# This section maps protobuf imports to Go packages. These will become
|
||||
# `-M` directives in the call to the go protobuf generator.
|
||||
[packages]
|
||||
"gogoproto/gogo.proto" = "github.com/gogo/protobuf/gogoproto"
|
||||
"google/protobuf/any.proto" = "github.com/gogo/protobuf/types"
|
||||
"google/protobuf/empty.proto" = "github.com/gogo/protobuf/types"
|
||||
"google/protobuf/descriptor.proto" = "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
|
||||
"google/protobuf/field_mask.proto" = "github.com/gogo/protobuf/types"
|
||||
"google/protobuf/timestamp.proto" = "github.com/gogo/protobuf/types"
|
||||
"google/protobuf/duration.proto" = "github.com/gogo/protobuf/types"
|
||||
"github/containerd/cgroups/stats/v1/metrics.proto" = "github.com/containerd/cgroups/stats/v1"
|
||||
|
||||
[[overrides]]
|
||||
prefixes = ["github.com/Microsoft/hcsshim/internal/shimdiag"]
|
||||
plugins = ["ttrpc"]
|
||||
|
||||
[[overrides]]
|
||||
prefixes = ["github.com/Microsoft/hcsshim/internal/computeagent"]
|
||||
plugins = ["ttrpc"]
|
||||
|
||||
[[overrides]]
|
||||
prefixes = ["github.com/Microsoft/hcsshim/internal/ncproxyttrpc"]
|
||||
plugins = ["ttrpc"]
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# hcsshim
|
||||
|
||||
[](https://github.com/microsoft/hcsshim/actions?query=branch%3Amaster)
|
||||
|
||||
This package contains the Golang interface for using the Windows [Host Compute Service](https://techcommunity.microsoft.com/t5/containers/introducing-the-host-compute-service-hcs/ba-p/382332) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS).
|
||||
|
||||
It is primarily used in the [Moby Project](https://github.com/moby/moby), but it can be freely used by other projects as well.
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
We also ask that contributors [sign their commits](https://git-scm.com/docs/git-commit) using `git commit -s` or `git commit --signoff` to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This project requires Golang 1.9 or newer to build.
|
||||
|
||||
For system requirements to run this project, see the Microsoft docs on [Windows Container requirements](https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/system-requirements).
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
Security issues and bugs should be reported privately, via email, to the Microsoft Security
|
||||
Response Center (MSRC) at [secure@microsoft.com](mailto:secure@microsoft.com). You should
|
||||
receive a response within 24 hours. If for some reason you do not, please follow up via
|
||||
email to ensure we received your original message. Further information, including the
|
||||
[MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in
|
||||
the [Security TechCenter](https://technet.microsoft.com/en-us/security/default).
|
||||
|
||||
For additional details, see [Report a Computer Security Vulnerability](https://technet.microsoft.com/en-us/security/ff852094.aspx) on Technet
|
||||
|
||||
---------------
|
||||
Copyright (c) 2018 Microsoft Corp. All rights reserved.
|
||||
38
common/vendor/github.com/Microsoft/hcsshim/computestorage/attach.go
generated
vendored
Normal file
38
common/vendor/github.com/Microsoft/hcsshim/computestorage/attach.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// AttachLayerStorageFilter sets up the layer storage filter on a writable
|
||||
// container layer.
|
||||
//
|
||||
// `layerPath` is a path to a directory the writable layer is mounted. If the
|
||||
// path does not end in a `\` the platform will append it automatically.
|
||||
//
|
||||
// `layerData` is the parent read-only layer data.
|
||||
func AttachLayerStorageFilter(ctx context.Context, layerPath string, layerData LayerData) (err error) {
|
||||
title := "hcsshim.AttachLayerStorageFilter"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("layerPath", layerPath),
|
||||
)
|
||||
|
||||
bytes, err := json.Marshal(layerData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = hcsAttachLayerStorageFilter(layerPath, string(bytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to attach layer storage filter")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
26
common/vendor/github.com/Microsoft/hcsshim/computestorage/destroy.go
generated
vendored
Normal file
26
common/vendor/github.com/Microsoft/hcsshim/computestorage/destroy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// DestroyLayer deletes a container layer.
|
||||
//
|
||||
// `layerPath` is a path to a directory containing the layer to export.
|
||||
func DestroyLayer(ctx context.Context, layerPath string) (err error) {
|
||||
title := "hcsshim.DestroyLayer"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("layerPath", layerPath))
|
||||
|
||||
err = hcsDestroyLayer(layerPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to destroy layer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
26
common/vendor/github.com/Microsoft/hcsshim/computestorage/detach.go
generated
vendored
Normal file
26
common/vendor/github.com/Microsoft/hcsshim/computestorage/detach.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// DetachLayerStorageFilter detaches the layer storage filter on a writable container layer.
|
||||
//
|
||||
// `layerPath` is a path to a directory containing the layer to export.
|
||||
func DetachLayerStorageFilter(ctx context.Context, layerPath string) (err error) {
|
||||
title := "hcsshim.DetachLayerStorageFilter"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("layerPath", layerPath))
|
||||
|
||||
err = hcsDetachLayerStorageFilter(layerPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to detach layer storage filter")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
46
common/vendor/github.com/Microsoft/hcsshim/computestorage/export.go
generated
vendored
Normal file
46
common/vendor/github.com/Microsoft/hcsshim/computestorage/export.go
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ExportLayer exports a container layer.
|
||||
//
|
||||
// `layerPath` is a path to a directory containing the layer to export.
|
||||
//
|
||||
// `exportFolderPath` is a pre-existing folder to export the layer to.
|
||||
//
|
||||
// `layerData` is the parent layer data.
|
||||
//
|
||||
// `options` are the export options applied to the exported layer.
|
||||
func ExportLayer(ctx context.Context, layerPath, exportFolderPath string, layerData LayerData, options ExportLayerOptions) (err error) {
|
||||
title := "hcsshim.ExportLayer"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("layerPath", layerPath),
|
||||
trace.StringAttribute("exportFolderPath", exportFolderPath),
|
||||
)
|
||||
|
||||
ldbytes, err := json.Marshal(layerData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obytes, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = hcsExportLayer(layerPath, exportFolderPath, string(ldbytes), string(obytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to export layer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
26
common/vendor/github.com/Microsoft/hcsshim/computestorage/format.go
generated
vendored
Normal file
26
common/vendor/github.com/Microsoft/hcsshim/computestorage/format.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// FormatWritableLayerVhd formats a virtual disk for use as a writable container layer.
|
||||
//
|
||||
// If the VHD is not mounted it will be temporarily mounted.
|
||||
func FormatWritableLayerVhd(ctx context.Context, vhdHandle windows.Handle) (err error) {
|
||||
title := "hcsshim.FormatWritableLayerVhd"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
|
||||
err = hcsFormatWritableLayerVhd(vhdHandle)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to format writable layer vhd")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
193
common/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go
generated
vendored
Normal file
193
common/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/go-winio/pkg/security"
|
||||
"github.com/Microsoft/go-winio/vhd"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const defaultVHDXBlockSizeInMB = 1
|
||||
|
||||
// SetupContainerBaseLayer is a helper to setup a containers scratch. It
|
||||
// will create and format the vhdx's inside and the size is configurable with the sizeInGB
|
||||
// parameter.
|
||||
//
|
||||
// `layerPath` is the path to the base container layer on disk.
|
||||
//
|
||||
// `baseVhdPath` is the path to where the base vhdx for the base layer should be created.
|
||||
//
|
||||
// `diffVhdPath` is the path where the differencing disk for the base layer should be created.
|
||||
//
|
||||
// `sizeInGB` is the size in gigabytes to make the base vhdx.
|
||||
func SetupContainerBaseLayer(ctx context.Context, layerPath, baseVhdPath, diffVhdPath string, sizeInGB uint64) (err error) {
|
||||
var (
|
||||
hivesPath = filepath.Join(layerPath, "Hives")
|
||||
layoutPath = filepath.Join(layerPath, "Layout")
|
||||
)
|
||||
|
||||
// We need to remove the hives directory and layout file as `SetupBaseOSLayer` fails if these files
|
||||
// already exist. `SetupBaseOSLayer` will create these files internally. We also remove the base and
|
||||
// differencing disks if they exist in case we're asking for a different size.
|
||||
if _, err := os.Stat(hivesPath); err == nil {
|
||||
if err := os.RemoveAll(hivesPath); err != nil {
|
||||
return errors.Wrap(err, "failed to remove prexisting hives directory")
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(layoutPath); err == nil {
|
||||
if err := os.RemoveAll(layoutPath); err != nil {
|
||||
return errors.Wrap(err, "failed to remove prexisting layout file")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(baseVhdPath); err == nil {
|
||||
if err := os.RemoveAll(baseVhdPath); err != nil {
|
||||
return errors.Wrap(err, "failed to remove base vhdx path")
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(diffVhdPath); err == nil {
|
||||
if err := os.RemoveAll(diffVhdPath); err != nil {
|
||||
return errors.Wrap(err, "failed to remove differencing vhdx")
|
||||
}
|
||||
}
|
||||
|
||||
createParams := &vhd.CreateVirtualDiskParameters{
|
||||
Version: 2,
|
||||
Version2: vhd.CreateVersion2{
|
||||
MaximumSize: sizeInGB * 1024 * 1024 * 1024,
|
||||
BlockSizeInBytes: defaultVHDXBlockSizeInMB * 1024 * 1024,
|
||||
},
|
||||
}
|
||||
handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create vhdx")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = syscall.CloseHandle(handle)
|
||||
os.RemoveAll(baseVhdPath)
|
||||
os.RemoveAll(diffVhdPath)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = FormatWritableLayerVhd(ctx, windows.Handle(handle)); err != nil {
|
||||
return err
|
||||
}
|
||||
// Base vhd handle must be closed before calling SetupBaseLayer in case of Container layer
|
||||
if err = syscall.CloseHandle(handle); err != nil {
|
||||
return errors.Wrap(err, "failed to close vhdx handle")
|
||||
}
|
||||
|
||||
options := OsLayerOptions{
|
||||
Type: OsLayerTypeContainer,
|
||||
}
|
||||
|
||||
// SetupBaseOSLayer expects an empty vhd handle for a container layer and will
|
||||
// error out otherwise.
|
||||
if err = SetupBaseOSLayer(ctx, layerPath, 0, options); err != nil {
|
||||
return err
|
||||
}
|
||||
// Create the differencing disk that will be what's copied for the final rw layer
|
||||
// for a container.
|
||||
if err = vhd.CreateDiffVhd(diffVhdPath, baseVhdPath, defaultVHDXBlockSizeInMB); err != nil {
|
||||
return errors.Wrap(err, "failed to create differencing disk")
|
||||
}
|
||||
|
||||
if err = security.GrantVmGroupAccess(baseVhdPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to grant vm group access to %s", baseVhdPath)
|
||||
}
|
||||
if err = security.GrantVmGroupAccess(diffVhdPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to grant vm group access to %s", diffVhdPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupUtilityVMBaseLayer is a helper to setup a UVMs scratch space. It will create and format
|
||||
// the vhdx inside and the size is configurable by the sizeInGB parameter.
|
||||
//
|
||||
// `uvmPath` is the path to the UtilityVM filesystem.
|
||||
//
|
||||
// `baseVhdPath` is the path to where the base vhdx for the UVM should be created.
|
||||
//
|
||||
// `diffVhdPath` is the path where the differencing disk for the UVM should be created.
|
||||
//
|
||||
// `sizeInGB` specifies the size in gigabytes to make the base vhdx.
|
||||
func SetupUtilityVMBaseLayer(ctx context.Context, uvmPath, baseVhdPath, diffVhdPath string, sizeInGB uint64) (err error) {
|
||||
// Remove the base and differencing disks if they exist in case we're asking for a different size.
|
||||
if _, err := os.Stat(baseVhdPath); err == nil {
|
||||
if err := os.RemoveAll(baseVhdPath); err != nil {
|
||||
return errors.Wrap(err, "failed to remove base vhdx")
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(diffVhdPath); err == nil {
|
||||
if err := os.RemoveAll(diffVhdPath); err != nil {
|
||||
return errors.Wrap(err, "failed to remove differencing vhdx")
|
||||
}
|
||||
}
|
||||
|
||||
// Just create the vhdx for utilityVM layer, no need to format it.
|
||||
createParams := &vhd.CreateVirtualDiskParameters{
|
||||
Version: 2,
|
||||
Version2: vhd.CreateVersion2{
|
||||
MaximumSize: sizeInGB * 1024 * 1024 * 1024,
|
||||
BlockSizeInBytes: defaultVHDXBlockSizeInMB * 1024 * 1024,
|
||||
},
|
||||
}
|
||||
handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create vhdx")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = syscall.CloseHandle(handle)
|
||||
os.RemoveAll(baseVhdPath)
|
||||
os.RemoveAll(diffVhdPath)
|
||||
}
|
||||
}()
|
||||
|
||||
// If it is a UtilityVM layer then the base vhdx must be attached when calling
|
||||
// `SetupBaseOSLayer`
|
||||
attachParams := &vhd.AttachVirtualDiskParameters{
|
||||
Version: 2,
|
||||
}
|
||||
if err := vhd.AttachVirtualDisk(handle, vhd.AttachVirtualDiskFlagNone, attachParams); err != nil {
|
||||
return errors.Wrapf(err, "failed to attach virtual disk")
|
||||
}
|
||||
|
||||
options := OsLayerOptions{
|
||||
Type: OsLayerTypeVM,
|
||||
}
|
||||
if err := SetupBaseOSLayer(ctx, uvmPath, windows.Handle(handle), options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Detach and close the handle after setting up the layer as we don't need the handle
|
||||
// for anything else and we no longer need to be attached either.
|
||||
if err = vhd.DetachVirtualDisk(handle); err != nil {
|
||||
return errors.Wrap(err, "failed to detach vhdx")
|
||||
}
|
||||
if err = syscall.CloseHandle(handle); err != nil {
|
||||
return errors.Wrap(err, "failed to close vhdx handle")
|
||||
}
|
||||
|
||||
// Create the differencing disk that will be what's copied for the final rw layer
|
||||
// for a container.
|
||||
if err = vhd.CreateDiffVhd(diffVhdPath, baseVhdPath, defaultVHDXBlockSizeInMB); err != nil {
|
||||
return errors.Wrap(err, "failed to create differencing disk")
|
||||
}
|
||||
|
||||
if err := security.GrantVmGroupAccess(baseVhdPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to grant vm group access to %s", baseVhdPath)
|
||||
}
|
||||
if err := security.GrantVmGroupAccess(diffVhdPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to grant vm group access to %s", diffVhdPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
41
common/vendor/github.com/Microsoft/hcsshim/computestorage/import.go
generated
vendored
Normal file
41
common/vendor/github.com/Microsoft/hcsshim/computestorage/import.go
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ImportLayer imports a container layer.
|
||||
//
|
||||
// `layerPath` is a path to a directory to import the layer to. If the directory
|
||||
// does not exist it will be automatically created.
|
||||
//
|
||||
// `sourceFolderpath` is a pre-existing folder that contains the layer to
|
||||
// import.
|
||||
//
|
||||
// `layerData` is the parent layer data.
|
||||
func ImportLayer(ctx context.Context, layerPath, sourceFolderPath string, layerData LayerData) (err error) {
|
||||
title := "hcsshim.ImportLayer"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("layerPath", layerPath),
|
||||
trace.StringAttribute("sourceFolderPath", sourceFolderPath),
|
||||
)
|
||||
|
||||
bytes, err := json.Marshal(layerData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = hcsImportLayer(layerPath, sourceFolderPath, string(bytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to import layer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
38
common/vendor/github.com/Microsoft/hcsshim/computestorage/initialize.go
generated
vendored
Normal file
38
common/vendor/github.com/Microsoft/hcsshim/computestorage/initialize.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// InitializeWritableLayer initializes a writable layer for a container.
|
||||
//
|
||||
// `layerPath` is a path to a directory the layer is mounted. If the
|
||||
// path does not end in a `\` the platform will append it automatically.
|
||||
//
|
||||
// `layerData` is the parent read-only layer data.
|
||||
func InitializeWritableLayer(ctx context.Context, layerPath string, layerData LayerData) (err error) {
|
||||
title := "hcsshim.InitializeWritableLayer"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("layerPath", layerPath),
|
||||
)
|
||||
|
||||
bytes, err := json.Marshal(layerData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Options are not used in the platform as of RS5
|
||||
err = hcsInitializeWritableLayer(layerPath, string(bytes), "")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to intitialize container layer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
27
common/vendor/github.com/Microsoft/hcsshim/computestorage/mount.go
generated
vendored
Normal file
27
common/vendor/github.com/Microsoft/hcsshim/computestorage/mount.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/interop"
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// GetLayerVhdMountPath returns the volume path for a virtual disk of a writable container layer.
|
||||
func GetLayerVhdMountPath(ctx context.Context, vhdHandle windows.Handle) (path string, err error) {
|
||||
title := "hcsshim.GetLayerVhdMountPath"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
|
||||
var mountPath *uint16
|
||||
err = hcsGetLayerVhdMountPath(vhdHandle, &mountPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to get vhd mount path")
|
||||
}
|
||||
path = interop.ConvertAndFreeCoTaskMemString(mountPath)
|
||||
return path, nil
|
||||
}
|
||||
74
common/vendor/github.com/Microsoft/hcsshim/computestorage/setup.go
generated
vendored
Normal file
74
common/vendor/github.com/Microsoft/hcsshim/computestorage/setup.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package computestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// SetupBaseOSLayer sets up a layer that contains a base OS for a container.
|
||||
//
|
||||
// `layerPath` is a path to a directory containing the layer.
|
||||
//
|
||||
// `vhdHandle` is an empty file handle of `options.Type == OsLayerTypeContainer`
|
||||
// or else it is a file handle to the 'SystemTemplateBase.vhdx' if `options.Type
|
||||
// == OsLayerTypeVm`.
|
||||
//
|
||||
// `options` are the options applied while processing the layer.
|
||||
func SetupBaseOSLayer(ctx context.Context, layerPath string, vhdHandle windows.Handle, options OsLayerOptions) (err error) {
|
||||
title := "hcsshim.SetupBaseOSLayer"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("layerPath", layerPath),
|
||||
)
|
||||
|
||||
bytes, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = hcsSetupBaseOSLayer(layerPath, vhdHandle, string(bytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to setup base OS layer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupBaseOSVolume sets up a volume that contains a base OS for a container.
|
||||
//
|
||||
// `layerPath` is a path to a directory containing the layer.
|
||||
//
|
||||
// `volumePath` is the path to the volume to be used for setup.
|
||||
//
|
||||
// `options` are the options applied while processing the layer.
|
||||
func SetupBaseOSVolume(ctx context.Context, layerPath, volumePath string, options OsLayerOptions) (err error) {
|
||||
if osversion.Get().Build < 19645 {
|
||||
return errors.New("SetupBaseOSVolume is not present on builds older than 19645")
|
||||
}
|
||||
title := "hcsshim.SetupBaseOSVolume"
|
||||
ctx, span := trace.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("layerPath", layerPath),
|
||||
trace.StringAttribute("volumePath", volumePath),
|
||||
)
|
||||
|
||||
bytes, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = hcsSetupBaseOSVolume(layerPath, volumePath, string(bytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to setup base OS layer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
50
common/vendor/github.com/Microsoft/hcsshim/computestorage/storage.go
generated
vendored
Normal file
50
common/vendor/github.com/Microsoft/hcsshim/computestorage/storage.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Package computestorage is a wrapper around the HCS storage APIs. These are new storage APIs introduced
|
||||
// separate from the original graphdriver calls intended to give more freedom around creating
|
||||
// and managing container layers and scratch spaces.
|
||||
package computestorage
|
||||
|
||||
import (
|
||||
hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
|
||||
)
|
||||
|
||||
//go:generate go run ../mksyscall_windows.go -output zsyscall_windows.go storage.go
|
||||
|
||||
//sys hcsImportLayer(layerPath string, sourceFolderPath string, layerData string) (hr error) = computestorage.HcsImportLayer?
|
||||
//sys hcsExportLayer(layerPath string, exportFolderPath string, layerData string, options string) (hr error) = computestorage.HcsExportLayer?
|
||||
//sys hcsDestroyLayer(layerPath string) (hr error) = computestorage.HcsDestoryLayer?
|
||||
//sys hcsSetupBaseOSLayer(layerPath string, handle windows.Handle, options string) (hr error) = computestorage.HcsSetupBaseOSLayer?
|
||||
//sys hcsInitializeWritableLayer(writableLayerPath string, layerData string, options string) (hr error) = computestorage.HcsInitializeWritableLayer?
|
||||
//sys hcsAttachLayerStorageFilter(layerPath string, layerData string) (hr error) = computestorage.HcsAttachLayerStorageFilter?
|
||||
//sys hcsDetachLayerStorageFilter(layerPath string) (hr error) = computestorage.HcsDetachLayerStorageFilter?
|
||||
//sys hcsFormatWritableLayerVhd(handle windows.Handle) (hr error) = computestorage.HcsFormatWritableLayerVhd?
|
||||
//sys hcsGetLayerVhdMountPath(vhdHandle windows.Handle, mountPath **uint16) (hr error) = computestorage.HcsGetLayerVhdMountPath?
|
||||
//sys hcsSetupBaseOSVolume(layerPath string, volumePath string, options string) (hr error) = computestorage.HcsSetupBaseOSVolume?
|
||||
|
||||
// LayerData is the data used to describe parent layer information.
|
||||
type LayerData struct {
|
||||
SchemaVersion hcsschema.Version `json:"SchemaVersion,omitempty"`
|
||||
Layers []hcsschema.Layer `json:"Layers,omitempty"`
|
||||
}
|
||||
|
||||
// ExportLayerOptions are the set of options that are used with the `computestorage.HcsExportLayer` syscall.
|
||||
type ExportLayerOptions struct {
|
||||
IsWritableLayer bool `json:"IsWritableLayer,omitempty"`
|
||||
}
|
||||
|
||||
// OsLayerType is the type of layer being operated on.
|
||||
type OsLayerType string
|
||||
|
||||
const (
|
||||
// OsLayerTypeContainer is a container layer.
|
||||
OsLayerTypeContainer OsLayerType = "Container"
|
||||
// OsLayerTypeVM is a virtual machine layer.
|
||||
OsLayerTypeVM OsLayerType = "Vm"
|
||||
)
|
||||
|
||||
// OsLayerOptions are the set of options that are used with the `SetupBaseOSLayer` and
|
||||
// `SetupBaseOSVolume` calls.
|
||||
type OsLayerOptions struct {
|
||||
Type OsLayerType `json:"Type,omitempty"`
|
||||
DisableCiCacheOptimization bool `json:"DisableCiCacheOptimization,omitempty"`
|
||||
SkipUpdateBcdForBoot bool `json:"SkipUpdateBcdForBoot,omitempty"`
|
||||
}
|
||||
319
common/vendor/github.com/Microsoft/hcsshim/computestorage/zsyscall_windows.go
generated
vendored
Normal file
319
common/vendor/github.com/Microsoft/hcsshim/computestorage/zsyscall_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
// Code generated mksyscall_windows.exe DO NOT EDIT
|
||||
|
||||
package computestorage
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modcomputestorage = windows.NewLazySystemDLL("computestorage.dll")
|
||||
|
||||
procHcsImportLayer = modcomputestorage.NewProc("HcsImportLayer")
|
||||
procHcsExportLayer = modcomputestorage.NewProc("HcsExportLayer")
|
||||
procHcsDestoryLayer = modcomputestorage.NewProc("HcsDestoryLayer")
|
||||
procHcsSetupBaseOSLayer = modcomputestorage.NewProc("HcsSetupBaseOSLayer")
|
||||
procHcsInitializeWritableLayer = modcomputestorage.NewProc("HcsInitializeWritableLayer")
|
||||
procHcsAttachLayerStorageFilter = modcomputestorage.NewProc("HcsAttachLayerStorageFilter")
|
||||
procHcsDetachLayerStorageFilter = modcomputestorage.NewProc("HcsDetachLayerStorageFilter")
|
||||
procHcsFormatWritableLayerVhd = modcomputestorage.NewProc("HcsFormatWritableLayerVhd")
|
||||
procHcsGetLayerVhdMountPath = modcomputestorage.NewProc("HcsGetLayerVhdMountPath")
|
||||
procHcsSetupBaseOSVolume = modcomputestorage.NewProc("HcsSetupBaseOSVolume")
|
||||
)
|
||||
|
||||
func hcsImportLayer(layerPath string, sourceFolderPath string, layerData string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *uint16
|
||||
_p1, hr = syscall.UTF16PtrFromString(sourceFolderPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p2 *uint16
|
||||
_p2, hr = syscall.UTF16PtrFromString(layerData)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsImportLayer(_p0, _p1, _p2)
|
||||
}
|
||||
|
||||
func _hcsImportLayer(layerPath *uint16, sourceFolderPath *uint16, layerData *uint16) (hr error) {
|
||||
if hr = procHcsImportLayer.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsImportLayer.Addr(), 3, uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(sourceFolderPath)), uintptr(unsafe.Pointer(layerData)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsExportLayer(layerPath string, exportFolderPath string, layerData string, options string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *uint16
|
||||
_p1, hr = syscall.UTF16PtrFromString(exportFolderPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p2 *uint16
|
||||
_p2, hr = syscall.UTF16PtrFromString(layerData)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p3 *uint16
|
||||
_p3, hr = syscall.UTF16PtrFromString(options)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsExportLayer(_p0, _p1, _p2, _p3)
|
||||
}
|
||||
|
||||
func _hcsExportLayer(layerPath *uint16, exportFolderPath *uint16, layerData *uint16, options *uint16) (hr error) {
|
||||
if hr = procHcsExportLayer.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall6(procHcsExportLayer.Addr(), 4, uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(exportFolderPath)), uintptr(unsafe.Pointer(layerData)), uintptr(unsafe.Pointer(options)), 0, 0)
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsDestroyLayer(layerPath string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsDestroyLayer(_p0)
|
||||
}
|
||||
|
||||
func _hcsDestroyLayer(layerPath *uint16) (hr error) {
|
||||
if hr = procHcsDestoryLayer.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsDestoryLayer.Addr(), 1, uintptr(unsafe.Pointer(layerPath)), 0, 0)
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsSetupBaseOSLayer(layerPath string, handle windows.Handle, options string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *uint16
|
||||
_p1, hr = syscall.UTF16PtrFromString(options)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsSetupBaseOSLayer(_p0, handle, _p1)
|
||||
}
|
||||
|
||||
func _hcsSetupBaseOSLayer(layerPath *uint16, handle windows.Handle, options *uint16) (hr error) {
|
||||
if hr = procHcsSetupBaseOSLayer.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsSetupBaseOSLayer.Addr(), 3, uintptr(unsafe.Pointer(layerPath)), uintptr(handle), uintptr(unsafe.Pointer(options)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsInitializeWritableLayer(writableLayerPath string, layerData string, options string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(writableLayerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *uint16
|
||||
_p1, hr = syscall.UTF16PtrFromString(layerData)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p2 *uint16
|
||||
_p2, hr = syscall.UTF16PtrFromString(options)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsInitializeWritableLayer(_p0, _p1, _p2)
|
||||
}
|
||||
|
||||
func _hcsInitializeWritableLayer(writableLayerPath *uint16, layerData *uint16, options *uint16) (hr error) {
|
||||
if hr = procHcsInitializeWritableLayer.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsInitializeWritableLayer.Addr(), 3, uintptr(unsafe.Pointer(writableLayerPath)), uintptr(unsafe.Pointer(layerData)), uintptr(unsafe.Pointer(options)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsAttachLayerStorageFilter(layerPath string, layerData string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *uint16
|
||||
_p1, hr = syscall.UTF16PtrFromString(layerData)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsAttachLayerStorageFilter(_p0, _p1)
|
||||
}
|
||||
|
||||
func _hcsAttachLayerStorageFilter(layerPath *uint16, layerData *uint16) (hr error) {
|
||||
if hr = procHcsAttachLayerStorageFilter.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsAttachLayerStorageFilter.Addr(), 2, uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(layerData)), 0)
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsDetachLayerStorageFilter(layerPath string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsDetachLayerStorageFilter(_p0)
|
||||
}
|
||||
|
||||
func _hcsDetachLayerStorageFilter(layerPath *uint16) (hr error) {
|
||||
if hr = procHcsDetachLayerStorageFilter.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsDetachLayerStorageFilter.Addr(), 1, uintptr(unsafe.Pointer(layerPath)), 0, 0)
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsFormatWritableLayerVhd(handle windows.Handle) (hr error) {
|
||||
if hr = procHcsFormatWritableLayerVhd.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsFormatWritableLayerVhd.Addr(), 1, uintptr(handle), 0, 0)
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsGetLayerVhdMountPath(vhdHandle windows.Handle, mountPath **uint16) (hr error) {
|
||||
if hr = procHcsGetLayerVhdMountPath.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsGetLayerVhdMountPath.Addr(), 2, uintptr(vhdHandle), uintptr(unsafe.Pointer(mountPath)), 0)
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hcsSetupBaseOSVolume(layerPath string, volumePath string, options string) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *uint16
|
||||
_p1, hr = syscall.UTF16PtrFromString(volumePath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
var _p2 *uint16
|
||||
_p2, hr = syscall.UTF16PtrFromString(options)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _hcsSetupBaseOSVolume(_p0, _p1, _p2)
|
||||
}
|
||||
|
||||
func _hcsSetupBaseOSVolume(layerPath *uint16, volumePath *uint16, options *uint16) (hr error) {
|
||||
if hr = procHcsSetupBaseOSVolume.Find(); hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.Syscall(procHcsSetupBaseOSVolume.Addr(), 3, uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(volumePath)), uintptr(unsafe.Pointer(options)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/hcs"
|
||||
"github.com/Microsoft/hcsshim/internal/mergemaps"
|
||||
"github.com/Microsoft/hcsshim/internal/schema1"
|
||||
)
|
||||
|
||||
// ContainerProperties holds the properties for a container and the processes running in that container
|
||||
type ContainerProperties = schema1.ContainerProperties
|
||||
|
||||
// MemoryStats holds the memory statistics for a container
|
||||
type MemoryStats = schema1.MemoryStats
|
||||
|
||||
// ProcessorStats holds the processor statistics for a container
|
||||
type ProcessorStats = schema1.ProcessorStats
|
||||
|
||||
// StorageStats holds the storage statistics for a container
|
||||
type StorageStats = schema1.StorageStats
|
||||
|
||||
// NetworkStats holds the network statistics for a container
|
||||
type NetworkStats = schema1.NetworkStats
|
||||
|
||||
// Statistics is the structure returned by a statistics call on a container
|
||||
type Statistics = schema1.Statistics
|
||||
|
||||
// ProcessList is the structure of an item returned by a ProcessList call on a container
|
||||
type ProcessListItem = schema1.ProcessListItem
|
||||
|
||||
// MappedVirtualDiskController is the structure of an item returned by a MappedVirtualDiskList call on a container
|
||||
type MappedVirtualDiskController = schema1.MappedVirtualDiskController
|
||||
|
||||
// Type of Request Support in ModifySystem
|
||||
type RequestType = schema1.RequestType
|
||||
|
||||
// Type of Resource Support in ModifySystem
|
||||
type ResourceType = schema1.ResourceType
|
||||
|
||||
// RequestType const
|
||||
const (
|
||||
Add = schema1.Add
|
||||
Remove = schema1.Remove
|
||||
Network = schema1.Network
|
||||
)
|
||||
|
||||
// ResourceModificationRequestResponse is the structure used to send request to the container to modify the system
|
||||
// Supported resource types are Network and Request Types are Add/Remove
|
||||
type ResourceModificationRequestResponse = schema1.ResourceModificationRequestResponse
|
||||
|
||||
type container struct {
|
||||
system *hcs.System
|
||||
waitOnce sync.Once
|
||||
waitErr error
|
||||
waitCh chan struct{}
|
||||
}
|
||||
|
||||
// createComputeSystemAdditionalJSON is read from the environment at initialisation
|
||||
// time. It allows an environment variable to define additional JSON which
|
||||
// is merged in the CreateComputeSystem call to HCS.
|
||||
var createContainerAdditionalJSON []byte
|
||||
|
||||
func init() {
|
||||
createContainerAdditionalJSON = ([]byte)(os.Getenv("HCSSHIM_CREATECONTAINER_ADDITIONALJSON"))
|
||||
}
|
||||
|
||||
// CreateContainer creates a new container with the given configuration but does not start it.
|
||||
func CreateContainer(id string, c *ContainerConfig) (Container, error) {
|
||||
fullConfig, err := mergemaps.MergeJSON(c, createContainerAdditionalJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to merge additional JSON '%s': %s", createContainerAdditionalJSON, err)
|
||||
}
|
||||
|
||||
system, err := hcs.CreateComputeSystem(context.Background(), id, fullConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container{system: system}, err
|
||||
}
|
||||
|
||||
// OpenContainer opens an existing container by ID.
|
||||
func OpenContainer(id string) (Container, error) {
|
||||
system, err := hcs.OpenComputeSystem(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container{system: system}, err
|
||||
}
|
||||
|
||||
// GetContainers gets a list of the containers on the system that match the query
|
||||
func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) {
|
||||
return hcs.GetComputeSystems(context.Background(), q)
|
||||
}
|
||||
|
||||
// Start synchronously starts the container.
|
||||
func (container *container) Start() error {
|
||||
return convertSystemError(container.system.Start(context.Background()), container)
|
||||
}
|
||||
|
||||
// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
|
||||
func (container *container) Shutdown() error {
|
||||
err := container.system.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
return convertSystemError(err, container)
|
||||
}
|
||||
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Shutdown"}
|
||||
}
|
||||
|
||||
// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
|
||||
func (container *container) Terminate() error {
|
||||
err := container.system.Terminate(context.Background())
|
||||
if err != nil {
|
||||
return convertSystemError(err, container)
|
||||
}
|
||||
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Terminate"}
|
||||
}
|
||||
|
||||
// Waits synchronously waits for the container to shutdown or terminate.
|
||||
func (container *container) Wait() error {
|
||||
err := container.system.Wait()
|
||||
if err == nil {
|
||||
err = container.system.ExitError()
|
||||
}
|
||||
return convertSystemError(err, container)
|
||||
}
|
||||
|
||||
// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
|
||||
// returns false if timeout occurs.
|
||||
func (container *container) WaitTimeout(timeout time.Duration) error {
|
||||
container.waitOnce.Do(func() {
|
||||
container.waitCh = make(chan struct{})
|
||||
go func() {
|
||||
container.waitErr = container.Wait()
|
||||
close(container.waitCh)
|
||||
}()
|
||||
})
|
||||
t := time.NewTimer(timeout)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-t.C:
|
||||
return &ContainerError{Container: container, Err: ErrTimeout, Operation: "hcsshim::ComputeSystem::Wait"}
|
||||
case <-container.waitCh:
|
||||
return container.waitErr
|
||||
}
|
||||
}
|
||||
|
||||
// Pause pauses the execution of a container.
|
||||
func (container *container) Pause() error {
|
||||
return convertSystemError(container.system.Pause(context.Background()), container)
|
||||
}
|
||||
|
||||
// Resume resumes the execution of a container.
|
||||
func (container *container) Resume() error {
|
||||
return convertSystemError(container.system.Resume(context.Background()), container)
|
||||
}
|
||||
|
||||
// HasPendingUpdates returns true if the container has updates pending to install
|
||||
func (container *container) HasPendingUpdates() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Statistics returns statistics for the container. This is a legacy v1 call
|
||||
func (container *container) Statistics() (Statistics, error) {
|
||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeStatistics)
|
||||
if err != nil {
|
||||
return Statistics{}, convertSystemError(err, container)
|
||||
}
|
||||
|
||||
return properties.Statistics, nil
|
||||
}
|
||||
|
||||
// ProcessList returns an array of ProcessListItems for the container. This is a legacy v1 call
|
||||
func (container *container) ProcessList() ([]ProcessListItem, error) {
|
||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeProcessList)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
|
||||
return properties.ProcessList, nil
|
||||
}
|
||||
|
||||
// This is a legacy v1 call
|
||||
func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskController, error) {
|
||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeMappedVirtualDisk)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
|
||||
return properties.MappedVirtualDiskControllers, nil
|
||||
}
|
||||
|
||||
// CreateProcess launches a new process within the container.
|
||||
func (container *container) CreateProcess(c *ProcessConfig) (Process, error) {
|
||||
p, err := container.system.CreateProcess(context.Background(), c)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
return &process{p: p.(*hcs.Process)}, nil
|
||||
}
|
||||
|
||||
// OpenProcess gets an interface to an existing process within the container.
|
||||
func (container *container) OpenProcess(pid int) (Process, error) {
|
||||
p, err := container.system.OpenProcess(context.Background(), pid)
|
||||
if err != nil {
|
||||
return nil, convertSystemError(err, container)
|
||||
}
|
||||
return &process{p: p}, nil
|
||||
}
|
||||
|
||||
// Close cleans up any state associated with the container but does not terminate or wait for it.
|
||||
func (container *container) Close() error {
|
||||
return convertSystemError(container.system.Close(), container)
|
||||
}
|
||||
|
||||
// Modify the System
|
||||
func (container *container) Modify(config *ResourceModificationRequestResponse) error {
|
||||
return convertSystemError(container.system.Modify(context.Background(), config), container)
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/hcs"
|
||||
"github.com/Microsoft/hcsshim/internal/hcserror"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists = hcs.exist
|
||||
ErrComputeSystemDoesNotExist = hcs.ErrComputeSystemDoesNotExist
|
||||
|
||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
||||
ErrElementNotFound = hcs.ErrElementNotFound
|
||||
|
||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
||||
ErrNotSupported = hcs.ErrNotSupported
|
||||
|
||||
// ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported
|
||||
// decimal -2147024883 / hex 0x8007000d
|
||||
ErrInvalidData = hcs.ErrInvalidData
|
||||
|
||||
// ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed
|
||||
ErrHandleClose = hcs.ErrHandleClose
|
||||
|
||||
// ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method
|
||||
ErrAlreadyClosed = hcs.ErrAlreadyClosed
|
||||
|
||||
// ErrInvalidNotificationType is an error encountered when an invalid notification type is used
|
||||
ErrInvalidNotificationType = hcs.ErrInvalidNotificationType
|
||||
|
||||
// ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation
|
||||
ErrInvalidProcessState = hcs.ErrInvalidProcessState
|
||||
|
||||
// ErrTimeout is an error encountered when waiting on a notification times out
|
||||
ErrTimeout = hcs.ErrTimeout
|
||||
|
||||
// ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for
|
||||
// a different expected notification
|
||||
ErrUnexpectedContainerExit = hcs.ErrUnexpectedContainerExit
|
||||
|
||||
// ErrUnexpectedProcessAbort is the error encountered when communication with the compute service
|
||||
// is lost while waiting for a notification
|
||||
ErrUnexpectedProcessAbort = hcs.ErrUnexpectedProcessAbort
|
||||
|
||||
// ErrUnexpectedValue is an error encountered when hcs returns an invalid value
|
||||
ErrUnexpectedValue = hcs.ErrUnexpectedValue
|
||||
|
||||
// ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container
|
||||
ErrVmcomputeAlreadyStopped = hcs.ErrVmcomputeAlreadyStopped
|
||||
|
||||
// ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously
|
||||
ErrVmcomputeOperationPending = hcs.ErrVmcomputeOperationPending
|
||||
|
||||
// ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation
|
||||
ErrVmcomputeOperationInvalidState = hcs.ErrVmcomputeOperationInvalidState
|
||||
|
||||
// ErrProcNotFound is an error encountered when the the process cannot be found
|
||||
ErrProcNotFound = hcs.ErrProcNotFound
|
||||
|
||||
// ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2
|
||||
// builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3.
|
||||
ErrVmcomputeOperationAccessIsDenied = hcs.ErrVmcomputeOperationAccessIsDenied
|
||||
|
||||
// ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management
|
||||
ErrVmcomputeInvalidJSON = hcs.ErrVmcomputeInvalidJSON
|
||||
|
||||
// ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message
|
||||
ErrVmcomputeUnknownMessage = hcs.ErrVmcomputeUnknownMessage
|
||||
|
||||
// ErrNotSupported is an error encountered when hcs doesn't support the request
|
||||
ErrPlatformNotSupported = hcs.ErrPlatformNotSupported
|
||||
)
|
||||
|
||||
type EndpointNotFoundError = hns.EndpointNotFoundError
|
||||
type NetworkNotFoundError = hns.NetworkNotFoundError
|
||||
|
||||
// ProcessError is an error encountered in HCS during an operation on a Process object
|
||||
type ProcessError struct {
|
||||
Process *process
|
||||
Operation string
|
||||
Err error
|
||||
Events []hcs.ErrorEvent
|
||||
}
|
||||
|
||||
// ContainerError is an error encountered in HCS during an operation on a Container object
|
||||
type ContainerError struct {
|
||||
Container *container
|
||||
Operation string
|
||||
Err error
|
||||
Events []hcs.ErrorEvent
|
||||
}
|
||||
|
||||
func (e *ContainerError) Error() string {
|
||||
if e == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
if e.Container == nil {
|
||||
return "unexpected nil container for error: " + e.Err.Error()
|
||||
}
|
||||
|
||||
s := "container " + e.Container.system.ID()
|
||||
|
||||
if e.Operation != "" {
|
||||
s += " encountered an error during " + e.Operation
|
||||
}
|
||||
|
||||
switch e.Err.(type) {
|
||||
case nil:
|
||||
break
|
||||
case syscall.Errno:
|
||||
s += fmt.Sprintf(": failure in a Windows system call: %s (0x%x)", e.Err, hcserror.Win32FromError(e.Err))
|
||||
default:
|
||||
s += fmt.Sprintf(": %s", e.Err.Error())
|
||||
}
|
||||
|
||||
for _, ev := range e.Events {
|
||||
s += "\n" + ev.String()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *ProcessError) Error() string {
|
||||
if e == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
if e.Process == nil {
|
||||
return "Unexpected nil process for error: " + e.Err.Error()
|
||||
}
|
||||
|
||||
s := fmt.Sprintf("process %d in container %s", e.Process.p.Pid(), e.Process.p.SystemID())
|
||||
if e.Operation != "" {
|
||||
s += " encountered an error during " + e.Operation
|
||||
}
|
||||
|
||||
switch e.Err.(type) {
|
||||
case nil:
|
||||
break
|
||||
case syscall.Errno:
|
||||
s += fmt.Sprintf(": failure in a Windows system call: %s (0x%x)", e.Err, hcserror.Win32FromError(e.Err))
|
||||
default:
|
||||
s += fmt.Sprintf(": %s", e.Err.Error())
|
||||
}
|
||||
|
||||
for _, ev := range e.Events {
|
||||
s += "\n" + ev.String()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// IsNotExist checks if an error is caused by the Container or Process not existing.
|
||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
||||
// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
|
||||
func IsNotExist(err error) bool {
|
||||
if _, ok := err.(EndpointNotFoundError); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := err.(NetworkNotFoundError); ok {
|
||||
return true
|
||||
}
|
||||
return hcs.IsNotExist(getInnerError(err))
|
||||
}
|
||||
|
||||
// IsAlreadyClosed checks if an error is caused by the Container or Process having been
|
||||
// already closed by a call to the Close() method.
|
||||
func IsAlreadyClosed(err error) bool {
|
||||
return hcs.IsAlreadyClosed(getInnerError(err))
|
||||
}
|
||||
|
||||
// IsPending returns a boolean indicating whether the error is that
|
||||
// the requested operation is being completed in the background.
|
||||
func IsPending(err error) bool {
|
||||
return hcs.IsPending(getInnerError(err))
|
||||
}
|
||||
|
||||
// IsTimeout returns a boolean indicating whether the error is caused by
|
||||
// a timeout waiting for the operation to complete.
|
||||
func IsTimeout(err error) bool {
|
||||
return hcs.IsTimeout(getInnerError(err))
|
||||
}
|
||||
|
||||
// IsAlreadyStopped returns a boolean indicating whether the error is caused by
|
||||
// a Container or Process being already stopped.
|
||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
||||
// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
|
||||
func IsAlreadyStopped(err error) bool {
|
||||
return hcs.IsAlreadyStopped(getInnerError(err))
|
||||
}
|
||||
|
||||
// IsNotSupported returns a boolean indicating whether the error is caused by
|
||||
// unsupported platform requests
|
||||
// Note: Currently Unsupported platform requests can be mean either
|
||||
// ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage
|
||||
// is thrown from the Platform
|
||||
func IsNotSupported(err error) bool {
|
||||
return hcs.IsNotSupported(getInnerError(err))
|
||||
}
|
||||
|
||||
// IsOperationInvalidState returns true when err is caused by
|
||||
// `ErrVmcomputeOperationInvalidState`.
|
||||
func IsOperationInvalidState(err error) bool {
|
||||
return hcs.IsOperationInvalidState(getInnerError(err))
|
||||
}
|
||||
|
||||
// IsAccessIsDenied returns true when err is caused by
|
||||
// `ErrVmcomputeOperationAccessIsDenied`.
|
||||
func IsAccessIsDenied(err error) bool {
|
||||
return hcs.IsAccessIsDenied(getInnerError(err))
|
||||
}
|
||||
|
||||
func getInnerError(err error) error {
|
||||
switch pe := err.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *ContainerError:
|
||||
err = pe.Err
|
||||
case *ProcessError:
|
||||
err = pe.Err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func convertSystemError(err error, c *container) error {
|
||||
if serr, ok := err.(*hcs.SystemError); ok {
|
||||
return &ContainerError{Container: c, Operation: serr.Op, Err: serr.Err, Events: serr.Events}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func convertProcessError(err error, p *process) error {
|
||||
if perr, ok := err.(*hcs.ProcessError); ok {
|
||||
return &ProcessError{Process: p, Operation: perr.Op, Err: perr.Err, Events: perr.Events}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Requirements so far:
|
||||
# dockerd running
|
||||
# - image microsoft/nanoserver (matching host base image) docker load -i c:\baseimages\nanoserver.tar
|
||||
# - image alpine (linux) docker pull --platform=linux alpine
|
||||
|
||||
|
||||
# TODO: Add this a parameter for debugging. ie "functional-tests -debug=$true"
|
||||
#$env:HCSSHIM_FUNCTIONAL_TESTS_DEBUG="yes please"
|
||||
|
||||
#pushd uvm
|
||||
go test -v -tags "functional uvmcreate uvmscratch uvmscsi uvmvpmem uvmvsmb uvmp9" ./...
|
||||
#popd
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
module github.com/Microsoft/hcsshim
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3
|
||||
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68
|
||||
github.com/containerd/console v1.0.1
|
||||
github.com/containerd/containerd v1.5.0-beta.4
|
||||
github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0
|
||||
github.com/containerd/ttrpc v1.0.2
|
||||
github.com/containerd/typeurl v1.0.1
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/urfave/cli v1.22.2
|
||||
go.opencensus.io v0.22.3
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492
|
||||
google.golang.org/grpc v1.33.2
|
||||
)
|
||||
|
|
@ -0,0 +1,851 @@
|
|||
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 h1:mw6pDQqv38/WGF1cO/jF5t/jyAJ2yi7CmtFLLO5tGFI=
|
||||
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
|
||||
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
|
||||
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
|
||||
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
|
||||
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
|
||||
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
|
||||
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
|
||||
github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E=
|
||||
github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=
|
||||
github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
|
||||
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
|
||||
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
|
||||
github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
||||
github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
|
||||
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68 h1:hkGVFjz+plgr5UfxZUTPFbUFIF/Km6/s+RVRIRHLrrY=
|
||||
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
|
||||
github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
|
||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
||||
github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
|
||||
github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
|
||||
github.com/containerd/containerd v1.5.0-beta.4 h1:zjz4MOAOFgdBlwid2nNUlJ3YLpVi/97L36lfMYJex60=
|
||||
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
|
||||
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
|
||||
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e h1:6JKvHHt396/qabvMhnhUZvWaHZzfVfldxE60TK8YLhg=
|
||||
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
|
||||
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
|
||||
github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
|
||||
github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d h1:u6sWqdNGAy7+O8qG/r1dqdnZE7IdEjteK3WGuvbfreo=
|
||||
github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
|
||||
github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU=
|
||||
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||
github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
|
||||
github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0 h1:e+50zk22gvHLJKe8+d+xSMyA88PPQk/XfWuUw1BdnPA=
|
||||
github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
|
||||
github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0=
|
||||
github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA=
|
||||
github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow=
|
||||
github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
|
||||
github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
|
||||
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||
github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||
github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
|
||||
github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||
github.com/containerd/ttrpc v1.0.2 h1:2/O3oTZN36q2xRolk0a2WWGgh7/Vf/liElg5hFYLX9U=
|
||||
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||
github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
|
||||
github.com/containerd/typeurl v1.0.1 h1:PvuK4E3D5S5q6IqsPDCy928FhP0LUIGcmZ/Yhgp5Djw=
|
||||
github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
|
||||
github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw=
|
||||
github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y=
|
||||
github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
|
||||
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=
|
||||
github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
|
||||
github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
|
||||
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.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-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
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+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
|
||||
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM=
|
||||
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d h1:pNa8metDkwZjb9g4T8s+krQ+HRgZAkqnXml+wNir/+s=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
||||
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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-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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
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/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
|
||||
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
|
||||
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
|
||||
k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
|
||||
k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
|
||||
k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
|
||||
k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
|
||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// Shim for the Host Compute Service (HCS) to manage Windows Server
|
||||
// containers and Hyper-V containers.
|
||||
|
||||
package hcsshim
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/hcserror"
|
||||
)
|
||||
|
||||
//go:generate go run mksyscall_windows.go -output zsyscall_windows.go hcsshim.go
|
||||
|
||||
//sys SetCurrentThreadCompartmentId(compartmentId uint32) (hr error) = iphlpapi.SetCurrentThreadCompartmentId
|
||||
|
||||
const (
|
||||
// Specific user-visible exit codes
|
||||
WaitErrExecFailed = 32767
|
||||
|
||||
ERROR_GEN_FAILURE = hcserror.ERROR_GEN_FAILURE
|
||||
ERROR_SHUTDOWN_IN_PROGRESS = syscall.Errno(1115)
|
||||
WSAEINVAL = syscall.Errno(10022)
|
||||
|
||||
// Timeout on wait calls
|
||||
TimeoutInfinite = 0xFFFFFFFF
|
||||
)
|
||||
|
||||
type HcsError = hcserror.HcsError
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
)
|
||||
|
||||
// HNSEndpoint represents a network endpoint in HNS
|
||||
type HNSEndpoint = hns.HNSEndpoint
|
||||
|
||||
// Namespace represents a Compartment.
|
||||
type Namespace = hns.Namespace
|
||||
|
||||
//SystemType represents the type of the system on which actions are done
|
||||
type SystemType string
|
||||
|
||||
// SystemType const
|
||||
const (
|
||||
ContainerType SystemType = "Container"
|
||||
VirtualMachineType SystemType = "VirtualMachine"
|
||||
HostType SystemType = "Host"
|
||||
)
|
||||
|
||||
// EndpointAttachDetachRequest is the structure used to send request to the container to modify the system
|
||||
// Supported resource types are Network and Request Types are Add/Remove
|
||||
type EndpointAttachDetachRequest = hns.EndpointAttachDetachRequest
|
||||
|
||||
// EndpointResquestResponse is object to get the endpoint request response
|
||||
type EndpointResquestResponse = hns.EndpointResquestResponse
|
||||
|
||||
// HNSEndpointRequest makes a HNS call to modify/query a network endpoint
|
||||
func HNSEndpointRequest(method, path, request string) (*HNSEndpoint, error) {
|
||||
return hns.HNSEndpointRequest(method, path, request)
|
||||
}
|
||||
|
||||
// HNSListEndpointRequest makes a HNS call to query the list of available endpoints
|
||||
func HNSListEndpointRequest() ([]HNSEndpoint, error) {
|
||||
return hns.HNSListEndpointRequest()
|
||||
}
|
||||
|
||||
// HotAttachEndpoint makes a HCS Call to attach the endpoint to the container
|
||||
func HotAttachEndpoint(containerID string, endpointID string) error {
|
||||
endpoint, err := GetHNSEndpointByID(endpointID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isAttached, err := endpoint.IsAttached(containerID)
|
||||
if isAttached {
|
||||
return err
|
||||
}
|
||||
return modifyNetworkEndpoint(containerID, endpointID, Add)
|
||||
}
|
||||
|
||||
// HotDetachEndpoint makes a HCS Call to detach the endpoint from the container
|
||||
func HotDetachEndpoint(containerID string, endpointID string) error {
|
||||
endpoint, err := GetHNSEndpointByID(endpointID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isAttached, err := endpoint.IsAttached(containerID)
|
||||
if !isAttached {
|
||||
return err
|
||||
}
|
||||
return modifyNetworkEndpoint(containerID, endpointID, Remove)
|
||||
}
|
||||
|
||||
// ModifyContainer corresponding to the container id, by sending a request
|
||||
func modifyContainer(id string, request *ResourceModificationRequestResponse) error {
|
||||
container, err := OpenContainer(id)
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
return ErrComputeSystemDoesNotExist
|
||||
}
|
||||
return getInnerError(err)
|
||||
}
|
||||
defer container.Close()
|
||||
err = container.Modify(request)
|
||||
if err != nil {
|
||||
if IsNotSupported(err) {
|
||||
return ErrPlatformNotSupported
|
||||
}
|
||||
return getInnerError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyNetworkEndpoint(containerID string, endpointID string, request RequestType) error {
|
||||
requestMessage := &ResourceModificationRequestResponse{
|
||||
Resource: Network,
|
||||
Request: request,
|
||||
Data: endpointID,
|
||||
}
|
||||
err := modifyContainer(containerID, requestMessage)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHNSEndpointByID get the Endpoint by ID
|
||||
func GetHNSEndpointByID(endpointID string) (*HNSEndpoint, error) {
|
||||
return hns.GetHNSEndpointByID(endpointID)
|
||||
}
|
||||
|
||||
// GetHNSEndpointByName gets the endpoint filtered by Name
|
||||
func GetHNSEndpointByName(endpointName string) (*HNSEndpoint, error) {
|
||||
return hns.GetHNSEndpointByName(endpointName)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
)
|
||||
|
||||
type HNSGlobals = hns.HNSGlobals
|
||||
type HNSVersion = hns.HNSVersion
|
||||
|
||||
var (
|
||||
HNSVersion1803 = hns.HNSVersion1803
|
||||
)
|
||||
|
||||
func GetHNSGlobals() (*HNSGlobals, error) {
|
||||
return hns.GetHNSGlobals()
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
)
|
||||
|
||||
// Subnet is assoicated with a network and represents a list
|
||||
// of subnets available to the network
|
||||
type Subnet = hns.Subnet
|
||||
|
||||
// MacPool is assoicated with a network and represents a list
|
||||
// of macaddresses available to the network
|
||||
type MacPool = hns.MacPool
|
||||
|
||||
// HNSNetwork represents a network in HNS
|
||||
type HNSNetwork = hns.HNSNetwork
|
||||
|
||||
// HNSNetworkRequest makes a call into HNS to update/query a single network
|
||||
func HNSNetworkRequest(method, path, request string) (*HNSNetwork, error) {
|
||||
return hns.HNSNetworkRequest(method, path, request)
|
||||
}
|
||||
|
||||
// HNSListNetworkRequest makes a HNS call to query the list of available networks
|
||||
func HNSListNetworkRequest(method, path, request string) ([]HNSNetwork, error) {
|
||||
return hns.HNSListNetworkRequest(method, path, request)
|
||||
}
|
||||
|
||||
// GetHNSNetworkByID
|
||||
func GetHNSNetworkByID(networkID string) (*HNSNetwork, error) {
|
||||
return hns.GetHNSNetworkByID(networkID)
|
||||
}
|
||||
|
||||
// GetHNSNetworkName filtered by Name
|
||||
func GetHNSNetworkByName(networkName string) (*HNSNetwork, error) {
|
||||
return hns.GetHNSNetworkByName(networkName)
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
)
|
||||
|
||||
// Type of Request Support in ModifySystem
|
||||
type PolicyType = hns.PolicyType
|
||||
|
||||
// RequestType const
|
||||
const (
|
||||
Nat = hns.Nat
|
||||
ACL = hns.ACL
|
||||
PA = hns.PA
|
||||
VLAN = hns.VLAN
|
||||
VSID = hns.VSID
|
||||
VNet = hns.VNet
|
||||
L2Driver = hns.L2Driver
|
||||
Isolation = hns.Isolation
|
||||
QOS = hns.QOS
|
||||
OutboundNat = hns.OutboundNat
|
||||
ExternalLoadBalancer = hns.ExternalLoadBalancer
|
||||
Route = hns.Route
|
||||
Proxy = hns.Proxy
|
||||
)
|
||||
|
||||
type ProxyPolicy = hns.ProxyPolicy
|
||||
|
||||
type NatPolicy = hns.NatPolicy
|
||||
|
||||
type QosPolicy = hns.QosPolicy
|
||||
|
||||
type IsolationPolicy = hns.IsolationPolicy
|
||||
|
||||
type VlanPolicy = hns.VlanPolicy
|
||||
|
||||
type VsidPolicy = hns.VsidPolicy
|
||||
|
||||
type PaPolicy = hns.PaPolicy
|
||||
|
||||
type OutboundNatPolicy = hns.OutboundNatPolicy
|
||||
|
||||
type ActionType = hns.ActionType
|
||||
type DirectionType = hns.DirectionType
|
||||
type RuleType = hns.RuleType
|
||||
|
||||
const (
|
||||
Allow = hns.Allow
|
||||
Block = hns.Block
|
||||
|
||||
In = hns.In
|
||||
Out = hns.Out
|
||||
|
||||
Host = hns.Host
|
||||
Switch = hns.Switch
|
||||
)
|
||||
|
||||
type ACLPolicy = hns.ACLPolicy
|
||||
|
||||
type Policy = hns.Policy
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
)
|
||||
|
||||
// RoutePolicy is a structure defining schema for Route based Policy
|
||||
type RoutePolicy = hns.RoutePolicy
|
||||
|
||||
// ELBPolicy is a structure defining schema for ELB LoadBalancing based Policy
|
||||
type ELBPolicy = hns.ELBPolicy
|
||||
|
||||
// LBPolicy is a structure defining schema for LoadBalancing based Policy
|
||||
type LBPolicy = hns.LBPolicy
|
||||
|
||||
// PolicyList is a structure defining schema for Policy list request
|
||||
type PolicyList = hns.PolicyList
|
||||
|
||||
// HNSPolicyListRequest makes a call into HNS to update/query a single network
|
||||
func HNSPolicyListRequest(method, path, request string) (*PolicyList, error) {
|
||||
return hns.HNSPolicyListRequest(method, path, request)
|
||||
}
|
||||
|
||||
// HNSListPolicyListRequest gets all the policy list
|
||||
func HNSListPolicyListRequest() ([]PolicyList, error) {
|
||||
return hns.HNSListPolicyListRequest()
|
||||
}
|
||||
|
||||
// PolicyListRequest makes a HNS call to modify/query a network policy list
|
||||
func PolicyListRequest(method, path, request string) (*PolicyList, error) {
|
||||
return hns.PolicyListRequest(method, path, request)
|
||||
}
|
||||
|
||||
// GetPolicyListByID get the policy list by ID
|
||||
func GetPolicyListByID(policyListID string) (*PolicyList, error) {
|
||||
return hns.GetPolicyListByID(policyListID)
|
||||
}
|
||||
|
||||
// AddLoadBalancer policy list for the specified endpoints
|
||||
func AddLoadBalancer(endpoints []HNSEndpoint, isILB bool, sourceVIP, vip string, protocol uint16, internalPort uint16, externalPort uint16) (*PolicyList, error) {
|
||||
return hns.AddLoadBalancer(endpoints, isILB, sourceVIP, vip, protocol, internalPort, externalPort)
|
||||
}
|
||||
|
||||
// AddRoute adds route policy list for the specified endpoints
|
||||
func AddRoute(endpoints []HNSEndpoint, destinationPrefix string, nextHop string, encapEnabled bool) (*PolicyList, error) {
|
||||
return hns.AddRoute(endpoints, destinationPrefix, nextHop, encapEnabled)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
)
|
||||
|
||||
type HNSSupportedFeatures = hns.HNSSupportedFeatures
|
||||
|
||||
type HNSAclFeatures = hns.HNSAclFeatures
|
||||
|
||||
func GetHNSSupportedFeatures() HNSSupportedFeatures {
|
||||
return hns.GetHNSSupportedFeatures()
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package hcsshim
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/schema1"
|
||||
)
|
||||
|
||||
// ProcessConfig is used as both the input of Container.CreateProcess
|
||||
// and to convert the parameters to JSON for passing onto the HCS
|
||||
type ProcessConfig = schema1.ProcessConfig
|
||||
|
||||
type Layer = schema1.Layer
|
||||
type MappedDir = schema1.MappedDir
|
||||
type MappedPipe = schema1.MappedPipe
|
||||
type HvRuntime = schema1.HvRuntime
|
||||
type MappedVirtualDisk = schema1.MappedVirtualDisk
|
||||
|
||||
// AssignedDevice represents a device that has been directly assigned to a container
|
||||
//
|
||||
// NOTE: Support added in RS5
|
||||
type AssignedDevice = schema1.AssignedDevice
|
||||
|
||||
// ContainerConfig is used as both the input of CreateContainer
|
||||
// and to convert the parameters to JSON for passing onto the HCS
|
||||
type ContainerConfig = schema1.ContainerConfig
|
||||
|
||||
type ComputeSystemQuery = schema1.ComputeSystemQuery
|
||||
|
||||
// Container represents a created (but not necessarily running) container.
|
||||
type Container interface {
|
||||
// Start synchronously starts the container.
|
||||
Start() error
|
||||
|
||||
// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
|
||||
Shutdown() error
|
||||
|
||||
// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
|
||||
Terminate() error
|
||||
|
||||
// Waits synchronously waits for the container to shutdown or terminate.
|
||||
Wait() error
|
||||
|
||||
// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
|
||||
// returns false if timeout occurs.
|
||||
WaitTimeout(time.Duration) error
|
||||
|
||||
// Pause pauses the execution of a container.
|
||||
Pause() error
|
||||
|
||||
// Resume resumes the execution of a container.
|
||||
Resume() error
|
||||
|
||||
// HasPendingUpdates returns true if the container has updates pending to install.
|
||||
HasPendingUpdates() (bool, error)
|
||||
|
||||
// Statistics returns statistics for a container.
|
||||
Statistics() (Statistics, error)
|
||||
|
||||
// ProcessList returns details for the processes in a container.
|
||||
ProcessList() ([]ProcessListItem, error)
|
||||
|
||||
// MappedVirtualDisks returns virtual disks mapped to a utility VM, indexed by controller
|
||||
MappedVirtualDisks() (map[int]MappedVirtualDiskController, error)
|
||||
|
||||
// CreateProcess launches a new process within the container.
|
||||
CreateProcess(c *ProcessConfig) (Process, error)
|
||||
|
||||
// OpenProcess gets an interface to an existing process within the container.
|
||||
OpenProcess(pid int) (Process, error)
|
||||
|
||||
// Close cleans up any state associated with the container but does not terminate or wait for it.
|
||||
Close() error
|
||||
|
||||
// Modify the System
|
||||
Modify(config *ResourceModificationRequestResponse) error
|
||||
}
|
||||
|
||||
// Process represents a running or exited process.
|
||||
type Process interface {
|
||||
// Pid returns the process ID of the process within the container.
|
||||
Pid() int
|
||||
|
||||
// Kill signals the process to terminate but does not wait for it to finish terminating.
|
||||
Kill() error
|
||||
|
||||
// Wait waits for the process to exit.
|
||||
Wait() error
|
||||
|
||||
// WaitTimeout waits for the process to exit or the duration to elapse. It returns
|
||||
// false if timeout occurs.
|
||||
WaitTimeout(time.Duration) error
|
||||
|
||||
// ExitCode returns the exit code of the process. The process must have
|
||||
// already terminated.
|
||||
ExitCode() (int, error)
|
||||
|
||||
// ResizeConsole resizes the console of the process.
|
||||
ResizeConsole(width, height uint16) error
|
||||
|
||||
// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
|
||||
// these pipes does not close the underlying pipes; it should be possible to
|
||||
// call this multiple times to get multiple interfaces.
|
||||
Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error)
|
||||
|
||||
// CloseStdin closes the write side of the stdin pipe so that the process is
|
||||
// notified on the read side that there is no more data in stdin.
|
||||
CloseStdin() error
|
||||
|
||||
// Close cleans up any state associated with the process but does not kill
|
||||
// or wait on it.
|
||||
Close() error
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package cow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/schema1"
|
||||
hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
|
||||
)
|
||||
|
||||
// Process is the interface for an OS process running in a container or utility VM.
|
||||
type Process interface {
|
||||
// Close releases resources associated with the process and closes the
|
||||
// writer and readers returned by Stdio. Depending on the implementation,
|
||||
// this may also terminate the process.
|
||||
Close() error
|
||||
// CloseStdin causes the process's stdin handle to receive EOF/EPIPE/whatever
|
||||
// is appropriate to indicate that no more data is available.
|
||||
CloseStdin(ctx context.Context) error
|
||||
// Pid returns the process ID.
|
||||
Pid() int
|
||||
// Stdio returns the stdio streams for a process. These may be nil if a stream
|
||||
// was not requested during CreateProcess.
|
||||
Stdio() (_ io.Writer, _ io.Reader, _ io.Reader)
|
||||
// ResizeConsole resizes the virtual terminal associated with the process.
|
||||
ResizeConsole(ctx context.Context, width, height uint16) error
|
||||
// Kill sends a SIGKILL or equivalent signal to the process and returns whether
|
||||
// the signal was delivered. It does not wait for the process to terminate.
|
||||
Kill(ctx context.Context) (bool, error)
|
||||
// Signal sends a signal to the process and returns whether the signal was
|
||||
// delivered. The input is OS specific (either
|
||||
// guestrequest.SignalProcessOptionsWCOW or
|
||||
// guestrequest.SignalProcessOptionsLCOW). It does not wait for the process
|
||||
// to terminate.
|
||||
Signal(ctx context.Context, options interface{}) (bool, error)
|
||||
// Wait waits for the process to complete, or for a connection to the process to be
|
||||
// terminated by some error condition (including calling Close).
|
||||
Wait() error
|
||||
// ExitCode returns the exit code of the process. Returns an error if the process is
|
||||
// not running.
|
||||
ExitCode() (int, error)
|
||||
}
|
||||
|
||||
// ProcessHost is the interface for creating processes.
|
||||
type ProcessHost interface {
|
||||
// CreateProcess creates a process. The configuration is host specific
|
||||
// (either hcsschema.ProcessParameters or lcow.ProcessParameters).
|
||||
CreateProcess(ctx context.Context, config interface{}) (Process, error)
|
||||
// OS returns the host's operating system, "linux" or "windows".
|
||||
OS() string
|
||||
// IsOCI specifies whether this is an OCI-compliant process host. If true,
|
||||
// then the configuration passed to CreateProcess should have an OCI process
|
||||
// spec (or nil if this is the initial process in an OCI container).
|
||||
// Otherwise, it should have the HCS-specific process parameters.
|
||||
IsOCI() bool
|
||||
}
|
||||
|
||||
// Container is the interface for container objects, either running on the host or
|
||||
// in a utility VM.
|
||||
type Container interface {
|
||||
ProcessHost
|
||||
// Close releases the resources associated with the container. Depending on
|
||||
// the implementation, this may also terminate the container.
|
||||
Close() error
|
||||
// ID returns the container ID.
|
||||
ID() string
|
||||
// Properties returns the requested container properties targeting a V1 schema container.
|
||||
Properties(ctx context.Context, types ...schema1.PropertyType) (*schema1.ContainerProperties, error)
|
||||
// PropertiesV2 returns the requested container properties targeting a V2 schema container.
|
||||
PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error)
|
||||
// Start starts a container.
|
||||
Start(ctx context.Context) error
|
||||
// Shutdown sends a shutdown request to the container (but does not wait for
|
||||
// the shutdown to complete).
|
||||
Shutdown(ctx context.Context) error
|
||||
// Terminate sends a terminate request to the container (but does not wait
|
||||
// for the terminate to complete).
|
||||
Terminate(ctx context.Context) error
|
||||
// Wait waits for the container to terminate, or for the connection to the
|
||||
// container to be terminated by some error condition (including calling
|
||||
// Close).
|
||||
Wait() error
|
||||
// Modify sends a request to modify container resources
|
||||
Modify(ctx context.Context, config interface{}) error
|
||||
}
|
||||
161
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/callback.go
generated
vendored
Normal file
161
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/callback.go
generated
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package hcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/interop"
|
||||
"github.com/Microsoft/hcsshim/internal/logfields"
|
||||
"github.com/Microsoft/hcsshim/internal/vmcompute"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
nextCallback uintptr
|
||||
callbackMap = map[uintptr]*notifcationWatcherContext{}
|
||||
callbackMapLock = sync.RWMutex{}
|
||||
|
||||
notificationWatcherCallback = syscall.NewCallback(notificationWatcher)
|
||||
|
||||
// Notifications for HCS_SYSTEM handles
|
||||
hcsNotificationSystemExited hcsNotification = 0x00000001
|
||||
hcsNotificationSystemCreateCompleted hcsNotification = 0x00000002
|
||||
hcsNotificationSystemStartCompleted hcsNotification = 0x00000003
|
||||
hcsNotificationSystemPauseCompleted hcsNotification = 0x00000004
|
||||
hcsNotificationSystemResumeCompleted hcsNotification = 0x00000005
|
||||
hcsNotificationSystemCrashReport hcsNotification = 0x00000006
|
||||
hcsNotificationSystemSiloJobCreated hcsNotification = 0x00000007
|
||||
hcsNotificationSystemSaveCompleted hcsNotification = 0x00000008
|
||||
hcsNotificationSystemRdpEnhancedModeStateChanged hcsNotification = 0x00000009
|
||||
hcsNotificationSystemShutdownFailed hcsNotification = 0x0000000A
|
||||
hcsNotificationSystemGetPropertiesCompleted hcsNotification = 0x0000000B
|
||||
hcsNotificationSystemModifyCompleted hcsNotification = 0x0000000C
|
||||
hcsNotificationSystemCrashInitiated hcsNotification = 0x0000000D
|
||||
hcsNotificationSystemGuestConnectionClosed hcsNotification = 0x0000000E
|
||||
|
||||
// Notifications for HCS_PROCESS handles
|
||||
hcsNotificationProcessExited hcsNotification = 0x00010000
|
||||
|
||||
// Common notifications
|
||||
hcsNotificationInvalid hcsNotification = 0x00000000
|
||||
hcsNotificationServiceDisconnect hcsNotification = 0x01000000
|
||||
)
|
||||
|
||||
type hcsNotification uint32
|
||||
|
||||
func (hn hcsNotification) String() string {
|
||||
switch hn {
|
||||
case hcsNotificationSystemExited:
|
||||
return "SystemExited"
|
||||
case hcsNotificationSystemCreateCompleted:
|
||||
return "SystemCreateCompleted"
|
||||
case hcsNotificationSystemStartCompleted:
|
||||
return "SystemStartCompleted"
|
||||
case hcsNotificationSystemPauseCompleted:
|
||||
return "SystemPauseCompleted"
|
||||
case hcsNotificationSystemResumeCompleted:
|
||||
return "SystemResumeCompleted"
|
||||
case hcsNotificationSystemCrashReport:
|
||||
return "SystemCrashReport"
|
||||
case hcsNotificationSystemSiloJobCreated:
|
||||
return "SystemSiloJobCreated"
|
||||
case hcsNotificationSystemSaveCompleted:
|
||||
return "SystemSaveCompleted"
|
||||
case hcsNotificationSystemRdpEnhancedModeStateChanged:
|
||||
return "SystemRdpEnhancedModeStateChanged"
|
||||
case hcsNotificationSystemShutdownFailed:
|
||||
return "SystemShutdownFailed"
|
||||
case hcsNotificationSystemGetPropertiesCompleted:
|
||||
return "SystemGetPropertiesCompleted"
|
||||
case hcsNotificationSystemModifyCompleted:
|
||||
return "SystemModifyCompleted"
|
||||
case hcsNotificationSystemCrashInitiated:
|
||||
return "SystemCrashInitiated"
|
||||
case hcsNotificationSystemGuestConnectionClosed:
|
||||
return "SystemGuestConnectionClosed"
|
||||
case hcsNotificationProcessExited:
|
||||
return "ProcessExited"
|
||||
case hcsNotificationInvalid:
|
||||
return "Invalid"
|
||||
case hcsNotificationServiceDisconnect:
|
||||
return "ServiceDisconnect"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown: %d", hn)
|
||||
}
|
||||
}
|
||||
|
||||
type notificationChannel chan error
|
||||
|
||||
type notifcationWatcherContext struct {
|
||||
channels notificationChannels
|
||||
handle vmcompute.HcsCallback
|
||||
|
||||
systemID string
|
||||
processID int
|
||||
}
|
||||
|
||||
type notificationChannels map[hcsNotification]notificationChannel
|
||||
|
||||
func newSystemChannels() notificationChannels {
|
||||
channels := make(notificationChannels)
|
||||
for _, notif := range []hcsNotification{
|
||||
hcsNotificationServiceDisconnect,
|
||||
hcsNotificationSystemExited,
|
||||
hcsNotificationSystemCreateCompleted,
|
||||
hcsNotificationSystemStartCompleted,
|
||||
hcsNotificationSystemPauseCompleted,
|
||||
hcsNotificationSystemResumeCompleted,
|
||||
hcsNotificationSystemSaveCompleted,
|
||||
} {
|
||||
channels[notif] = make(notificationChannel, 1)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
func newProcessChannels() notificationChannels {
|
||||
channels := make(notificationChannels)
|
||||
for _, notif := range []hcsNotification{
|
||||
hcsNotificationServiceDisconnect,
|
||||
hcsNotificationProcessExited,
|
||||
} {
|
||||
channels[notif] = make(notificationChannel, 1)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
func closeChannels(channels notificationChannels) {
|
||||
for _, c := range channels {
|
||||
close(c)
|
||||
}
|
||||
}
|
||||
|
||||
func notificationWatcher(notificationType hcsNotification, callbackNumber uintptr, notificationStatus uintptr, notificationData *uint16) uintptr {
|
||||
var result error
|
||||
if int32(notificationStatus) < 0 {
|
||||
result = interop.Win32FromHresult(notificationStatus)
|
||||
}
|
||||
|
||||
callbackMapLock.RLock()
|
||||
context := callbackMap[callbackNumber]
|
||||
callbackMapLock.RUnlock()
|
||||
|
||||
if context == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
log := logrus.WithFields(logrus.Fields{
|
||||
"notification-type": notificationType.String(),
|
||||
"system-id": context.systemID,
|
||||
})
|
||||
if context.processID != 0 {
|
||||
log.Data[logfields.ProcessID] = context.processID
|
||||
}
|
||||
log.Debug("HCS notification")
|
||||
|
||||
if channel, ok := context.channels[notificationType]; ok {
|
||||
channel <- result
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
329
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/errors.go
generated
vendored
Normal file
329
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
package hcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists
|
||||
ErrComputeSystemDoesNotExist = syscall.Errno(0xc037010e)
|
||||
|
||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
||||
ErrElementNotFound = syscall.Errno(0x490)
|
||||
|
||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
||||
ErrNotSupported = syscall.Errno(0x32)
|
||||
|
||||
// ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported
|
||||
// decimal -2147024883 / hex 0x8007000d
|
||||
ErrInvalidData = syscall.Errno(0xd)
|
||||
|
||||
// ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed
|
||||
ErrHandleClose = errors.New("hcsshim: the handle generating this notification has been closed")
|
||||
|
||||
// ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method
|
||||
ErrAlreadyClosed = errors.New("hcsshim: the handle has already been closed")
|
||||
|
||||
// ErrInvalidNotificationType is an error encountered when an invalid notification type is used
|
||||
ErrInvalidNotificationType = errors.New("hcsshim: invalid notification type")
|
||||
|
||||
// ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation
|
||||
ErrInvalidProcessState = errors.New("the process is in an invalid state for the attempted operation")
|
||||
|
||||
// ErrTimeout is an error encountered when waiting on a notification times out
|
||||
ErrTimeout = errors.New("hcsshim: timeout waiting for notification")
|
||||
|
||||
// ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for
|
||||
// a different expected notification
|
||||
ErrUnexpectedContainerExit = errors.New("unexpected container exit")
|
||||
|
||||
// ErrUnexpectedProcessAbort is the error encountered when communication with the compute service
|
||||
// is lost while waiting for a notification
|
||||
ErrUnexpectedProcessAbort = errors.New("lost communication with compute service")
|
||||
|
||||
// ErrUnexpectedValue is an error encountered when hcs returns an invalid value
|
||||
ErrUnexpectedValue = errors.New("unexpected value returned from hcs")
|
||||
|
||||
// ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container
|
||||
ErrVmcomputeAlreadyStopped = syscall.Errno(0xc0370110)
|
||||
|
||||
// ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously
|
||||
ErrVmcomputeOperationPending = syscall.Errno(0xC0370103)
|
||||
|
||||
// ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation
|
||||
ErrVmcomputeOperationInvalidState = syscall.Errno(0xc0370105)
|
||||
|
||||
// ErrProcNotFound is an error encountered when the the process cannot be found
|
||||
ErrProcNotFound = syscall.Errno(0x7f)
|
||||
|
||||
// ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2
|
||||
// builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3.
|
||||
ErrVmcomputeOperationAccessIsDenied = syscall.Errno(0x5)
|
||||
|
||||
// ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management
|
||||
ErrVmcomputeInvalidJSON = syscall.Errno(0xc037010d)
|
||||
|
||||
// ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message
|
||||
ErrVmcomputeUnknownMessage = syscall.Errno(0xc037010b)
|
||||
|
||||
// ErrVmcomputeUnexpectedExit is an error encountered when the compute system terminates unexpectedly
|
||||
ErrVmcomputeUnexpectedExit = syscall.Errno(0xC0370106)
|
||||
|
||||
// ErrNotSupported is an error encountered when hcs doesn't support the request
|
||||
ErrPlatformNotSupported = errors.New("unsupported platform request")
|
||||
)
|
||||
|
||||
type ErrorEvent struct {
|
||||
Message string `json:"Message,omitempty"` // Fully formated error message
|
||||
StackTrace string `json:"StackTrace,omitempty"` // Stack trace in string form
|
||||
Provider string `json:"Provider,omitempty"`
|
||||
EventID uint16 `json:"EventId,omitempty"`
|
||||
Flags uint32 `json:"Flags,omitempty"`
|
||||
Source string `json:"Source,omitempty"`
|
||||
//Data []EventData `json:"Data,omitempty"` // Omit this as HCS doesn't encode this well. It's more confusing to include. It is however logged in debug mode (see processHcsResult function)
|
||||
}
|
||||
|
||||
type hcsResult struct {
|
||||
Error int32
|
||||
ErrorMessage string
|
||||
ErrorEvents []ErrorEvent `json:"ErrorEvents,omitempty"`
|
||||
}
|
||||
|
||||
func (ev *ErrorEvent) String() string {
|
||||
evs := "[Event Detail: " + ev.Message
|
||||
if ev.StackTrace != "" {
|
||||
evs += " Stack Trace: " + ev.StackTrace
|
||||
}
|
||||
if ev.Provider != "" {
|
||||
evs += " Provider: " + ev.Provider
|
||||
}
|
||||
if ev.EventID != 0 {
|
||||
evs = fmt.Sprintf("%s EventID: %d", evs, ev.EventID)
|
||||
}
|
||||
if ev.Flags != 0 {
|
||||
evs = fmt.Sprintf("%s flags: %d", evs, ev.Flags)
|
||||
}
|
||||
if ev.Source != "" {
|
||||
evs += " Source: " + ev.Source
|
||||
}
|
||||
evs += "]"
|
||||
return evs
|
||||
}
|
||||
|
||||
func processHcsResult(ctx context.Context, resultJSON string) []ErrorEvent {
|
||||
if resultJSON != "" {
|
||||
result := &hcsResult{}
|
||||
if err := json.Unmarshal([]byte(resultJSON), result); err != nil {
|
||||
log.G(ctx).WithError(err).Warning("Could not unmarshal HCS result")
|
||||
return nil
|
||||
}
|
||||
return result.ErrorEvents
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type HcsError struct {
|
||||
Op string
|
||||
Err error
|
||||
Events []ErrorEvent
|
||||
}
|
||||
|
||||
var _ net.Error = &HcsError{}
|
||||
|
||||
func (e *HcsError) Error() string {
|
||||
s := e.Op + ": " + e.Err.Error()
|
||||
for _, ev := range e.Events {
|
||||
s += "\n" + ev.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *HcsError) Temporary() bool {
|
||||
err, ok := e.Err.(net.Error)
|
||||
return ok && err.Temporary()
|
||||
}
|
||||
|
||||
func (e *HcsError) Timeout() bool {
|
||||
err, ok := e.Err.(net.Error)
|
||||
return ok && err.Timeout()
|
||||
}
|
||||
|
||||
// ProcessError is an error encountered in HCS during an operation on a Process object
|
||||
type ProcessError struct {
|
||||
SystemID string
|
||||
Pid int
|
||||
Op string
|
||||
Err error
|
||||
Events []ErrorEvent
|
||||
}
|
||||
|
||||
var _ net.Error = &ProcessError{}
|
||||
|
||||
// SystemError is an error encountered in HCS during an operation on a Container object
|
||||
type SystemError struct {
|
||||
ID string
|
||||
Op string
|
||||
Err error
|
||||
Events []ErrorEvent
|
||||
}
|
||||
|
||||
var _ net.Error = &SystemError{}
|
||||
|
||||
func (e *SystemError) Error() string {
|
||||
s := e.Op + " " + e.ID + ": " + e.Err.Error()
|
||||
for _, ev := range e.Events {
|
||||
s += "\n" + ev.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *SystemError) Temporary() bool {
|
||||
err, ok := e.Err.(net.Error)
|
||||
return ok && err.Temporary()
|
||||
}
|
||||
|
||||
func (e *SystemError) Timeout() bool {
|
||||
err, ok := e.Err.(net.Error)
|
||||
return ok && err.Timeout()
|
||||
}
|
||||
|
||||
func makeSystemError(system *System, op string, err error, events []ErrorEvent) error {
|
||||
// Don't double wrap errors
|
||||
if _, ok := err.(*SystemError); ok {
|
||||
return err
|
||||
}
|
||||
return &SystemError{
|
||||
ID: system.ID(),
|
||||
Op: op,
|
||||
Err: err,
|
||||
Events: events,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ProcessError) Error() string {
|
||||
s := fmt.Sprintf("%s %s:%d: %s", e.Op, e.SystemID, e.Pid, e.Err.Error())
|
||||
for _, ev := range e.Events {
|
||||
s += "\n" + ev.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *ProcessError) Temporary() bool {
|
||||
err, ok := e.Err.(net.Error)
|
||||
return ok && err.Temporary()
|
||||
}
|
||||
|
||||
func (e *ProcessError) Timeout() bool {
|
||||
err, ok := e.Err.(net.Error)
|
||||
return ok && err.Timeout()
|
||||
}
|
||||
|
||||
func makeProcessError(process *Process, op string, err error, events []ErrorEvent) error {
|
||||
// Don't double wrap errors
|
||||
if _, ok := err.(*ProcessError); ok {
|
||||
return err
|
||||
}
|
||||
return &ProcessError{
|
||||
Pid: process.Pid(),
|
||||
SystemID: process.SystemID(),
|
||||
Op: op,
|
||||
Err: err,
|
||||
Events: events,
|
||||
}
|
||||
}
|
||||
|
||||
// IsNotExist checks if an error is caused by the Container or Process not existing.
|
||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
||||
// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
|
||||
func IsNotExist(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrComputeSystemDoesNotExist ||
|
||||
err == ErrElementNotFound ||
|
||||
err == ErrProcNotFound
|
||||
}
|
||||
|
||||
// IsAlreadyClosed checks if an error is caused by the Container or Process having been
|
||||
// already closed by a call to the Close() method.
|
||||
func IsAlreadyClosed(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrAlreadyClosed
|
||||
}
|
||||
|
||||
// IsPending returns a boolean indicating whether the error is that
|
||||
// the requested operation is being completed in the background.
|
||||
func IsPending(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrVmcomputeOperationPending
|
||||
}
|
||||
|
||||
// IsTimeout returns a boolean indicating whether the error is caused by
|
||||
// a timeout waiting for the operation to complete.
|
||||
func IsTimeout(err error) bool {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return true
|
||||
}
|
||||
err = getInnerError(err)
|
||||
return err == ErrTimeout
|
||||
}
|
||||
|
||||
// IsAlreadyStopped returns a boolean indicating whether the error is caused by
|
||||
// a Container or Process being already stopped.
|
||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
||||
// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
|
||||
func IsAlreadyStopped(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrVmcomputeAlreadyStopped ||
|
||||
err == ErrElementNotFound ||
|
||||
err == ErrProcNotFound
|
||||
}
|
||||
|
||||
// IsNotSupported returns a boolean indicating whether the error is caused by
|
||||
// unsupported platform requests
|
||||
// Note: Currently Unsupported platform requests can be mean either
|
||||
// ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage
|
||||
// is thrown from the Platform
|
||||
func IsNotSupported(err error) bool {
|
||||
err = getInnerError(err)
|
||||
// If Platform doesn't recognize or support the request sent, below errors are seen
|
||||
return err == ErrVmcomputeInvalidJSON ||
|
||||
err == ErrInvalidData ||
|
||||
err == ErrNotSupported ||
|
||||
err == ErrVmcomputeUnknownMessage
|
||||
}
|
||||
|
||||
// IsOperationInvalidState returns true when err is caused by
|
||||
// `ErrVmcomputeOperationInvalidState`.
|
||||
func IsOperationInvalidState(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrVmcomputeOperationInvalidState
|
||||
}
|
||||
|
||||
// IsAccessIsDenied returns true when err is caused by
|
||||
// `ErrVmcomputeOperationAccessIsDenied`.
|
||||
func IsAccessIsDenied(err error) bool {
|
||||
err = getInnerError(err)
|
||||
return err == ErrVmcomputeOperationAccessIsDenied
|
||||
}
|
||||
|
||||
func getInnerError(err error) error {
|
||||
switch pe := err.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *HcsError:
|
||||
err = pe.Err
|
||||
case *SystemError:
|
||||
err = pe.Err
|
||||
case *ProcessError:
|
||||
err = pe.Err
|
||||
}
|
||||
return err
|
||||
}
|
||||
472
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/process.go
generated
vendored
Normal file
472
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/process.go
generated
vendored
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
package hcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/log"
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/Microsoft/hcsshim/internal/vmcompute"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ContainerError is an error encountered in HCS
|
||||
type Process struct {
|
||||
handleLock sync.RWMutex
|
||||
handle vmcompute.HcsProcess
|
||||
processID int
|
||||
system *System
|
||||
hasCachedStdio bool
|
||||
stdioLock sync.Mutex
|
||||
stdin io.WriteCloser
|
||||
stdout io.ReadCloser
|
||||
stderr io.ReadCloser
|
||||
callbackNumber uintptr
|
||||
|
||||
closedWaitOnce sync.Once
|
||||
waitBlock chan struct{}
|
||||
exitCode int
|
||||
waitError error
|
||||
}
|
||||
|
||||
func newProcess(process vmcompute.HcsProcess, processID int, computeSystem *System) *Process {
|
||||
return &Process{
|
||||
handle: process,
|
||||
processID: processID,
|
||||
system: computeSystem,
|
||||
waitBlock: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type processModifyRequest struct {
|
||||
Operation string
|
||||
ConsoleSize *consoleSize `json:",omitempty"`
|
||||
CloseHandle *closeHandle `json:",omitempty"`
|
||||
}
|
||||
|
||||
type consoleSize struct {
|
||||
Height uint16
|
||||
Width uint16
|
||||
}
|
||||
|
||||
type closeHandle struct {
|
||||
Handle string
|
||||
}
|
||||
|
||||
type processStatus struct {
|
||||
ProcessID uint32
|
||||
Exited bool
|
||||
ExitCode uint32
|
||||
LastWaitResult int32
|
||||
}
|
||||
|
||||
const stdIn string = "StdIn"
|
||||
|
||||
const (
|
||||
modifyConsoleSize string = "ConsoleSize"
|
||||
modifyCloseHandle string = "CloseHandle"
|
||||
)
|
||||
|
||||
// Pid returns the process ID of the process within the container.
|
||||
func (process *Process) Pid() int {
|
||||
return process.processID
|
||||
}
|
||||
|
||||
// SystemID returns the ID of the process's compute system.
|
||||
func (process *Process) SystemID() string {
|
||||
return process.system.ID()
|
||||
}
|
||||
|
||||
func (process *Process) processSignalResult(ctx context.Context, err error) (bool, error) {
|
||||
switch err {
|
||||
case nil:
|
||||
return true, nil
|
||||
case ErrVmcomputeOperationInvalidState, ErrComputeSystemDoesNotExist, ErrElementNotFound:
|
||||
select {
|
||||
case <-process.waitBlock:
|
||||
// The process exit notification has already arrived.
|
||||
default:
|
||||
// The process should be gone, but we have not received the notification.
|
||||
// After a second, force unblock the process wait to work around a possible
|
||||
// deadlock in the HCS.
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
process.closedWaitOnce.Do(func() {
|
||||
log.G(ctx).WithError(err).Warn("force unblocking process waits")
|
||||
process.exitCode = -1
|
||||
process.waitError = err
|
||||
close(process.waitBlock)
|
||||
})
|
||||
}()
|
||||
}
|
||||
return false, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Signal signals the process with `options`.
|
||||
//
|
||||
// For LCOW `guestrequest.SignalProcessOptionsLCOW`.
|
||||
//
|
||||
// For WCOW `guestrequest.SignalProcessOptionsWCOW`.
|
||||
func (process *Process) Signal(ctx context.Context, options interface{}) (bool, error) {
|
||||
process.handleLock.RLock()
|
||||
defer process.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::Process::Signal"
|
||||
|
||||
if process.handle == 0 {
|
||||
return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
optionsb, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsSignalProcess(ctx, process.handle, string(optionsb))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
delivered, err := process.processSignalResult(ctx, err)
|
||||
if err != nil {
|
||||
err = makeProcessError(process, operation, err, events)
|
||||
}
|
||||
return delivered, err
|
||||
}
|
||||
|
||||
// Kill signals the process to terminate but does not wait for it to finish terminating.
|
||||
func (process *Process) Kill(ctx context.Context) (bool, error) {
|
||||
process.handleLock.RLock()
|
||||
defer process.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::Process::Kill"
|
||||
|
||||
if process.handle == 0 {
|
||||
return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsTerminateProcess(ctx, process.handle)
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
delivered, err := process.processSignalResult(ctx, err)
|
||||
if err != nil {
|
||||
err = makeProcessError(process, operation, err, events)
|
||||
}
|
||||
return delivered, err
|
||||
}
|
||||
|
||||
// waitBackground waits for the process exit notification. Once received sets
|
||||
// `process.waitError` (if any) and unblocks all `Wait` calls.
|
||||
//
|
||||
// This MUST be called exactly once per `process.handle` but `Wait` is safe to
|
||||
// call multiple times.
|
||||
func (process *Process) waitBackground() {
|
||||
operation := "hcsshim::Process::waitBackground"
|
||||
ctx, span := trace.StartSpan(context.Background(), operation)
|
||||
defer span.End()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("cid", process.SystemID()),
|
||||
trace.Int64Attribute("pid", int64(process.processID)))
|
||||
|
||||
var (
|
||||
err error
|
||||
exitCode = -1
|
||||
propertiesJSON string
|
||||
resultJSON string
|
||||
)
|
||||
|
||||
err = waitForNotification(ctx, process.callbackNumber, hcsNotificationProcessExited, nil)
|
||||
if err != nil {
|
||||
err = makeProcessError(process, operation, err, nil)
|
||||
log.G(ctx).WithError(err).Error("failed wait")
|
||||
} else {
|
||||
process.handleLock.RLock()
|
||||
defer process.handleLock.RUnlock()
|
||||
|
||||
// Make sure we didnt race with Close() here
|
||||
if process.handle != 0 {
|
||||
propertiesJSON, resultJSON, err = vmcompute.HcsGetProcessProperties(ctx, process.handle)
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
err = makeProcessError(process, operation, err, events) //nolint:ineffassign
|
||||
} else {
|
||||
properties := &processStatus{}
|
||||
err = json.Unmarshal([]byte(propertiesJSON), properties)
|
||||
if err != nil {
|
||||
err = makeProcessError(process, operation, err, nil) //nolint:ineffassign
|
||||
} else {
|
||||
if properties.LastWaitResult != 0 {
|
||||
log.G(ctx).WithField("wait-result", properties.LastWaitResult).Warning("non-zero last wait result")
|
||||
} else {
|
||||
exitCode = int(properties.ExitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.G(ctx).WithField("exitCode", exitCode).Debug("process exited")
|
||||
|
||||
process.closedWaitOnce.Do(func() {
|
||||
process.exitCode = exitCode
|
||||
process.waitError = err
|
||||
close(process.waitBlock)
|
||||
})
|
||||
oc.SetSpanStatus(span, err)
|
||||
}
|
||||
|
||||
// Wait waits for the process to exit. If the process has already exited returns
|
||||
// the pervious error (if any).
|
||||
func (process *Process) Wait() error {
|
||||
<-process.waitBlock
|
||||
return process.waitError
|
||||
}
|
||||
|
||||
// ResizeConsole resizes the console of the process.
|
||||
func (process *Process) ResizeConsole(ctx context.Context, width, height uint16) error {
|
||||
process.handleLock.RLock()
|
||||
defer process.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::Process::ResizeConsole"
|
||||
|
||||
if process.handle == 0 {
|
||||
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
modifyRequest := processModifyRequest{
|
||||
Operation: modifyConsoleSize,
|
||||
ConsoleSize: &consoleSize{
|
||||
Height: height,
|
||||
Width: width,
|
||||
},
|
||||
}
|
||||
|
||||
modifyRequestb, err := json.Marshal(modifyRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return makeProcessError(process, operation, err, events)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExitCode returns the exit code of the process. The process must have
|
||||
// already terminated.
|
||||
func (process *Process) ExitCode() (int, error) {
|
||||
select {
|
||||
case <-process.waitBlock:
|
||||
if process.waitError != nil {
|
||||
return -1, process.waitError
|
||||
}
|
||||
return process.exitCode, nil
|
||||
default:
|
||||
return -1, makeProcessError(process, "hcsshim::Process::ExitCode", ErrInvalidProcessState, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// StdioLegacy returns the stdin, stdout, and stderr pipes, respectively. Closing
|
||||
// these pipes does not close the underlying pipes. Once returned, these pipes
|
||||
// are the responsibility of the caller to close.
|
||||
func (process *Process) StdioLegacy() (_ io.WriteCloser, _ io.ReadCloser, _ io.ReadCloser, err error) {
|
||||
operation := "hcsshim::Process::StdioLegacy"
|
||||
ctx, span := trace.StartSpan(context.Background(), operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("cid", process.SystemID()),
|
||||
trace.Int64Attribute("pid", int64(process.processID)))
|
||||
|
||||
process.handleLock.RLock()
|
||||
defer process.handleLock.RUnlock()
|
||||
|
||||
if process.handle == 0 {
|
||||
return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
process.stdioLock.Lock()
|
||||
defer process.stdioLock.Unlock()
|
||||
if process.hasCachedStdio {
|
||||
stdin, stdout, stderr := process.stdin, process.stdout, process.stderr
|
||||
process.stdin, process.stdout, process.stderr = nil, nil, nil
|
||||
process.hasCachedStdio = false
|
||||
return stdin, stdout, stderr, nil
|
||||
}
|
||||
|
||||
processInfo, resultJSON, err := vmcompute.HcsGetProcessInfo(ctx, process.handle)
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, nil, nil, makeProcessError(process, operation, err, events)
|
||||
}
|
||||
|
||||
pipes, err := makeOpenFiles([]syscall.Handle{processInfo.StdInput, processInfo.StdOutput, processInfo.StdError})
|
||||
if err != nil {
|
||||
return nil, nil, nil, makeProcessError(process, operation, err, nil)
|
||||
}
|
||||
|
||||
return pipes[0], pipes[1], pipes[2], nil
|
||||
}
|
||||
|
||||
// Stdio returns the stdin, stdout, and stderr pipes, respectively.
|
||||
// To close them, close the process handle.
|
||||
func (process *Process) Stdio() (stdin io.Writer, stdout, stderr io.Reader) {
|
||||
process.stdioLock.Lock()
|
||||
defer process.stdioLock.Unlock()
|
||||
return process.stdin, process.stdout, process.stderr
|
||||
}
|
||||
|
||||
// CloseStdin closes the write side of the stdin pipe so that the process is
|
||||
// notified on the read side that there is no more data in stdin.
|
||||
func (process *Process) CloseStdin(ctx context.Context) error {
|
||||
process.handleLock.RLock()
|
||||
defer process.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::Process::CloseStdin"
|
||||
|
||||
if process.handle == 0 {
|
||||
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
modifyRequest := processModifyRequest{
|
||||
Operation: modifyCloseHandle,
|
||||
CloseHandle: &closeHandle{
|
||||
Handle: stdIn,
|
||||
},
|
||||
}
|
||||
|
||||
modifyRequestb, err := json.Marshal(modifyRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return makeProcessError(process, operation, err, events)
|
||||
}
|
||||
|
||||
process.stdioLock.Lock()
|
||||
if process.stdin != nil {
|
||||
process.stdin.Close()
|
||||
process.stdin = nil
|
||||
}
|
||||
process.stdioLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close cleans up any state associated with the process but does not kill
|
||||
// or wait on it.
|
||||
func (process *Process) Close() (err error) {
|
||||
operation := "hcsshim::Process::Close"
|
||||
ctx, span := trace.StartSpan(context.Background(), operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("cid", process.SystemID()),
|
||||
trace.Int64Attribute("pid", int64(process.processID)))
|
||||
|
||||
process.handleLock.Lock()
|
||||
defer process.handleLock.Unlock()
|
||||
|
||||
// Don't double free this
|
||||
if process.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
process.stdioLock.Lock()
|
||||
if process.stdin != nil {
|
||||
process.stdin.Close()
|
||||
process.stdin = nil
|
||||
}
|
||||
if process.stdout != nil {
|
||||
process.stdout.Close()
|
||||
process.stdout = nil
|
||||
}
|
||||
if process.stderr != nil {
|
||||
process.stderr.Close()
|
||||
process.stderr = nil
|
||||
}
|
||||
process.stdioLock.Unlock()
|
||||
|
||||
if err = process.unregisterCallback(ctx); err != nil {
|
||||
return makeProcessError(process, operation, err, nil)
|
||||
}
|
||||
|
||||
if err = vmcompute.HcsCloseProcess(ctx, process.handle); err != nil {
|
||||
return makeProcessError(process, operation, err, nil)
|
||||
}
|
||||
|
||||
process.handle = 0
|
||||
process.closedWaitOnce.Do(func() {
|
||||
process.exitCode = -1
|
||||
process.waitError = ErrAlreadyClosed
|
||||
close(process.waitBlock)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (process *Process) registerCallback(ctx context.Context) error {
|
||||
callbackContext := ¬ifcationWatcherContext{
|
||||
channels: newProcessChannels(),
|
||||
systemID: process.SystemID(),
|
||||
processID: process.processID,
|
||||
}
|
||||
|
||||
callbackMapLock.Lock()
|
||||
callbackNumber := nextCallback
|
||||
nextCallback++
|
||||
callbackMap[callbackNumber] = callbackContext
|
||||
callbackMapLock.Unlock()
|
||||
|
||||
callbackHandle, err := vmcompute.HcsRegisterProcessCallback(ctx, process.handle, notificationWatcherCallback, callbackNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
callbackContext.handle = callbackHandle
|
||||
process.callbackNumber = callbackNumber
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (process *Process) unregisterCallback(ctx context.Context) error {
|
||||
callbackNumber := process.callbackNumber
|
||||
|
||||
callbackMapLock.RLock()
|
||||
callbackContext := callbackMap[callbackNumber]
|
||||
callbackMapLock.RUnlock()
|
||||
|
||||
if callbackContext == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
handle := callbackContext.handle
|
||||
|
||||
if handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// vmcompute.HcsUnregisterProcessCallback has its own synchronization to
|
||||
// wait for all callbacks to complete. We must NOT hold the callbackMapLock.
|
||||
err := vmcompute.HcsUnregisterProcessCallback(ctx, handle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
closeChannels(callbackContext.channels)
|
||||
|
||||
callbackMapLock.Lock()
|
||||
delete(callbackMap, callbackNumber)
|
||||
callbackMapLock.Unlock()
|
||||
|
||||
handle = 0 //nolint:ineffassign
|
||||
|
||||
return nil
|
||||
}
|
||||
49
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/service.go
generated
vendored
Normal file
49
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/service.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package hcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
|
||||
"github.com/Microsoft/hcsshim/internal/vmcompute"
|
||||
)
|
||||
|
||||
// GetServiceProperties returns properties of the host compute service.
|
||||
func GetServiceProperties(ctx context.Context, q hcsschema.PropertyQuery) (*hcsschema.ServiceProperties, error) {
|
||||
operation := "hcsshim::GetServiceProperties"
|
||||
|
||||
queryb, err := json.Marshal(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
propertiesJSON, resultJSON, err := vmcompute.HcsGetServiceProperties(ctx, string(queryb))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, &HcsError{Op: operation, Err: err, Events: events}
|
||||
}
|
||||
|
||||
if propertiesJSON == "" {
|
||||
return nil, ErrUnexpectedValue
|
||||
}
|
||||
properties := &hcsschema.ServiceProperties{}
|
||||
if err := json.Unmarshal([]byte(propertiesJSON), properties); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return properties, nil
|
||||
}
|
||||
|
||||
// ModifyServiceSettings modifies settings of the host compute service.
|
||||
func ModifyServiceSettings(ctx context.Context, settings hcsschema.ModificationRequest) error {
|
||||
operation := "hcsshim::ModifyServiceSettings"
|
||||
|
||||
settingsJSON, err := json.Marshal(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resultJSON, err := vmcompute.HcsModifyServiceSettings(ctx, string(settingsJSON))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return &HcsError{Op: operation, Err: err, Events: events}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
637
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go
generated
vendored
Normal file
637
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go
generated
vendored
Normal file
|
|
@ -0,0 +1,637 @@
|
|||
package hcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/cow"
|
||||
"github.com/Microsoft/hcsshim/internal/log"
|
||||
"github.com/Microsoft/hcsshim/internal/oc"
|
||||
"github.com/Microsoft/hcsshim/internal/schema1"
|
||||
hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
|
||||
"github.com/Microsoft/hcsshim/internal/timeout"
|
||||
"github.com/Microsoft/hcsshim/internal/vmcompute"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
type System struct {
|
||||
handleLock sync.RWMutex
|
||||
handle vmcompute.HcsSystem
|
||||
id string
|
||||
callbackNumber uintptr
|
||||
|
||||
closedWaitOnce sync.Once
|
||||
waitBlock chan struct{}
|
||||
waitError error
|
||||
exitError error
|
||||
os, typ string
|
||||
}
|
||||
|
||||
func newSystem(id string) *System {
|
||||
return &System{
|
||||
id: id,
|
||||
waitBlock: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// CreateComputeSystem creates a new compute system with the given configuration but does not start it.
|
||||
func CreateComputeSystem(ctx context.Context, id string, hcsDocumentInterface interface{}) (_ *System, err error) {
|
||||
operation := "hcsshim::CreateComputeSystem"
|
||||
|
||||
// hcsCreateComputeSystemContext is an async operation. Start the outer span
|
||||
// here to measure the full create time.
|
||||
ctx, span := trace.StartSpan(ctx, operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("cid", id))
|
||||
|
||||
computeSystem := newSystem(id)
|
||||
|
||||
hcsDocumentB, err := json.Marshal(hcsDocumentInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hcsDocument := string(hcsDocumentB)
|
||||
|
||||
var (
|
||||
identity syscall.Handle
|
||||
resultJSON string
|
||||
createError error
|
||||
)
|
||||
computeSystem.handle, resultJSON, createError = vmcompute.HcsCreateComputeSystem(ctx, id, hcsDocument, identity)
|
||||
if createError == nil || IsPending(createError) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
computeSystem.Close()
|
||||
}
|
||||
}()
|
||||
if err = computeSystem.registerCallback(ctx); err != nil {
|
||||
// Terminate the compute system if it still exists. We're okay to
|
||||
// ignore a failure here.
|
||||
_ = computeSystem.Terminate(ctx)
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
events, err := processAsyncHcsResult(ctx, createError, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemCreateCompleted, &timeout.SystemCreate)
|
||||
if err != nil {
|
||||
if err == ErrTimeout {
|
||||
// Terminate the compute system if it still exists. We're okay to
|
||||
// ignore a failure here.
|
||||
_ = computeSystem.Terminate(ctx)
|
||||
}
|
||||
return nil, makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
go computeSystem.waitBackground()
|
||||
if err = computeSystem.getCachedProperties(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return computeSystem, nil
|
||||
}
|
||||
|
||||
// OpenComputeSystem opens an existing compute system by ID.
|
||||
func OpenComputeSystem(ctx context.Context, id string) (*System, error) {
|
||||
operation := "hcsshim::OpenComputeSystem"
|
||||
|
||||
computeSystem := newSystem(id)
|
||||
handle, resultJSON, err := vmcompute.HcsOpenComputeSystem(ctx, id)
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
computeSystem.handle = handle
|
||||
defer func() {
|
||||
if err != nil {
|
||||
computeSystem.Close()
|
||||
}
|
||||
}()
|
||||
if err = computeSystem.registerCallback(ctx); err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
go computeSystem.waitBackground()
|
||||
if err = computeSystem.getCachedProperties(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return computeSystem, nil
|
||||
}
|
||||
|
||||
func (computeSystem *System) getCachedProperties(ctx context.Context) error {
|
||||
props, err := computeSystem.Properties(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
computeSystem.typ = strings.ToLower(props.SystemType)
|
||||
computeSystem.os = strings.ToLower(props.RuntimeOSType)
|
||||
if computeSystem.os == "" && computeSystem.typ == "container" {
|
||||
// Pre-RS5 HCS did not return the OS, but it only supported containers
|
||||
// that ran Windows.
|
||||
computeSystem.os = "windows"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OS returns the operating system of the compute system, "linux" or "windows".
|
||||
func (computeSystem *System) OS() string {
|
||||
return computeSystem.os
|
||||
}
|
||||
|
||||
// IsOCI returns whether processes in the compute system should be created via
|
||||
// OCI.
|
||||
func (computeSystem *System) IsOCI() bool {
|
||||
return computeSystem.os == "linux" && computeSystem.typ == "container"
|
||||
}
|
||||
|
||||
// GetComputeSystems gets a list of the compute systems on the system that match the query
|
||||
func GetComputeSystems(ctx context.Context, q schema1.ComputeSystemQuery) ([]schema1.ContainerProperties, error) {
|
||||
operation := "hcsshim::GetComputeSystems"
|
||||
|
||||
queryb, err := json.Marshal(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
computeSystemsJSON, resultJSON, err := vmcompute.HcsEnumerateComputeSystems(ctx, string(queryb))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, &HcsError{Op: operation, Err: err, Events: events}
|
||||
}
|
||||
|
||||
if computeSystemsJSON == "" {
|
||||
return nil, ErrUnexpectedValue
|
||||
}
|
||||
computeSystems := []schema1.ContainerProperties{}
|
||||
if err = json.Unmarshal([]byte(computeSystemsJSON), &computeSystems); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return computeSystems, nil
|
||||
}
|
||||
|
||||
// Start synchronously starts the computeSystem.
|
||||
func (computeSystem *System) Start(ctx context.Context) (err error) {
|
||||
operation := "hcsshim::System::Start"
|
||||
|
||||
// hcsStartComputeSystemContext is an async operation. Start the outer span
|
||||
// here to measure the full start time.
|
||||
ctx, span := trace.StartSpan(ctx, operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
|
||||
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsStartComputeSystem(ctx, computeSystem.handle, "")
|
||||
events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemStartCompleted, &timeout.SystemStart)
|
||||
if err != nil {
|
||||
return makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns the compute system's identifier.
|
||||
func (computeSystem *System) ID() string {
|
||||
return computeSystem.id
|
||||
}
|
||||
|
||||
// Shutdown requests a compute system shutdown.
|
||||
func (computeSystem *System) Shutdown(ctx context.Context) error {
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::System::Shutdown"
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsShutdownComputeSystem(ctx, computeSystem.handle, "")
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
switch err {
|
||||
case nil, ErrVmcomputeAlreadyStopped, ErrComputeSystemDoesNotExist, ErrVmcomputeOperationPending:
|
||||
default:
|
||||
return makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Terminate requests a compute system terminate.
|
||||
func (computeSystem *System) Terminate(ctx context.Context) error {
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::System::Terminate"
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsTerminateComputeSystem(ctx, computeSystem.handle, "")
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
switch err {
|
||||
case nil, ErrVmcomputeAlreadyStopped, ErrComputeSystemDoesNotExist, ErrVmcomputeOperationPending:
|
||||
default:
|
||||
return makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitBackground waits for the compute system exit notification. Once received
|
||||
// sets `computeSystem.waitError` (if any) and unblocks all `Wait` calls.
|
||||
//
|
||||
// This MUST be called exactly once per `computeSystem.handle` but `Wait` is
|
||||
// safe to call multiple times.
|
||||
func (computeSystem *System) waitBackground() {
|
||||
operation := "hcsshim::System::waitBackground"
|
||||
ctx, span := trace.StartSpan(context.Background(), operation)
|
||||
defer span.End()
|
||||
span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
|
||||
|
||||
err := waitForNotification(ctx, computeSystem.callbackNumber, hcsNotificationSystemExited, nil)
|
||||
switch err {
|
||||
case nil:
|
||||
log.G(ctx).Debug("system exited")
|
||||
case ErrVmcomputeUnexpectedExit:
|
||||
log.G(ctx).Debug("unexpected system exit")
|
||||
computeSystem.exitError = makeSystemError(computeSystem, operation, err, nil)
|
||||
err = nil
|
||||
default:
|
||||
err = makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
computeSystem.closedWaitOnce.Do(func() {
|
||||
computeSystem.waitError = err
|
||||
close(computeSystem.waitBlock)
|
||||
})
|
||||
oc.SetSpanStatus(span, err)
|
||||
}
|
||||
|
||||
// Wait synchronously waits for the compute system to shutdown or terminate. If
|
||||
// the compute system has already exited returns the previous error (if any).
|
||||
func (computeSystem *System) Wait() error {
|
||||
<-computeSystem.waitBlock
|
||||
return computeSystem.waitError
|
||||
}
|
||||
|
||||
// ExitError returns an error describing the reason the compute system terminated.
|
||||
func (computeSystem *System) ExitError() error {
|
||||
select {
|
||||
case <-computeSystem.waitBlock:
|
||||
if computeSystem.waitError != nil {
|
||||
return computeSystem.waitError
|
||||
}
|
||||
return computeSystem.exitError
|
||||
default:
|
||||
return errors.New("container not exited")
|
||||
}
|
||||
}
|
||||
|
||||
// Properties returns the requested container properties targeting a V1 schema container.
|
||||
func (computeSystem *System) Properties(ctx context.Context, types ...schema1.PropertyType) (*schema1.ContainerProperties, error) {
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::System::Properties"
|
||||
|
||||
queryBytes, err := json.Marshal(schema1.PropertyQuery{PropertyTypes: types})
|
||||
if err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
|
||||
propertiesJSON, resultJSON, err := vmcompute.HcsGetComputeSystemProperties(ctx, computeSystem.handle, string(queryBytes))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
if propertiesJSON == "" {
|
||||
return nil, ErrUnexpectedValue
|
||||
}
|
||||
properties := &schema1.ContainerProperties{}
|
||||
if err := json.Unmarshal([]byte(propertiesJSON), properties); err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
|
||||
return properties, nil
|
||||
}
|
||||
|
||||
// PropertiesV2 returns the requested container properties targeting a V2 schema container.
|
||||
func (computeSystem *System) PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error) {
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::System::PropertiesV2"
|
||||
|
||||
queryBytes, err := json.Marshal(hcsschema.PropertyQuery{PropertyTypes: types})
|
||||
if err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
|
||||
propertiesJSON, resultJSON, err := vmcompute.HcsGetComputeSystemProperties(ctx, computeSystem.handle, string(queryBytes))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
if propertiesJSON == "" {
|
||||
return nil, ErrUnexpectedValue
|
||||
}
|
||||
properties := &hcsschema.Properties{}
|
||||
if err := json.Unmarshal([]byte(propertiesJSON), properties); err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
|
||||
return properties, nil
|
||||
}
|
||||
|
||||
// Pause pauses the execution of the computeSystem. This feature is not enabled in TP5.
|
||||
func (computeSystem *System) Pause(ctx context.Context) (err error) {
|
||||
operation := "hcsshim::System::Pause"
|
||||
|
||||
// hcsPauseComputeSystemContext is an async peration. Start the outer span
|
||||
// here to measure the full pause time.
|
||||
ctx, span := trace.StartSpan(ctx, operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
|
||||
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsPauseComputeSystem(ctx, computeSystem.handle, "")
|
||||
events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemPauseCompleted, &timeout.SystemPause)
|
||||
if err != nil {
|
||||
return makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resume resumes the execution of the computeSystem. This feature is not enabled in TP5.
|
||||
func (computeSystem *System) Resume(ctx context.Context) (err error) {
|
||||
operation := "hcsshim::System::Resume"
|
||||
|
||||
// hcsResumeComputeSystemContext is an async operation. Start the outer span
|
||||
// here to measure the full restore time.
|
||||
ctx, span := trace.StartSpan(ctx, operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
|
||||
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
resultJSON, err := vmcompute.HcsResumeComputeSystem(ctx, computeSystem.handle, "")
|
||||
events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemResumeCompleted, &timeout.SystemResume)
|
||||
if err != nil {
|
||||
return makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save the compute system
|
||||
func (computeSystem *System) Save(ctx context.Context, options interface{}) (err error) {
|
||||
operation := "hcsshim::System::Save"
|
||||
|
||||
// hcsSaveComputeSystemContext is an async peration. Start the outer span
|
||||
// here to measure the full save time.
|
||||
ctx, span := trace.StartSpan(ctx, operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
|
||||
|
||||
saveOptions, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
result, err := vmcompute.HcsSaveComputeSystem(ctx, computeSystem.handle, string(saveOptions))
|
||||
events, err := processAsyncHcsResult(ctx, err, result, computeSystem.callbackNumber, hcsNotificationSystemSaveCompleted, &timeout.SystemSave)
|
||||
if err != nil {
|
||||
return makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (computeSystem *System) createProcess(ctx context.Context, operation string, c interface{}) (*Process, *vmcompute.HcsProcessInformation, error) {
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return nil, nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
configurationb, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return nil, nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
|
||||
configuration := string(configurationb)
|
||||
processInfo, processHandle, resultJSON, err := vmcompute.HcsCreateProcess(ctx, computeSystem.handle, configuration)
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, nil, makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
log.G(ctx).WithField("pid", processInfo.ProcessId).Debug("created process pid")
|
||||
return newProcess(processHandle, int(processInfo.ProcessId), computeSystem), &processInfo, nil
|
||||
}
|
||||
|
||||
// CreateProcess launches a new process within the computeSystem.
|
||||
func (computeSystem *System) CreateProcess(ctx context.Context, c interface{}) (cow.Process, error) {
|
||||
operation := "hcsshim::System::CreateProcess"
|
||||
process, processInfo, err := computeSystem.createProcess(ctx, operation, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
process.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
pipes, err := makeOpenFiles([]syscall.Handle{processInfo.StdInput, processInfo.StdOutput, processInfo.StdError})
|
||||
if err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
process.stdin = pipes[0]
|
||||
process.stdout = pipes[1]
|
||||
process.stderr = pipes[2]
|
||||
process.hasCachedStdio = true
|
||||
|
||||
if err = process.registerCallback(ctx); err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
go process.waitBackground()
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
// OpenProcess gets an interface to an existing process within the computeSystem.
|
||||
func (computeSystem *System) OpenProcess(ctx context.Context, pid int) (*Process, error) {
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::System::OpenProcess"
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
processHandle, resultJSON, err := vmcompute.HcsOpenProcess(ctx, computeSystem.handle, uint32(pid))
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
process := newProcess(processHandle, pid, computeSystem)
|
||||
if err = process.registerCallback(ctx); err != nil {
|
||||
return nil, makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
go process.waitBackground()
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
// Close cleans up any state associated with the compute system but does not terminate or wait for it.
|
||||
func (computeSystem *System) Close() (err error) {
|
||||
operation := "hcsshim::System::Close"
|
||||
ctx, span := trace.StartSpan(context.Background(), operation)
|
||||
defer span.End()
|
||||
defer func() { oc.SetSpanStatus(span, err) }()
|
||||
span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
|
||||
|
||||
computeSystem.handleLock.Lock()
|
||||
defer computeSystem.handleLock.Unlock()
|
||||
|
||||
// Don't double free this
|
||||
if computeSystem.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = computeSystem.unregisterCallback(ctx); err != nil {
|
||||
return makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
|
||||
err = vmcompute.HcsCloseComputeSystem(ctx, computeSystem.handle)
|
||||
if err != nil {
|
||||
return makeSystemError(computeSystem, operation, err, nil)
|
||||
}
|
||||
|
||||
computeSystem.handle = 0
|
||||
computeSystem.closedWaitOnce.Do(func() {
|
||||
computeSystem.waitError = ErrAlreadyClosed
|
||||
close(computeSystem.waitBlock)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (computeSystem *System) registerCallback(ctx context.Context) error {
|
||||
callbackContext := ¬ifcationWatcherContext{
|
||||
channels: newSystemChannels(),
|
||||
systemID: computeSystem.id,
|
||||
}
|
||||
|
||||
callbackMapLock.Lock()
|
||||
callbackNumber := nextCallback
|
||||
nextCallback++
|
||||
callbackMap[callbackNumber] = callbackContext
|
||||
callbackMapLock.Unlock()
|
||||
|
||||
callbackHandle, err := vmcompute.HcsRegisterComputeSystemCallback(ctx, computeSystem.handle, notificationWatcherCallback, callbackNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
callbackContext.handle = callbackHandle
|
||||
computeSystem.callbackNumber = callbackNumber
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (computeSystem *System) unregisterCallback(ctx context.Context) error {
|
||||
callbackNumber := computeSystem.callbackNumber
|
||||
|
||||
callbackMapLock.RLock()
|
||||
callbackContext := callbackMap[callbackNumber]
|
||||
callbackMapLock.RUnlock()
|
||||
|
||||
if callbackContext == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
handle := callbackContext.handle
|
||||
|
||||
if handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// hcsUnregisterComputeSystemCallback has its own syncronization
|
||||
// to wait for all callbacks to complete. We must NOT hold the callbackMapLock.
|
||||
err := vmcompute.HcsUnregisterComputeSystemCallback(ctx, handle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
closeChannels(callbackContext.channels)
|
||||
|
||||
callbackMapLock.Lock()
|
||||
delete(callbackMap, callbackNumber)
|
||||
callbackMapLock.Unlock()
|
||||
|
||||
handle = 0 //nolint:ineffassign
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Modify the System by sending a request to HCS
|
||||
func (computeSystem *System) Modify(ctx context.Context, config interface{}) error {
|
||||
computeSystem.handleLock.RLock()
|
||||
defer computeSystem.handleLock.RUnlock()
|
||||
|
||||
operation := "hcsshim::System::Modify"
|
||||
|
||||
if computeSystem.handle == 0 {
|
||||
return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
|
||||
}
|
||||
|
||||
requestBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestJSON := string(requestBytes)
|
||||
resultJSON, err := vmcompute.HcsModifyComputeSystem(ctx, computeSystem.handle, requestJSON)
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if err != nil {
|
||||
return makeSystemError(computeSystem, operation, err, events)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package hcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
diskutil "github.com/Microsoft/go-winio/vhd"
|
||||
"github.com/Microsoft/hcsshim/computestorage"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// makeOpenFiles calls winio.MakeOpenFile for each handle in a slice but closes all the handles
|
||||
// if there is an error.
|
||||
func makeOpenFiles(hs []syscall.Handle) (_ []io.ReadWriteCloser, err error) {
|
||||
fs := make([]io.ReadWriteCloser, len(hs))
|
||||
for i, h := range hs {
|
||||
if h != syscall.Handle(0) {
|
||||
if err == nil {
|
||||
fs[i], err = winio.MakeOpenFile(h)
|
||||
}
|
||||
if err != nil {
|
||||
syscall.Close(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
for _, f := range fs {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
// CreateNTFSVHD creates a VHD formatted with NTFS of size `sizeGB` at the given `vhdPath`.
|
||||
func CreateNTFSVHD(ctx context.Context, vhdPath string, sizeGB uint32) (err error) {
|
||||
if err := diskutil.CreateVhdx(vhdPath, sizeGB, 1); err != nil {
|
||||
return errors.Wrap(err, "failed to create VHD")
|
||||
}
|
||||
|
||||
vhd, err := diskutil.OpenVirtualDisk(vhdPath, diskutil.VirtualDiskAccessNone, diskutil.OpenVirtualDiskFlagNone)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open VHD")
|
||||
}
|
||||
defer func() {
|
||||
err2 := windows.CloseHandle(windows.Handle(vhd))
|
||||
if err == nil {
|
||||
err = errors.Wrap(err2, "failed to close VHD")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := computestorage.FormatWritableLayerVhd(ctx, windows.Handle(vhd)); err != nil {
|
||||
return errors.Wrap(err, "failed to format VHD")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
68
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/waithelper.go
generated
vendored
Normal file
68
common/vendor/github.com/Microsoft/hcsshim/internal/hcs/waithelper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package hcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/log"
|
||||
)
|
||||
|
||||
func processAsyncHcsResult(ctx context.Context, err error, resultJSON string, callbackNumber uintptr, expectedNotification hcsNotification, timeout *time.Duration) ([]ErrorEvent, error) {
|
||||
events := processHcsResult(ctx, resultJSON)
|
||||
if IsPending(err) {
|
||||
return nil, waitForNotification(ctx, callbackNumber, expectedNotification, timeout)
|
||||
}
|
||||
|
||||
return events, err
|
||||
}
|
||||
|
||||
func waitForNotification(ctx context.Context, callbackNumber uintptr, expectedNotification hcsNotification, timeout *time.Duration) error {
|
||||
callbackMapLock.RLock()
|
||||
if _, ok := callbackMap[callbackNumber]; !ok {
|
||||
callbackMapLock.RUnlock()
|
||||
log.G(ctx).WithField("callbackNumber", callbackNumber).Error("failed to waitForNotification: callbackNumber does not exist in callbackMap")
|
||||
return ErrHandleClose
|
||||
}
|
||||
channels := callbackMap[callbackNumber].channels
|
||||
callbackMapLock.RUnlock()
|
||||
|
||||
expectedChannel := channels[expectedNotification]
|
||||
if expectedChannel == nil {
|
||||
log.G(ctx).WithField("type", expectedNotification).Error("unknown notification type in waitForNotification")
|
||||
return ErrInvalidNotificationType
|
||||
}
|
||||
|
||||
var c <-chan time.Time
|
||||
if timeout != nil {
|
||||
timer := time.NewTimer(*timeout)
|
||||
c = timer.C
|
||||
defer timer.Stop()
|
||||
}
|
||||
|
||||
select {
|
||||
case err, ok := <-expectedChannel:
|
||||
if !ok {
|
||||
return ErrHandleClose
|
||||
}
|
||||
return err
|
||||
case err, ok := <-channels[hcsNotificationSystemExited]:
|
||||
if !ok {
|
||||
return ErrHandleClose
|
||||
}
|
||||
// If the expected notification is hcsNotificationSystemExited which of the two selects
|
||||
// chosen is random. Return the raw error if hcsNotificationSystemExited is expected
|
||||
if channels[hcsNotificationSystemExited] == expectedChannel {
|
||||
return err
|
||||
}
|
||||
return ErrUnexpectedContainerExit
|
||||
case _, ok := <-channels[hcsNotificationServiceDisconnect]:
|
||||
if !ok {
|
||||
return ErrHandleClose
|
||||
}
|
||||
// hcsNotificationServiceDisconnect should never be an expected notification
|
||||
// it does not need the same handling as hcsNotificationSystemExited
|
||||
return ErrUnexpectedProcessAbort
|
||||
case <-c:
|
||||
return ErrTimeout
|
||||
}
|
||||
}
|
||||
47
common/vendor/github.com/Microsoft/hcsshim/internal/hcserror/hcserror.go
generated
vendored
Normal file
47
common/vendor/github.com/Microsoft/hcsshim/internal/hcserror/hcserror.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package hcserror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const ERROR_GEN_FAILURE = syscall.Errno(31)
|
||||
|
||||
type HcsError struct {
|
||||
title string
|
||||
rest string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *HcsError) Error() string {
|
||||
s := e.title
|
||||
if len(s) > 0 && s[len(s)-1] != ' ' {
|
||||
s += " "
|
||||
}
|
||||
s += fmt.Sprintf("failed in Win32: %s (0x%x)", e.Err, Win32FromError(e.Err))
|
||||
if e.rest != "" {
|
||||
if e.rest[0] != ' ' {
|
||||
s += " "
|
||||
}
|
||||
s += e.rest
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func New(err error, title, rest string) error {
|
||||
// Pass through DLL errors directly since they do not originate from HCS.
|
||||
if _, ok := err.(*syscall.DLLError); ok {
|
||||
return err
|
||||
}
|
||||
return &HcsError{title, rest, err}
|
||||
}
|
||||
|
||||
func Win32FromError(err error) uint32 {
|
||||
if herr, ok := err.(*HcsError); ok {
|
||||
return Win32FromError(herr.Err)
|
||||
}
|
||||
if code, ok := err.(syscall.Errno); ok {
|
||||
return uint32(code)
|
||||
}
|
||||
return uint32(ERROR_GEN_FAILURE)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package hns
|
||||
|
||||
import "fmt"
|
||||
|
||||
//go:generate go run ../../mksyscall_windows.go -output zsyscall_windows.go hns.go
|
||||
|
||||
//sys _hnsCall(method string, path string, object string, response **uint16) (hr error) = vmcompute.HNSCall?
|
||||
|
||||
type EndpointNotFoundError struct {
|
||||
EndpointName string
|
||||
}
|
||||
|
||||
func (e EndpointNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Endpoint %s not found", e.EndpointName)
|
||||
}
|
||||
|
||||
type NetworkNotFoundError struct {
|
||||
NetworkName string
|
||||
}
|
||||
|
||||
func (e NetworkNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Network %s not found", e.NetworkName)
|
||||
}
|
||||
308
common/vendor/github.com/Microsoft/hcsshim/internal/hns/hnsendpoint.go
generated
vendored
Normal file
308
common/vendor/github.com/Microsoft/hcsshim/internal/hns/hnsendpoint.go
generated
vendored
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
package hns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HNSEndpoint represents a network endpoint in HNS
|
||||
type HNSEndpoint struct {
|
||||
Id string `json:"ID,omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
VirtualNetwork string `json:",omitempty"`
|
||||
VirtualNetworkName string `json:",omitempty"`
|
||||
Policies []json.RawMessage `json:",omitempty"`
|
||||
MacAddress string `json:",omitempty"`
|
||||
IPAddress net.IP `json:",omitempty"`
|
||||
IPv6Address net.IP `json:",omitempty"`
|
||||
DNSSuffix string `json:",omitempty"`
|
||||
DNSServerList string `json:",omitempty"`
|
||||
GatewayAddress string `json:",omitempty"`
|
||||
GatewayAddressV6 string `json:",omitempty"`
|
||||
EnableInternalDNS bool `json:",omitempty"`
|
||||
DisableICC bool `json:",omitempty"`
|
||||
PrefixLength uint8 `json:",omitempty"`
|
||||
IPv6PrefixLength uint8 `json:",omitempty"`
|
||||
IsRemoteEndpoint bool `json:",omitempty"`
|
||||
EnableLowMetric bool `json:",omitempty"`
|
||||
Namespace *Namespace `json:",omitempty"`
|
||||
EncapOverhead uint16 `json:",omitempty"`
|
||||
}
|
||||
|
||||
//SystemType represents the type of the system on which actions are done
|
||||
type SystemType string
|
||||
|
||||
// SystemType const
|
||||
const (
|
||||
ContainerType SystemType = "Container"
|
||||
VirtualMachineType SystemType = "VirtualMachine"
|
||||
HostType SystemType = "Host"
|
||||
)
|
||||
|
||||
// EndpointAttachDetachRequest is the structure used to send request to the container to modify the system
|
||||
// Supported resource types are Network and Request Types are Add/Remove
|
||||
type EndpointAttachDetachRequest struct {
|
||||
ContainerID string `json:"ContainerId,omitempty"`
|
||||
SystemType SystemType `json:"SystemType"`
|
||||
CompartmentID uint16 `json:"CompartmentId,omitempty"`
|
||||
VirtualNICName string `json:"VirtualNicName,omitempty"`
|
||||
}
|
||||
|
||||
// EndpointResquestResponse is object to get the endpoint request response
|
||||
type EndpointResquestResponse struct {
|
||||
Success bool
|
||||
Error string
|
||||
}
|
||||
|
||||
// HNSEndpointRequest makes a HNS call to modify/query a network endpoint
|
||||
func HNSEndpointRequest(method, path, request string) (*HNSEndpoint, error) {
|
||||
endpoint := &HNSEndpoint{}
|
||||
err := hnsCall(method, "/endpoints/"+path, request, &endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// HNSListEndpointRequest makes a HNS call to query the list of available endpoints
|
||||
func HNSListEndpointRequest() ([]HNSEndpoint, error) {
|
||||
var endpoint []HNSEndpoint
|
||||
err := hnsCall("GET", "/endpoints/", "", &endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// GetHNSEndpointByID get the Endpoint by ID
|
||||
func GetHNSEndpointByID(endpointID string) (*HNSEndpoint, error) {
|
||||
return HNSEndpointRequest("GET", endpointID, "")
|
||||
}
|
||||
|
||||
// GetHNSEndpointByName gets the endpoint filtered by Name
|
||||
func GetHNSEndpointByName(endpointName string) (*HNSEndpoint, error) {
|
||||
hnsResponse, err := HNSListEndpointRequest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, hnsEndpoint := range hnsResponse {
|
||||
if hnsEndpoint.Name == endpointName {
|
||||
return &hnsEndpoint, nil
|
||||
}
|
||||
}
|
||||
return nil, EndpointNotFoundError{EndpointName: endpointName}
|
||||
}
|
||||
|
||||
type endpointAttachInfo struct {
|
||||
SharedContainers json.RawMessage `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (endpoint *HNSEndpoint) IsAttached(vID string) (bool, error) {
|
||||
attachInfo := endpointAttachInfo{}
|
||||
err := hnsCall("GET", "/endpoints/"+endpoint.Id, "", &attachInfo)
|
||||
|
||||
// Return false allows us to just return the err
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(string(attachInfo.SharedContainers)), strings.ToLower(vID)) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
}
|
||||
|
||||
// Create Endpoint by sending EndpointRequest to HNS. TODO: Create a separate HNS interface to place all these methods
|
||||
func (endpoint *HNSEndpoint) Create() (*HNSEndpoint, error) {
|
||||
operation := "Create"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
|
||||
jsonString, err := json.Marshal(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return HNSEndpointRequest("POST", "", string(jsonString))
|
||||
}
|
||||
|
||||
// Delete Endpoint by sending EndpointRequest to HNS
|
||||
func (endpoint *HNSEndpoint) Delete() (*HNSEndpoint, error) {
|
||||
operation := "Delete"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
|
||||
return HNSEndpointRequest("DELETE", endpoint.Id, "")
|
||||
}
|
||||
|
||||
// Update Endpoint
|
||||
func (endpoint *HNSEndpoint) Update() (*HNSEndpoint, error) {
|
||||
operation := "Update"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
jsonString, err := json.Marshal(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = hnsCall("POST", "/endpoints/"+endpoint.Id, string(jsonString), &endpoint)
|
||||
|
||||
return endpoint, err
|
||||
}
|
||||
|
||||
// ApplyACLPolicy applies a set of ACL Policies on the Endpoint
|
||||
func (endpoint *HNSEndpoint) ApplyACLPolicy(policies ...*ACLPolicy) error {
|
||||
operation := "ApplyACLPolicy"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
|
||||
for _, policy := range policies {
|
||||
if policy == nil {
|
||||
continue
|
||||
}
|
||||
jsonString, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint.Policies = append(endpoint.Policies, jsonString)
|
||||
}
|
||||
|
||||
_, err := endpoint.Update()
|
||||
return err
|
||||
}
|
||||
|
||||
// ApplyProxyPolicy applies a set of Proxy Policies on the Endpoint
|
||||
func (endpoint *HNSEndpoint) ApplyProxyPolicy(policies ...*ProxyPolicy) error {
|
||||
operation := "ApplyProxyPolicy"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
|
||||
for _, policy := range policies {
|
||||
if policy == nil {
|
||||
continue
|
||||
}
|
||||
jsonString, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint.Policies = append(endpoint.Policies, jsonString)
|
||||
}
|
||||
|
||||
_, err := endpoint.Update()
|
||||
return err
|
||||
}
|
||||
|
||||
// ContainerAttach attaches an endpoint to container
|
||||
func (endpoint *HNSEndpoint) ContainerAttach(containerID string, compartmentID uint16) error {
|
||||
operation := "ContainerAttach"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
|
||||
requestMessage := &EndpointAttachDetachRequest{
|
||||
ContainerID: containerID,
|
||||
CompartmentID: compartmentID,
|
||||
SystemType: ContainerType,
|
||||
}
|
||||
response := &EndpointResquestResponse{}
|
||||
jsonString, err := json.Marshal(requestMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hnsCall("POST", "/endpoints/"+endpoint.Id+"/attach", string(jsonString), &response)
|
||||
}
|
||||
|
||||
// ContainerDetach detaches an endpoint from container
|
||||
func (endpoint *HNSEndpoint) ContainerDetach(containerID string) error {
|
||||
operation := "ContainerDetach"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
|
||||
requestMessage := &EndpointAttachDetachRequest{
|
||||
ContainerID: containerID,
|
||||
SystemType: ContainerType,
|
||||
}
|
||||
response := &EndpointResquestResponse{}
|
||||
|
||||
jsonString, err := json.Marshal(requestMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hnsCall("POST", "/endpoints/"+endpoint.Id+"/detach", string(jsonString), &response)
|
||||
}
|
||||
|
||||
// HostAttach attaches a nic on the host
|
||||
func (endpoint *HNSEndpoint) HostAttach(compartmentID uint16) error {
|
||||
operation := "HostAttach"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
requestMessage := &EndpointAttachDetachRequest{
|
||||
CompartmentID: compartmentID,
|
||||
SystemType: HostType,
|
||||
}
|
||||
response := &EndpointResquestResponse{}
|
||||
|
||||
jsonString, err := json.Marshal(requestMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hnsCall("POST", "/endpoints/"+endpoint.Id+"/attach", string(jsonString), &response)
|
||||
|
||||
}
|
||||
|
||||
// HostDetach detaches a nic on the host
|
||||
func (endpoint *HNSEndpoint) HostDetach() error {
|
||||
operation := "HostDetach"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
requestMessage := &EndpointAttachDetachRequest{
|
||||
SystemType: HostType,
|
||||
}
|
||||
response := &EndpointResquestResponse{}
|
||||
|
||||
jsonString, err := json.Marshal(requestMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hnsCall("POST", "/endpoints/"+endpoint.Id+"/detach", string(jsonString), &response)
|
||||
}
|
||||
|
||||
// VirtualMachineNICAttach attaches a endpoint to a virtual machine
|
||||
func (endpoint *HNSEndpoint) VirtualMachineNICAttach(virtualMachineNICName string) error {
|
||||
operation := "VirtualMachineNicAttach"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
requestMessage := &EndpointAttachDetachRequest{
|
||||
VirtualNICName: virtualMachineNICName,
|
||||
SystemType: VirtualMachineType,
|
||||
}
|
||||
response := &EndpointResquestResponse{}
|
||||
|
||||
jsonString, err := json.Marshal(requestMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hnsCall("POST", "/endpoints/"+endpoint.Id+"/attach", string(jsonString), &response)
|
||||
}
|
||||
|
||||
// VirtualMachineNICDetach detaches a endpoint from a virtual machine
|
||||
func (endpoint *HNSEndpoint) VirtualMachineNICDetach() error {
|
||||
operation := "VirtualMachineNicDetach"
|
||||
title := "hcsshim::HNSEndpoint::" + operation
|
||||
logrus.Debugf(title+" id=%s", endpoint.Id)
|
||||
|
||||
requestMessage := &EndpointAttachDetachRequest{
|
||||
SystemType: VirtualMachineType,
|
||||
}
|
||||
response := &EndpointResquestResponse{}
|
||||
|
||||
jsonString, err := json.Marshal(requestMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hnsCall("POST", "/endpoints/"+endpoint.Id+"/detach", string(jsonString), &response)
|
||||
}
|
||||
49
common/vendor/github.com/Microsoft/hcsshim/internal/hns/hnsfuncs.go
generated
vendored
Normal file
49
common/vendor/github.com/Microsoft/hcsshim/internal/hns/hnsfuncs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package hns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/hcserror"
|
||||
"github.com/Microsoft/hcsshim/internal/interop"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func hnsCallRawResponse(method, path, request string) (*hnsResponse, error) {
|
||||
var responseBuffer *uint16
|
||||
logrus.Debugf("[%s]=>[%s] Request : %s", method, path, request)
|
||||
|
||||
err := _hnsCall(method, path, request, &responseBuffer)
|
||||
if err != nil {
|
||||
return nil, hcserror.New(err, "hnsCall ", "")
|
||||
}
|
||||
response := interop.ConvertAndFreeCoTaskMemString(responseBuffer)
|
||||
|
||||
hnsresponse := &hnsResponse{}
|
||||
if err = json.Unmarshal([]byte(response), &hnsresponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hnsresponse, nil
|
||||
}
|
||||
|
||||
func hnsCall(method, path, request string, returnResponse interface{}) error {
|
||||
hnsresponse, err := hnsCallRawResponse(method, path, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed during hnsCallRawResponse: %v", err)
|
||||
}
|
||||
if !hnsresponse.Success {
|
||||
return fmt.Errorf("hns failed with error : %s", hnsresponse.Error)
|
||||
}
|
||||
|
||||
if len(hnsresponse.Output) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Network Response : %s", hnsresponse.Output)
|
||||
err = json.Unmarshal(hnsresponse.Output, returnResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
28
common/vendor/github.com/Microsoft/hcsshim/internal/hns/hnsglobals.go
generated
vendored
Normal file
28
common/vendor/github.com/Microsoft/hcsshim/internal/hns/hnsglobals.go
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package hns
|
||||
|
||||
type HNSGlobals struct {
|
||||
Version HNSVersion `json:"Version"`
|
||||
}
|
||||
|
||||
type HNSVersion struct {
|
||||
Major int `json:"Major"`
|
||||
Minor int `json:"Minor"`
|
||||
}
|
||||
|
||||
var (
|
||||
HNSVersion1803 = HNSVersion{Major: 7, Minor: 2}
|
||||
)
|
||||
|
||||
func GetHNSGlobals() (*HNSGlobals, error) {
|
||||
var version HNSVersion
|
||||
err := hnsCall("GET", "/globals/version", "", &version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
globals := &HNSGlobals{
|
||||
Version: version,
|
||||
}
|
||||
|
||||
return globals, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue