Update Knative dependencies to v1.1 (#925)

* Update Knative dependencies to v1.1 (0.28)

* Update vendor dir

* Update lincenses

* Fix docker push test

* Refactor docker client interface
This commit is contained in:
David Simansky 2022-03-31 15:45:45 +02:00 committed by GitHub
parent 8312b5c560
commit bf68b221a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
326 changed files with 13026 additions and 3677 deletions

View File

@ -35,10 +35,7 @@ type CredentialsProvider func(ctx context.Context, registry string) (Credentials
// PusherDockerClient is sub-interface of client.CommonAPIClient required by pusher. // PusherDockerClient is sub-interface of client.CommonAPIClient required by pusher.
type PusherDockerClient interface { type PusherDockerClient interface {
NegotiateAPIVersion(ctx context.Context) daemon.Client
ImageSave(context.Context, []string) (io.ReadCloser, error)
ImageLoad(context.Context, io.Reader, bool) (types.ImageLoadResponse, error)
ImageTag(context.Context, string, string) error
ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error)
Close() error Close() error
} }

View File

@ -179,6 +179,10 @@ func TestNonDaemonPush(t *testing.T) {
return f, nil return f, nil
} }
dockerClient.imageInspect = func(ctx context.Context, s string) (types.ImageInspect, []byte, error) {
return types.ImageInspect{ID: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, []byte{}, nil
}
dockerClientFactory := func() (docker.PusherDockerClient, error) { dockerClientFactory := func() (docker.PusherDockerClient, error) {
return dockerClient, nil return dockerClient, nil
} }
@ -284,6 +288,7 @@ type mockPusherDockerClient struct {
negotiateAPIVersion func(ctx context.Context) negotiateAPIVersion func(ctx context.Context)
imagePush func(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) imagePush func(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error)
imageSave func(ctx context.Context, strings []string) (io.ReadCloser, error) imageSave func(ctx context.Context, strings []string) (io.ReadCloser, error)
imageInspect func(ctx context.Context, s string) (types.ImageInspect, []byte, error)
close func() error close func() error
} }
@ -303,6 +308,10 @@ func (m *mockPusherDockerClient) ImageTag(ctx context.Context, s string, s2 stri
panic("implement me") panic("implement me")
} }
func (m *mockPusherDockerClient) ImageInspectWithRaw(ctx context.Context, s string) (types.ImageInspect, []byte, error) {
return m.imageInspect(ctx, s)
}
func (m *mockPusherDockerClient) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) { func (m *mockPusherDockerClient) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return m.imagePush(ctx, ref, options) return m.imagePush(ctx, ref, options)
} }

26
go.mod
View File

@ -11,37 +11,39 @@ require (
github.com/cloudevents/sdk-go/v2 v2.5.0 github.com/cloudevents/sdk-go/v2 v2.5.0
github.com/containers/image/v5 v5.10.6 github.com/containers/image/v5 v5.10.6
github.com/coreos/go-semver v0.3.0 github.com/coreos/go-semver v0.3.0
github.com/docker/cli v20.10.10+incompatible github.com/docker/cli v20.10.12+incompatible
github.com/docker/docker v20.10.10+incompatible github.com/docker/docker v20.10.12+incompatible
github.com/docker/docker-credential-helpers v0.6.4 github.com/docker/docker-credential-helpers v0.6.4
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2 github.com/go-git/go-git/v5 v5.4.2
github.com/google/go-cmp v0.5.6 github.com/go-logr/logr v1.2.2 // indirect
github.com/google/go-containerregistry v0.6.0 github.com/google/go-cmp v0.5.7
github.com/google/go-containerregistry v0.8.1-0.20220219142810-1571d7fdc46e
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/opencontainers/image-spec v1.0.3-0.20211202222133-eacdcc10569b github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198
github.com/ory/viper v1.7.5 github.com/ory/viper v1.7.5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.3.0
github.com/tektoncd/cli v0.22.0 github.com/tektoncd/cli v0.22.0
github.com/tektoncd/pipeline v0.32.1 github.com/tektoncd/pipeline v0.32.1
github.com/whilp/git-urls v1.0.0 github.com/whilp/git-urls v1.0.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.22.5 k8s.io/api v0.22.5
k8s.io/apimachinery v0.22.5 k8s.io/apimachinery v0.22.5
k8s.io/client-go v0.22.5 k8s.io/client-go v0.22.5
k8s.io/klog/v2 v2.40.1 // indirect
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a
knative.dev/client v0.27.0 knative.dev/client v0.28.0
knative.dev/eventing v0.27.2 knative.dev/eventing v0.28.4
knative.dev/hack v0.0.0-20220216040439-0456e8bf6547 knative.dev/hack v0.0.0-20211203062838-e11ac125e707
knative.dev/pkg v0.0.0-20220215153400-3c00bb0157b9 knative.dev/pkg v0.0.0-20220222214539-0b8a9403de7e
knative.dev/serving v0.27.1 knative.dev/serving v0.28.4
) )
// temporary set higher version of buildpacks/imgutil to get better performance for podman // temporary set higher version of buildpacks/imgutil to get better performance for podman

209
go.sum
View File

@ -32,18 +32,24 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0 h1:w6LozQJyDDEyhf64Uusu1LCcnLt0I1VMLiJC2kV+eXk=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 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.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.1.0 h1:pyPhehLfZ6pVzRgJmXGYvCY4K7WSWRhVw0AwhgVvS84=
cloud.google.com/go/compute v1.1.0/go.mod h1:2NIffxgWfORSI7EOYMFatGTfjMLnqrOKBEyYb6NoRgA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 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/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 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.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -126,6 +132,7 @@ github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
@ -146,16 +153,18 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= 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-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.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= 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.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= 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 v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
github.com/Microsoft/hcsshim v0.8.23 h1:47MSwtKGXet80aIn+7h4YI6fwPmwIghAnsx2aOUrG2M=
github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg=
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= 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/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/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@ -226,6 +235,7 @@ github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3st
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@ -262,6 +272,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI=
@ -293,6 +305,7 @@ github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9cop
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -301,12 +314,14 @@ github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
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/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg=
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU=
github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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/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/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -315,6 +330,9 @@ github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvso
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= 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/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudevents/conformance v0.2.0/go.mod h1:rHKDwylBH89Rns6U3wL9ww8bg9/4GbwRCDNuyoC6bcc= github.com/cloudevents/conformance v0.2.0/go.mod h1:rHKDwylBH89Rns6U3wL9ww8bg9/4GbwRCDNuyoC6bcc=
@ -330,7 +348,9 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
@ -365,12 +385,15 @@ github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/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.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.9/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.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.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
github.com/containerd/containerd v1.5.2 h1:MG/Bg1pbmMb61j3wHCFWPxESXHieiKr2xG64px/k8zQ=
github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s=
github.com/containerd/containerd v1.5.9 h1:rs6Xg1gtIxaeyG+Smsb/0xaSDu1VgFhOCKBXxMxbsF4=
github.com/containerd/containerd v1.5.9/go.mod h1:fvQqCfadDGga5HZyn3j4+dx56qj2I9YwBrlSdalvJYQ=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= 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-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-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@ -401,13 +424,16 @@ github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oM
github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/stargz-snapshotter/estargz v0.0.0-20201223015020-a9a0c2d64694/go.mod h1:E9uVkkBKf0EaC39j2JVW9EzdNhYvpz6eQIjILHebruk= github.com/containerd/stargz-snapshotter/estargz v0.0.0-20201223015020-a9a0c2d64694/go.mod h1:E9uVkkBKf0EaC39j2JVW9EzdNhYvpz6eQIjILHebruk=
github.com/containerd/stargz-snapshotter/estargz v0.6.4/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= github.com/containerd/stargz-snapshotter/estargz v0.6.4/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw=
github.com/containerd/stargz-snapshotter/estargz v0.7.0 h1:1d/rydzTywc76lnjJb6qbPCiTiCwts49AzKps/Ecblw=
github.com/containerd/stargz-snapshotter/estargz v0.7.0/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= github.com/containerd/stargz-snapshotter/estargz v0.7.0/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw=
github.com/containerd/stargz-snapshotter/estargz v0.10.1/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0=
github.com/containerd/stargz-snapshotter/estargz v0.11.0 h1:t0IW5kOmY7AXDAWRUs2uVzDhijAUOAYVr/dyRhOQvBg=
github.com/containerd/stargz-snapshotter/estargz v0.11.0/go.mod h1:/KsZXsJRllMbTKFfG0miFQWViQKdI9+9aSXs+HN0+ac=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= 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-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 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.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= 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 v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
@ -454,6 +480,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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-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/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
@ -492,8 +519,10 @@ github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop
github.com/docker/cli v20.10.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.10+incompatible h1:kcbwdgWbrBOH8QwQzaJmyriHwF7XIl4HT1qh0HTRys4=
github.com/docker/cli v20.10.10+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.10+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA=
github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= 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-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
@ -503,8 +532,10 @@ github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r
github.com/docker/docker v20.10.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.10+incompatible h1:GKkP0T7U4ks6X3lmmHKC2QDprnpRJor2Z5a8m62R9ZM=
github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U=
github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
@ -546,8 +577,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE=
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
@ -875,18 +908,21 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-containerregistry v0.4.1-0.20210128200529-19c2b639fab1/go.mod h1:GU9FUA/X9rd2cV3ZoUNaWihp27tki6/38EsVzL2Dyzc= github.com/google/go-containerregistry v0.4.1-0.20210128200529-19c2b639fab1/go.mod h1:GU9FUA/X9rd2cV3ZoUNaWihp27tki6/38EsVzL2Dyzc=
github.com/google/go-containerregistry v0.5.2-0.20210604130445-3bfab55f3bd9/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q= github.com/google/go-containerregistry v0.5.2-0.20210604130445-3bfab55f3bd9/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q=
github.com/google/go-containerregistry v0.5.2-0.20210609162550-f0ce2270b3b4/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q= github.com/google/go-containerregistry v0.5.2-0.20210609162550-f0ce2270b3b4/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q=
github.com/google/go-containerregistry v0.5.2-0.20210709161016-b448abac9a70/go.mod h1:nS0LmwaZT5kotebyNVC7ik7lC4OHOzGcctvvSosKlAc= github.com/google/go-containerregistry v0.5.2-0.20210709161016-b448abac9a70/go.mod h1:nS0LmwaZT5kotebyNVC7ik7lC4OHOzGcctvvSosKlAc=
github.com/google/go-containerregistry v0.6.0 h1:niQ+8XD//kKgArIFwDVBXsWVWbde16LPdHMyNwSC8h4=
github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw= github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw=
github.com/google/go-containerregistry v0.7.1-0.20211118220127-abdc633f8305/go.mod h1:6cMIl1RfryEiPzBE67OgtZdEiLWz4myqCQIiBMy3CsM=
github.com/google/go-containerregistry v0.8.1-0.20220219142810-1571d7fdc46e h1:1KGep3inUBSyCDbpk4noN5CfemOpEpQPPnN7UbBrdCU=
github.com/google/go-containerregistry v0.8.1-0.20220219142810-1571d7fdc46e/go.mod h1:MMbnwuvLeZJRPqhTs8jDWc8xGlOs5YCGx1TSc/qdExk=
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210129212729-5c4818de4025/go.mod h1:n9wRxRfKkHy6ZFyj0jJQHw11P+mGLnED4sqegwrXxDk= github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210129212729-5c4818de4025/go.mod h1:n9wRxRfKkHy6ZFyj0jJQHw11P+mGLnED4sqegwrXxDk=
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210610160139-c086c7f16d4e/go.mod h1:u9BUkrFoN0hojbyaW5occdRyQvT74KjJKx2VClbrDC8= github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210610160139-c086c7f16d4e/go.mod h1:u9BUkrFoN0hojbyaW5occdRyQvT74KjJKx2VClbrDC8=
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210624211700-ce35c99b3faf/go.mod h1:j3IqhBG3Ox1NXmmhbWU4UmiHVAf2dUgB7le1Ch7JZQ0=
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210918223331-0e8b581974dd/go.mod h1:j3IqhBG3Ox1NXmmhbWU4UmiHVAf2dUgB7le1Ch7JZQ0= github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20210918223331-0e8b581974dd/go.mod h1:j3IqhBG3Ox1NXmmhbWU4UmiHVAf2dUgB7le1Ch7JZQ0=
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211118220127-abdc633f8305/go.mod h1:/wxgGW8xlALyJwgyqAOe19EMFzNi+pePDhT33XMD1Yo=
github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM= github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM=
@ -1001,22 +1037,28 @@ github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4/go.mod h1:5Scbynm8dF1
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/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-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= 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/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
@ -1039,10 +1081,13 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E= github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0=
github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU=
@ -1055,6 +1100,7 @@ github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ikawaha/goahttpcheck v1.3.1/go.mod h1:RFH8+oq3GOW2yiRwI5tcx3FrVmONlT4DMmu80dsoBRE= github.com/ikawaha/goahttpcheck v1.3.1/go.mod h1:RFH8+oq3GOW2yiRwI5tcx3FrVmONlT4DMmu80dsoBRE=
@ -1163,8 +1209,9 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 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/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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@ -1197,8 +1244,9 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8
github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw=
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
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.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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -1245,6 +1293,7 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -1282,8 +1331,9 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@ -1322,6 +1372,7 @@ github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/4
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
@ -1344,8 +1395,9 @@ github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
@ -1367,8 +1419,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
@ -1453,27 +1506,33 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 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/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.3-0.20211202222133-eacdcc10569b h1:0kCLoY3q1n4zDPYBdGhE/kdcyLWl/aAQmJFQrCPNJ6k= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.3-0.20211202222133-eacdcc10569b/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q= github.com/opencontainers/image-spec v1.0.3-0.20211202222133-eacdcc10569b/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q=
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg=
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 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 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-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-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc91/go.mod h1:3Sm6Dt7OT8z88EbdQqqcRN2oCT54jbi72tT/HqgflT8= github.com/opencontainers/runc v1.0.0-rc91/go.mod h1:3Sm6Dt7OT8z88EbdQqqcRN2oCT54jbi72tT/HqgflT8=
github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM=
github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg=
github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 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.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-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.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/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-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/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/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= 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/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/opencontainers/selinux v1.8.2 h1:c4ca10UMgRcvZ6h0K4HtS15UaVSBEaE+iln2LVpAuGc=
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -1496,6 +1555,7 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -1540,6 +1600,7 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
@ -1567,7 +1628,6 @@ github.com/prometheus/common v0.19.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@ -1624,6 +1684,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/rs/dnscache v0.0.0-20210201191234-295bba877686/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
@ -1640,6 +1701,7 @@ github.com/sabhiram/go-gitignore v0.0.0-20201211074657-223ce5d391b0 h1:4Q/TASkyj
github.com/sabhiram/go-gitignore v0.0.0-20201211074657-223ce5d391b0/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ= github.com/sabhiram/go-gitignore v0.0.0-20201211074657-223ce5d391b0/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -1688,6 +1750,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@ -1700,8 +1763,9 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -1715,8 +1779,9 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
@ -1797,6 +1862,7 @@ github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5
github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo=
github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible/go.mod h1:Smz/ZWfhKRcyDDChZkG3CyTHdj87lHzio/HOCkbndXM= github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible/go.mod h1:Smz/ZWfhKRcyDDChZkG3CyTHdj87lHzio/HOCkbndXM=
github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@ -1806,6 +1872,7 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
github.com/urfave/cli v1.20.0/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.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -1814,6 +1881,8 @@ github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8JDaKXR4JGI= github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8JDaKXR4JGI=
github.com/vdemeester/k8s-pkg-credentialprovider v1.19.7/go.mod h1:K2nMO14cgZitdwBqdQps9tInJgcaXcU/7q5F59lpbNI= github.com/vdemeester/k8s-pkg-credentialprovider v1.19.7/go.mod h1:K2nMO14cgZitdwBqdQps9tInJgcaXcU/7q5F59lpbNI=
github.com/vdemeester/k8s-pkg-credentialprovider v1.20.7/go.mod h1:K2nMO14cgZitdwBqdQps9tInJgcaXcU/7q5F59lpbNI= github.com/vdemeester/k8s-pkg-credentialprovider v1.20.7/go.mod h1:K2nMO14cgZitdwBqdQps9tInJgcaXcU/7q5F59lpbNI=
@ -1831,7 +1900,6 @@ github.com/wavesoftware/go-ensure v1.0.0/go.mod h1:K2UAFSwMTvpiRGay/M3aEYYuurcR8
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
@ -1878,8 +1946,11 @@ go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6y
go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
@ -2107,6 +2178,7 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o
golang.org/x/net v0.0.0-20210326220855-61e056675ecf/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210326220855-61e056675ecf/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -2114,11 +2186,15 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211205041911-012df41ee64c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -2258,6 +2334,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -2280,23 +2357,31 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2445,8 +2530,9 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -2499,8 +2585,12 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
google.golang.org/api v0.61.0 h1:TXXKS1slM3b2bZNJwD5DV/Tp6/M2cLzLOLh9PjDhrw8= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.65.0 h1:MTW9c+LIBAbwoS1Gb+YV7NjFBt2f7GtAS5hIzh2NjgQ=
google.golang.org/api v0.65.0/go.mod h1:ArYhxgGadlWmqO1IqVujw6Cs8IdD33bTmzKo2Sh+cbg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -2591,10 +2681,19 @@ google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEc
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12 h1:DN5b3HU13J4sMd/QjDx34U6afpaexKTDdop+26pdjdk=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 h1:YxHp5zqIcAShDEvRr5/0rVESVS+njYF68PSdazrNLJo=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@ -2630,9 +2729,11 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -2675,8 +2776,9 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
@ -2815,7 +2917,6 @@ k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAE
k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo v0.0.0-20210203185629-de9496dff47b/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210203185629-de9496dff47b/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo v0.0.0-20210915205010-39e73c8a59cd/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
@ -2845,42 +2946,38 @@ k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
knative.dev/caching v0.0.0-20210803185815-4e553d2275a0/go.mod h1:Vs+HND39+KKaIQp9M3m3Jmt4YtznpitDQ3n53gxbDYQ= knative.dev/caching v0.0.0-20210803185815-4e553d2275a0/go.mod h1:Vs+HND39+KKaIQp9M3m3Jmt4YtznpitDQ3n53gxbDYQ=
knative.dev/caching v0.0.0-20211101215439-72577a3c0ce1/go.mod h1:hz6tnVAmRLvPoprw+0wsA9IrZC3MF5l2Hn/2JT28Yk0= knative.dev/caching v0.0.0-20211206133228-c29dc56d8f03/go.mod h1:xki+LBTL1riXSoU2dKznqUfgOlQ2eO/F1WF+GMXxH0k=
knative.dev/client v0.27.0 h1:qZkdFFCjPDsQrfHQMd1vPanDxEVloEJeUrrC3a52pK4= knative.dev/client v0.28.0 h1:x+wghPn3tZ/rPf7gsBcEro43Ev4zSwq91NGfNi/7lPI=
knative.dev/client v0.27.0/go.mod h1:FmXziLwolJtqaH3Jxe8cxqWjBUHXU6LsRsMyzlMSFFM= knative.dev/client v0.28.0/go.mod h1:QcPrTsTdc/3sGKugtjL9yYyTwcNvD5Su/KHltFVLlJ0=
knative.dev/eventing v0.25.0/go.mod h1:8jIsrnSONPgv+m63OTzpwZQJiQASYl77C3llCyYlBMU= knative.dev/eventing v0.25.0/go.mod h1:8jIsrnSONPgv+m63OTzpwZQJiQASYl77C3llCyYlBMU=
knative.dev/eventing v0.27.0/go.mod h1:4ppWQEQ/2B66/YFENDmV1Gjxs4meLpz6UTUgLkkINt4= knative.dev/eventing v0.28.0/go.mod h1:zxoB37kQxEkfCvKA7wZuHOPYInz6SFJzrhmG9IAd/iY=
knative.dev/eventing v0.27.2 h1:Zslo5WhIxAHAztLjoSCcnW52SNO2hIumK9dMp9V/Zc8= knative.dev/eventing v0.28.4 h1:frHm4zNcJaAfA1m11pn+ueyAyqQOSmy5j9I4dpf2c0M=
knative.dev/eventing v0.27.2/go.mod h1:k/Xt54hNh9YfMSYdMTkJ0TNrkwU9hL+Frn+n9zsAdaE= knative.dev/eventing v0.28.4/go.mod h1:zxoB37kQxEkfCvKA7wZuHOPYInz6SFJzrhmG9IAd/iY=
knative.dev/hack v0.0.0-20210325223819-b6ab329907d3/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= knative.dev/hack v0.0.0-20210325223819-b6ab329907d3/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack v0.0.0-20210622141627-e28525d8d260/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= knative.dev/hack v0.0.0-20210622141627-e28525d8d260/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack v0.0.0-20210806075220-815cd312d65c/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= knative.dev/hack v0.0.0-20210806075220-815cd312d65c/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack v0.0.0-20211101195839-11d193bf617b/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= knative.dev/hack v0.0.0-20211122162614-813559cefdda/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack v0.0.0-20211112152736-4de3924914b2/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= knative.dev/hack v0.0.0-20211203062838-e11ac125e707 h1:Nx3HBoTHjYzXT9xxh5j6A8pMapNqyDLqjl784YxWPDQ=
knative.dev/hack v0.0.0-20211122163517-fe1340f21191/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack v0.0.0-20211203062838-e11ac125e707/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= knative.dev/hack v0.0.0-20211203062838-e11ac125e707/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack v0.0.0-20220209225905-7331bb16ba00/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack v0.0.0-20220216040439-0456e8bf6547 h1:lEWsaG/yMLLp3onNushrawsHFXD4LXCXTo5FUUa2GiU=
knative.dev/hack v0.0.0-20220216040439-0456e8bf6547/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
knative.dev/hack/schema v0.0.0-20210622141627-e28525d8d260/go.mod h1:ffjwmdcrH5vN3mPhO8RrF2KfNnbHeCE2C60A+2cv3U0= knative.dev/hack/schema v0.0.0-20210622141627-e28525d8d260/go.mod h1:ffjwmdcrH5vN3mPhO8RrF2KfNnbHeCE2C60A+2cv3U0=
knative.dev/hack/schema v0.0.0-20211101195839-11d193bf617b/go.mod h1:ffjwmdcrH5vN3mPhO8RrF2KfNnbHeCE2C60A+2cv3U0= knative.dev/hack/schema v0.0.0-20211203062838-e11ac125e707/go.mod h1:ffjwmdcrH5vN3mPhO8RrF2KfNnbHeCE2C60A+2cv3U0=
knative.dev/hack/schema v0.0.0-20211122163517-fe1340f21191/go.mod h1:ffjwmdcrH5vN3mPhO8RrF2KfNnbHeCE2C60A+2cv3U0=
knative.dev/networking v0.0.0-20210803181815-acdfd41c575c/go.mod h1:UA9m1M3rGssy63gVwjSh7CYoWTKZNO8cnY9QsIu7tyo= knative.dev/networking v0.0.0-20210803181815-acdfd41c575c/go.mod h1:UA9m1M3rGssy63gVwjSh7CYoWTKZNO8cnY9QsIu7tyo=
knative.dev/networking v0.0.0-20211101215640-8c71a2708e7d h1:nCnuNfcLWuyAdZYgVfwSooa3+p2ebx+V+qhGXXF/FIk= knative.dev/networking v0.0.0-20211209101835-8ef631418fc0/go.mod h1:+ozCw7PVf//G9+HOW04hfWnU8UJE5fmWAQkb+ieMaXY=
knative.dev/networking v0.0.0-20211101215640-8c71a2708e7d/go.mod h1:7SKKM4MsBANrXNRZhb/zMkNjTdxYbNjwQDWgu+Fyye4= knative.dev/networking v0.0.0-20220120045035-ec849677a316 h1:fHgHP3nnJSfq115RgTMYVHPQBsBKQYtjUVGhMT/VLaA=
knative.dev/networking v0.0.0-20220120045035-ec849677a316/go.mod h1:+ozCw7PVf//G9+HOW04hfWnU8UJE5fmWAQkb+ieMaXY=
knative.dev/pkg v0.0.0-20210331065221-952fdd90dbb0/go.mod h1:PD5g8hUCXq6iR3tILjmZeJBvQfXGnHMPKryq54qHJhg= knative.dev/pkg v0.0.0-20210331065221-952fdd90dbb0/go.mod h1:PD5g8hUCXq6iR3tILjmZeJBvQfXGnHMPKryq54qHJhg=
knative.dev/pkg v0.0.0-20210803160015-21eb4c167cc5/go.mod h1:RPk5txNA3apR9X40D4MpUOP9/VqOG8CrtABWfOwGVS4= knative.dev/pkg v0.0.0-20210803160015-21eb4c167cc5/go.mod h1:RPk5txNA3apR9X40D4MpUOP9/VqOG8CrtABWfOwGVS4=
knative.dev/pkg v0.0.0-20210827184538-2bd91f75571c/go.mod h1:jMSqkNMsrzuy+XR4Yr/BMy7SDVbUOl3KKB6+5MR+ZU8= knative.dev/pkg v0.0.0-20210827184538-2bd91f75571c/go.mod h1:jMSqkNMsrzuy+XR4Yr/BMy7SDVbUOl3KKB6+5MR+ZU8=
knative.dev/pkg v0.0.0-20211101212339-96c0204a70dc/go.mod h1:SkfDk9bWIiNZD7XtILGkG7AKVyF/M6M0bGxLgl0SYL8= knative.dev/pkg v0.0.0-20211206113427-18589ac7627e/go.mod h1:E6B4RTjZyxe55a0kxOlnEHEl71zuG7gghnqYvNBKwBw=
knative.dev/pkg v0.0.0-20220104185830-52e42b760b54/go.mod h1:189cvGP0mwpqwZGFrLk5WuERIsNI/J6HuQ1CIX7SXxY= knative.dev/pkg v0.0.0-20220104185830-52e42b760b54/go.mod h1:189cvGP0mwpqwZGFrLk5WuERIsNI/J6HuQ1CIX7SXxY=
knative.dev/pkg v0.0.0-20220215153400-3c00bb0157b9 h1:u0vYuNe0BTFvbqN8euaauL/kN3jR7mjmxMc+QRnWsUw= knative.dev/pkg v0.0.0-20220222214539-0b8a9403de7e h1:+5xPVmp1aajA1z1BxGmUrPLj8y0GEDdHh0y+YTi8XHk=
knative.dev/pkg v0.0.0-20220215153400-3c00bb0157b9/go.mod h1:6ZoCgi60jSUn/WrwTGNAZbsz5/kmwiZZD8EovSLzYZ4= knative.dev/pkg v0.0.0-20220222214539-0b8a9403de7e/go.mod h1:E6B4RTjZyxe55a0kxOlnEHEl71zuG7gghnqYvNBKwBw=
knative.dev/reconciler-test v0.0.0-20210803183715-b61cc77c06f6/go.mod h1:+Kovy+G5zXZNcuO/uB+zfo37vFKZzsLIlWezt/nKMz0= knative.dev/reconciler-test v0.0.0-20210803183715-b61cc77c06f6/go.mod h1:+Kovy+G5zXZNcuO/uB+zfo37vFKZzsLIlWezt/nKMz0=
knative.dev/reconciler-test v0.0.0-20211101214439-9839937c9b13/go.mod h1:gTsbLk496j/M9xqMpx/liyCQ0X3bwDpRtcs2Zzws364= knative.dev/reconciler-test v0.0.0-20211207070557-0d138a88867b/go.mod h1:dCq1Fuu+eUISdnxABMvoDhefF91DYwE6O3rTYTraXbw=
knative.dev/serving v0.25.0/go.mod h1:24E4fVyViFnz8aAaafzdrYKB7CAsQr4FMU7QXoIE6CI= knative.dev/serving v0.25.0/go.mod h1:24E4fVyViFnz8aAaafzdrYKB7CAsQr4FMU7QXoIE6CI=
knative.dev/serving v0.27.0/go.mod h1:Lcqv3K/DeDt6ZoeWTgpjxc/0teS7uMebnnU+gXIw99c= knative.dev/serving v0.28.0/go.mod h1:1d8YYUu0hY19KlIRs2SgAn/o64Hr265+3fhOtV3FFVA=
knative.dev/serving v0.27.1 h1:d3cazLiXkvFIcqPYGlV+qvGkMdqb5yI6kiw0FovC4dc= knative.dev/serving v0.28.4 h1:745+sWSEaSxDVpKXqmPVaKb4nzo1n8W6cw++TCvHD3U=
knative.dev/serving v0.27.1/go.mod h1:70KmpwlBztk82pHmwRUkPGasQChFmefUyZWMDOzEoKk= knative.dev/serving v0.28.4/go.mod h1:jTq5OY/B4PazjfQI5MjTMEDdZgxlWRXA7qIU8FrDwSU=
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=

View File

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

View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -11,12 +11,27 @@ package.
Please see the LICENSE file for licensing information. Please see the LICENSE file for licensing information.
This project has adopted the [Microsoft Open Source Code of ## Contributing
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.
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 require that contributors sign their commits 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. Please see https://developercertificate.org/ for more info, as well as to make sure that you can
attest to the rules listed. Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
## 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.
## Special Thanks
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
for another named pipe implementation. for another named pipe implementation.

View File

@ -2,6 +2,7 @@ package osversion
import ( import (
"fmt" "fmt"
"sync"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
@ -15,11 +16,17 @@ type OSVersion struct {
Build uint16 Build uint16
} }
var (
osv OSVersion
once sync.Once
)
// Get gets the operating system version on Windows. // Get gets the operating system version on Windows.
// The calling application must be manifested to get the correct version information. // The calling application must be manifested to get the correct version information.
func Get() OSVersion { func Get() OSVersion {
once.Do(func() {
var err error var err error
osv := OSVersion{} osv = OSVersion{}
osv.Version, err = windows.GetVersion() osv.Version, err = windows.GetVersion()
if err != nil { if err != nil {
// GetVersion never fails. // GetVersion never fails.
@ -28,6 +35,7 @@ func Get() OSVersion {
osv.MajorVersion = uint8(osv.Version & 0xFF) osv.MajorVersion = uint8(osv.Version & 0xFF)
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF) osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
osv.Build = uint16(osv.Version >> 16) osv.Build = uint16(osv.Version >> 16)
})
return osv return osv
} }

View File

@ -2,10 +2,9 @@
*Go language library to map between non-negative integers and boolean values* *Go language library to map between non-negative integers and boolean values*
[![Test](https://github.com/willf/bitset/workflows/Test/badge.svg)](https://github.com/willf/bitset/actions?query=workflow%3ATest) [![Test](https://github.com/bits-and-blooms/bitset/workflows/Test/badge.svg)](https://github.com/willf/bitset/actions?query=workflow%3ATest)
[![Master Coverage Status](https://coveralls.io/repos/willf/bitset/badge.svg?branch=master&service=github)](https://coveralls.io/github/willf/bitset?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/willf/bitset)](https://goreportcard.com/report/github.com/willf/bitset) [![Go Report Card](https://goreportcard.com/badge/github.com/willf/bitset)](https://goreportcard.com/report/github.com/willf/bitset)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/willf/bitset?tab=doc)](https://pkg.go.dev/github.com/willf/bitset?tab=doc) [![PkgGoDev](https://pkg.go.dev/badge/github.com/bits-and-blooms/bitset?tab=doc)](https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc)
## Description ## Description
@ -30,7 +29,7 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"github.com/willf/bitset" "github.com/bits-and-blooms/bitset"
) )
func main() { func main() {
@ -63,7 +62,7 @@ func main() {
As an alternative to BitSets, one should check out the 'big' package, which provides a (less set-theoretical) view of bitsets. As an alternative to BitSets, one should check out the 'big' package, which provides a (less set-theoretical) view of bitsets.
Package documentation is at: https://pkg.go.dev/github.com/willf/bitset?tab=doc Package documentation is at: https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc
## Memory Usage ## Memory Usage
@ -78,7 +77,7 @@ It is possible that a later version will match the `math/bits` return signature
## Installation ## Installation
```bash ```bash
go get github.com/willf/bitset go get github.com/bits-and-blooms/bitset
``` ```
## Contributing ## Contributing

View File

@ -209,6 +209,27 @@ func (b *BitSet) Flip(i uint) *BitSet {
return b return b
} }
// FlipRange bit in [start, end).
// If end>= Cap(), this function will panic.
// Warning: using a very large value for 'end'
// may lead to a memory shortage and a panic: the caller is responsible
// for providing sensible parameters in line with their memory capacity.
func (b *BitSet) FlipRange(start, end uint) *BitSet {
if start >= end {
return b
}
b.extendSetMaybe(end - 1)
var startWord uint = start >> log2WordSize
var endWord uint = end >> log2WordSize
b.set[startWord] ^= ^(^uint64(0) << (start & (wordSize - 1)))
for i := startWord; i < endWord; i++ {
b.set[i] = ^b.set[i]
}
b.set[endWord] ^= ^uint64(0) >> (-end & (wordSize - 1))
return b
}
// Shrink shrinks BitSet so that the provided value is the last possible // Shrink shrinks BitSet so that the provided value is the last possible
// set value. It clears all bits > the provided index and reduces the size // set value. It clears all bits > the provided index and reduces the size
// and length of the set. // and length of the set.
@ -519,7 +540,7 @@ func (b *BitSet) Copy(c *BitSet) (count uint) {
} }
// Count (number of set bits). // Count (number of set bits).
// Also known as "popcount" or "popularity count". // Also known as "popcount" or "population count".
func (b *BitSet) Count() uint { func (b *BitSet) Count() uint {
if b != nil && b.set != nil { if b != nil && b.set != nil {
return uint(popcntSlice(b.set)) return uint(popcntSlice(b.set))

3
vendor/github.com/bits-and-blooms/bitset/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/bits-and-blooms/bitset
go 1.14

View File

@ -1,8 +0,0 @@
language: go
go:
- "1.x"
- master
env:
- TAGS=""
- TAGS="-tags purego"
script: go test $TAGS -v ./...

View File

@ -1,7 +1,7 @@
# xxhash # xxhash
[![GoDoc](https://godoc.org/github.com/cespare/xxhash?status.svg)](https://godoc.org/github.com/cespare/xxhash) [![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
[![Build Status](https://travis-ci.org/cespare/xxhash.svg?branch=master)](https://travis-ci.org/cespare/xxhash) [![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
xxhash is a Go implementation of the 64-bit xxhash is a Go implementation of the 64-bit
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a [xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
@ -64,4 +64,6 @@ $ go test -benchtime 10s -bench '/xxhash,direct,bytes'
- [InfluxDB](https://github.com/influxdata/influxdb) - [InfluxDB](https://github.com/influxdata/influxdb)
- [Prometheus](https://github.com/prometheus/prometheus) - [Prometheus](https://github.com/prometheus/prometheus)
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache) - [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache)

View File

@ -193,7 +193,6 @@ func (d *Digest) UnmarshalBinary(b []byte) error {
b, d.v4 = consumeUint64(b) b, d.v4 = consumeUint64(b)
b, d.total = consumeUint64(b) b, d.total = consumeUint64(b)
copy(d.mem[:], b) copy(d.mem[:], b)
b = b[len(d.mem):]
d.n = int(d.total % uint64(len(d.mem))) d.n = int(d.total % uint64(len(d.mem)))
return nil return nil
} }

View File

@ -6,7 +6,7 @@
// Register allocation: // Register allocation:
// AX h // AX h
// CX pointer to advance through b // SI pointer to advance through b
// DX n // DX n
// BX loop end // BX loop end
// R8 v1, k1 // R8 v1, k1
@ -16,39 +16,39 @@
// R12 tmp // R12 tmp
// R13 prime1v // R13 prime1v
// R14 prime2v // R14 prime2v
// R15 prime4v // DI prime4v
// round reads from and advances the buffer pointer in CX. // round reads from and advances the buffer pointer in SI.
// It assumes that R13 has prime1v and R14 has prime2v. // It assumes that R13 has prime1v and R14 has prime2v.
#define round(r) \ #define round(r) \
MOVQ (CX), R12 \ MOVQ (SI), R12 \
ADDQ $8, CX \ ADDQ $8, SI \
IMULQ R14, R12 \ IMULQ R14, R12 \
ADDQ R12, r \ ADDQ R12, r \
ROLQ $31, r \ ROLQ $31, r \
IMULQ R13, r IMULQ R13, r
// mergeRound applies a merge round on the two registers acc and val. // mergeRound applies a merge round on the two registers acc and val.
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v. // It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
#define mergeRound(acc, val) \ #define mergeRound(acc, val) \
IMULQ R14, val \ IMULQ R14, val \
ROLQ $31, val \ ROLQ $31, val \
IMULQ R13, val \ IMULQ R13, val \
XORQ val, acc \ XORQ val, acc \
IMULQ R13, acc \ IMULQ R13, acc \
ADDQ R15, acc ADDQ DI, acc
// func Sum64(b []byte) uint64 // func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT, $0-32 TEXT ·Sum64(SB), NOSPLIT, $0-32
// Load fixed primes. // Load fixed primes.
MOVQ ·prime1v(SB), R13 MOVQ ·prime1v(SB), R13
MOVQ ·prime2v(SB), R14 MOVQ ·prime2v(SB), R14
MOVQ ·prime4v(SB), R15 MOVQ ·prime4v(SB), DI
// Load slice. // Load slice.
MOVQ b_base+0(FP), CX MOVQ b_base+0(FP), SI
MOVQ b_len+8(FP), DX MOVQ b_len+8(FP), DX
LEAQ (CX)(DX*1), BX LEAQ (SI)(DX*1), BX
// The first loop limit will be len(b)-32. // The first loop limit will be len(b)-32.
SUBQ $32, BX SUBQ $32, BX
@ -65,14 +65,14 @@ TEXT ·Sum64(SB), NOSPLIT, $0-32
XORQ R11, R11 XORQ R11, R11
SUBQ R13, R11 SUBQ R13, R11
// Loop until CX > BX. // Loop until SI > BX.
blockLoop: blockLoop:
round(R8) round(R8)
round(R9) round(R9)
round(R10) round(R10)
round(R11) round(R11)
CMPQ CX, BX CMPQ SI, BX
JLE blockLoop JLE blockLoop
MOVQ R8, AX MOVQ R8, AX
@ -100,16 +100,16 @@ noBlocks:
afterBlocks: afterBlocks:
ADDQ DX, AX ADDQ DX, AX
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8. // Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
ADDQ $24, BX ADDQ $24, BX
CMPQ CX, BX CMPQ SI, BX
JG fourByte JG fourByte
wordLoop: wordLoop:
// Calculate k1. // Calculate k1.
MOVQ (CX), R8 MOVQ (SI), R8
ADDQ $8, CX ADDQ $8, SI
IMULQ R14, R8 IMULQ R14, R8
ROLQ $31, R8 ROLQ $31, R8
IMULQ R13, R8 IMULQ R13, R8
@ -117,18 +117,18 @@ wordLoop:
XORQ R8, AX XORQ R8, AX
ROLQ $27, AX ROLQ $27, AX
IMULQ R13, AX IMULQ R13, AX
ADDQ R15, AX ADDQ DI, AX
CMPQ CX, BX CMPQ SI, BX
JLE wordLoop JLE wordLoop
fourByte: fourByte:
ADDQ $4, BX ADDQ $4, BX
CMPQ CX, BX CMPQ SI, BX
JG singles JG singles
MOVL (CX), R8 MOVL (SI), R8
ADDQ $4, CX ADDQ $4, SI
IMULQ R13, R8 IMULQ R13, R8
XORQ R8, AX XORQ R8, AX
@ -138,19 +138,19 @@ fourByte:
singles: singles:
ADDQ $4, BX ADDQ $4, BX
CMPQ CX, BX CMPQ SI, BX
JGE finalize JGE finalize
singlesLoop: singlesLoop:
MOVBQZX (CX), R12 MOVBQZX (SI), R12
ADDQ $1, CX ADDQ $1, SI
IMULQ ·prime5v(SB), R12 IMULQ ·prime5v(SB), R12
XORQ R12, AX XORQ R12, AX
ROLQ $11, AX ROLQ $11, AX
IMULQ R13, AX IMULQ R13, AX
CMPQ CX, BX CMPQ SI, BX
JL singlesLoop JL singlesLoop
finalize: finalize:
@ -179,9 +179,9 @@ TEXT ·writeBlocks(SB), NOSPLIT, $0-40
MOVQ ·prime2v(SB), R14 MOVQ ·prime2v(SB), R14
// Load slice. // Load slice.
MOVQ b_base+8(FP), CX MOVQ b_base+8(FP), SI
MOVQ b_len+16(FP), DX MOVQ b_len+16(FP), DX
LEAQ (CX)(DX*1), BX LEAQ (SI)(DX*1), BX
SUBQ $32, BX SUBQ $32, BX
// Load vN from d. // Load vN from d.
@ -199,7 +199,7 @@ blockLoop:
round(R10) round(R10)
round(R11) round(R11)
CMPQ CX, BX CMPQ SI, BX
JLE blockLoop JLE blockLoop
// Copy vN back to d. // Copy vN back to d.
@ -208,8 +208,8 @@ blockLoop:
MOVQ R10, 16(AX) MOVQ R10, 16(AX)
MOVQ R11, 24(AX) MOVQ R11, 24(AX)
// The number of bytes written is CX minus the old base pointer. // The number of bytes written is SI minus the old base pointer.
SUBQ b_base+8(FP), CX SUBQ b_base+8(FP), SI
MOVQ CX, ret+32(FP) MOVQ SI, ret+32(FP)
RET RET

View File

@ -6,41 +6,52 @@
package xxhash package xxhash
import ( import (
"reflect"
"unsafe" "unsafe"
) )
// Notes:
//
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
// for some discussion about these unsafe conversions.
//
// In the future it's possible that compiler optimizations will make these // In the future it's possible that compiler optimizations will make these
// unsafe operations unnecessary: https://golang.org/issue/2205. // XxxString functions unnecessary by realizing that calls such as
// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
// If that happens, even if we keep these functions they can be replaced with
// the trivial safe code.
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
// //
// Both of these wrapper functions still incur function call overhead since they // var b []byte
// will not be inlined. We could write Go/asm copies of Sum64 and Digest.Write // bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
// for strings to squeeze out a bit more speed. Mid-stack inlining should // bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
// eventually fix this. // bh.Len = len(s)
// bh.Cap = len(s)
//
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
// weight to this sequence of expressions that any function that uses it will
// not be inlined. Instead, the functions below use a different unsafe
// conversion designed to minimize the inliner weight and allow both to be
// inlined. There is also a test (TestInlining) which verifies that these are
// inlined.
//
// See https://github.com/golang/go/issues/42739 for discussion.
// Sum64String computes the 64-bit xxHash digest of s. // Sum64String computes the 64-bit xxHash digest of s.
// It may be faster than Sum64([]byte(s)) by avoiding a copy. // It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 { func Sum64String(s string) uint64 {
var b []byte b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
bh.Len = len(s)
bh.Cap = len(s)
return Sum64(b) return Sum64(b)
} }
// WriteString adds more data to d. It always returns len(s), nil. // WriteString adds more data to d. It always returns len(s), nil.
// It may be faster than Write([]byte(s)) by avoiding a copy. // It may be faster than Write([]byte(s)) by avoiding a copy.
func (d *Digest) WriteString(s string) (n int, err error) { func (d *Digest) WriteString(s string) (n int, err error) {
var b []byte d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) // d.Write always returns len(s), nil.
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data // Ignoring the return output and returning these fixed values buys a
bh.Len = len(s) // savings of 6 in the inliner's cost model.
bh.Cap = len(s) return len(s), nil
return d.Write(b) }
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
// of the first two words is the same as the layout of a string.
type sliceHeader struct {
s string
cap int
} }

View File

@ -26,7 +26,6 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -48,6 +47,7 @@ type options struct {
compressionLevel int compressionLevel int
prioritizedFiles []string prioritizedFiles []string
missedPrioritizedFiles *[]string missedPrioritizedFiles *[]string
compression Compression
} }
type Option func(o *options) error type Option func(o *options) error
@ -95,6 +95,15 @@ func WithAllowPrioritizeNotFound(missedFiles *[]string) Option {
} }
} }
// WithCompression specifies compression algorithm to be used.
// Default is gzip.
func WithCompression(compression Compression) Option {
return func(o *options) error {
o.compression = compression
return nil
}
}
// Blob is an eStargz blob. // Blob is an eStargz blob.
type Blob struct { type Blob struct {
io.ReadCloser io.ReadCloser
@ -126,6 +135,9 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) {
return nil, err return nil, err
} }
} }
if opts.compression == nil {
opts.compression = newGzipCompressionWithLevel(opts.compressionLevel)
}
layerFiles := newTempFiles() layerFiles := newTempFiles()
defer func() { defer func() {
if rErr != nil { if rErr != nil {
@ -155,7 +167,7 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) {
if err != nil { if err != nil {
return err return err
} }
sw := NewWriterLevel(esgzFile, opts.compressionLevel) sw := NewWriterWithCompressor(esgzFile, opts.compression)
sw.ChunkSize = opts.chunkSize sw.ChunkSize = opts.chunkSize
if err := sw.AppendTar(readerFromEntries(parts...)); err != nil { if err := sw.AppendTar(readerFromEntries(parts...)); err != nil {
return err return err
@ -187,11 +199,12 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) {
diffID := digest.Canonical.Digester() diffID := digest.Canonical.Digester()
pr, pw := io.Pipe() pr, pw := io.Pipe()
go func() { go func() {
r, err := gzip.NewReader(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw)) r, err := opts.compression.Reader(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw))
if err != nil { if err != nil {
pw.CloseWithError(err) pw.CloseWithError(err)
return return
} }
defer r.Close()
if _, err := io.Copy(diffID.Hash(), r); err != nil { if _, err := io.Copy(diffID.Hash(), r); err != nil {
pw.CloseWithError(err) pw.CloseWithError(err)
return return
@ -213,7 +226,7 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) {
// Writers doesn't write TOC and footer to the underlying writers so they can be // Writers doesn't write TOC and footer to the underlying writers so they can be
// combined into a single eStargz and tocAndFooter returned by this function can // combined into a single eStargz and tocAndFooter returned by this function can
// be appended at the tail of that combined blob. // be appended at the tail of that combined blob.
func closeWithCombine(compressionLevel int, ws ...*Writer) (tocAndFooter io.Reader, tocDgst digest.Digest, err error) { func closeWithCombine(compressionLevel int, ws ...*Writer) (tocAndFooterR io.Reader, tocDgst digest.Digest, err error) {
if len(ws) == 0 { if len(ws) == 0 {
return nil, "", fmt.Errorf("at least one writer must be passed") return nil, "", fmt.Errorf("at least one writer must be passed")
} }
@ -230,7 +243,7 @@ func closeWithCombine(compressionLevel int, ws ...*Writer) (tocAndFooter io.Read
} }
} }
var ( var (
mtoc = new(jtoc) mtoc = new(JTOC)
currentOffset int64 currentOffset int64
) )
mtoc.Version = ws[0].toc.Version mtoc.Version = ws[0].toc.Version
@ -248,40 +261,16 @@ func closeWithCombine(compressionLevel int, ws ...*Writer) (tocAndFooter io.Read
currentOffset += w.cw.n currentOffset += w.cw.n
} }
tocJSON, err := json.MarshalIndent(mtoc, "", "\t") return tocAndFooter(ws[0].compressor, mtoc, currentOffset)
}
func tocAndFooter(compressor Compressor, toc *JTOC, offset int64) (io.Reader, digest.Digest, error) {
buf := new(bytes.Buffer)
tocDigest, err := compressor.WriteTOCAndFooter(buf, offset, toc, nil)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
pr, pw := io.Pipe() return buf, tocDigest, nil
go func() {
zw, _ := gzip.NewWriterLevel(pw, compressionLevel)
tw := tar.NewWriter(zw)
if err := tw.WriteHeader(&tar.Header{
Typeflag: tar.TypeReg,
Name: TOCTarName,
Size: int64(len(tocJSON)),
}); err != nil {
pw.CloseWithError(err)
return
}
if _, err := tw.Write(tocJSON); err != nil {
pw.CloseWithError(err)
return
}
if err := tw.Close(); err != nil {
pw.CloseWithError(err)
return
}
if err := zw.Close(); err != nil {
pw.CloseWithError(err)
return
}
pw.Close()
}()
return io.MultiReader(
pr,
bytes.NewReader(footerBytes(currentOffset)),
), digest.FromBytes(tocJSON), nil
} }
// divideEntries divides passed entries to the parts at least the number specified by the // divideEntries divides passed entries to the parts at least the number specified by the

View File

@ -23,13 +23,10 @@
package estargz package estargz
import ( import (
"archive/tar"
"bufio" "bufio"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/sha256" "crypto/sha256"
"encoding/binary"
"encoding/json"
"fmt" "fmt"
"hash" "hash"
"io" "io"
@ -37,7 +34,6 @@ import (
"os" "os"
"path" "path"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -45,12 +41,13 @@ import (
"github.com/containerd/stargz-snapshotter/estargz/errorutil" "github.com/containerd/stargz-snapshotter/estargz/errorutil"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/vbatts/tar-split/archive/tar"
) )
// A Reader permits random access reads from a stargz file. // A Reader permits random access reads from a stargz file.
type Reader struct { type Reader struct {
sr *io.SectionReader sr *io.SectionReader
toc *jtoc toc *JTOC
tocDigest digest.Digest tocDigest digest.Digest
// m stores all non-chunk entries, keyed by name. // m stores all non-chunk entries, keyed by name.
@ -60,39 +57,116 @@ type Reader struct {
// are split up. For a file with a single chunk, it's only // are split up. For a file with a single chunk, it's only
// stored in m. // stored in m.
chunks map[string][]*TOCEntry chunks map[string][]*TOCEntry
decompressor Decompressor
}
type openOpts struct {
tocOffset int64
decompressors []Decompressor
telemetry *Telemetry
}
// OpenOption is an option used during opening the layer
type OpenOption func(o *openOpts) error
// WithTOCOffset option specifies the offset of TOC
func WithTOCOffset(tocOffset int64) OpenOption {
return func(o *openOpts) error {
o.tocOffset = tocOffset
return nil
}
}
// WithDecompressors option specifies decompressors to use.
// Default is gzip-based decompressor.
func WithDecompressors(decompressors ...Decompressor) OpenOption {
return func(o *openOpts) error {
o.decompressors = decompressors
return nil
}
}
// WithTelemetry option specifies the telemetry hooks
func WithTelemetry(telemetry *Telemetry) OpenOption {
return func(o *openOpts) error {
o.telemetry = telemetry
return nil
}
}
// MeasureLatencyHook is a func which takes start time and records the diff
type MeasureLatencyHook func(time.Time)
// Telemetry is a struct which defines telemetry hooks. By implementing these hooks you should be able to record
// the latency metrics of the respective steps of estargz open operation. To be used with estargz.OpenWithTelemetry(...)
type Telemetry struct {
GetFooterLatency MeasureLatencyHook // measure time to get stargz footer (in milliseconds)
GetTocLatency MeasureLatencyHook // measure time to GET TOC JSON (in milliseconds)
DeserializeTocLatency MeasureLatencyHook // measure time to deserialize TOC JSON (in milliseconds)
} }
// Open opens a stargz file for reading. // Open opens a stargz file for reading.
// The behavior is configurable using options.
// //
// Note that each entry name is normalized as the path that is relative to root. // Note that each entry name is normalized as the path that is relative to root.
func Open(sr *io.SectionReader) (*Reader, error) { func Open(sr *io.SectionReader, opt ...OpenOption) (*Reader, error) {
tocOff, footerSize, err := OpenFooter(sr) var opts openOpts
for _, o := range opt {
if err := o(&opts); err != nil {
return nil, err
}
}
gzipCompressors := []Decompressor{new(GzipDecompressor), new(LegacyGzipDecompressor)}
decompressors := append(gzipCompressors, opts.decompressors...)
// Determine the size to fetch. Try to fetch as many bytes as possible.
fetchSize := maxFooterSize(sr.Size(), decompressors...)
if maybeTocOffset := opts.tocOffset; maybeTocOffset > fetchSize {
if maybeTocOffset > sr.Size() {
return nil, fmt.Errorf("blob size %d is smaller than the toc offset", sr.Size())
}
fetchSize = sr.Size() - maybeTocOffset
}
start := time.Now() // before getting layer footer
footer := make([]byte, fetchSize)
if _, err := sr.ReadAt(footer, sr.Size()-fetchSize); err != nil {
return nil, fmt.Errorf("error reading footer: %v", err)
}
if opts.telemetry != nil && opts.telemetry.GetFooterLatency != nil {
opts.telemetry.GetFooterLatency(start)
}
var allErr []error
var found bool
var r *Reader
for _, d := range decompressors {
fSize := d.FooterSize()
fOffset := positive(int64(len(footer)) - fSize)
maybeTocBytes := footer[:fOffset]
_, tocOffset, tocSize, err := d.ParseFooter(footer[fOffset:])
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error parsing footer") allErr = append(allErr, err)
continue
} }
tocTargz := make([]byte, sr.Size()-tocOff-footerSize) if tocSize <= 0 {
if _, err := sr.ReadAt(tocTargz, tocOff); err != nil { tocSize = sr.Size() - tocOffset - fSize
return nil, fmt.Errorf("error reading %d byte TOC targz: %v", len(tocTargz), err)
} }
zr, err := gzip.NewReader(bytes.NewReader(tocTargz)) if tocSize < int64(len(maybeTocBytes)) {
if err != nil { maybeTocBytes = maybeTocBytes[:tocSize]
return nil, fmt.Errorf("malformed TOC gzip header: %v", err)
} }
zr.Multistream(false) r, err = parseTOC(d, sr, tocOffset, tocSize, maybeTocBytes, opts)
tr := tar.NewReader(zr) if err == nil {
h, err := tr.Next() found = true
if err != nil { break
return nil, fmt.Errorf("failed to find tar header in TOC gzip stream: %v", err)
} }
if h.Name != TOCTarName { allErr = append(allErr, err)
return nil, fmt.Errorf("TOC tar entry had name %q; expected %q", h.Name, TOCTarName)
} }
dgstr := digest.Canonical.Digester() if !found {
toc := new(jtoc) return nil, errorutil.Aggregate(allErr)
if err := json.NewDecoder(io.TeeReader(tr, dgstr.Hash())).Decode(&toc); err != nil {
return nil, fmt.Errorf("error decoding TOC JSON: %v", err)
} }
r := &Reader{sr: sr, toc: toc, tocDigest: dgstr.Digest()}
if err := r.initFields(); err != nil { if err := r.initFields(); err != nil {
return nil, fmt.Errorf("failed to initialize fields of entries: %v", err) return nil, fmt.Errorf("failed to initialize fields of entries: %v", err)
} }
@ -100,17 +174,26 @@ func Open(sr *io.SectionReader) (*Reader, error) {
} }
// OpenFooter extracts and parses footer from the given blob. // OpenFooter extracts and parses footer from the given blob.
// only supports gzip-based eStargz.
func OpenFooter(sr *io.SectionReader) (tocOffset int64, footerSize int64, rErr error) { func OpenFooter(sr *io.SectionReader) (tocOffset int64, footerSize int64, rErr error) {
if sr.Size() < FooterSize && sr.Size() < legacyFooterSize { if sr.Size() < FooterSize && sr.Size() < legacyFooterSize {
return 0, 0, fmt.Errorf("blob size %d is smaller than the footer size", sr.Size()) return 0, 0, fmt.Errorf("blob size %d is smaller than the footer size", sr.Size())
} }
// TODO: read a bigger chunk (1MB?) at once here to hopefully
// get the TOC + footer in one go.
var footer [FooterSize]byte var footer [FooterSize]byte
if _, err := sr.ReadAt(footer[:], sr.Size()-FooterSize); err != nil { if _, err := sr.ReadAt(footer[:], sr.Size()-FooterSize); err != nil {
return 0, 0, fmt.Errorf("error reading footer: %v", err) return 0, 0, fmt.Errorf("error reading footer: %v", err)
} }
return parseFooter(footer[:]) var allErr []error
for _, d := range []Decompressor{new(GzipDecompressor), new(LegacyGzipDecompressor)} {
fSize := d.FooterSize()
fOffset := positive(int64(len(footer)) - fSize)
_, tocOffset, _, err := d.ParseFooter(footer[fOffset:])
if err == nil {
return tocOffset, fSize, err
}
allErr = append(allErr, err)
}
return 0, 0, errorutil.Aggregate(allErr)
} }
// initFields populates the Reader from r.toc after decoding it from // initFields populates the Reader from r.toc after decoding it from
@ -196,12 +279,12 @@ func (r *Reader) initFields() error {
pdir := r.getOrCreateDir(pdirName) pdir := r.getOrCreateDir(pdirName)
ent.NumLink++ // at least one name(ent.Name) references this entry. ent.NumLink++ // at least one name(ent.Name) references this entry.
if ent.Type == "hardlink" { if ent.Type == "hardlink" {
if org, ok := r.m[cleanEntryName(ent.LinkName)]; ok { org, err := r.getSource(ent)
if err != nil {
return err
}
org.NumLink++ // original entry is referenced by this ent.Name. org.NumLink++ // original entry is referenced by this ent.Name.
ent = org ent = org
} else {
return fmt.Errorf("%q is a hardlink but the linkname %q isn't found", ent.Name, ent.LinkName)
}
} }
pdir.addChild(path.Base(name), ent) pdir.addChild(path.Base(name), ent)
} }
@ -220,6 +303,20 @@ func (r *Reader) initFields() error {
return nil return nil
} }
func (r *Reader) getSource(ent *TOCEntry) (_ *TOCEntry, err error) {
if ent.Type == "hardlink" {
org, ok := r.m[cleanEntryName(ent.LinkName)]
if !ok {
return nil, fmt.Errorf("%q is a hardlink but the linkname %q isn't found", ent.Name, ent.LinkName)
}
ent, err = r.getSource(org)
if err != nil {
return nil, err
}
}
return ent, nil
}
func parentDir(p string) string { func parentDir(p string) string {
dir, _ := path.Split(p) dir, _ := path.Split(p)
return strings.TrimSuffix(dir, "/") return strings.TrimSuffix(dir, "/")
@ -243,6 +340,10 @@ func (r *Reader) getOrCreateDir(d string) *TOCEntry {
return e return e
} }
func (r *Reader) TOCDigest() digest.Digest {
return r.tocDigest
}
// VerifyTOC checks that the TOC JSON in the passed blob matches the // VerifyTOC checks that the TOC JSON in the passed blob matches the
// passed digests and that the TOC JSON contains digests for all chunks // passed digests and that the TOC JSON contains digests for all chunks
// contained in the blob. If the verification succceeds, this function // contained in the blob. If the verification succceeds, this function
@ -252,33 +353,73 @@ func (r *Reader) VerifyTOC(tocDigest digest.Digest) (TOCEntryVerifier, error) {
if r.tocDigest != tocDigest { if r.tocDigest != tocDigest {
return nil, fmt.Errorf("invalid TOC JSON %q; want %q", r.tocDigest, tocDigest) return nil, fmt.Errorf("invalid TOC JSON %q; want %q", r.tocDigest, tocDigest)
} }
digestMap := make(map[int64]digest.Digest) // map from chunk offset to the digest return r.Verifiers()
}
// Verifiers returns TOCEntryVerifier of this chunk. Use VerifyTOC instead in most cases
// because this doesn't verify TOC.
func (r *Reader) Verifiers() (TOCEntryVerifier, error) {
chunkDigestMap := make(map[int64]digest.Digest) // map from chunk offset to the chunk digest
regDigestMap := make(map[int64]digest.Digest) // map from chunk offset to the reg file digest
var chunkDigestMapIncomplete bool
var regDigestMapIncomplete bool
var containsChunk bool
for _, e := range r.toc.Entries { for _, e := range r.toc.Entries {
if e.Type == "reg" || e.Type == "chunk" { if e.Type != "reg" && e.Type != "chunk" {
if e.Type == "reg" && e.Size == 0 { continue
continue // ignores empty file
} }
// offset must be unique in stargz blob // offset must be unique in stargz blob
if _, ok := digestMap[e.Offset]; ok { _, dOK := chunkDigestMap[e.Offset]
_, rOK := regDigestMap[e.Offset]
if dOK || rOK {
return nil, fmt.Errorf("offset %d found twice", e.Offset) return nil, fmt.Errorf("offset %d found twice", e.Offset)
} }
// all chunk entries must contain digest if e.Type == "reg" {
if e.ChunkDigest == "" { if e.Size == 0 {
return nil, fmt.Errorf("ChunkDigest of %q(off=%d) not found in TOC JSON", continue // ignores empty file
e.Name, e.Offset)
} }
// record the digest of regular file payload
if e.Digest != "" {
d, err := digest.Parse(e.Digest)
if err != nil {
return nil, errors.Wrapf(err,
"failed to parse regular file digest %q", e.Digest)
}
regDigestMap[e.Offset] = d
} else {
regDigestMapIncomplete = true
}
} else {
containsChunk = true // this layer contains "chunk" entries.
}
// "reg" also can contain ChunkDigest (e.g. when "reg" is the first entry of
// chunked file)
if e.ChunkDigest != "" {
d, err := digest.Parse(e.ChunkDigest) d, err := digest.Parse(e.ChunkDigest)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to parse digest %q", e.ChunkDigest) return nil, errors.Wrapf(err,
"failed to parse chunk digest %q", e.ChunkDigest)
} }
digestMap[e.Offset] = d chunkDigestMap[e.Offset] = d
} else {
chunkDigestMapIncomplete = true
} }
} }
return &verifier{digestMap: digestMap}, nil if chunkDigestMapIncomplete {
// Though some chunk digests are not found, if this layer doesn't contain
// "chunk"s and all digest of "reg" files are recorded, we can use them instead.
if !containsChunk && !regDigestMapIncomplete {
return &verifier{digestMap: regDigestMap}, nil
}
return nil, fmt.Errorf("some ChunkDigest not found in TOC JSON")
}
return &verifier{digestMap: chunkDigestMap}, nil
} }
// verifier is an implementation of TOCEntryVerifier which holds verifiers keyed by // verifier is an implementation of TOCEntryVerifier which holds verifiers keyed by
@ -337,7 +478,11 @@ func (r *Reader) Lookup(path string) (e *TOCEntry, ok bool) {
} }
e, ok = r.m[path] e, ok = r.m[path]
if ok && e.Type == "hardlink" { if ok && e.Type == "hardlink" {
e, ok = r.m[e.LinkName] var err error
e, err = r.getSource(e)
if err != nil {
return nil, false
}
} }
return return
} }
@ -413,17 +558,17 @@ func (fr *fileReader) ReadAt(p []byte, off int64) (n int, err error) {
off -= ent.ChunkOffset off -= ent.ChunkOffset
finalEnt := fr.ents[len(fr.ents)-1] finalEnt := fr.ents[len(fr.ents)-1]
gzOff := ent.Offset compressedOff := ent.Offset
// gzBytesRemain is the number of compressed gzip bytes in this // compressedBytesRemain is the number of compressed bytes in this
// file remaining, over 1+ gzip chunks. // file remaining, over 1+ chunks.
gzBytesRemain := finalEnt.NextOffset() - gzOff compressedBytesRemain := finalEnt.NextOffset() - compressedOff
sr := io.NewSectionReader(fr.r.sr, gzOff, gzBytesRemain) sr := io.NewSectionReader(fr.r.sr, compressedOff, compressedBytesRemain)
const maxGZread = 2 << 20 const maxRead = 2 << 20
var bufSize = maxGZread var bufSize = maxRead
if gzBytesRemain < maxGZread { if compressedBytesRemain < maxRead {
bufSize = int(gzBytesRemain) bufSize = int(compressedBytesRemain)
} }
br := bufio.NewReaderSize(sr, bufSize) br := bufio.NewReaderSize(sr, bufSize)
@ -431,14 +576,15 @@ func (fr *fileReader) ReadAt(p []byte, off int64) (n int, err error) {
return 0, fmt.Errorf("fileReader.ReadAt.peek: %v", err) return 0, fmt.Errorf("fileReader.ReadAt.peek: %v", err)
} }
gz, err := gzip.NewReader(br) dr, err := fr.r.decompressor.Reader(br)
if err != nil { if err != nil {
return 0, fmt.Errorf("fileReader.ReadAt.gzipNewReader: %v", err) return 0, fmt.Errorf("fileReader.ReadAt.decompressor.Reader: %v", err)
} }
if n, err := io.CopyN(ioutil.Discard, gz, off); n != off || err != nil { defer dr.Close()
if n, err := io.CopyN(ioutil.Discard, dr, off); n != off || err != nil {
return 0, fmt.Errorf("discard of %d bytes = %v, %v", off, n, err) return 0, fmt.Errorf("discard of %d bytes = %v, %v", off, n, err)
} }
return io.ReadFull(gz, p) return io.ReadFull(dr, p)
} }
// A Writer writes stargz files. // A Writer writes stargz files.
@ -447,14 +593,14 @@ func (fr *fileReader) ReadAt(p []byte, off int64) (n int, err error) {
type Writer struct { type Writer struct {
bw *bufio.Writer bw *bufio.Writer
cw *countWriter cw *countWriter
toc *jtoc toc *JTOC
diffHash hash.Hash // SHA-256 of uncompressed tar diffHash hash.Hash // SHA-256 of uncompressed tar
closed bool closed bool
gz *gzip.Writer gz io.WriteCloser
lastUsername map[int]string lastUsername map[int]string
lastGroupname map[int]string lastGroupname map[int]string
compressionLevel int compressor Compressor
// ChunkSize optionally controls the maximum number of bytes // ChunkSize optionally controls the maximum number of bytes
// of data of a regular file that can be written in one gzip // of data of a regular file that can be written in one gzip
@ -463,16 +609,21 @@ type Writer struct {
ChunkSize int ChunkSize int
} }
// currentGzipWriter writes to the current w.gz field, which can // currentCompressionWriter writes to the current w.gz field, which can
// change throughout writing a tar entry. // change throughout writing a tar entry.
// //
// Additionally, it updates w's SHA-256 of the uncompressed bytes // Additionally, it updates w's SHA-256 of the uncompressed bytes
// of the tar file. // of the tar file.
type currentGzipWriter struct{ w *Writer } type currentCompressionWriter struct{ w *Writer }
func (cgw currentGzipWriter) Write(p []byte) (int, error) { func (ccw currentCompressionWriter) Write(p []byte) (int, error) {
cgw.w.diffHash.Write(p) ccw.w.diffHash.Write(p)
return cgw.w.gz.Write(p) if ccw.w.gz == nil {
if err := ccw.w.condOpenGz(); err != nil {
return 0, err
}
}
return ccw.w.gz.Write(p)
} }
func (w *Writer) chunkSize() int { func (w *Writer) chunkSize() int {
@ -482,26 +633,53 @@ func (w *Writer) chunkSize() int {
return w.ChunkSize return w.ChunkSize
} }
// NewWriter returns a new stargz writer writing to w. // Unpack decompresses the given estargz blob and returns a ReadCloser of the tar blob.
// TOC JSON and footer are removed.
func Unpack(sr *io.SectionReader, c Decompressor) (io.ReadCloser, error) {
footerSize := c.FooterSize()
if sr.Size() < footerSize {
return nil, fmt.Errorf("blob is too small; %d < %d", sr.Size(), footerSize)
}
footerOffset := sr.Size() - footerSize
footer := make([]byte, footerSize)
if _, err := sr.ReadAt(footer, footerOffset); err != nil {
return nil, err
}
blobPayloadSize, _, _, err := c.ParseFooter(footer)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse footer")
}
return c.Reader(io.LimitReader(sr, blobPayloadSize))
}
// NewWriter returns a new stargz writer (gzip-based) writing to w.
// //
// The writer must be closed to write its trailing table of contents. // The writer must be closed to write its trailing table of contents.
func NewWriter(w io.Writer) *Writer { func NewWriter(w io.Writer) *Writer {
return NewWriterLevel(w, gzip.BestCompression) return NewWriterLevel(w, gzip.BestCompression)
} }
// NewWriterLevel returns a new stargz writer writing to w. // NewWriterLevel returns a new stargz writer (gzip-based) writing to w.
// The compression level is configurable. // The compression level is configurable.
// //
// The writer must be closed to write its trailing table of contents. // The writer must be closed to write its trailing table of contents.
func NewWriterLevel(w io.Writer, compressionLevel int) *Writer { func NewWriterLevel(w io.Writer, compressionLevel int) *Writer {
return NewWriterWithCompressor(w, NewGzipCompressorWithLevel(compressionLevel))
}
// NewWriterWithCompressor returns a new stargz writer writing to w.
// The compression method is configurable.
//
// The writer must be closed to write its trailing table of contents.
func NewWriterWithCompressor(w io.Writer, c Compressor) *Writer {
bw := bufio.NewWriter(w) bw := bufio.NewWriter(w)
cw := &countWriter{w: bw} cw := &countWriter{w: bw}
return &Writer{ return &Writer{
bw: bw, bw: bw,
cw: cw, cw: cw,
toc: &jtoc{Version: 1}, toc: &JTOC{Version: 1},
diffHash: sha256.New(), diffHash: sha256.New(),
compressionLevel: compressionLevel, compressor: c,
} }
} }
@ -517,42 +695,16 @@ func (w *Writer) Close() (digest.Digest, error) {
return "", err return "", err
} }
// Write the TOC index. // Write the TOC index and footer.
tocOff := w.cw.n tocDigest, err := w.compressor.WriteTOCAndFooter(w.cw, w.cw.n, w.toc, w.diffHash)
w.gz, _ = gzip.NewWriterLevel(w.cw, w.compressionLevel)
tw := tar.NewWriter(currentGzipWriter{w})
tocJSON, err := json.MarshalIndent(w.toc, "", "\t")
if err != nil { if err != nil {
return "", err return "", err
} }
if err := tw.WriteHeader(&tar.Header{
Typeflag: tar.TypeReg,
Name: TOCTarName,
Size: int64(len(tocJSON)),
}); err != nil {
return "", err
}
if _, err := tw.Write(tocJSON); err != nil {
return "", err
}
if err := tw.Close(); err != nil {
return "", err
}
if err := w.closeGz(); err != nil {
return "", err
}
// And a little footer with pointer to the TOC gzip stream.
if _, err := w.bw.Write(footerBytes(tocOff)); err != nil {
return "", err
}
if err := w.bw.Flush(); err != nil { if err := w.bw.Flush(); err != nil {
return "", err return "", err
} }
return digest.FromBytes(tocJSON), nil return tocDigest, nil
} }
func (w *Writer) closeGz() error { func (w *Writer) closeGz() error {
@ -584,39 +736,82 @@ func (w *Writer) nameIfChanged(mp *map[int]string, id int, name string) string {
return name return name
} }
func (w *Writer) condOpenGz() { func (w *Writer) condOpenGz() (err error) {
if w.gz == nil { if w.gz == nil {
w.gz, _ = gzip.NewWriterLevel(w.cw, w.compressionLevel) w.gz, err = w.compressor.Writer(w.cw)
} }
return
} }
// AppendTar reads the tar or tar.gz file from r and appends // AppendTar reads the tar or tar.gz file from r and appends
// each of its contents to w. // each of its contents to w.
// //
// The input r can optionally be gzip compressed but the output will // The input r can optionally be gzip compressed but the output will
// always be gzip compressed. // always be compressed by the specified compressor.
func (w *Writer) AppendTar(r io.Reader) error { func (w *Writer) AppendTar(r io.Reader) error {
return w.appendTar(r, false)
}
// AppendTarLossLess reads the tar or tar.gz file from r and appends
// each of its contents to w.
//
// The input r can optionally be gzip compressed but the output will
// always be compressed by the specified compressor.
//
// The difference of this func with AppendTar is that this writes
// the input tar stream into w without any modification (e.g. to header bytes).
//
// Note that if the input tar stream already contains TOC JSON, this returns
// error because w cannot overwrite the TOC JSON to the one generated by w without
// lossy modification. To avoid this error, if the input stream is known to be stargz/estargz,
// you shoud decompress it and remove TOC JSON in advance.
func (w *Writer) AppendTarLossLess(r io.Reader) error {
return w.appendTar(r, true)
}
func (w *Writer) appendTar(r io.Reader, lossless bool) error {
var src io.Reader
br := bufio.NewReader(r) br := bufio.NewReader(r)
var tr *tar.Reader
if isGzip(br) { if isGzip(br) {
// NewReader can't fail if isGzip returned true.
zr, _ := gzip.NewReader(br) zr, _ := gzip.NewReader(br)
tr = tar.NewReader(zr) src = zr
} else { } else {
tr = tar.NewReader(br) src = io.Reader(br)
}
dst := currentCompressionWriter{w}
var tw *tar.Writer
if !lossless {
tw = tar.NewWriter(dst) // use tar writer only when this isn't lossless mode.
}
tr := tar.NewReader(src)
if lossless {
tr.RawAccounting = true
} }
for { for {
h, err := tr.Next() h, err := tr.Next()
if err == io.EOF { if err == io.EOF {
if lossless {
if remain := tr.RawBytes(); len(remain) > 0 {
// Collect the remaining null bytes.
// https://github.com/vbatts/tar-split/blob/80a436fd6164c557b131f7c59ed69bd81af69761/concept/main.go#L49-L53
if _, err := dst.Write(remain); err != nil {
return err
}
}
}
break break
} }
if err != nil { if err != nil {
return fmt.Errorf("error reading from source tar: tar.Reader.Next: %v", err) return fmt.Errorf("error reading from source tar: tar.Reader.Next: %v", err)
} }
if h.Name == TOCTarName { if cleanEntryName(h.Name) == TOCTarName {
// It is possible for a layer to be "stargzified" twice during the // It is possible for a layer to be "stargzified" twice during the
// distribution lifecycle. So we reserve "TOCTarName" here to avoid // distribution lifecycle. So we reserve "TOCTarName" here to avoid
// duplicated entries in the resulting layer. // duplicated entries in the resulting layer.
if lossless {
// We cannot handle this in lossless way.
return fmt.Errorf("existing TOC JSON is not allowed; decompress layer before append")
}
continue continue
} }
@ -639,11 +834,18 @@ func (w *Writer) AppendTar(r io.Reader) error {
ModTime3339: formatModtime(h.ModTime), ModTime3339: formatModtime(h.ModTime),
Xattrs: xattrs, Xattrs: xattrs,
} }
w.condOpenGz() if err := w.condOpenGz(); err != nil {
tw := tar.NewWriter(currentGzipWriter{w}) return err
}
if tw != nil {
if err := tw.WriteHeader(h); err != nil { if err := tw.WriteHeader(h); err != nil {
return err return err
} }
} else {
if _, err := dst.Write(tr.RawBytes()); err != nil {
return err
}
}
switch h.Typeflag { switch h.Typeflag {
case tar.TypeLink: case tar.TypeLink:
ent.Type = "hardlink" ent.Type = "hardlink"
@ -699,10 +901,18 @@ func (w *Writer) AppendTar(r io.Reader) error {
ent.ChunkOffset = written ent.ChunkOffset = written
chunkDigest := digest.Canonical.Digester() chunkDigest := digest.Canonical.Digester()
w.condOpenGz() if err := w.condOpenGz(); err != nil {
return err
}
teeChunk := io.TeeReader(tee, chunkDigest.Hash()) teeChunk := io.TeeReader(tee, chunkDigest.Hash())
if _, err := io.CopyN(tw, teeChunk, chunkSize); err != nil { var out io.Writer
if tw != nil {
out = tw
} else {
out = dst
}
if _, err := io.CopyN(out, teeChunk, chunkSize); err != nil {
return fmt.Errorf("error copying %q: %v", h.Name, err) return fmt.Errorf("error copying %q: %v", h.Name, err)
} }
ent.ChunkDigest = chunkDigest.Digest().String() ent.ChunkDigest = chunkDigest.Digest().String()
@ -719,11 +929,18 @@ func (w *Writer) AppendTar(r io.Reader) error {
if payloadDigest != nil { if payloadDigest != nil {
regFileEntry.Digest = payloadDigest.Digest().String() regFileEntry.Digest = payloadDigest.Digest().String()
} }
if tw != nil {
if err := tw.Flush(); err != nil { if err := tw.Flush(); err != nil {
return err return err
} }
} }
return nil }
remainDest := ioutil.Discard
if lossless {
remainDest = dst // Preserve the remaining bytes in lossless mode
}
_, err := io.Copy(remainDest, src)
return err
} }
// DiffID returns the SHA-256 of the uncompressed tar bytes. // DiffID returns the SHA-256 of the uncompressed tar bytes.
@ -732,83 +949,54 @@ func (w *Writer) DiffID() string {
return fmt.Sprintf("sha256:%x", w.diffHash.Sum(nil)) return fmt.Sprintf("sha256:%x", w.diffHash.Sum(nil))
} }
// footerBytes returns the 51 bytes footer. func maxFooterSize(blobSize int64, decompressors ...Decompressor) (res int64) {
func footerBytes(tocOff int64) []byte { for _, d := range decompressors {
buf := bytes.NewBuffer(make([]byte, 0, FooterSize)) if s := d.FooterSize(); res < s && s <= blobSize {
gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) // MUST be NoCompression to keep 51 bytes res = s
// Extra header indicating the offset of TOCJSON
// https://tools.ietf.org/html/rfc1952#section-2.3.1.1
header := make([]byte, 4)
header[0], header[1] = 'S', 'G'
subfield := fmt.Sprintf("%016xSTARGZ", tocOff)
binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952
gz.Header.Extra = append(header, []byte(subfield)...)
gz.Close()
if buf.Len() != FooterSize {
panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize))
} }
return buf.Bytes() }
return
} }
func parseFooter(p []byte) (tocOffset int64, footerSize int64, rErr error) { func parseTOC(d Decompressor, sr *io.SectionReader, tocOff, tocSize int64, tocBytes []byte, opts openOpts) (*Reader, error) {
var allErr []error if len(tocBytes) > 0 {
start := time.Now()
tocOffset, err := parseEStargzFooter(p) toc, tocDgst, err := d.ParseTOC(bytes.NewReader(tocBytes))
if err == nil { if err == nil {
return tocOffset, FooterSize, nil if opts.telemetry != nil && opts.telemetry.DeserializeTocLatency != nil {
opts.telemetry.DeserializeTocLatency(start)
}
return &Reader{
sr: sr,
toc: toc,
tocDigest: tocDgst,
decompressor: d,
}, nil
}
} }
allErr = append(allErr, err)
pad := len(p) - legacyFooterSize start := time.Now()
if pad < 0 { tocBytes = make([]byte, tocSize)
pad = 0 if _, err := sr.ReadAt(tocBytes, tocOff); err != nil {
return nil, fmt.Errorf("error reading %d byte TOC targz: %v", len(tocBytes), err)
} }
tocOffset, err = parseLegacyFooter(p[pad:]) if opts.telemetry != nil && opts.telemetry.GetTocLatency != nil {
if err == nil { opts.telemetry.GetTocLatency(start)
return tocOffset, legacyFooterSize, nil
} }
return 0, 0, errorutil.Aggregate(append(allErr, err)) start = time.Now()
} toc, tocDgst, err := d.ParseTOC(bytes.NewReader(tocBytes))
func parseEStargzFooter(p []byte) (tocOffset int64, err error) {
if len(p) != FooterSize {
return 0, fmt.Errorf("invalid length %d cannot be parsed", len(p))
}
zr, err := gzip.NewReader(bytes.NewReader(p))
if err != nil { if err != nil {
return 0, err return nil, err
} }
extra := zr.Header.Extra if opts.telemetry != nil && opts.telemetry.DeserializeTocLatency != nil {
si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:] opts.telemetry.DeserializeTocLatency(start)
if si1 != 'S' || si2 != 'G' {
return 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2)
} }
if slen := binary.LittleEndian.Uint16(subfieldlen); slen != uint16(16+len("STARGZ")) { return &Reader{
return 0, fmt.Errorf("invalid length of subfield %d; want %d", slen, 16+len("STARGZ")) sr: sr,
} toc: toc,
if string(subfield[16:]) != "STARGZ" { tocDigest: tocDgst,
return 0, fmt.Errorf("STARGZ magic string must be included in the footer subfield") decompressor: d,
} }, nil
return strconv.ParseInt(string(subfield[:16]), 16, 64)
}
func parseLegacyFooter(p []byte) (tocOffset int64, err error) {
if len(p) != legacyFooterSize {
return 0, fmt.Errorf("legacy: invalid length %d cannot be parsed", len(p))
}
zr, err := gzip.NewReader(bytes.NewReader(p))
if err != nil {
return 0, errors.Wrapf(err, "legacy: failed to get footer gzip reader")
}
extra := zr.Header.Extra
if len(extra) != 16+len("STARGZ") {
return 0, fmt.Errorf("legacy: invalid stargz's extra field size")
}
if string(extra[16:]) != "STARGZ" {
return 0, fmt.Errorf("legacy: magic string STARGZ not found")
}
return strconv.ParseInt(string(extra[:16]), 16, 64)
} }
func formatModtime(t time.Time) string { func formatModtime(t time.Time) string {
@ -847,3 +1035,10 @@ func isGzip(br *bufio.Reader) bool {
peek, _ := br.Peek(3) peek, _ := br.Peek(3)
return len(peek) >= 3 && peek[0] == gzipID1 && peek[1] == gzipID2 && peek[2] == gzipDeflate return len(peek) >= 3 && peek[0] == gzipID1 && peek[1] == gzipID2 && peek[2] == gzipDeflate
} }
func positive(n int64) int64 {
if n < 0 {
return 0
}
return n
}

View File

@ -3,8 +3,9 @@ module github.com/containerd/stargz-snapshotter/estargz
go 1.16 go 1.16
require ( require (
github.com/klauspost/compress v1.12.3 github.com/klauspost/compress v1.14.2
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/vbatts/tar-split v0.11.2
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
) )

View File

@ -1,10 +1,22 @@
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw=
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 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/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= 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/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -0,0 +1,238 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Copyright 2019 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package estargz
import (
"archive/tar"
"bytes"
"compress/gzip"
"encoding/binary"
"encoding/json"
"fmt"
"hash"
"io"
"strconv"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
type gzipCompression struct {
*GzipCompressor
*GzipDecompressor
}
func newGzipCompressionWithLevel(level int) Compression {
return &gzipCompression{
&GzipCompressor{level},
&GzipDecompressor{},
}
}
func NewGzipCompressor() *GzipCompressor {
return &GzipCompressor{gzip.BestCompression}
}
func NewGzipCompressorWithLevel(level int) *GzipCompressor {
return &GzipCompressor{level}
}
type GzipCompressor struct {
compressionLevel int
}
func (gc *GzipCompressor) Writer(w io.Writer) (io.WriteCloser, error) {
return gzip.NewWriterLevel(w, gc.compressionLevel)
}
func (gc *GzipCompressor) WriteTOCAndFooter(w io.Writer, off int64, toc *JTOC, diffHash hash.Hash) (digest.Digest, error) {
tocJSON, err := json.MarshalIndent(toc, "", "\t")
if err != nil {
return "", err
}
gz, _ := gzip.NewWriterLevel(w, gc.compressionLevel)
gw := io.Writer(gz)
if diffHash != nil {
gw = io.MultiWriter(gz, diffHash)
}
tw := tar.NewWriter(gw)
if err := tw.WriteHeader(&tar.Header{
Typeflag: tar.TypeReg,
Name: TOCTarName,
Size: int64(len(tocJSON)),
}); err != nil {
return "", err
}
if _, err := tw.Write(tocJSON); err != nil {
return "", err
}
if err := tw.Close(); err != nil {
return "", err
}
if err := gz.Close(); err != nil {
return "", err
}
if _, err := w.Write(gzipFooterBytes(off)); err != nil {
return "", err
}
return digest.FromBytes(tocJSON), nil
}
// gzipFooterBytes returns the 51 bytes footer.
func gzipFooterBytes(tocOff int64) []byte {
buf := bytes.NewBuffer(make([]byte, 0, FooterSize))
gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) // MUST be NoCompression to keep 51 bytes
// Extra header indicating the offset of TOCJSON
// https://tools.ietf.org/html/rfc1952#section-2.3.1.1
header := make([]byte, 4)
header[0], header[1] = 'S', 'G'
subfield := fmt.Sprintf("%016xSTARGZ", tocOff)
binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952
gz.Header.Extra = append(header, []byte(subfield)...)
gz.Close()
if buf.Len() != FooterSize {
panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize))
}
return buf.Bytes()
}
type GzipDecompressor struct{}
func (gz *GzipDecompressor) Reader(r io.Reader) (io.ReadCloser, error) {
return gzip.NewReader(r)
}
func (gz *GzipDecompressor) ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) {
return parseTOCEStargz(r)
}
func (gz *GzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) {
if len(p) != FooterSize {
return 0, 0, 0, fmt.Errorf("invalid length %d cannot be parsed", len(p))
}
zr, err := gzip.NewReader(bytes.NewReader(p))
if err != nil {
return 0, 0, 0, err
}
defer zr.Close()
extra := zr.Header.Extra
si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:]
if si1 != 'S' || si2 != 'G' {
return 0, 0, 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2)
}
if slen := binary.LittleEndian.Uint16(subfieldlen); slen != uint16(16+len("STARGZ")) {
return 0, 0, 0, fmt.Errorf("invalid length of subfield %d; want %d", slen, 16+len("STARGZ"))
}
if string(subfield[16:]) != "STARGZ" {
return 0, 0, 0, fmt.Errorf("STARGZ magic string must be included in the footer subfield")
}
tocOffset, err = strconv.ParseInt(string(subfield[:16]), 16, 64)
if err != nil {
return 0, 0, 0, errors.Wrapf(err, "legacy: failed to parse toc offset")
}
return tocOffset, tocOffset, 0, nil
}
func (gz *GzipDecompressor) FooterSize() int64 {
return FooterSize
}
func (gz *GzipDecompressor) DecompressTOC(r io.Reader) (tocJSON io.ReadCloser, err error) {
return decompressTOCEStargz(r)
}
type LegacyGzipDecompressor struct{}
func (gz *LegacyGzipDecompressor) Reader(r io.Reader) (io.ReadCloser, error) {
return gzip.NewReader(r)
}
func (gz *LegacyGzipDecompressor) ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) {
return parseTOCEStargz(r)
}
func (gz *LegacyGzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) {
if len(p) != legacyFooterSize {
return 0, 0, 0, fmt.Errorf("legacy: invalid length %d cannot be parsed", len(p))
}
zr, err := gzip.NewReader(bytes.NewReader(p))
if err != nil {
return 0, 0, 0, errors.Wrapf(err, "legacy: failed to get footer gzip reader")
}
defer zr.Close()
extra := zr.Header.Extra
if len(extra) != 16+len("STARGZ") {
return 0, 0, 0, fmt.Errorf("legacy: invalid stargz's extra field size")
}
if string(extra[16:]) != "STARGZ" {
return 0, 0, 0, fmt.Errorf("legacy: magic string STARGZ not found")
}
tocOffset, err = strconv.ParseInt(string(extra[:16]), 16, 64)
if err != nil {
return 0, 0, 0, errors.Wrapf(err, "legacy: failed to parse toc offset")
}
return tocOffset, tocOffset, 0, nil
}
func (gz *LegacyGzipDecompressor) FooterSize() int64 {
return legacyFooterSize
}
func (gz *LegacyGzipDecompressor) DecompressTOC(r io.Reader) (tocJSON io.ReadCloser, err error) {
return decompressTOCEStargz(r)
}
func parseTOCEStargz(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) {
tr, err := decompressTOCEStargz(r)
if err != nil {
return nil, "", err
}
dgstr := digest.Canonical.Digester()
toc = new(JTOC)
if err := json.NewDecoder(io.TeeReader(tr, dgstr.Hash())).Decode(&toc); err != nil {
return nil, "", fmt.Errorf("error decoding TOC JSON: %v", err)
}
if err := tr.Close(); err != nil {
return nil, "", err
}
return toc, dgstr.Digest(), nil
}
func decompressTOCEStargz(r io.Reader) (tocJSON io.ReadCloser, err error) {
zr, err := gzip.NewReader(r)
if err != nil {
return nil, fmt.Errorf("malformed TOC gzip header: %v", err)
}
zr.Multistream(false)
tr := tar.NewReader(zr)
h, err := tr.Next()
if err != nil {
return nil, fmt.Errorf("failed to find tar header in TOC gzip stream: %v", err)
}
if h.Name != TOCTarName {
return nil, fmt.Errorf("TOC tar entry had name %q; expected %q", h.Name, TOCTarName)
}
return readCloser{tr, zr.Close}, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,8 @@ package estargz
import ( import (
"archive/tar" "archive/tar"
"hash"
"io"
"os" "os"
"path" "path"
"time" "time"
@ -90,8 +92,8 @@ const (
landmarkContents = 0xf landmarkContents = 0xf
) )
// jtoc is the JSON-serialized table of contents index of the files in the stargz file. // JTOC is the JSON-serialized table of contents index of the files in the stargz file.
type jtoc struct { type JTOC struct {
Version int `json:"version"` Version int `json:"version"`
Entries []*TOCEntry `json:"entries"` Entries []*TOCEntry `json:"entries"`
} }
@ -262,3 +264,53 @@ type TOCEntryVerifier interface {
// contents of the specified TOCEntry. // contents of the specified TOCEntry.
Verifier(ce *TOCEntry) (digest.Verifier, error) Verifier(ce *TOCEntry) (digest.Verifier, error)
} }
// Compression provides the compression helper to be used creating and parsing eStargz.
// This package provides gzip-based Compression by default, but any compression
// algorithm (e.g. zstd) can be used as long as it implements Compression.
type Compression interface {
Compressor
Decompressor
}
// Compressor represents the helper mothods to be used for creating eStargz.
type Compressor interface {
// Writer returns WriteCloser to be used for writing a chunk to eStargz.
// Everytime a chunk is written, the WriteCloser is closed and Writer is
// called again for writing the next chunk.
Writer(w io.Writer) (io.WriteCloser, error)
// WriteTOCAndFooter is called to write JTOC to the passed Writer.
// diffHash calculates the DiffID (uncompressed sha256 hash) of the blob
// WriteTOCAndFooter can optionally write anything that affects DiffID calculation
// (e.g. uncompressed TOC JSON).
//
// This function returns tocDgst that represents the digest of TOC that will be used
// to verify this blob when it's parsed.
WriteTOCAndFooter(w io.Writer, off int64, toc *JTOC, diffHash hash.Hash) (tocDgst digest.Digest, err error)
}
// Decompressor represents the helper mothods to be used for parsing eStargz.
type Decompressor interface {
// Reader returns ReadCloser to be used for decompressing file payload.
Reader(r io.Reader) (io.ReadCloser, error)
// FooterSize returns the size of the footer of this blob.
FooterSize() int64
// ParseFooter parses the footer and returns the offset and (compressed) size of TOC.
// payloadBlobSize is the (compressed) size of the blob payload (i.e. the size between
// the top until the TOC JSON).
//
// Here, tocSize is optional. If tocSize <= 0, it's by default the size of the range
// from tocOffset until the beginning of the footer (blob size - tocOff - FooterSize).
ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error)
// ParseTOC parses TOC from the passed reader. The reader provides the partial contents
// of the underlying blob that has the range specified by ParseFooter method.
//
// This function returns tocDgst that represents the digest of TOC that will be used
// to verify this blob. This must match to the value returned from
// Compressor.WriteTOCAndFooter that is used when creating this blob.
ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error)
}

View File

@ -111,7 +111,7 @@ type timeApproximator struct {
func (a timeApproximator) compare(x, y time.Time) bool { func (a timeApproximator) compare(x, y time.Time) bool {
// Avoid subtracting times to avoid overflow when the // Avoid subtracting times to avoid overflow when the
// difference is larger than the largest representible duration. // difference is larger than the largest representable duration.
if x.After(y) { if x.After(y) {
// Ensure x is always before y // Ensure x is always before y
x, y = y, x x, y = y, x

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build go1.13
// +build go1.13 // +build go1.13
package cmpopts package cmpopts

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !go1.13
// +build !go1.13 // +build !go1.13
// TODO(≥go1.13): For support on <go1.13, we use the xerrors package. // TODO(≥go1.13): For support on <go1.13, we use the xerrors package.

View File

@ -36,7 +36,6 @@ import (
"strings" "strings"
"github.com/google/go-cmp/cmp/internal/diff" "github.com/google/go-cmp/cmp/internal/diff"
"github.com/google/go-cmp/cmp/internal/flags"
"github.com/google/go-cmp/cmp/internal/function" "github.com/google/go-cmp/cmp/internal/function"
"github.com/google/go-cmp/cmp/internal/value" "github.com/google/go-cmp/cmp/internal/value"
) )
@ -319,7 +318,6 @@ func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
} }
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
v = sanitizeValue(v, f.Type().In(0))
if !s.dynChecker.Next() { if !s.dynChecker.Next() {
return f.Call([]reflect.Value{v})[0] return f.Call([]reflect.Value{v})[0]
} }
@ -343,8 +341,6 @@ func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
} }
func (s *state) callTTBFunc(f, x, y reflect.Value) bool { func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
x = sanitizeValue(x, f.Type().In(0))
y = sanitizeValue(y, f.Type().In(1))
if !s.dynChecker.Next() { if !s.dynChecker.Next() {
return f.Call([]reflect.Value{x, y})[0].Bool() return f.Call([]reflect.Value{x, y})[0].Bool()
} }
@ -372,19 +368,6 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
ret = f.Call(vs)[0] ret = f.Call(vs)[0]
} }
// sanitizeValue converts nil interfaces of type T to those of type R,
// assuming that T is assignable to R.
// Otherwise, it returns the input value as is.
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
// TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/22143).
if !flags.AtLeastGo110 {
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
return reflect.New(t).Elem()
}
}
return v
}
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
var addr bool var addr bool
var vax, vay reflect.Value // Addressable versions of vx and vy var vax, vay reflect.Value // Addressable versions of vx and vy

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build purego
// +build purego // +build purego
package cmp package cmp

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !purego
// +build !purego // +build !purego
package cmp package cmp

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cmp_debug
// +build !cmp_debug // +build !cmp_debug
package diff package diff

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build cmp_debug
// +build cmp_debug // +build cmp_debug
package diff package diff

View File

@ -1,10 +0,0 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.10
package flags
// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
const AtLeastGo110 = false

View File

@ -1,10 +0,0 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.10
package flags
// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
const AtLeastGo110 = true

View File

@ -9,6 +9,8 @@ import (
"strconv" "strconv"
) )
var anyType = reflect.TypeOf((*interface{})(nil)).Elem()
// TypeString is nearly identical to reflect.Type.String, // TypeString is nearly identical to reflect.Type.String,
// but has an additional option to specify that full type names be used. // but has an additional option to specify that full type names be used.
func TypeString(t reflect.Type, qualified bool) string { func TypeString(t reflect.Type, qualified bool) string {
@ -20,6 +22,11 @@ func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte
// of the same name and within the same package, // of the same name and within the same package,
// but declared within the namespace of different functions. // but declared within the namespace of different functions.
// Use the "any" alias instead of "interface{}" for better readability.
if t == anyType {
return append(b, "any"...)
}
// Named type. // Named type.
if t.Name() != "" { if t.Name() != "" {
if qualified && t.PkgPath() != "" { if qualified && t.PkgPath() != "" {

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build purego
// +build purego // +build purego
package value package value

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !purego
// +build !purego // +build !purego
package value package value

View File

@ -178,7 +178,7 @@ type structField struct {
unexported bool unexported bool
mayForce bool // Forcibly allow visibility mayForce bool // Forcibly allow visibility
paddr bool // Was parent addressable? paddr bool // Was parent addressable?
pvx, pvy reflect.Value // Parent values (always addressible) pvx, pvy reflect.Value // Parent values (always addressable)
field reflect.StructField // Field information field reflect.StructField // Field information
} }

View File

@ -207,9 +207,10 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
// Check whether this is a []byte of text data. // Check whether this is a []byte of text data.
if t.Elem() == reflect.TypeOf(byte(0)) { if t.Elem() == reflect.TypeOf(byte(0)) {
b := v.Bytes() b := v.Bytes()
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) } isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
out = opts.formatString("", string(b)) out = opts.formatString("", string(b))
skipType = true
return opts.WithTypeMode(emitType).FormatType(t, out) return opts.WithTypeMode(emitType).FormatType(t, out)
} }
} }

View File

@ -80,7 +80,7 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
} }
// Use specialized string diffing for longer slices or strings. // Use specialized string diffing for longer slices or strings.
const minLength = 64 const minLength = 32
return vx.Len() >= minLength && vy.Len() >= minLength return vx.Len() >= minLength && vy.Len() >= minLength
} }
@ -563,10 +563,10 @@ func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []d
nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified
ny := ds.NumIdentical + ds.NumInserted + ds.NumModified ny := ds.NumIdentical + ds.NumInserted + ds.NumModified
var numLeadingIdentical, numTrailingIdentical int var numLeadingIdentical, numTrailingIdentical int
for i := 0; i < nx && i < ny && eq(ix+i, iy+i); i++ { for j := 0; j < nx && j < ny && eq(ix+j, iy+j); j++ {
numLeadingIdentical++ numLeadingIdentical++
} }
for i := 0; i < nx && i < ny && eq(ix+nx-1-i, iy+ny-1-i); i++ { for j := 0; j < nx && j < ny && eq(ix+nx-1-j, iy+ny-1-j); j++ {
numTrailingIdentical++ numTrailingIdentical++
} }
if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 { if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 {

View File

@ -115,3 +115,32 @@ func Is(r io.Reader) (bool, error) {
} }
return bytes.Equal(magicHeader, gzipMagicHeader), nil return bytes.Equal(magicHeader, gzipMagicHeader), nil
} }
// PeekReader is an io.Reader that also implements Peek a la bufio.Reader.
type PeekReader interface {
io.Reader
Peek(n int) ([]byte, error)
}
// Peek detects whether the input stream is gzip compressed.
//
// If r implements Peek, we will use that directly, otherwise a small number
// of bytes are buffered to Peek at the gzip header, and the returned
// PeekReader can be used as a replacement for the consumed input io.Reader.
func Peek(r io.Reader) (bool, PeekReader, error) {
var pr PeekReader
if p, ok := r.(PeekReader); ok {
pr = p
} else {
pr = bufio.NewReader(r)
}
header, err := pr.Peek(2)
if err != nil {
// https://github.com/google/go-containerregistry/issues/367
if err == io.EOF {
return false, pr, nil
}
return false, pr, err
}
return bytes.Equal(header, gzipMagicHeader), pr, nil
}

View File

@ -38,6 +38,18 @@ type verifyReader struct {
gotSize, wantSize int64 gotSize, wantSize int64
} }
// Error provides information about the failed hash verification.
type Error struct {
got string
want v1.Hash
gotSize int64
}
func (v Error) Error() string {
return fmt.Sprintf("error verifying %s checksum after reading %d bytes; got %q, want %q",
v.want.Algorithm, v.gotSize, v.got, v.want)
}
// Read implements io.Reader // Read implements io.Reader
func (vc *verifyReader) Read(b []byte) (int, error) { func (vc *verifyReader) Read(b []byte) (int, error) {
n, err := vc.inner.Read(b) n, err := vc.inner.Read(b)
@ -46,10 +58,13 @@ func (vc *verifyReader) Read(b []byte) (int, error) {
if vc.wantSize != SizeUnknown && vc.gotSize != vc.wantSize { if vc.wantSize != SizeUnknown && vc.gotSize != vc.wantSize {
return n, fmt.Errorf("error verifying size; got %d, want %d", vc.gotSize, vc.wantSize) return n, fmt.Errorf("error verifying size; got %d, want %d", vc.gotSize, vc.wantSize)
} }
got := hex.EncodeToString(vc.hasher.Sum(make([]byte, 0, vc.hasher.Size()))) got := hex.EncodeToString(vc.hasher.Sum(nil))
if want := vc.expected.Hex; got != want { if want := vc.expected.Hex; got != want {
return n, fmt.Errorf("error verifying %s checksum after reading %d bytes; got %q, want %q", return n, Error{
vc.expected.Algorithm, vc.gotSize, got, want) got: vc.expected.Algorithm + ":" + got,
want: vc.expected,
gotSize: vc.gotSize,
}
} }
} }
return n, err return n, err
@ -69,9 +84,9 @@ func ReadCloser(r io.ReadCloser, size int64, h v1.Hash) (io.ReadCloser, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var r2 io.Reader = r r2 := io.TeeReader(r, w) // pass all writes to the hasher.
if size != SizeUnknown { if size != SizeUnknown {
r2 = io.LimitReader(io.TeeReader(r, w), size) r2 = io.LimitReader(r2, size) // if we know the size, limit to that size.
} }
return &and.ReadCloser{ return &and.ReadCloser{
Reader: &verifyReader{ Reader: &verifyReader{

View File

@ -4,15 +4,15 @@
This README outlines how we acquire and use credentials when interacting with a registry. This README outlines how we acquire and use credentials when interacting with a registry.
As much as possible, we attempt to emulate docker's authentication behavior and configuration so that this library "just works" if you've already configured credentials that work with docker; however, when things don't work, a basic understanding of what's going on can help with debugging. As much as possible, we attempt to emulate `docker`'s authentication behavior and configuration so that this library "just works" if you've already configured credentials that work with `docker`; however, when things don't work, a basic understanding of what's going on can help with debugging.
The official documentation for how docker authentication works is (reasonably) scattered across several different sites and GitHub repositories, so we've tried to summarize the relevant bits here. The official documentation for how authentication with `docker` works is (reasonably) scattered across several different sites and GitHub repositories, so we've tried to summarize the relevant bits here.
## tl;dr for consumers of this package ## tl;dr for consumers of this package
By default, [`pkg/v1/remote`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote) uses [`Anonymous`](https://godoc.org/github.com/google/go-containerregistry/pkg/authn#Anonymous) credentials (i.e. _none_), which for most registries will only allow read access to public images. By default, [`pkg/v1/remote`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote) uses [`Anonymous`](https://godoc.org/github.com/google/go-containerregistry/pkg/authn#Anonymous) credentials (i.e. _none_), which for most registries will only allow read access to public images.
To use the credentials found in your docker config file, you can use the [`DefaultKeychain`](https://godoc.org/github.com/google/go-containerregistry/pkg/authn#DefaultKeychain), e.g.: To use the credentials found in your Docker config file, you can use the [`DefaultKeychain`](https://godoc.org/github.com/google/go-containerregistry/pkg/authn#DefaultKeychain), e.g.:
```go ```go
package main package main
@ -42,15 +42,95 @@ func main() {
} }
``` ```
(If you're only using [gcr.io](https://gcr.io), see the [`pkg/v1/google.Keychain`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/google#Keychain), which emulates [`docker-credential-gcr`](https://github.com/GoogleCloudPlatform/docker-credential-gcr).) The `DefaultKeychain` will use credentials as described in your Docker config file -- usually `~/.docker/config.json`, or `%USERPROFILE%\.docker\config.json` on Windows -- or the location described by the `DOCKER_CONFIG` environment variable, if set.
## The Config File If those are not found, `DefaultKeychain` will look for credentials configured using [Podman's expectation](https://docs.podman.io/en/latest/markdown/podman-login.1.html) that these are found in `${XDG_RUNTIME_DIR}/containers/auth.json`.
This file contains various configuration options for docker and is (by default) located at: [See below](#docker-config-auth) for more information about what is configured in this file.
* `$HOME/.docker/config.json` (on linux and darwin), or
* `%USERPROFILE%\.docker\config.json` (on windows).
You can override this location with the `DOCKER_CONFIG` environment variable. ## Emulating Cloud Provider Credential Helpers
[`pkg/v1/google.Keychain`](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/v1/google#Keychain) provides a `Keychain` implementation that emulates [`docker-credential-gcr`](https://github.com/GoogleCloudPlatform/docker-credential-gcr) to find credentials in the environment.
See [`google.NewEnvAuthenticator`](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/v1/google#NewEnvAuthenticator) and [`google.NewGcloudAuthenticator`](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/v1/google#NewGcloudAuthenticator) for more information.
To emulate other credential helpers without requiring them to be available as executables, [`NewKeychainFromHelper`](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/authn#NewKeychainFromHelper) provides an adapter that takes a Go implementation satisfying a subset of the [`credentials.Helper`](https://pkg.go.dev/github.com/docker/docker-credential-helpers/credentials#Helper) interface, and makes it available as a `Keychain`.
This means that you can emulate, for example, [Amazon ECR's `docker-credential-ecr-login` credential helper](https://github.com/awslabs/amazon-ecr-credential-helper) using the same implementation:
```go
import (
ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
"github.com/awslabs/amazon-ecr-credential-helper/ecr-login/api"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
func main() {
// ...
ecrHelper := ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}}
img, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.NewKeychainFromHelper(ecrHelper)))
if err != nil {
panic(err)
}
// ...
}
```
Likewise, you can emulate [Azure's ACR `docker-credential-acr-env` credential helper](https://github.com/chrismellard/docker-credential-acr-env):
```go
import (
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
func main() {
// ...
acrHelper := credhelper.NewACRCredentialsHelper()
img, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.NewKeychainFromHelper(acrHelper)))
if err != nil {
panic(err)
}
// ...
}
```
<!-- TODO(jasonhall): Wrap these in docker-credential-magic and reference those from here. -->
## Using Multiple `Keychain`s
[`NewMultiKeychain`](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/authn#NewMultiKeychain) allows you to specify multiple `Keychain` implementations, which will be checked in order when credentials are needed.
For example:
```go
kc := authn.NewMultiKeychain(
authn.DefaultKeychain,
google.Keychain,
authn.NewFromHelper(ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}}),
authn.NewFromHelper(acr.ACRCredHelper{}),
)
```
This multi-keychain will:
- first check for credentials found in the Docker config file, as describe above, then
- check for GCP credentials available in the environment, as described above, then
- check for ECR credentials by emulating the ECR credential helper, then
- check for ACR credentials by emulating the ACR credential helper.
If any keychain implementation is able to provide credentials for the request, they will be used, and further keychain implementations will not be consulted.
If no implementations are able to provide credentials, `Anonymous` credentials will be used.
## Docker Config Auth
What follows attempts to gather useful information about Docker's config.json and make it available in one place.
If you have questions, please [file an issue](https://github.com/google/go-containerregistry/issues/new).
### Plaintext ### Plaintext
@ -92,7 +172,7 @@ For what it's worth, this config file is equivalent to:
### Helpers ### Helpers
If you log in like this, docker will warn you that you should use a [credential helper](https://docs.docker.com/engine/reference/commandline/login/#credentials-store), and you should! If you log in like this, `docker` will warn you that you should use a [credential helper](https://docs.docker.com/engine/reference/commandline/login/#credentials-store), and you should!
To configure a global credential helper: To configure a global credential helper:
```json ```json

View File

@ -16,11 +16,14 @@ package authn
import ( import (
"os" "os"
"path/filepath"
"sync" "sync"
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types" "github.com/docker/cli/cli/config/types"
"github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
"github.com/mitchellh/go-homedir"
) )
// Resource represents a registry or repository that can be authenticated against. // Resource represents a registry or repository that can be authenticated against.
@ -62,28 +65,76 @@ const (
func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) { func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
dk.mu.Lock() dk.mu.Lock()
defer dk.mu.Unlock() defer dk.mu.Unlock()
cf, err := config.Load(os.Getenv("DOCKER_CONFIG"))
// Podman users may have their container registry auth configured in a
// different location, that Docker packages aren't aware of.
// If the Docker config file isn't found, we'll fallback to look where
// Podman configures it, and parse that as a Docker auth config instead.
// First, check $HOME/.docker/config.json
foundDockerConfig := false
home, err := homedir.Dir()
if err == nil {
if _, err := os.Stat(filepath.Join(home, ".docker/config.json")); err == nil {
foundDockerConfig = true
}
}
// If $HOME/.docker/config.json isn't found, check $DOCKER_CONFIG (if set)
if !foundDockerConfig && os.Getenv("DOCKER_CONFIG") != "" {
if _, err := os.Stat(filepath.Join(os.Getenv("DOCKER_CONFIG"), "config.json")); err == nil {
foundDockerConfig = true
}
}
// If either of those locations are found, load it using Docker's
// config.Load, which may fail if the config can't be parsed.
//
// If neither was found, look for Podman's auth at
// $XDG_RUNTIME_DIR/containers/auth.json and attempt to load it as a
// Docker config.
//
// If neither are found, fallback to Anonymous.
var cf *configfile.ConfigFile
if foundDockerConfig {
cf, err = config.Load(os.Getenv("DOCKER_CONFIG"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else {
f, err := os.Open(filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "containers/auth.json"))
if err != nil {
return Anonymous, nil
}
defer f.Close()
cf, err = config.LoadFromReader(f)
if err != nil {
return nil, err
}
}
// See: // See:
// https://github.com/google/ko/issues/90 // https://github.com/google/ko/issues/90
// https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404 // https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404
key := target.RegistryStr() var cfg, empty types.AuthConfig
for _, key := range []string{
target.String(),
target.RegistryStr(),
} {
if key == name.DefaultRegistry { if key == name.DefaultRegistry {
key = DefaultAuthKey key = DefaultAuthKey
} }
cfg, err := cf.GetAuthConfig(key) cfg, err = cf.GetAuthConfig(key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if cfg != empty {
empty := types.AuthConfig{} break
}
}
if cfg == empty { if cfg == empty {
return Anonymous, nil return Anonymous, nil
} }
return FromConfig(AuthConfig{ return FromConfig(AuthConfig{
Username: cfg.Username, Username: cfg.Username,
Password: cfg.Password, Password: cfg.Password,
@ -92,3 +143,32 @@ func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
RegistryToken: cfg.RegistryToken, RegistryToken: cfg.RegistryToken,
}), nil }), nil
} }
// Helper is a subset of the Docker credential helper credentials.Helper
// interface used by NewKeychainFromHelper.
//
// See:
// https://pkg.go.dev/github.com/docker/docker-credential-helpers/credentials#Helper
type Helper interface {
Get(serverURL string) (string, string, error)
}
// NewKeychainFromHelper returns a Keychain based on a Docker credential helper
// implementation that can Get username and password credentials for a given
// server URL.
func NewKeychainFromHelper(h Helper) Keychain { return wrapper{h} }
type wrapper struct{ h Helper }
func (w wrapper) Resolve(r Resource) (Authenticator, error) {
u, p, err := w.h.Get(r.RegistryStr())
if err != nil {
return Anonymous, nil
}
// If the secret being stored is an identity token, the Username should be set to <token>
// ref: https://docs.docker.com/engine/reference/commandline/login/#credential-helper-protocol
if u == "<token>" {
return FromConfig(AuthConfig{Username: u, IdentityToken: p}), nil
}
return FromConfig(AuthConfig{Username: u, Password: p}), nil
}

View File

@ -35,9 +35,9 @@ func stripRunesFn(runes string) func(rune) rune {
func checkElement(name, element, allowedRunes string, minRunes, maxRunes int) error { func checkElement(name, element, allowedRunes string, minRunes, maxRunes int) error {
numRunes := utf8.RuneCountInString(element) numRunes := utf8.RuneCountInString(element)
if (numRunes < minRunes) || (maxRunes < numRunes) { if (numRunes < minRunes) || (maxRunes < numRunes) {
return NewErrBadName("%s must be between %d and %d runes in length: %s", name, minRunes, maxRunes, element) return newErrBadName("%s must be between %d and %d characters in length: %s", name, minRunes, maxRunes, element)
} else if len(strings.Map(stripRunesFn(allowedRunes), element)) != 0 { } else if len(strings.Map(stripRunesFn(allowedRunes), element)) != 0 {
return NewErrBadName("%s can only contain the runes `%s`: %s", name, allowedRunes, element) return newErrBadName("%s can only contain the characters `%s`: %s", name, allowedRunes, element)
} }
return nil return nil
} }

View File

@ -69,7 +69,7 @@ func NewDigest(name string, opts ...Option) (Digest, error) {
// Split on "@" // Split on "@"
parts := strings.Split(name, digestDelim) parts := strings.Split(name, digestDelim)
if len(parts) != 2 { if len(parts) != 2 {
return Digest{}, NewErrBadName("a digest must contain exactly one '@' separator (e.g. registry/repository@digest) saw: %s", name) return Digest{}, newErrBadName("a digest must contain exactly one '@' separator (e.g. registry/repository@digest) saw: %s", name)
} }
base := parts[0] base := parts[0]
digest := parts[1] digest := parts[1]

View File

@ -14,7 +14,10 @@
package name package name
import "fmt" import (
"errors"
"fmt"
)
// ErrBadName is an error for when a bad docker name is supplied. // ErrBadName is an error for when a bad docker name is supplied.
type ErrBadName struct { type ErrBadName struct {
@ -25,13 +28,15 @@ func (e *ErrBadName) Error() string {
return e.info return e.info
} }
// NewErrBadName returns a ErrBadName which returns the given formatted string from Error(). // newErrBadName returns a ErrBadName which returns the given formatted string from Error().
func NewErrBadName(fmtStr string, args ...interface{}) *ErrBadName { func newErrBadName(fmtStr string, args ...interface{}) *ErrBadName {
return &ErrBadName{fmt.Sprintf(fmtStr, args...)} return &ErrBadName{fmt.Sprintf(fmtStr, args...)}
} }
// IsErrBadName returns true if the given error is an ErrBadName. // IsErrBadName returns true if the given error is an ErrBadName.
//
// Deprecated: Use errors.Is.
func IsErrBadName(err error) bool { func IsErrBadName(err error) bool {
_, ok := err.(*ErrBadName) var berr *ErrBadName
return ok return errors.As(err, &berr)
} }

View File

@ -44,8 +44,7 @@ func ParseReference(s string, opts ...Option) (Reference, error) {
if d, err := NewDigest(s, opts...); err == nil { if d, err := NewDigest(s, opts...); err == nil {
return d, nil return d, nil
} }
return nil, NewErrBadName("could not parse reference: " + s) return nil, newErrBadName("could not parse reference: " + s)
} }
type stringConst string type stringConst string

View File

@ -98,7 +98,7 @@ func checkRegistry(name string) error {
// Per RFC 3986, registries (authorities) are required to be prefixed with "//" // Per RFC 3986, registries (authorities) are required to be prefixed with "//"
// url.Host == hostname[:port] == authority // url.Host == hostname[:port] == authority
if url, err := url.Parse("//" + name); err != nil || url.Host != name { if url, err := url.Parse("//" + name); err != nil || url.Host != name {
return NewErrBadName("registries must be valid RFC 3986 URI authorities: %s", name) return newErrBadName("registries must be valid RFC 3986 URI authorities: %s", name)
} }
return nil return nil
} }
@ -108,7 +108,7 @@ func checkRegistry(name string) error {
func NewRegistry(name string, opts ...Option) (Registry, error) { func NewRegistry(name string, opts ...Option) (Registry, error) {
opt := makeOptions(opts...) opt := makeOptions(opts...)
if opt.strict && len(name) == 0 { if opt.strict && len(name) == 0 {
return Registry{}, NewErrBadName("strict validation requires the registry to be explicitly defined") return Registry{}, newErrBadName("strict validation requires the registry to be explicitly defined")
} }
if err := checkRegistry(name); err != nil { if err := checkRegistry(name); err != nil {

View File

@ -72,7 +72,7 @@ func checkRepository(repository string) error {
func NewRepository(name string, opts ...Option) (Repository, error) { func NewRepository(name string, opts ...Option) (Repository, error) {
opt := makeOptions(opts...) opt := makeOptions(opts...)
if len(name) == 0 { if len(name) == 0 {
return Repository{}, NewErrBadName("a repository name must be specified") return Repository{}, newErrBadName("a repository name must be specified")
} }
var registry string var registry string
@ -95,7 +95,7 @@ func NewRepository(name string, opts ...Option) (Repository, error) {
return Repository{}, err return Repository{}, err
} }
if hasImplicitNamespace(repo, reg) && opt.strict { if hasImplicitNamespace(repo, reg) && opt.strict {
return Repository{}, NewErrBadName("strict validation requires the full repository path (missing 'library')") return Repository{}, newErrBadName("strict validation requires the full repository path (missing 'library')")
} }
return Repository{reg, repo}, nil return Repository{reg, repo}, nil
} }

View File

@ -0,0 +1,14 @@
# `pkg/registry`
This package implements a Docker v2 registry and the OCI distribution specification.
It is designed to be used anywhere a low dependency container registry is needed, with an initial focus on tests.
Its goal is to be standards compliant and its strictness will increase over time.
This is currently a low flightmiles system. It's likely quite safe to use in tests; If you're using it in production, please let us know how and send us PRs for integration tests.
Before sending a PR, understand that the expectation of this package is that it remain free of extraneous dependencies.
This means that we expect `pkg/registry` to only have dependencies on Go's standard library, and other packages in `go-containerregistry`.
You may be asked to change your code to reduce dependencies, and your PR might be rejected if this is deemed impossible.

View File

@ -16,15 +16,20 @@ package registry
import ( import (
"bytes" "bytes"
"crypto/sha256" "context"
"encoding/hex" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log"
"math/rand" "math/rand"
"net/http" "net/http"
"path" "path"
"strings" "strings"
"sync" "sync"
"github.com/google/go-containerregistry/internal/verify"
v1 "github.com/google/go-containerregistry/pkg/v1"
) )
// Returns whether this url should be handled by the blob handler // Returns whether this url should be handled by the blob handler
@ -44,10 +49,91 @@ func isBlob(req *http.Request) bool {
elem[len(elem)-2] == "uploads") elem[len(elem)-2] == "uploads")
} }
// blobHandler represents a minimal blob storage backend, capable of serving
// blob contents.
type blobHandler interface {
// Get gets the blob contents, or errNotFound if the blob wasn't found.
Get(ctx context.Context, repo string, h v1.Hash) (io.ReadCloser, error)
}
// blobStatHandler is an extension interface representing a blob storage
// backend that can serve metadata about blobs.
type blobStatHandler interface {
// Stat returns the size of the blob, or errNotFound if the blob wasn't
// found, or redirectError if the blob can be found elsewhere.
Stat(ctx context.Context, repo string, h v1.Hash) (int64, error)
}
// blobPutHandler is an extension interface representing a blob storage backend
// that can write blob contents.
type blobPutHandler interface {
// Put puts the blob contents.
//
// The contents will be verified against the expected size and digest
// as the contents are read, and an error will be returned if these
// don't match. Implementations should return that error, or a wrapper
// around that error, to return the correct error when these don't match.
Put(ctx context.Context, repo string, h v1.Hash, rc io.ReadCloser) error
}
// redirectError represents a signal that the blob handler doesn't have the blob
// contents, but that those contents are at another location which registry
// clients should redirect to.
type redirectError struct {
// Location is the location to find the contents.
Location string
// Code is the HTTP redirect status code to return to clients.
Code int
}
func (e redirectError) Error() string { return fmt.Sprintf("redirecting (%d): %s", e.Code, e.Location) }
// errNotFound represents an error locating the blob.
var errNotFound = errors.New("not found")
type memHandler struct {
m map[string][]byte
lock sync.Mutex
}
func (m *memHandler) Stat(_ context.Context, _ string, h v1.Hash) (int64, error) {
m.lock.Lock()
defer m.lock.Unlock()
b, found := m.m[h.String()]
if !found {
return 0, errNotFound
}
return int64(len(b)), nil
}
func (m *memHandler) Get(_ context.Context, _ string, h v1.Hash) (io.ReadCloser, error) {
m.lock.Lock()
defer m.lock.Unlock()
b, found := m.m[h.String()]
if !found {
return nil, errNotFound
}
return ioutil.NopCloser(bytes.NewReader(b)), nil
}
func (m *memHandler) Put(_ context.Context, _ string, h v1.Hash, rc io.ReadCloser) error {
m.lock.Lock()
defer m.lock.Unlock()
defer rc.Close()
all, err := ioutil.ReadAll(rc)
if err != nil {
return err
}
m.m[h.String()] = all
return nil
}
// blobs // blobs
type blobs struct { type blobs struct {
// Blobs are content addresses. we store them globally underneath their sha and make no distinctions per image. blobHandler blobHandler
contents map[string][]byte
// Each upload gets a unique id that writes occur to until finalized. // Each upload gets a unique id that writes occur to until finalized.
uploads map[string][]byte uploads map[string][]byte
lock sync.Mutex lock sync.Mutex
@ -72,73 +158,177 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
digest := req.URL.Query().Get("digest") digest := req.URL.Query().Get("digest")
contentRange := req.Header.Get("Content-Range") contentRange := req.Header.Get("Content-Range")
if req.Method == "HEAD" { repo := req.URL.Host + path.Join(elem[1:len(elem)-2]...)
b.lock.Lock()
defer b.lock.Unlock()
b, ok := b.contents[target]
if !ok {
return &regError{
Status: http.StatusNotFound,
Code: "BLOB_UNKNOWN",
Message: "Unknown blob",
}
}
resp.Header().Set("Content-Length", fmt.Sprint(len(b))) switch req.Method {
resp.Header().Set("Docker-Content-Digest", target) case http.MethodHead:
resp.WriteHeader(http.StatusOK) h, err := v1.NewHash(target)
return nil if err != nil {
}
if req.Method == "GET" {
b.lock.Lock()
defer b.lock.Unlock()
b, ok := b.contents[target]
if !ok {
return &regError{
Status: http.StatusNotFound,
Code: "BLOB_UNKNOWN",
Message: "Unknown blob",
}
}
resp.Header().Set("Content-Length", fmt.Sprint(len(b)))
resp.Header().Set("Docker-Content-Digest", target)
resp.WriteHeader(http.StatusOK)
io.Copy(resp, bytes.NewReader(b))
return nil
}
if req.Method == "POST" && target == "uploads" && digest != "" {
l := &bytes.Buffer{}
io.Copy(l, req.Body)
rd := sha256.Sum256(l.Bytes())
d := "sha256:" + hex.EncodeToString(rd[:])
if d != digest {
return &regError{ return &regError{
Status: http.StatusBadRequest, Status: http.StatusBadRequest,
Code: "DIGEST_INVALID", Code: "NAME_INVALID",
Message: "digest does not match contents", Message: "invalid digest",
} }
} }
b.lock.Lock() var size int64
defer b.lock.Unlock() if bsh, ok := b.blobHandler.(blobStatHandler); ok {
b.contents[d] = l.Bytes() size, err = bsh.Stat(req.Context(), repo, h)
resp.Header().Set("Docker-Content-Digest", d) if errors.Is(err, errNotFound) {
return regErrBlobUnknown
} else if err != nil {
var rerr redirectError
if errors.As(err, &rerr) {
http.Redirect(resp, req, rerr.Location, rerr.Code)
return nil
}
return regErrInternal(err)
}
} else {
rc, err := b.blobHandler.Get(req.Context(), repo, h)
if errors.Is(err, errNotFound) {
return regErrBlobUnknown
} else if err != nil {
var rerr redirectError
if errors.As(err, &rerr) {
http.Redirect(resp, req, rerr.Location, rerr.Code)
return nil
}
return regErrInternal(err)
}
defer rc.Close()
size, err = io.Copy(ioutil.Discard, rc)
if err != nil {
return regErrInternal(err)
}
}
resp.Header().Set("Content-Length", fmt.Sprint(size))
resp.Header().Set("Docker-Content-Digest", h.String())
resp.WriteHeader(http.StatusOK)
return nil
case http.MethodGet:
h, err := v1.NewHash(target)
if err != nil {
return &regError{
Status: http.StatusBadRequest,
Code: "NAME_INVALID",
Message: "invalid digest",
}
}
var size int64
var r io.Reader
if bsh, ok := b.blobHandler.(blobStatHandler); ok {
size, err = bsh.Stat(req.Context(), repo, h)
if errors.Is(err, errNotFound) {
return regErrBlobUnknown
} else if err != nil {
var rerr redirectError
if errors.As(err, &rerr) {
http.Redirect(resp, req, rerr.Location, rerr.Code)
return nil
}
return regErrInternal(err)
}
rc, err := b.blobHandler.Get(req.Context(), repo, h)
if errors.Is(err, errNotFound) {
return regErrBlobUnknown
} else if err != nil {
var rerr redirectError
if errors.As(err, &rerr) {
http.Redirect(resp, req, rerr.Location, rerr.Code)
return nil
}
return regErrInternal(err)
}
defer rc.Close()
r = rc
} else {
tmp, err := b.blobHandler.Get(req.Context(), repo, h)
if errors.Is(err, errNotFound) {
return regErrBlobUnknown
} else if err != nil {
var rerr redirectError
if errors.As(err, &rerr) {
http.Redirect(resp, req, rerr.Location, rerr.Code)
return nil
}
return regErrInternal(err)
}
defer tmp.Close()
var buf bytes.Buffer
io.Copy(&buf, tmp)
size = int64(buf.Len())
r = &buf
}
resp.Header().Set("Content-Length", fmt.Sprint(size))
resp.Header().Set("Docker-Content-Digest", h.String())
resp.WriteHeader(http.StatusOK)
io.Copy(resp, r)
return nil
case http.MethodPost:
bph, ok := b.blobHandler.(blobPutHandler)
if !ok {
return regErrUnsupported
}
// It is weird that this is "target" instead of "service", but
// that's how the index math works out above.
if target != "uploads" {
return &regError{
Status: http.StatusBadRequest,
Code: "METHOD_UNKNOWN",
Message: fmt.Sprintf("POST to /blobs must be followed by /uploads, got %s", target),
}
}
if digest != "" {
h, err := v1.NewHash(digest)
if err != nil {
return regErrDigestInvalid
}
vrc, err := verify.ReadCloser(req.Body, req.ContentLength, h)
if err != nil {
return regErrInternal(err)
}
defer vrc.Close()
if err = bph.Put(req.Context(), repo, h, vrc); err != nil {
if errors.As(err, &verify.Error{}) {
log.Printf("Digest mismatch: %v", err)
return regErrDigestMismatch
}
return regErrInternal(err)
}
resp.Header().Set("Docker-Content-Digest", h.String())
resp.WriteHeader(http.StatusCreated) resp.WriteHeader(http.StatusCreated)
return nil return nil
} }
if req.Method == "POST" && target == "uploads" && digest == "" {
id := fmt.Sprint(rand.Int63()) id := fmt.Sprint(rand.Int63())
resp.Header().Set("Location", "/"+path.Join("v2", path.Join(elem[1:len(elem)-2]...), "blobs/uploads", id)) resp.Header().Set("Location", "/"+path.Join("v2", path.Join(elem[1:len(elem)-2]...), "blobs/uploads", id))
resp.Header().Set("Range", "0-0") resp.Header().Set("Range", "0-0")
resp.WriteHeader(http.StatusAccepted) resp.WriteHeader(http.StatusAccepted)
return nil return nil
case http.MethodPatch:
if service != "uploads" {
return &regError{
Status: http.StatusBadRequest,
Code: "METHOD_UNKNOWN",
Message: fmt.Sprintf("PATCH to /blobs must be followed by /uploads, got %s", service),
}
} }
if req.Method == "PATCH" && service == "uploads" && contentRange != "" { if contentRange != "" {
start, end := 0, 0 start, end := 0, 0
if _, err := fmt.Sscanf(contentRange, "%d-%d", &start, &end); err != nil { if _, err := fmt.Sscanf(contentRange, "%d-%d", &start, &end); err != nil {
return &regError{ return &regError{
@ -165,7 +355,6 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
return nil return nil
} }
if req.Method == "PATCH" && service == "uploads" && contentRange == "" {
b.lock.Lock() b.lock.Lock()
defer b.lock.Unlock() defer b.lock.Unlock()
if _, ok := b.uploads[target]; ok { if _, ok := b.uploads[target]; ok {
@ -184,9 +373,22 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
resp.Header().Set("Range", fmt.Sprintf("0-%d", len(l.Bytes())-1)) resp.Header().Set("Range", fmt.Sprintf("0-%d", len(l.Bytes())-1))
resp.WriteHeader(http.StatusNoContent) resp.WriteHeader(http.StatusNoContent)
return nil return nil
case http.MethodPut:
bph, ok := b.blobHandler.(blobPutHandler)
if !ok {
return regErrUnsupported
} }
if req.Method == "PUT" && service == "uploads" && digest == "" { if service != "uploads" {
return &regError{
Status: http.StatusBadRequest,
Code: "METHOD_UNKNOWN",
Message: fmt.Sprintf("PUT to /blobs must be followed by /uploads, got %s", service),
}
}
if digest == "" {
return &regError{ return &regError{
Status: http.StatusBadRequest, Status: http.StatusBadRequest,
Code: "DIGEST_INVALID", Code: "DIGEST_INVALID",
@ -194,31 +396,50 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
} }
} }
if req.Method == "PUT" && service == "uploads" && digest != "" {
b.lock.Lock() b.lock.Lock()
defer b.lock.Unlock() defer b.lock.Unlock()
l := bytes.NewBuffer(b.uploads[target])
io.Copy(l, req.Body) h, err := v1.NewHash(digest)
rd := sha256.Sum256(l.Bytes()) if err != nil {
d := "sha256:" + hex.EncodeToString(rd[:])
if d != digest {
return &regError{ return &regError{
Status: http.StatusBadRequest, Status: http.StatusBadRequest,
Code: "DIGEST_INVALID", Code: "NAME_INVALID",
Message: "digest does not match contents", Message: "invalid digest",
} }
} }
b.contents[d] = l.Bytes() defer req.Body.Close()
in := ioutil.NopCloser(io.MultiReader(bytes.NewBuffer(b.uploads[target]), req.Body))
size := int64(verify.SizeUnknown)
if req.ContentLength > 0 {
size = int64(len(b.uploads[target])) + req.ContentLength
}
vrc, err := verify.ReadCloser(in, size, h)
if err != nil {
return regErrInternal(err)
}
defer vrc.Close()
if err := bph.Put(req.Context(), repo, h, vrc); err != nil {
if errors.As(err, &verify.Error{}) {
log.Printf("Digest mismatch: %v", err)
return regErrDigestMismatch
}
return regErrInternal(err)
}
delete(b.uploads, target) delete(b.uploads, target)
resp.Header().Set("Docker-Content-Digest", d) resp.Header().Set("Docker-Content-Digest", h.String())
resp.WriteHeader(http.StatusCreated) resp.WriteHeader(http.StatusCreated)
return nil return nil
}
default:
return &regError{ return &regError{
Status: http.StatusBadRequest, Status: http.StatusBadRequest,
Code: "METHOD_UNKNOWN", Code: "METHOD_UNKNOWN",
Message: "We don't understand your method + url", Message: "We don't understand your method + url",
} }
}
} }

View File

@ -44,3 +44,36 @@ func (r *regError) Write(resp http.ResponseWriter) error {
}, },
}) })
} }
// regErrInternal returns an internal server error.
func regErrInternal(err error) *regError {
return &regError{
Status: http.StatusInternalServerError,
Code: "INTERNAL_SERVER_ERROR",
Message: err.Error(),
}
}
var regErrBlobUnknown = &regError{
Status: http.StatusNotFound,
Code: "BLOB_UNKNOWN",
Message: "Unknown blob",
}
var regErrUnsupported = &regError{
Status: http.StatusMethodNotAllowed,
Code: "UNSUPPORTED",
Message: "Unsupported operation",
}
var regErrDigestMismatch = &regError{
Status: http.StatusBadRequest,
Code: "DIGEST_INVALID",
Message: "digest does not match contents",
}
var regErrDigestInvalid = &regError{
Status: http.StatusBadRequest,
Code: "NAME_INVALID",
Message: "invalid digest",
}

View File

@ -89,7 +89,8 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
target := elem[len(elem)-1] target := elem[len(elem)-1]
repo := strings.Join(elem[1:len(elem)-2], "/") repo := strings.Join(elem[1:len(elem)-2], "/")
if req.Method == "GET" { switch req.Method {
case http.MethodGet:
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
@ -117,9 +118,8 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
resp.WriteHeader(http.StatusOK) resp.WriteHeader(http.StatusOK)
io.Copy(resp, bytes.NewReader(m.blob)) io.Copy(resp, bytes.NewReader(m.blob))
return nil return nil
}
if req.Method == "HEAD" { case http.MethodHead:
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if _, ok := m.manifests[repo]; !ok { if _, ok := m.manifests[repo]; !ok {
@ -144,9 +144,8 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
resp.Header().Set("Content-Length", fmt.Sprint(len(m.blob))) resp.Header().Set("Content-Length", fmt.Sprint(len(m.blob)))
resp.WriteHeader(http.StatusOK) resp.WriteHeader(http.StatusOK)
return nil return nil
}
if req.Method == "PUT" { case http.MethodPut:
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if _, ok := m.manifests[repo]; !ok { if _, ok := m.manifests[repo]; !ok {
@ -200,9 +199,8 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
resp.Header().Set("Docker-Content-Digest", digest) resp.Header().Set("Docker-Content-Digest", digest)
resp.WriteHeader(http.StatusCreated) resp.WriteHeader(http.StatusCreated)
return nil return nil
}
if req.Method == "DELETE" { case http.MethodDelete:
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if _, ok := m.manifests[repo]; !ok { if _, ok := m.manifests[repo]; !ok {
@ -225,13 +223,14 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
delete(m.manifests[repo], target) delete(m.manifests[repo], target)
resp.WriteHeader(http.StatusAccepted) resp.WriteHeader(http.StatusAccepted)
return nil return nil
}
default:
return &regError{ return &regError{
Status: http.StatusBadRequest, Status: http.StatusBadRequest,
Code: "METHOD_UNKNOWN", Code: "METHOD_UNKNOWN",
Message: "We don't understand your method + url", Message: "We don't understand your method + url",
} }
}
} }
func (m *manifests) handleTags(resp http.ResponseWriter, req *http.Request) *regError { func (m *manifests) handleTags(resp http.ResponseWriter, req *http.Request) *regError {

View File

@ -77,7 +77,7 @@ func New(opts ...Option) http.Handler {
r := &registry{ r := &registry{
log: log.New(os.Stderr, "", log.LstdFlags), log: log.New(os.Stderr, "", log.LstdFlags),
blobs: blobs{ blobs: blobs{
contents: map[string][]byte{}, blobHandler: &memHandler{m: map[string][]byte{}},
uploads: map[string][]byte{}, uploads: map[string][]byte{},
}, },
manifests: manifests{ manifests: manifests{

View File

@ -24,8 +24,19 @@ import (
"github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/google/go-containerregistry/pkg/v1/types"
) )
type image struct {
ref name.Reference
opener *imageOpener
tarballImage v1.Image
id *v1.Hash
once sync.Once
err error
}
type imageOpener struct { type imageOpener struct {
ref name.Reference ref name.Reference
ctx context.Context ctx context.Context
@ -85,5 +96,109 @@ func Image(ref name.Reference, options ...Option) (v1.Image, error) {
ctx: o.ctx, ctx: o.ctx,
} }
return tarball.Image(i.opener(), nil) img := &image{
ref: ref,
opener: i,
}
// Eagerly fetch Image ID to ensure it actually exists.
// https://github.com/google/go-containerregistry/issues/1186
id, err := img.ConfigName()
if err != nil {
return nil, err
}
img.id = &id
return img, nil
}
func (i *image) initialize() error {
// Don't re-initialize tarball if already initialized.
if i.tarballImage == nil {
i.once.Do(func() {
i.tarballImage, i.err = tarball.Image(i.opener.opener(), nil)
})
}
return i.err
}
func (i *image) Layers() ([]v1.Layer, error) {
if err := i.initialize(); err != nil {
return nil, err
}
return i.tarballImage.Layers()
}
func (i *image) MediaType() (types.MediaType, error) {
if err := i.initialize(); err != nil {
return "", err
}
return i.tarballImage.MediaType()
}
func (i *image) Size() (int64, error) {
if err := i.initialize(); err != nil {
return 0, err
}
return i.tarballImage.Size()
}
func (i *image) ConfigName() (v1.Hash, error) {
if i.id != nil {
return *i.id, nil
}
res, _, err := i.opener.client.ImageInspectWithRaw(i.opener.ctx, i.ref.String())
if err != nil {
return v1.Hash{}, err
}
return v1.NewHash(res.ID)
}
func (i *image) ConfigFile() (*v1.ConfigFile, error) {
if err := i.initialize(); err != nil {
return nil, err
}
return i.tarballImage.ConfigFile()
}
func (i *image) RawConfigFile() ([]byte, error) {
if err := i.initialize(); err != nil {
return nil, err
}
return i.tarballImage.RawConfigFile()
}
func (i *image) Digest() (v1.Hash, error) {
if err := i.initialize(); err != nil {
return v1.Hash{}, err
}
return i.tarballImage.Digest()
}
func (i *image) Manifest() (*v1.Manifest, error) {
if err := i.initialize(); err != nil {
return nil, err
}
return i.tarballImage.Manifest()
}
func (i *image) RawManifest() ([]byte, error) {
if err := i.initialize(); err != nil {
return nil, err
}
return i.tarballImage.RawManifest()
}
func (i *image) LayerByDigest(h v1.Hash) (v1.Layer, error) {
if err := i.initialize(); err != nil {
return nil, err
}
return i.tarballImage.LayerByDigest(h)
}
func (i *image) LayerByDiffID(h v1.Hash) (v1.Layer, error) {
if err := i.initialize(); err != nil {
return nil, err
}
return i.tarballImage.LayerByDiffID(h)
} }

View File

@ -99,4 +99,5 @@ type Client interface {
ImageSave(context.Context, []string) (io.ReadCloser, error) ImageSave(context.Context, []string) (io.ReadCloser, error)
ImageLoad(context.Context, io.Reader, bool) (types.ImageLoadResponse, error) ImageLoad(context.Context, io.Reader, bool) (types.ImageLoadResponse, error)
ImageTag(context.Context, string, string) error ImageTag(context.Context, string, string) error
ImageInspectWithRaw(context.Context, string) (types.ImageInspect, []byte, error)
} }

View File

@ -49,13 +49,13 @@ func Write(tag name.Tag, img v1.Image, options ...Option) (string, error) {
// write the image in docker save format first, then load it // write the image in docker save format first, then load it
resp, err := o.client.ImageLoad(o.ctx, pr, false) resp, err := o.client.ImageLoad(o.ctx, pr, false)
if err != nil { if err != nil {
return "", fmt.Errorf("error loading image: %v", err) return "", fmt.Errorf("error loading image: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body) b, err := ioutil.ReadAll(resp.Body)
response := string(b) response := string(b)
if err != nil { if err != nil {
return response, fmt.Errorf("error reading load response body: %v", err) return response, fmt.Errorf("error reading load response body: %w", err)
} }
return response, nil return response, nil
} }

View File

@ -93,17 +93,10 @@ func (li *layoutImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error)
for _, desc := range manifest.Layers { for _, desc := range manifest.Layers {
if h == desc.Digest { if h == desc.Digest {
switch desc.MediaType {
case types.OCILayer, types.DockerLayer:
return &compressedBlob{ return &compressedBlob{
path: li.path, path: li.path,
desc: desc, desc: desc,
}, nil }, nil
default:
// TODO: We assume everything is a compressed blob, but that might not be true.
// TODO: Handle foreign layers.
return nil, fmt.Errorf("unexpected media type: %v for layer: %v", desc.MediaType, desc.Digest)
}
} }
} }
@ -131,6 +124,11 @@ func (b *compressedBlob) MediaType() (types.MediaType, error) {
return b.desc.MediaType, nil return b.desc.MediaType, nil
} }
// Descriptor implements partial.withDescriptor.
func (b *compressedBlob) Descriptor() (*v1.Descriptor, error) {
return &b.desc, nil
}
// See partial.Exists. // See partial.Exists.
func (b *compressedBlob) Exists() (bool, error) { func (b *compressedBlob) Exists() (bool, error) {
_, err := os.Stat(b.path.blobPath(b.desc.Digest)) _, err := os.Stat(b.path.blobPath(b.desc.Digest))

View File

@ -17,15 +17,19 @@ package layout
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/google/go-containerregistry/pkg/logs"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/match" "github.com/google/go-containerregistry/pkg/v1/match"
"github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/stream"
"github.com/google/go-containerregistry/pkg/v1/types" "github.com/google/go-containerregistry/pkg/v1/types"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@ -216,40 +220,109 @@ func (l Path) WriteFile(name string, data []byte, perm os.FileMode) error {
} }
return ioutil.WriteFile(l.path(name), data, perm) return ioutil.WriteFile(l.path(name), data, perm)
} }
// WriteBlob copies a file to the blobs/ directory in the Path from the given ReadCloser at // WriteBlob copies a file to the blobs/ directory in the Path from the given ReadCloser at
// blobs/{hash.Algorithm}/{hash.Hex}. // blobs/{hash.Algorithm}/{hash.Hex}.
func (l Path) WriteBlob(hash v1.Hash, r io.ReadCloser) error { func (l Path) WriteBlob(hash v1.Hash, r io.ReadCloser) error {
return l.writeBlob(hash, -1, r, nil)
}
func (l Path) writeBlob(hash v1.Hash, size int64, rc io.ReadCloser, renamer func() (v1.Hash, error)) error {
if hash.Hex == "" && renamer == nil {
panic("writeBlob called an invalid hash and no renamer")
}
dir := l.path("blobs", hash.Algorithm) dir := l.path("blobs", hash.Algorithm)
if err := os.MkdirAll(dir, os.ModePerm); err != nil && !os.IsExist(err) { if err := os.MkdirAll(dir, os.ModePerm); err != nil && !os.IsExist(err) {
return err return err
} }
// Check if blob already exists and is the correct size
file := filepath.Join(dir, hash.Hex) file := filepath.Join(dir, hash.Hex)
if _, err := os.Stat(file); err == nil { if s, err := os.Stat(file); err == nil && !s.IsDir() && (s.Size() == size || size == -1) {
// Blob already exists, that's fine.
return nil return nil
} }
w, err := os.Create(file)
// If a renamer func was provided write to a temporary file
open := func() (*os.File, error) { return os.Create(file) }
if renamer != nil {
open = func() (*os.File, error) { return ioutil.TempFile(dir, hash.Hex) }
}
w, err := open()
if err != nil { if err != nil {
return err return err
} }
if renamer != nil {
// Delete temp file if an error is encountered before renaming
defer func() {
if err := os.Remove(w.Name()); err != nil && !errors.Is(err, os.ErrNotExist) {
logs.Warn.Printf("error removing temporary file after encountering an error while writing blob: %v", err)
}
}()
}
defer w.Close() defer w.Close()
_, err = io.Copy(w, r) // Write to file and exit if not renaming
if n, err := io.Copy(w, rc); err != nil || renamer == nil {
return err return err
} else if size != -1 && n != size {
return fmt.Errorf("expected blob size %d, but only wrote %d", size, n)
}
// Always close reader before renaming, since Close computes the digest in
// the case of streaming layers. If Close is not called explicitly, it will
// occur in a goroutine that is not guaranteed to succeed before renamer is
// called. When renamer is the layer's Digest method, it can return
// ErrNotComputed.
if err := rc.Close(); err != nil {
return err
}
// Always close file before renaming
if err := w.Close(); err != nil {
return err
}
// Rename file based on the final hash
finalHash, err := renamer()
if err != nil {
return fmt.Errorf("error getting final digest of layer: %w", err)
}
renamePath := l.path("blobs", finalHash.Algorithm, finalHash.Hex)
return os.Rename(w.Name(), renamePath)
} }
// TODO: A streaming version of WriteBlob so we don't have to know the hash // writeLayer writes the compressed layer to a blob. Unlike WriteBlob it will
// before we write it. // write to a temporary file (suffixed with .tmp) within the layout until the
// compressed reader is fully consumed and written to disk. Also unlike
// TODO: For streaming layers we should write to a tmp file then Rename to the // WriteBlob, it will not skip writing and exit without error when a blob file
// final digest. // exists, but does not have the correct size. (The blob hash is not
// considered, because it may be expensive to compute.)
func (l Path) writeLayer(layer v1.Layer) error { func (l Path) writeLayer(layer v1.Layer) error {
d, err := layer.Digest() d, err := layer.Digest()
if err != nil { if errors.Is(err, stream.ErrNotComputed) {
// Allow digest errors, since streams may not have calculated the hash
// yet. Instead, use an empty value, which will be transformed into a
// random file name with `ioutil.TempFile` and the final digest will be
// calculated after writing to a temp file and before renaming to the
// final path.
d = v1.Hash{Algorithm: "sha256", Hex: ""}
} else if err != nil {
return err
}
s, err := layer.Size()
if errors.Is(err, stream.ErrNotComputed) {
// Allow size errors, since streams may not have calculated the size
// yet. Instead, use zero as a sentinel value meaning that no size
// comparison can be done and any sized blob file should be considered
// valid and not overwritten.
//
// TODO: Provide an option to always overwrite blobs.
s = -1
} else if err != nil {
return err return err
} }
@ -258,7 +331,10 @@ func (l Path) writeLayer(layer v1.Layer) error {
return err return err
} }
return l.WriteBlob(d, r) if err := l.writeBlob(d, s, r, layer.Digest); err != nil {
return fmt.Errorf("error writing layer: %w", err)
}
return nil
} }
// RemoveBlob removes a file from the blobs directory in the Path // RemoveBlob removes a file from the blobs directory in the Path
@ -410,7 +486,6 @@ func (l Path) WriteIndex(ii v1.ImageIndex) error {
indexFile := filepath.Join("blobs", h.Algorithm, h.Hex) indexFile := filepath.Join("blobs", h.Algorithm, h.Hex)
return l.writeIndexToFile(indexFile, ii) return l.writeIndexToFile(indexFile, ii)
} }
// Write constructs a Path at path from an ImageIndex. // Write constructs a Path at path from an ImageIndex.

View File

@ -18,7 +18,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"strings"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/partial"
@ -35,6 +34,7 @@ type image struct {
manifest *v1.Manifest manifest *v1.Manifest
annotations map[string]string annotations map[string]string
mediaType *types.MediaType mediaType *types.MediaType
configMediaType *types.MediaType
diffIDMap map[v1.Hash]v1.Layer diffIDMap map[v1.Hash]v1.Layer
digestMap map[v1.Hash]v1.Layer digestMap map[v1.Hash]v1.Layer
} }
@ -135,14 +135,13 @@ func (i *image) compute() error {
manifest.Config.Data = rcfg manifest.Config.Data = rcfg
} }
// With OCI media types, this should not be set, see discussion: // If the user wants to mutate the media type of the config
// https://github.com/opencontainers/image-spec/pull/795 if i.configMediaType != nil {
if i.mediaType != nil { manifest.Config.MediaType = *i.configMediaType
if strings.Contains(string(*i.mediaType), types.OCIVendorPrefix) {
manifest.MediaType = ""
} else if strings.Contains(string(*i.mediaType), types.DockerVendorPrefix) {
manifest.MediaType = *i.mediaType
} }
if i.mediaType != nil {
manifest.MediaType = *i.mediaType
} }
if i.annotations != nil { if i.annotations != nil {
@ -166,7 +165,7 @@ func (i *image) compute() error {
// Layers returns the ordered collection of filesystem layers that comprise this image. // Layers returns the ordered collection of filesystem layers that comprise this image.
// The order of the list is oldest/base layer first, and most-recent/top layer last. // The order of the list is oldest/base layer first, and most-recent/top layer last.
func (i *image) Layers() ([]v1.Layer, error) { func (i *image) Layers() ([]v1.Layer, error) {
if err := i.compute(); err == stream.ErrNotComputed { if err := i.compute(); errors.Is(err, stream.ErrNotComputed) {
// Image contains a streamable layer which has not yet been // Image contains a streamable layer which has not yet been
// consumed. Just return the layers we have in case the caller // consumed. Just return the layers we have in case the caller
// is going to consume the layers. // is going to consume the layers.
@ -210,7 +209,7 @@ func (i *image) ConfigFile() (*v1.ConfigFile, error) {
if err := i.compute(); err != nil { if err := i.compute(); err != nil {
return nil, err return nil, err
} }
return i.configFile, nil return i.configFile.DeepCopy(), nil
} }
// RawConfigFile returns the serialized bytes of ConfigFile() // RawConfigFile returns the serialized bytes of ConfigFile()
@ -242,7 +241,7 @@ func (i *image) Manifest() (*v1.Manifest, error) {
if err := i.compute(); err != nil { if err := i.compute(); err != nil {
return nil, err return nil, err
} }
return i.manifest, nil return i.manifest.DeepCopy(), nil
} }
// RawManifest returns the serialized bytes of Manifest() // RawManifest returns the serialized bytes of Manifest()

View File

@ -17,7 +17,6 @@ package mutate
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"github.com/google/go-containerregistry/pkg/logs" "github.com/google/go-containerregistry/pkg/logs"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
@ -131,15 +130,9 @@ func (i *index) compute() error {
manifest.Manifests = manifests manifest.Manifests = manifests
// With OCI media types, this should not be set, see discussion:
// https://github.com/opencontainers/image-spec/pull/795
if i.mediaType != nil { if i.mediaType != nil {
if strings.Contains(string(*i.mediaType), types.OCIVendorPrefix) {
manifest.MediaType = ""
} else if strings.Contains(string(*i.mediaType), types.DockerVendorPrefix) {
manifest.MediaType = *i.mediaType manifest.MediaType = *i.mediaType
} }
}
if i.annotations != nil { if i.annotations != nil {
if manifest.Annotations == nil { if manifest.Annotations == nil {
@ -197,7 +190,7 @@ func (i *index) IndexManifest() (*v1.IndexManifest, error) {
if err := i.compute(); err != nil { if err := i.compute(); err != nil {
return nil, err return nil, err
} }
return i.manifest, nil return i.manifest.DeepCopy(), nil
} }
// RawManifest returns the serialized bytes of Manifest() // RawManifest returns the serialized bytes of Manifest()

View File

@ -18,6 +18,7 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -241,7 +242,7 @@ func extract(img v1.Image, w io.Writer) error {
layers, err := img.Layers() layers, err := img.Layers()
if err != nil { if err != nil {
return fmt.Errorf("retrieving image layers: %v", err) return fmt.Errorf("retrieving image layers: %w", err)
} }
// we iterate through the layers in reverse order because it makes handling // we iterate through the layers in reverse order because it makes handling
// whiteout layers more efficient, since we can just keep track of the removed // whiteout layers more efficient, since we can just keep track of the removed
@ -250,19 +251,23 @@ func extract(img v1.Image, w io.Writer) error {
layer := layers[i] layer := layers[i]
layerReader, err := layer.Uncompressed() layerReader, err := layer.Uncompressed()
if err != nil { if err != nil {
return fmt.Errorf("reading layer contents: %v", err) return fmt.Errorf("reading layer contents: %w", err)
} }
defer layerReader.Close() defer layerReader.Close()
tarReader := tar.NewReader(layerReader) tarReader := tar.NewReader(layerReader)
for { for {
header, err := tarReader.Next() header, err := tarReader.Next()
if err == io.EOF { if errors.Is(err, io.EOF) {
break break
} }
if err != nil { if err != nil {
return fmt.Errorf("reading tar: %v", err) return fmt.Errorf("reading tar: %w", err)
} }
// Some tools prepend everything with "./", so if we don't Clean the
// name, we may have duplicate entries, which angers tar-split.
header.Name = filepath.Clean(header.Name)
basename := filepath.Base(header.Name) basename := filepath.Base(header.Name)
dirname := filepath.Dir(header.Name) dirname := filepath.Dir(header.Name)
tombstone := strings.HasPrefix(basename, whiteoutPrefix) tombstone := strings.HasPrefix(basename, whiteoutPrefix)
@ -294,7 +299,7 @@ func extract(img v1.Image, w io.Writer) error {
if !tombstone { if !tombstone {
tarWriter.WriteHeader(header) tarWriter.WriteHeader(header)
if header.Size > 0 { if header.Size > 0 {
if _, err := io.Copy(tarWriter, tarReader); err != nil { if _, err := io.CopyN(tarWriter, tarReader, header.Size); err != nil {
return err return err
} }
} }
@ -327,32 +332,32 @@ func Time(img v1.Image, t time.Time) (v1.Image, error) {
layers, err := img.Layers() layers, err := img.Layers()
if err != nil { if err != nil {
return nil, fmt.Errorf("getting image layers: %v", err) return nil, fmt.Errorf("getting image layers: %w", err)
} }
// Strip away all timestamps from layers // Strip away all timestamps from layers
var newLayers []v1.Layer newLayers := make([]v1.Layer, len(layers))
for _, layer := range layers { for idx, layer := range layers {
newLayer, err := layerTime(layer, t) newLayer, err := layerTime(layer, t)
if err != nil { if err != nil {
return nil, fmt.Errorf("setting layer times: %v", err) return nil, fmt.Errorf("setting layer times: %w", err)
} }
newLayers = append(newLayers, newLayer) newLayers[idx] = newLayer
} }
newImage, err = AppendLayers(newImage, newLayers...) newImage, err = AppendLayers(newImage, newLayers...)
if err != nil { if err != nil {
return nil, fmt.Errorf("appending layers: %v", err) return nil, fmt.Errorf("appending layers: %w", err)
} }
ocf, err := img.ConfigFile() ocf, err := img.ConfigFile()
if err != nil { if err != nil {
return nil, fmt.Errorf("getting original config file: %v", err) return nil, fmt.Errorf("getting original config file: %w", err)
} }
cf, err := newImage.ConfigFile() cf, err := newImage.ConfigFile()
if err != nil { if err != nil {
return nil, fmt.Errorf("setting config file: %v", err) return nil, fmt.Errorf("setting config file: %w", err)
} }
cfg := cf.DeepCopy() cfg := cf.DeepCopy()
@ -366,8 +371,13 @@ func Time(img v1.Image, t time.Time) (v1.Image, error) {
// Strip away timestamps from the config file // Strip away timestamps from the config file
cfg.Created = v1.Time{Time: t} cfg.Created = v1.Time{Time: t}
for _, h := range cfg.History { for i, h := range cfg.History {
h.Created = v1.Time{Time: t} h.Created = v1.Time{Time: t}
h.CreatedBy = ocf.History[i].CreatedBy
h.Comment = ocf.History[i].Comment
h.EmptyLayer = ocf.History[i].EmptyLayer
// Explicitly ignore Author field; which hinders reproducibility
cfg.History[i] = h
} }
return ConfigFile(newImage, cfg) return ConfigFile(newImage, cfg)
@ -376,7 +386,7 @@ func Time(img v1.Image, t time.Time) (v1.Image, error) {
func layerTime(layer v1.Layer, t time.Time) (v1.Layer, error) { func layerTime(layer v1.Layer, t time.Time) (v1.Layer, error) {
layerReader, err := layer.Uncompressed() layerReader, err := layer.Uncompressed()
if err != nil { if err != nil {
return nil, fmt.Errorf("getting layer: %v", err) return nil, fmt.Errorf("getting layer: %w", err)
} }
defer layerReader.Close() defer layerReader.Close()
w := new(bytes.Buffer) w := new(bytes.Buffer)
@ -386,21 +396,22 @@ func layerTime(layer v1.Layer, t time.Time) (v1.Layer, error) {
tarReader := tar.NewReader(layerReader) tarReader := tar.NewReader(layerReader)
for { for {
header, err := tarReader.Next() header, err := tarReader.Next()
if err == io.EOF { if errors.Is(err, io.EOF) {
break break
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("reading layer: %v", err) return nil, fmt.Errorf("reading layer: %w", err)
} }
header.ModTime = t header.ModTime = t
if err := tarWriter.WriteHeader(header); err != nil { if err := tarWriter.WriteHeader(header); err != nil {
return nil, fmt.Errorf("writing tar header: %v", err) return nil, fmt.Errorf("writing tar header: %w", err)
} }
if header.Typeflag == tar.TypeReg { if header.Typeflag == tar.TypeReg {
if _, err = io.Copy(tarWriter, tarReader); err != nil { // TODO(#1168): This should be lazy, and not buffer the entire layer contents.
return nil, fmt.Errorf("writing layer file: %v", err) if _, err = io.CopyN(tarWriter, tarReader, header.Size); err != nil {
return nil, fmt.Errorf("writing layer file: %w", err)
} }
} }
} }
@ -416,7 +427,7 @@ func layerTime(layer v1.Layer, t time.Time) (v1.Layer, error) {
} }
layer, err = tarball.LayerFromOpener(opener) layer, err = tarball.LayerFromOpener(opener)
if err != nil { if err != nil {
return nil, fmt.Errorf("creating layer: %v", err) return nil, fmt.Errorf("creating layer: %w", err)
} }
return layer, nil return layer, nil
@ -455,6 +466,14 @@ func MediaType(img v1.Image, mt types.MediaType) v1.Image {
} }
} }
// ConfigMediaType modifies the MediaType() of the given image's Config.
func ConfigMediaType(img v1.Image, mt types.MediaType) v1.Image {
return &image{
base: img,
configMediaType: &mt,
}
}
// IndexMediaType modifies the MediaType() of the given index. // IndexMediaType modifies the MediaType() of the given index.
func IndexMediaType(idx v1.ImageIndex, mt types.MediaType) v1.ImageIndex { func IndexMediaType(idx v1.ImageIndex, mt types.MediaType) v1.ImageIndex {
return &index{ return &index{

View File

@ -27,7 +27,7 @@ func Rebase(orig, oldBase, newBase v1.Image) (v1.Image, error) {
// not based on oldBase at all. // not based on oldBase at all.
origLayers, err := orig.Layers() origLayers, err := orig.Layers()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get layers for original: %v", err) return nil, fmt.Errorf("failed to get layers for original: %w", err)
} }
oldBaseLayers, err := oldBase.Layers() oldBaseLayers, err := oldBase.Layers()
if err != nil { if err != nil {
@ -39,11 +39,11 @@ func Rebase(orig, oldBase, newBase v1.Image) (v1.Image, error) {
for i, l := range oldBaseLayers { for i, l := range oldBaseLayers {
oldLayerDigest, err := l.Digest() oldLayerDigest, err := l.Digest()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get digest of layer %d of %q: %v", i, oldBase, err) return nil, fmt.Errorf("failed to get digest of layer %d of %q: %w", i, oldBase, err)
} }
origLayerDigest, err := origLayers[i].Digest() origLayerDigest, err := origLayers[i].Digest()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get digest of layer %d of %q: %v", i, orig, err) return nil, fmt.Errorf("failed to get digest of layer %d of %q: %w", i, orig, err)
} }
if oldLayerDigest != origLayerDigest { if oldLayerDigest != origLayerDigest {
return nil, fmt.Errorf("image %q is not based on %q (layer %d mismatch)", orig, oldBase, i) return nil, fmt.Errorf("image %q is not based on %q (layer %d mismatch)", orig, oldBase, i)
@ -52,17 +52,17 @@ func Rebase(orig, oldBase, newBase v1.Image) (v1.Image, error) {
oldConfig, err := oldBase.ConfigFile() oldConfig, err := oldBase.ConfigFile()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get config for old base: %v", err) return nil, fmt.Errorf("failed to get config for old base: %w", err)
} }
origConfig, err := orig.ConfigFile() origConfig, err := orig.ConfigFile()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get config for original: %v", err) return nil, fmt.Errorf("failed to get config for original: %w", err)
} }
newConfig, err := newBase.ConfigFile() newConfig, err := newBase.ConfigFile()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get config for new base: %v", err) return nil, fmt.Errorf("could not get config for new base: %w", err)
} }
// Stitch together an image that contains: // Stitch together an image that contains:
@ -72,13 +72,13 @@ func Rebase(orig, oldBase, newBase v1.Image) (v1.Image, error) {
// - new base image's history + top of original image's history // - new base image's history + top of original image's history
rebasedImage, err := Config(empty.Image, *origConfig.Config.DeepCopy()) rebasedImage, err := Config(empty.Image, *origConfig.Config.DeepCopy())
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create empty image with original config: %v", err) return nil, fmt.Errorf("failed to create empty image with original config: %w", err)
} }
// Add new config properties from existing images. // Add new config properties from existing images.
rebasedConfig, err := rebasedImage.ConfigFile() rebasedConfig, err := rebasedImage.ConfigFile()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get config for rebased image: %v", err) return nil, fmt.Errorf("could not get config for rebased image: %w", err)
} }
// OS/Arch properties from new base // OS/Arch properties from new base
rebasedConfig.Architecture = newConfig.Architecture rebasedConfig.Architecture = newConfig.Architecture
@ -88,24 +88,24 @@ func Rebase(orig, oldBase, newBase v1.Image) (v1.Image, error) {
// Apply config properties to rebased. // Apply config properties to rebased.
rebasedImage, err = ConfigFile(rebasedImage, rebasedConfig) rebasedImage, err = ConfigFile(rebasedImage, rebasedConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to replace config for rebased image: %v", err) return nil, fmt.Errorf("failed to replace config for rebased image: %w", err)
} }
// Get new base layers and config for history. // Get new base layers and config for history.
newBaseLayers, err := newBase.Layers() newBaseLayers, err := newBase.Layers()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get new base layers for new base: %v", err) return nil, fmt.Errorf("could not get new base layers for new base: %w", err)
} }
// Add new base layers. // Add new base layers.
rebasedImage, err = Append(rebasedImage, createAddendums(0, 0, newConfig.History, newBaseLayers)...) rebasedImage, err = Append(rebasedImage, createAddendums(0, 0, newConfig.History, newBaseLayers)...)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to append new base image: %v", err) return nil, fmt.Errorf("failed to append new base image: %w", err)
} }
// Add original layers above the old base. // Add original layers above the old base.
rebasedImage, err = Append(rebasedImage, createAddendums(len(oldConfig.History), len(oldBaseLayers)+1, origConfig.History, origLayers)...) rebasedImage, err = Append(rebasedImage, createAddendums(len(oldConfig.History), len(oldBaseLayers)+1, origConfig.History, origLayers)...)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to append original image: %v", err) return nil, fmt.Errorf("failed to append original image: %w", err)
} }
return rebasedImage, nil return rebasedImage, nil

View File

@ -29,7 +29,7 @@ In a tarball, blobs are (often) uncompressed, so it's easiest to implement a `v1
of uncompressed layers. `tarball.uncompressedImage` does this by implementing `UncompressedImageCore`: of uncompressed layers. `tarball.uncompressedImage` does this by implementing `UncompressedImageCore`:
```go ```go
type CompressedImageCore interface { type UncompressedImageCore interface {
RawConfigFile() ([]byte, error) RawConfigFile() ([]byte, error)
MediaType() (types.MediaType, error) MediaType() (types.MediaType, error)
LayerByDiffID(v1.Hash) (UncompressedLayer, error) LayerByDiffID(v1.Hash) (UncompressedLayer, error)

View File

@ -17,6 +17,7 @@ package partial
import ( import (
"io" "io"
"github.com/google/go-containerregistry/internal/and"
"github.com/google/go-containerregistry/internal/gzip" "github.com/google/go-containerregistry/internal/gzip"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types" "github.com/google/go-containerregistry/pkg/v1/types"
@ -45,11 +46,28 @@ type compressedLayerExtender struct {
// Uncompressed implements v1.Layer // Uncompressed implements v1.Layer
func (cle *compressedLayerExtender) Uncompressed() (io.ReadCloser, error) { func (cle *compressedLayerExtender) Uncompressed() (io.ReadCloser, error) {
r, err := cle.Compressed() rc, err := cle.Compressed()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return gzip.UnzipReadCloser(r)
// Often, the "compressed" bytes are not actually gzip-compressed.
// Peek at the first two bytes to determine whether or not it's correct to
// wrap this with gzip.UnzipReadCloser.
gzipped, pr, err := gzip.Peek(rc)
if err != nil {
return nil, err
}
prc := &and.ReadCloser{
Reader: pr,
CloseFunc: rc.Close,
}
if !gzipped {
return prc, nil
}
return gzip.UnzipReadCloser(prc)
} }
// DiffID implements v1.Layer // DiffID implements v1.Layer

View File

@ -26,7 +26,7 @@ func FindManifests(index v1.ImageIndex, matcher match.Matcher) ([]v1.Descriptor,
// get the actual manifest list // get the actual manifest list
indexManifest, err := index.IndexManifest() indexManifest, err := index.IndexManifest()
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get raw index: %v", err) return nil, fmt.Errorf("unable to get raw index: %w", err)
} }
manifests := []v1.Descriptor{} manifests := []v1.Descriptor{}
// try to get the root of our image // try to get the root of our image

View File

@ -15,7 +15,9 @@
package v1 package v1
import ( import (
"fmt"
"sort" "sort"
"strings"
) )
// Platform represents the target os/arch for an image. // Platform represents the target os/arch for an image.
@ -28,11 +30,59 @@ type Platform struct {
Features []string `json:"features,omitempty"` Features []string `json:"features,omitempty"`
} }
func (p Platform) String() string {
if p.OS == "" {
return ""
}
var b strings.Builder
b.WriteString(p.OS)
if p.Architecture != "" {
b.WriteString("/")
b.WriteString(p.Architecture)
}
if p.Variant != "" {
b.WriteString("/")
b.WriteString(p.Variant)
}
if p.OSVersion != "" {
b.WriteString(":")
b.WriteString(p.OSVersion)
}
return b.String()
}
// ParsePlatform parses a string representing a Platform, if possible.
func ParsePlatform(s string) (*Platform, error) {
var p Platform
parts := strings.Split(strings.TrimSpace(s), ":")
if len(parts) == 2 {
p.OSVersion = parts[1]
}
parts = strings.Split(parts[0], "/")
if len(parts) > 0 {
p.OS = parts[0]
}
if len(parts) > 1 {
p.Architecture = parts[1]
}
if len(parts) > 2 {
p.Variant = parts[2]
}
if len(parts) > 3 {
return nil, fmt.Errorf("too many slashes in platform spec: %s", s)
}
return &p, nil
}
// Equals returns true if the given platform is semantically equivalent to this one. // Equals returns true if the given platform is semantically equivalent to this one.
// The order of Features and OSFeatures is not important. // The order of Features and OSFeatures is not important.
func (p Platform) Equals(o Platform) bool { func (p Platform) Equals(o Platform) bool {
return p.OS == o.OS && p.Architecture == o.Architecture && p.Variant == o.Variant && p.OSVersion == o.OSVersion && return p.OS == o.OS &&
stringSliceEqualIgnoreOrder(p.OSFeatures, o.OSFeatures) && stringSliceEqualIgnoreOrder(p.Features, o.Features) p.Architecture == o.Architecture &&
p.Variant == o.Variant &&
p.OSVersion == o.OSVersion &&
stringSliceEqualIgnoreOrder(p.OSFeatures, o.OSFeatures) &&
stringSliceEqualIgnoreOrder(p.Features, o.Features)
} }
// stringSliceEqual compares 2 string slices and returns if their contents are identical. // stringSliceEqual compares 2 string slices and returns if their contents are identical.
@ -50,10 +100,9 @@ func stringSliceEqual(a, b []string) bool {
// stringSliceEqualIgnoreOrder compares 2 string slices and returns if their contents are identical, ignoring order // stringSliceEqualIgnoreOrder compares 2 string slices and returns if their contents are identical, ignoring order
func stringSliceEqualIgnoreOrder(a, b []string) bool { func stringSliceEqualIgnoreOrder(a, b []string) bool {
a1, b1 := a[:], b[:] if a != nil && b != nil {
if a1 != nil && b1 != nil { sort.Strings(a)
sort.Strings(a1) sort.Strings(b)
sort.Strings(b1)
} }
return stringSliceEqual(a1, b1) return stringSliceEqual(a, b)
} }

View File

@ -91,9 +91,10 @@ func Catalog(ctx context.Context, target name.Registry, options ...Option) ([]st
Scheme: target.Scheme(), Scheme: target.Scheme(),
Host: target.RegistryStr(), Host: target.RegistryStr(),
Path: "/v2/_catalog", Path: "/v2/_catalog",
// ECR returns an error if n > 1000: }
// https://github.com/google/go-containerregistry/issues/1091
RawQuery: "n=1000", if o.pageSize > 0 {
uri.RawQuery = fmt.Sprintf("n=%d", o.pageSize)
} }
client := http.Client{Transport: tr} client := http.Client{Transport: tr}

View File

@ -20,13 +20,13 @@ import (
func CheckPushPermission(ref name.Reference, kc authn.Keychain, t http.RoundTripper) error { func CheckPushPermission(ref name.Reference, kc authn.Keychain, t http.RoundTripper) error {
auth, err := kc.Resolve(ref.Context().Registry) auth, err := kc.Resolve(ref.Context().Registry)
if err != nil { if err != nil {
return fmt.Errorf("resolving authorization for %v failed: %v", ref.Context().Registry, err) return fmt.Errorf("resolving authorization for %v failed: %w", ref.Context().Registry, err)
} }
scopes := []string{ref.Scope(transport.PushScope)} scopes := []string{ref.Scope(transport.PushScope)}
tr, err := transport.New(ref.Context().Registry, auth, t, scopes) tr, err := transport.New(ref.Context().Registry, auth, t, scopes)
if err != nil { if err != nil {
return fmt.Errorf("creating push check transport for %v failed: %v", ref.Context().Registry, err) return fmt.Errorf("creating push check transport for %v failed: %w", ref.Context().Registry, err)
} }
// TODO(jasonhall): Against GCR, just doing the token handshake is // TODO(jasonhall): Against GCR, just doing the token handshake is
// enough, but this doesn't extend to Dockerhub // enough, but this doesn't extend to Dockerhub

View File

@ -147,6 +147,40 @@ func (r *remoteIndex) Layer(h v1.Hash) (v1.Layer, error) {
return nil, fmt.Errorf("layer not found: %s", h) return nil, fmt.Errorf("layer not found: %s", h)
} }
// Experiment with a better API for v1.ImageIndex. We might want to move this
// to partial?
func (r *remoteIndex) Manifests() ([]partial.Describable, error) {
m, err := r.IndexManifest()
if err != nil {
return nil, err
}
manifests := []partial.Describable{}
for _, desc := range m.Manifests {
switch {
case desc.MediaType.IsImage():
img, err := r.Image(desc.Digest)
if err != nil {
return nil, err
}
manifests = append(manifests, img)
case desc.MediaType.IsIndex():
idx, err := r.ImageIndex(desc.Digest)
if err != nil {
return nil, err
}
manifests = append(manifests, idx)
default:
layer, err := r.Layer(desc.Digest)
if err != nil {
return nil, err
}
manifests = append(manifests, layer)
}
}
return manifests, nil
}
func (r *remoteIndex) imageByPlatform(platform v1.Platform) (v1.Image, error) { func (r *remoteIndex) imageByPlatform(platform v1.Platform) (v1.Image, error) {
desc, err := r.childByPlatform(platform) desc, err := r.childByPlatform(platform)
if err != nil { if err != nil {
@ -180,7 +214,7 @@ func (r *remoteIndex) childByPlatform(platform v1.Platform) (*Descriptor, error)
return r.childDescriptor(childDesc, platform) return r.childDescriptor(childDesc, platform)
} }
} }
return nil, fmt.Errorf("no child with platform %s/%s in index %s", platform.OS, platform.Architecture, r.Ref) return nil, fmt.Errorf("no child with platform %+v in index %s", platform, r.Ref)
} }
func (r *remoteIndex) childByHash(h v1.Hash) (*Descriptor, error) { func (r *remoteIndex) childByHash(h v1.Hash) (*Descriptor, error) {

View File

@ -55,9 +55,10 @@ func List(repo name.Repository, options ...Option) ([]string, error) {
Scheme: repo.Registry.Scheme(), Scheme: repo.Registry.Scheme(),
Host: repo.Registry.RegistryStr(), Host: repo.Registry.RegistryStr(),
Path: fmt.Sprintf("/v2/%s/tags/list", repo.RepositoryStr()), Path: fmt.Sprintf("/v2/%s/tags/list", repo.RepositoryStr()),
// ECR returns an error if n > 1000: }
// https://github.com/google/go-containerregistry/issues/681
RawQuery: "n=1000", if o.pageSize > 0 {
uri.RawQuery = fmt.Sprintf("n=%d", o.pageSize)
} }
client := http.Client{Transport: tr} client := http.Client{Transport: tr}

View File

@ -15,6 +15,7 @@
package remote package remote
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
@ -91,12 +92,14 @@ func MultiWrite(m map[name.Reference]Taggable, options ...Option) (rerr error) {
context: o.context, context: o.context,
updates: o.updates, updates: o.updates,
lastUpdate: &v1.Update{}, lastUpdate: &v1.Update{},
backoff: o.retryBackoff,
predicate: o.retryPredicate,
} }
// Collect the total size of blobs and manifests we're about to write. // Collect the total size of blobs and manifests we're about to write.
if o.updates != nil { if o.updates != nil {
defer close(o.updates) defer close(o.updates)
defer func() { sendError(o.updates, rerr) }() defer func() { _ = sendError(o.updates, rerr) }()
for _, b := range blobs { for _, b := range blobs {
size, err := b.Size() size, err := b.Size()
if err != nil { if err != nil {
@ -133,12 +136,13 @@ func MultiWrite(m map[name.Reference]Taggable, options ...Option) (rerr error) {
// Upload individual blobs and collect any errors. // Upload individual blobs and collect any errors.
blobChan := make(chan v1.Layer, 2*o.jobs) blobChan := make(chan v1.Layer, 2*o.jobs)
g, ctx := errgroup.WithContext(o.context) ctx := o.context
g, gctx := errgroup.WithContext(o.context)
for i := 0; i < o.jobs; i++ { for i := 0; i < o.jobs; i++ {
// Start N workers consuming blobs to upload. // Start N workers consuming blobs to upload.
g.Go(func() error { g.Go(func() error {
for b := range blobChan { for b := range blobChan {
if err := w.uploadOne(b); err != nil { if err := w.uploadOne(gctx, b); err != nil {
return err return err
} }
} }
@ -150,8 +154,8 @@ func MultiWrite(m map[name.Reference]Taggable, options ...Option) (rerr error) {
for _, b := range blobs { for _, b := range blobs {
select { select {
case blobChan <- b: case blobChan <- b:
case <-ctx.Done(): case <-gctx.Done():
return ctx.Err() return gctx.Err()
} }
} }
return nil return nil
@ -160,7 +164,8 @@ func MultiWrite(m map[name.Reference]Taggable, options ...Option) (rerr error) {
return err return err
} }
commitMany := func(m map[name.Reference]Taggable) error { commitMany := func(ctx context.Context, m map[name.Reference]Taggable) error {
g, ctx := errgroup.WithContext(ctx)
// With all of the constituent elements uploaded, upload the manifests // With all of the constituent elements uploaded, upload the manifests
// to commit the images and indexes, and collect any errors. // to commit the images and indexes, and collect any errors.
type task struct { type task struct {
@ -172,7 +177,7 @@ func MultiWrite(m map[name.Reference]Taggable, options ...Option) (rerr error) {
// Start N workers consuming tasks to upload manifests. // Start N workers consuming tasks to upload manifests.
g.Go(func() error { g.Go(func() error {
for t := range taskChan { for t := range taskChan {
if err := w.commitManifest(t.i, t.ref); err != nil { if err := w.commitManifest(ctx, t.i, t.ref); err != nil {
return err return err
} }
} }
@ -189,19 +194,19 @@ func MultiWrite(m map[name.Reference]Taggable, options ...Option) (rerr error) {
} }
// Push originally requested image manifests. These have no // Push originally requested image manifests. These have no
// dependencies. // dependencies.
if err := commitMany(images); err != nil { if err := commitMany(ctx, images); err != nil {
return err return err
} }
// Push new manifests from lowest levels up. // Push new manifests from lowest levels up.
for i := len(newManifests) - 1; i >= 0; i-- { for i := len(newManifests) - 1; i >= 0; i-- {
if err := commitMany(newManifests[i]); err != nil { if err := commitMany(ctx, newManifests[i]); err != nil {
return err return err
} }
} }
// Push originally requested index manifests, which might depend on // Push originally requested index manifests, which might depend on
// newly discovered manifests. // newly discovered manifests.
return commitMany(indexes) return commitMany(ctx, indexes)
} }
// addIndexBlobs adds blobs to the set of blobs we intend to upload, and // addIndexBlobs adds blobs to the set of blobs we intend to upload, and

View File

@ -17,8 +17,13 @@ package remote
import ( import (
"context" "context"
"errors" "errors"
"io"
"net"
"net/http" "net/http"
"syscall"
"time"
"github.com/google/go-containerregistry/internal/retry"
"github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/logs" "github.com/google/go-containerregistry/pkg/logs"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
@ -38,6 +43,9 @@ type options struct {
userAgent string userAgent string
allowNondistributableArtifacts bool allowNondistributableArtifacts bool
updates chan<- v1.Update updates chan<- v1.Update
pageSize int
retryBackoff Backoff
retryPredicate retry.Predicate
} }
var defaultPlatform = v1.Platform{ var defaultPlatform = v1.Platform{
@ -45,15 +53,63 @@ var defaultPlatform = v1.Platform{
OS: "linux", OS: "linux",
} }
const defaultJobs = 4 // Backoff is an alias of retry.Backoff to expose this configuration option to consumers of this lib
type Backoff = retry.Backoff
var defaultRetryPredicate retry.Predicate = func(err error) bool {
// Various failure modes here, as we're often reading from and writing to
// the network.
if retry.IsTemporary(err) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) || errors.Is(err, syscall.EPIPE) {
logs.Warn.Printf("retrying %v", err)
return true
}
return false
}
// Try this three times, waiting 1s after first failure, 3s after second.
var defaultRetryBackoff = Backoff{
Duration: 1.0 * time.Second,
Factor: 3.0,
Jitter: 0.1,
Steps: 3,
}
const (
defaultJobs = 4
// ECR returns an error if n > 1000:
// https://github.com/google/go-containerregistry/issues/1091
defaultPageSize = 1000
)
// DefaultTransport is based on http.DefaultTransport with modifications
// documented inline below.
var DefaultTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
// By default we wrap the transport in retries, so reduce the
// default dial timeout to 5s to avoid 5x 30s of connection
// timeouts when doing the "ping" on certain http registries.
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
func makeOptions(target authn.Resource, opts ...Option) (*options, error) { func makeOptions(target authn.Resource, opts ...Option) (*options, error) {
o := &options{ o := &options{
auth: authn.Anonymous, auth: authn.Anonymous,
transport: http.DefaultTransport, transport: DefaultTransport,
platform: defaultPlatform, platform: defaultPlatform,
context: context.Background(), context: context.Background(),
jobs: defaultJobs, jobs: defaultJobs,
pageSize: defaultPageSize,
retryPredicate: defaultRetryPredicate,
retryBackoff: defaultRetryBackoff,
} }
for _, option := range opts { for _, option := range opts {
@ -70,6 +126,9 @@ func makeOptions(target authn.Resource, opts ...Option) (*options, error) {
o.auth = auth o.auth = auth
} }
// transport.Wrapper is a signal that consumers are opt-ing into providing their own transport without any additional wrapping.
// This is to allow consumers full control over the transports logic, such as providing retry logic.
if _, ok := o.transport.(*transport.Wrapper); !ok {
// Wrap the transport in something that logs requests and responses. // Wrap the transport in something that logs requests and responses.
// It's expensive to generate the dumps, so skip it if we're writing // It's expensive to generate the dumps, so skip it if we're writing
// to nothing. // to nothing.
@ -84,14 +143,17 @@ func makeOptions(target authn.Resource, opts ...Option) (*options, error) {
if o.userAgent != "" { if o.userAgent != "" {
o.transport = transport.NewUserAgent(o.transport, o.userAgent) o.transport = transport.NewUserAgent(o.transport, o.userAgent)
} }
}
return o, nil return o, nil
} }
// WithTransport is a functional option for overriding the default transport // WithTransport is a functional option for overriding the default transport
// for remote operations. // for remote operations.
// If transport.Wrapper is provided, this signals that the consumer does *not* want any further wrapping to occur.
// i.e. logging, retry and useragent
// //
// The default transport its http.DefaultTransport. // The default transport is DefaultTransport.
func WithTransport(t http.RoundTripper) Option { func WithTransport(t http.RoundTripper) Option {
return func(o *options) error { return func(o *options) error {
o.transport = t o.transport = t
@ -193,3 +255,30 @@ func WithProgress(updates chan<- v1.Update) Option {
return nil return nil
} }
} }
// WithPageSize sets the given size as the value of parameter 'n' in the request.
//
// To omit the `n` parameter entirely, use WithPageSize(0).
// The default value is 1000.
func WithPageSize(size int) Option {
return func(o *options) error {
o.pageSize = size
return nil
}
}
// WithRetryBackoff sets the httpBackoff for retry HTTP operations.
func WithRetryBackoff(backoff Backoff) Option {
return func(o *options) error {
o.retryBackoff = backoff
return nil
}
}
// WithRetryPredicate sets the predicate for retry HTTP operations.
func WithRetryPredicate(predicate retry.Predicate) Option {
return func(o *options) error {
o.retryPredicate = predicate
return nil
}
}

View File

@ -17,6 +17,7 @@ package transport
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
@ -139,7 +140,8 @@ func (bt *bearerTransport) refresh(ctx context.Context) error {
// the Username should be set to <token>, which indicates // the Username should be set to <token>, which indicates
// we are using an oauth flow. // we are using an oauth flow.
content, err = bt.refreshOauth(ctx) content, err = bt.refreshOauth(ctx)
if terr, ok := err.(*Error); ok && terr.StatusCode == http.StatusNotFound { var terr *Error
if errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound {
// Note: Not all token servers implement oauth2. // Note: Not all token servers implement oauth2.
// If the request to the endpoint returns 404 using the HTTP POST method, // If the request to the endpoint returns 404 using the HTTP POST method,
// refer to Token Documentation for using the HTTP GET method supported by all token servers. // refer to Token Documentation for using the HTTP GET method supported by all token servers.

View File

@ -46,10 +46,10 @@ type Error struct {
Errors []Diagnostic `json:"errors,omitempty"` Errors []Diagnostic `json:"errors,omitempty"`
// The http status code returned. // The http status code returned.
StatusCode int StatusCode int
// The request that failed.
Request *http.Request
// The raw body if we couldn't understand it. // The raw body if we couldn't understand it.
rawBody string rawBody string
// The request that failed.
request *http.Request
} }
// Check that Error implements error // Check that Error implements error
@ -58,8 +58,8 @@ var _ error = (*Error)(nil)
// Error implements error // Error implements error
func (e *Error) Error() string { func (e *Error) Error() string {
prefix := "" prefix := ""
if e.request != nil { if e.Request != nil {
prefix = fmt.Sprintf("%s %s: ", e.request.Method, redactURL(e.request.URL)) prefix = fmt.Sprintf("%s %s: ", e.Request.Method, redactURL(e.Request.URL))
} }
return prefix + e.responseErr() return prefix + e.responseErr()
} }
@ -68,7 +68,7 @@ func (e *Error) responseErr() string {
switch len(e.Errors) { switch len(e.Errors) {
case 0: case 0:
if len(e.rawBody) == 0 { if len(e.rawBody) == 0 {
if e.request != nil && e.request.Method == http.MethodHead { if e.Request != nil && e.Request.Method == http.MethodHead {
return fmt.Sprintf("unexpected status code %d %s (HEAD responses have no body, use GET for details)", e.StatusCode, http.StatusText(e.StatusCode)) return fmt.Sprintf("unexpected status code %d %s (HEAD responses have no body, use GET for details)", e.StatusCode, http.StatusText(e.StatusCode))
} }
return fmt.Sprintf("unexpected status code %d %s", e.StatusCode, http.StatusText(e.StatusCode)) return fmt.Sprintf("unexpected status code %d %s", e.StatusCode, http.StatusText(e.StatusCode))
@ -154,12 +154,19 @@ const (
DeniedErrorCode ErrorCode = "DENIED" DeniedErrorCode ErrorCode = "DENIED"
UnsupportedErrorCode ErrorCode = "UNSUPPORTED" UnsupportedErrorCode ErrorCode = "UNSUPPORTED"
TooManyRequestsErrorCode ErrorCode = "TOOMANYREQUESTS" TooManyRequestsErrorCode ErrorCode = "TOOMANYREQUESTS"
UnknownErrorCode ErrorCode = "UNKNOWN"
// This isn't defined by either docker or OCI spec, but is defined by docker/distribution:
// https://github.com/distribution/distribution/blob/6a977a5a754baa213041443f841705888107362a/registry/api/errcode/register.go#L60
UnavailableErrorCode ErrorCode = "UNAVAILABLE"
) )
// TODO: Include other error types. // TODO: Include other error types.
var temporaryErrorCodes = map[ErrorCode]struct{}{ var temporaryErrorCodes = map[ErrorCode]struct{}{
BlobUploadInvalidErrorCode: {}, BlobUploadInvalidErrorCode: {},
TooManyRequestsErrorCode: {}, TooManyRequestsErrorCode: {},
UnknownErrorCode: {},
UnavailableErrorCode: {},
} }
var temporaryStatusCodes = map[int]struct{}{ var temporaryStatusCodes = map[int]struct{}{
@ -167,6 +174,7 @@ var temporaryStatusCodes = map[int]struct{}{
http.StatusInternalServerError: {}, http.StatusInternalServerError: {},
http.StatusBadGateway: {}, http.StatusBadGateway: {},
http.StatusServiceUnavailable: {}, http.StatusServiceUnavailable: {},
http.StatusGatewayTimeout: {},
} }
// CheckError returns a structured error if the response status is not in codes. // CheckError returns a structured error if the response status is not in codes.
@ -191,7 +199,7 @@ func CheckError(resp *http.Response, codes ...int) error {
structuredError.rawBody = string(b) structuredError.rawBody = string(b)
structuredError.StatusCode = resp.StatusCode structuredError.StatusCode = resp.StatusCode
structuredError.request = resp.Request structuredError.Request = resp.Request
return structuredError return structuredError
} }

View File

@ -79,7 +79,7 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes
schemes = append(schemes, "http") schemes = append(schemes, "http")
} }
var errs []string var errs []error
for _, scheme := range schemes { for _, scheme := range schemes {
url := fmt.Sprintf("%s://%s/v2/", scheme, reg.Name()) url := fmt.Sprintf("%s://%s/v2/", scheme, reg.Name())
req, err := http.NewRequest(http.MethodGet, url, nil) req, err := http.NewRequest(http.MethodGet, url, nil)
@ -88,7 +88,7 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes
} }
resp, err := client.Do(req.WithContext(ctx)) resp, err := client.Do(req.WithContext(ctx))
if err != nil { if err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
// Potentially retry with http. // Potentially retry with http.
continue continue
} }
@ -125,11 +125,10 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes
return nil, CheckError(resp, http.StatusOK, http.StatusUnauthorized) return nil, CheckError(resp, http.StatusOK, http.StatusUnauthorized)
} }
} }
return nil, errors.New(strings.Join(errs, "; ")) return nil, multierrs(errs)
} }
func pickFromMultipleChallenges(challenges []authchallenge.Challenge) authchallenge.Challenge { func pickFromMultipleChallenges(challenges []authchallenge.Challenge) authchallenge.Challenge {
// It might happen there are multiple www-authenticate headers, e.g. `Negotiate` and `Basic`. // It might happen there are multiple www-authenticate headers, e.g. `Negotiate` and `Basic`.
// Picking simply the first one could result eventually in `unrecognized challenge` error, // Picking simply the first one could result eventually in `unrecognized challenge` error,
// that's why we're looping through the challenges in search for one that can be handled. // that's why we're looping through the challenges in search for one that can be handled.
@ -146,3 +145,36 @@ func pickFromMultipleChallenges(challenges []authchallenge.Challenge) authchalle
return challenges[0] return challenges[0]
} }
type multierrs []error
func (m multierrs) Error() string {
var b strings.Builder
hasWritten := false
for _, err := range m {
if hasWritten {
b.WriteString("; ")
}
hasWritten = true
b.WriteString(err.Error())
}
return b.String()
}
func (m multierrs) As(target interface{}) bool {
for _, err := range m {
if errors.As(err, target) {
return true
}
}
return false
}
func (m multierrs) Is(target error) bool {
for _, err := range m {
if errors.Is(err, target) {
return true
}
}
return false
}

View File

@ -46,8 +46,11 @@ type options struct {
predicate retry.Predicate predicate retry.Predicate
} }
// Backoff is an alias of retry.Backoff to expose this configuration option to consumers of this lib
type Backoff = retry.Backoff
// WithRetryBackoff sets the backoff for retry operations. // WithRetryBackoff sets the backoff for retry operations.
func WithRetryBackoff(backoff retry.Backoff) Option { func WithRetryBackoff(backoff Backoff) Option {
return func(o *options) { return func(o *options) {
o.backoff = backoff o.backoff = backoff
} }

View File

@ -68,10 +68,8 @@ func NewWithContext(ctx context.Context, reg name.Registry, auth authn.Authentic
} }
switch pr.challenge.Canonical() { switch pr.challenge.Canonical() {
case anonymous: case anonymous, basic:
return t, nil return &Wrapper{&basicTransport{inner: t, auth: auth, target: reg.RegistryStr()}}, nil
case basic:
return &basicTransport{inner: t, auth: auth, target: reg.RegistryStr()}, nil
case bearer: case bearer:
// We require the realm, which tells us where to send our Basic auth to turn it into Bearer auth. // We require the realm, which tells us where to send our Basic auth to turn it into Bearer auth.
realm, ok := pr.parameters["realm"] realm, ok := pr.parameters["realm"]
@ -96,8 +94,19 @@ func NewWithContext(ctx context.Context, reg name.Registry, auth authn.Authentic
if err := bt.refresh(ctx); err != nil { if err := bt.refresh(ctx); err != nil {
return nil, err return nil, err
} }
return bt, nil return &Wrapper{bt}, nil
default: default:
return nil, fmt.Errorf("unrecognized challenge: %s", pr.challenge) return nil, fmt.Errorf("unrecognized challenge: %s", pr.challenge)
} }
} }
// Wrapper results in *not* wrapping supplied transport with additional logic such as retries, useragent and debug logging
// Consumers are opt-ing into providing their own transport without any additional wrapping.
type Wrapper struct {
inner http.RoundTripper
}
// RoundTrip delegates to the inner RoundTripper
func (w *Wrapper) RoundTrip(in *http.Request) (*http.Response, error) {
return w.inner.RoundTrip(in)
}

View File

@ -24,8 +24,6 @@ import (
"net/url" "net/url"
"strings" "strings"
"sync/atomic" "sync/atomic"
"syscall"
"time"
"github.com/google/go-containerregistry/internal/redact" "github.com/google/go-containerregistry/internal/redact"
"github.com/google/go-containerregistry/internal/retry" "github.com/google/go-containerregistry/internal/retry"
@ -59,12 +57,12 @@ func Write(ref name.Reference, img v1.Image, options ...Option) (rerr error) {
return err return err
} }
defer close(o.updates) defer close(o.updates)
defer func() { sendError(o.updates, rerr) }() defer func() { _ = sendError(o.updates, rerr) }()
} }
return writeImage(ref, img, o, lastUpdate) return writeImage(o.context, ref, img, o, lastUpdate)
} }
func writeImage(ref name.Reference, img v1.Image, o *options, lastUpdate *v1.Update) error { func writeImage(ctx context.Context, ref name.Reference, img v1.Image, o *options, lastUpdate *v1.Update) error {
ls, err := img.Layers() ls, err := img.Layers()
if err != nil { if err != nil {
return err return err
@ -77,19 +75,21 @@ func writeImage(ref name.Reference, img v1.Image, o *options, lastUpdate *v1.Upd
w := writer{ w := writer{
repo: ref.Context(), repo: ref.Context(),
client: &http.Client{Transport: tr}, client: &http.Client{Transport: tr},
context: o.context, context: ctx,
updates: o.updates, updates: o.updates,
lastUpdate: lastUpdate, lastUpdate: lastUpdate,
backoff: o.retryBackoff,
predicate: o.retryPredicate,
} }
// Upload individual blobs and collect any errors. // Upload individual blobs and collect any errors.
blobChan := make(chan v1.Layer, 2*o.jobs) blobChan := make(chan v1.Layer, 2*o.jobs)
g, ctx := errgroup.WithContext(o.context) g, gctx := errgroup.WithContext(ctx)
for i := 0; i < o.jobs; i++ { for i := 0; i < o.jobs; i++ {
// Start N workers consuming blobs to upload. // Start N workers consuming blobs to upload.
g.Go(func() error { g.Go(func() error {
for b := range blobChan { for b := range blobChan {
if err := w.uploadOne(b); err != nil { if err := w.uploadOne(gctx, b); err != nil {
return err return err
} }
} }
@ -128,15 +128,12 @@ func writeImage(ref name.Reference, img v1.Image, o *options, lastUpdate *v1.Upd
} }
select { select {
case blobChan <- l: case blobChan <- l:
case <-ctx.Done(): case <-gctx.Done():
return ctx.Err() return gctx.Err()
} }
} }
return nil return nil
}) })
if err := g.Wait(); err != nil {
return err
}
if l, err := partial.ConfigLayer(img); err != nil { if l, err := partial.ConfigLayer(img); err != nil {
// We can't read the ConfigLayer, possibly because of streaming layers, // We can't read the ConfigLayer, possibly because of streaming layers,
@ -151,13 +148,13 @@ func writeImage(ref name.Reference, img v1.Image, o *options, lastUpdate *v1.Upd
if err != nil { if err != nil {
return err return err
} }
if err := w.uploadOne(l); err != nil { if err := w.uploadOne(ctx, l); err != nil {
return err return err
} }
} else { } else {
// We *can* read the ConfigLayer, so upload it concurrently with the layers. // We *can* read the ConfigLayer, so upload it concurrently with the layers.
g.Go(func() error { g.Go(func() error {
return w.uploadOne(l) return w.uploadOne(gctx, l)
}) })
// Wait for the layers + config. // Wait for the layers + config.
@ -168,7 +165,7 @@ func writeImage(ref name.Reference, img v1.Image, o *options, lastUpdate *v1.Upd
// With all of the constituent elements uploaded, upload the manifest // With all of the constituent elements uploaded, upload the manifest
// to commit the image. // to commit the image.
return w.commitManifest(img, ref) return w.commitManifest(ctx, img, ref)
} }
// writer writes the elements of an image to a remote image reference. // writer writes the elements of an image to a remote image reference.
@ -179,6 +176,8 @@ type writer struct {
updates chan<- v1.Update updates chan<- v1.Update
lastUpdate *v1.Update lastUpdate *v1.Update
backoff Backoff
predicate retry.Predicate
} }
func sendError(ch chan<- v1.Update, err error) error { func sendError(ch chan<- v1.Update, err error) error {
@ -405,30 +404,12 @@ func (w *writer) incrProgress(written int64) {
} }
w.updates <- v1.Update{ w.updates <- v1.Update{
Total: w.lastUpdate.Total, Total: w.lastUpdate.Total,
Complete: atomic.AddInt64(&w.lastUpdate.Complete, int64(written)), Complete: atomic.AddInt64(&w.lastUpdate.Complete, written),
} }
} }
var shouldRetry retry.Predicate = func(err error) bool {
// Various failure modes here, as we're often reading from and writing to
// the network.
if retry.IsTemporary(err) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.EPIPE) {
logs.Warn.Printf("retrying %v", err)
return true
}
return false
}
// Try this three times, waiting 1s after first failure, 3s after second.
var backoff = retry.Backoff{
Duration: 1.0 * time.Second,
Factor: 3.0,
Jitter: 0.1,
Steps: 3,
}
// uploadOne performs a complete upload of a single layer. // uploadOne performs a complete upload of a single layer.
func (w *writer) uploadOne(l v1.Layer) error { func (w *writer) uploadOne(ctx context.Context, l v1.Layer) error {
var from, mount string var from, mount string
if h, err := l.Digest(); err == nil { if h, err := l.Digest(); err == nil {
// If we know the digest, this isn't a streaming layer. Do an existence // If we know the digest, this isn't a streaming layer. Do an existence
@ -455,8 +436,6 @@ func (w *writer) uploadOne(l v1.Layer) error {
} }
} }
ctx := w.context
tryUpload := func() error { tryUpload := func() error {
location, mounted, err := w.initiateUpload(from, mount) location, mounted, err := w.initiateUpload(from, mount)
if err != nil { if err != nil {
@ -508,14 +487,14 @@ func (w *writer) uploadOne(l v1.Layer) error {
return nil return nil
} }
return retry.Retry(tryUpload, shouldRetry, backoff) return retry.Retry(tryUpload, w.predicate, w.backoff)
} }
type withLayer interface { type withLayer interface {
Layer(v1.Hash) (v1.Layer, error) Layer(v1.Hash) (v1.Layer, error)
} }
func (w *writer) writeIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error { func (w *writer) writeIndex(ctx context.Context, ref name.Reference, ii v1.ImageIndex, options ...Option) error {
index, err := ii.IndexManifest() index, err := ii.IndexManifest()
if err != nil { if err != nil {
return err return err
@ -544,7 +523,7 @@ func (w *writer) writeIndex(ref name.Reference, ii v1.ImageIndex, options ...Opt
if err != nil { if err != nil {
return err return err
} }
if err := w.writeIndex(ref, ii); err != nil { if err := w.writeIndex(ctx, ref, ii, options...); err != nil {
return err return err
} }
case types.OCIManifestSchema1, types.DockerManifestSchema2: case types.OCIManifestSchema1, types.DockerManifestSchema2:
@ -552,7 +531,7 @@ func (w *writer) writeIndex(ref name.Reference, ii v1.ImageIndex, options ...Opt
if err != nil { if err != nil {
return err return err
} }
if err := writeImage(ref, img, o, w.lastUpdate); err != nil { if err := writeImage(ctx, ref, img, o, w.lastUpdate); err != nil {
return err return err
} }
default: default:
@ -562,7 +541,7 @@ func (w *writer) writeIndex(ref name.Reference, ii v1.ImageIndex, options ...Opt
if err != nil { if err != nil {
return err return err
} }
if err := w.uploadOne(layer); err != nil { if err := w.uploadOne(ctx, layer); err != nil {
return err return err
} }
} }
@ -571,7 +550,7 @@ func (w *writer) writeIndex(ref name.Reference, ii v1.ImageIndex, options ...Opt
// With all of the constituent elements uploaded, upload the manifest // With all of the constituent elements uploaded, upload the manifest
// to commit the image. // to commit the image.
return w.commitManifest(ii, ref) return w.commitManifest(ctx, ii, ref)
} }
type withMediaType interface { type withMediaType interface {
@ -617,7 +596,7 @@ func unpackTaggable(t Taggable) ([]byte, *v1.Descriptor, error) {
} }
// commitManifest does a PUT of the image's manifest. // commitManifest does a PUT of the image's manifest.
func (w *writer) commitManifest(t Taggable, ref name.Reference) error { func (w *writer) commitManifest(ctx context.Context, t Taggable, ref name.Reference) error {
tryUpload := func() error { tryUpload := func() error {
raw, desc, err := unpackTaggable(t) raw, desc, err := unpackTaggable(t)
if err != nil { if err != nil {
@ -633,7 +612,7 @@ func (w *writer) commitManifest(t Taggable, ref name.Reference) error {
} }
req.Header.Set("Content-Type", string(desc.MediaType)) req.Header.Set("Content-Type", string(desc.MediaType))
resp, err := w.client.Do(req.WithContext(w.context)) resp, err := w.client.Do(req.WithContext(ctx))
if err != nil { if err != nil {
return err return err
} }
@ -649,7 +628,7 @@ func (w *writer) commitManifest(t Taggable, ref name.Reference) error {
return nil return nil
} }
return retry.Retry(tryUpload, shouldRetry, backoff) return retry.Retry(tryUpload, w.predicate, w.backoff)
} }
func scopesForUploadingImage(repo name.Repository, layers []v1.Layer) []string { func scopesForUploadingImage(repo name.Repository, layers []v1.Layer) []string {
@ -696,6 +675,8 @@ func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) (rerr e
client: &http.Client{Transport: tr}, client: &http.Client{Transport: tr},
context: o.context, context: o.context,
updates: o.updates, updates: o.updates,
backoff: o.retryBackoff,
predicate: o.retryPredicate,
} }
if o.updates != nil { if o.updates != nil {
@ -708,7 +689,7 @@ func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) (rerr e
defer func() { sendError(o.updates, rerr) }() defer func() { sendError(o.updates, rerr) }()
} }
return w.writeIndex(ref, ii, options...) return w.writeIndex(o.context, ref, ii, options...)
} }
// countImage counts the total size of all layers + config blob + manifest for // countImage counts the total size of all layers + config blob + manifest for
@ -835,6 +816,8 @@ func WriteLayer(repo name.Repository, layer v1.Layer, options ...Option) (rerr e
client: &http.Client{Transport: tr}, client: &http.Client{Transport: tr},
context: o.context, context: o.context,
updates: o.updates, updates: o.updates,
backoff: o.retryBackoff,
predicate: o.retryPredicate,
} }
if o.updates != nil { if o.updates != nil {
@ -851,7 +834,7 @@ func WriteLayer(repo name.Repository, layer v1.Layer, options ...Option) (rerr e
} }
w.lastUpdate = &v1.Update{Total: size} w.lastUpdate = &v1.Update{Total: size}
} }
return w.uploadOne(layer) return w.uploadOne(o.context, layer)
} }
// Tag adds a tag to the given Taggable via PUT /v2/.../manifests/<tag> // Tag adds a tag to the given Taggable via PUT /v2/.../manifests/<tag>
@ -901,7 +884,9 @@ func Put(ref name.Reference, t Taggable, options ...Option) error {
repo: ref.Context(), repo: ref.Context(),
client: &http.Client{Transport: tr}, client: &http.Client{Transport: tr},
context: o.context, context: o.context,
backoff: o.retryBackoff,
predicate: o.retryPredicate,
} }
return w.commitManifest(t, ref) return w.commitManifest(o.context, t, ref)
} }

View File

@ -126,18 +126,36 @@ func (l *Layer) Compressed() (io.ReadCloser, error) {
return newCompressedReader(l) return newCompressedReader(l)
} }
// finalize sets the layer to consumed and computes all hash and size values.
func (l *Layer) finalize(uncompressed, compressed hash.Hash, size int64) error {
l.mu.Lock()
defer l.mu.Unlock()
diffID, err := v1.NewHash("sha256:" + hex.EncodeToString(uncompressed.Sum(nil)))
if err != nil {
return err
}
l.diffID = &diffID
digest, err := v1.NewHash("sha256:" + hex.EncodeToString(compressed.Sum(nil)))
if err != nil {
return err
}
l.digest = &digest
l.size = size
l.consumed = true
return nil
}
type compressedReader struct { type compressedReader struct {
closer io.Closer // original blob's Closer.
h, zh hash.Hash // collects digests of compressed and uncompressed stream.
pr io.Reader pr io.Reader
bw *bufio.Writer closer func() error
count *countWriter
l *Layer // stream.Layer to update upon Close.
} }
func newCompressedReader(l *Layer) (*compressedReader, error) { func newCompressedReader(l *Layer) (*compressedReader, error) {
// Collect digests of compressed and uncompressed stream and size of
// compressed stream.
h := sha256.New() h := sha256.New()
zh := sha256.New() zh := sha256.New()
count := &countWriter{} count := &countWriter{}
@ -158,24 +176,74 @@ func newCompressedReader(l *Layer) (*compressedReader, error) {
return nil, err return nil, err
} }
doneDigesting := make(chan struct{})
cr := &compressedReader{ cr := &compressedReader{
closer: newMultiCloser(zw, l.blob),
pr: pr, pr: pr,
bw: bw, closer: func() error {
h: h, // Immediately close pw without error. There are three ways to get
zh: zh, // here.
count: count, //
l: l, // 1. There was a copy error due from the underlying reader, in which
// case the error will not be overwritten.
// 2. Copying from the underlying reader completed successfully.
// 3. Close has been called before the underlying reader has been
// fully consumed. In this case pw must be closed in order to
// keep the flush of bw from blocking indefinitely.
//
// NOTE: pw.Close never returns an error. The signature is only to
// implement io.Closer.
_ = pw.Close()
// Close the inner ReadCloser.
//
// NOTE: net/http will call close on success, so if we've already
// closed the inner rc, it's not an error.
if err := l.blob.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
return err
}
// Finalize layer with its digest and size values.
<-doneDigesting
return l.finalize(h, zh, count.n)
},
} }
go func() { go func() {
if _, err := io.Copy(io.MultiWriter(h, zw), l.blob); err != nil { // Copy blob into the gzip writer, which also hashes and counts the
// size of the compressed output, and hasher of the raw contents.
_, copyErr := io.Copy(io.MultiWriter(h, zw), l.blob)
// Close the gzip writer once copying is done. If this is done in the
// Close method of compressedReader instead, then it can cause a panic
// when the compressedReader is closed before the blob is fully
// consumed and io.Copy in this goroutine is still blocking.
closeErr := zw.Close()
// Check errors from writing and closing streams.
if copyErr != nil {
close(doneDigesting)
pw.CloseWithError(copyErr)
return
}
if closeErr != nil {
close(doneDigesting)
pw.CloseWithError(closeErr)
return
}
// Flush the buffer once all writes are complete to the gzip writer.
if err := bw.Flush(); err != nil {
close(doneDigesting)
pw.CloseWithError(err) pw.CloseWithError(err)
return return
} }
// Now close the compressed reader, to flush the gzip stream
// and calculate digest/diffID/size. This will cause pr to // Notify closer that digests are done being written.
// return EOF which will cause readers of the Compressed stream close(doneDigesting)
// to finish reading.
// Close the compressed reader to calculate digest/diffID/size. This
// will cause pr to return EOF which will cause readers of the
// Compressed stream to finish reading.
pw.CloseWithError(cr.Close()) pw.CloseWithError(cr.Close())
}() }()
@ -184,36 +252,7 @@ func newCompressedReader(l *Layer) (*compressedReader, error) {
func (cr *compressedReader) Read(b []byte) (int, error) { return cr.pr.Read(b) } func (cr *compressedReader) Read(b []byte) (int, error) { return cr.pr.Read(b) }
func (cr *compressedReader) Close() error { func (cr *compressedReader) Close() error { return cr.closer() }
cr.l.mu.Lock()
defer cr.l.mu.Unlock()
// Close the inner ReadCloser.
if err := cr.closer.Close(); err != nil {
return err
}
// Flush the buffer.
if err := cr.bw.Flush(); err != nil {
return err
}
diffID, err := v1.NewHash("sha256:" + hex.EncodeToString(cr.h.Sum(nil)))
if err != nil {
return err
}
cr.l.diffID = &diffID
digest, err := v1.NewHash("sha256:" + hex.EncodeToString(cr.zh.Sum(nil)))
if err != nil {
return err
}
cr.l.digest = &digest
cr.l.size = cr.count.n
cr.l.consumed = true
return nil
}
// countWriter counts bytes written to it. // countWriter counts bytes written to it.
type countWriter struct{ n int64 } type countWriter struct{ n int64 }
@ -222,21 +261,3 @@ func (c *countWriter) Write(p []byte) (int, error) {
c.n += int64(len(p)) c.n += int64(len(p))
return len(p), nil return len(p), nil
} }
// multiCloser is a Closer that collects multiple Closers and Closes them in order.
type multiCloser []io.Closer
var _ io.Closer = (multiCloser)(nil)
func newMultiCloser(c ...io.Closer) multiCloser { return multiCloser(c) }
func (m multiCloser) Close() error {
for _, c := range m {
// NOTE: net/http will call close on success, so if we've already
// closed the inner rc, it's not an error.
if err := c.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
return err
}
}
return nil
}

View File

@ -227,7 +227,7 @@ func extractFileFromTar(opener Opener, filePath string) (io.ReadCloser, error) {
tf := tar.NewReader(f) tf := tar.NewReader(f)
for { for {
hdr, err := tf.Next() hdr, err := tf.Next()
if err == io.EOF { if errors.Is(err, io.EOF) {
break break
} }
if err != nil { if err != nil {
@ -236,7 +236,7 @@ func extractFileFromTar(opener Opener, filePath string) (io.ReadCloser, error) {
if hdr.Name == filePath { if hdr.Name == filePath {
if hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink { if hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink {
currentDir := filepath.Dir(filePath) currentDir := filepath.Dir(filePath)
return extractFileFromTar(opener, path.Join(currentDir, hdr.Linkname)) return extractFileFromTar(opener, path.Join(currentDir, path.Clean(hdr.Linkname)))
} }
close = false close = false
return tarFile{ return tarFile{

View File

@ -17,6 +17,7 @@ package tarball
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -39,6 +40,7 @@ type layer struct {
compression int compression int
annotations map[string]string annotations map[string]string
estgzopts []estargz.Option estgzopts []estargz.Option
mediaType types.MediaType
} }
// Descriptor implements partial.withDescriptor. // Descriptor implements partial.withDescriptor.
@ -51,7 +53,7 @@ func (l *layer) Descriptor() (*v1.Descriptor, error) {
Size: l.size, Size: l.size,
Digest: digest, Digest: digest,
Annotations: l.annotations, Annotations: l.annotations,
MediaType: types.DockerLayer, MediaType: l.mediaType,
}, nil }, nil
} }
@ -82,7 +84,7 @@ func (l *layer) Size() (int64, error) {
// MediaType implements v1.Layer // MediaType implements v1.Layer
func (l *layer) MediaType() (types.MediaType, error) { func (l *layer) MediaType() (types.MediaType, error) {
return types.DockerLayer, nil return l.mediaType, nil
} }
// LayerOption applies options to layer // LayerOption applies options to layer
@ -96,6 +98,13 @@ func WithCompressionLevel(level int) LayerOption {
} }
} }
// WithMediaType is a functional option for overriding the layer's media type.
func WithMediaType(mt types.MediaType) LayerOption {
return func(l *layer) {
l.mediaType = mt
}
}
// WithCompressedCaching is a functional option that overrides the // WithCompressedCaching is a functional option that overrides the
// logic for accessing the compressed bytes to memoize the result // logic for accessing the compressed bytes to memoize the result
// and avoid expensive repeated gzips. // and avoid expensive repeated gzips.
@ -204,6 +213,7 @@ func LayerFromOpener(opener Opener, opts ...LayerOption) (v1.Layer, error) {
layer := &layer{ layer := &layer{
compression: gzip.BestSpeed, compression: gzip.BestSpeed,
annotations: make(map[string]string, 1), annotations: make(map[string]string, 1),
mediaType: types.DockerLayer,
} }
if estgz := os.Getenv("GGCR_EXPERIMENT_ESTARGZ"); estgz == "1" { if estgz := os.Getenv("GGCR_EXPERIMENT_ESTARGZ"); estgz == "1" {
@ -249,15 +259,19 @@ func LayerFromOpener(opener Opener, opts ...LayerOption) (v1.Layer, error) {
} }
// LayerFromReader returns a v1.Layer given a io.Reader. // LayerFromReader returns a v1.Layer given a io.Reader.
//
// The reader's contents are read and buffered to a temp file in the process.
//
// Deprecated: Use LayerFromOpener or stream.NewLayer instead, if possible.
func LayerFromReader(reader io.Reader, opts ...LayerOption) (v1.Layer, error) { func LayerFromReader(reader io.Reader, opts ...LayerOption) (v1.Layer, error) {
// Buffering due to Opener requiring multiple calls. tmp, err := ioutil.TempFile("", "")
a, err := ioutil.ReadAll(reader)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("creating temp file to buffer reader: %w", err)
} }
return LayerFromOpener(func() (io.ReadCloser, error) { if _, err := io.Copy(tmp, reader); err != nil {
return ioutil.NopCloser(bytes.NewReader(a)), nil return nil, fmt.Errorf("writing temp file to buffer reader: %w", err)
}, opts...) }
return LayerFromFile(tmp.Name(), opts...)
} }
func computeDigest(opener Opener) (v1.Hash, int64, error) { func computeDigest(opener Opener) (v1.Hash, int64, error) {

View File

@ -98,7 +98,7 @@ func MultiRefWrite(refToImage map[name.Reference]v1.Image, w io.Writer, opts ...
} }
} }
size, _, mBytes, err := getSizeAndManifest(refToImage) size, mBytes, err := getSizeAndManifest(refToImage)
if err != nil { if err != nil {
return sendUpdateReturn(o, err) return sendUpdateReturn(o, err)
} }
@ -290,25 +290,25 @@ func calculateManifest(refToImage map[name.Reference]v1.Image) (m Manifest, err
// CalculateSize calculates the expected complete size of the output tar file // CalculateSize calculates the expected complete size of the output tar file
func CalculateSize(refToImage map[name.Reference]v1.Image) (size int64, err error) { func CalculateSize(refToImage map[name.Reference]v1.Image) (size int64, err error) {
size, _, _, err = getSizeAndManifest(refToImage) size, _, err = getSizeAndManifest(refToImage)
return size, err return size, err
} }
func getSizeAndManifest(refToImage map[name.Reference]v1.Image) (size int64, m Manifest, mBytes []byte, err error) { func getSizeAndManifest(refToImage map[name.Reference]v1.Image) (int64, []byte, error) {
m, err = calculateManifest(refToImage) m, err := calculateManifest(refToImage)
if err != nil { if err != nil {
return 0, nil, nil, fmt.Errorf("unable to calculate manifest: %v", err) return 0, nil, fmt.Errorf("unable to calculate manifest: %w", err)
} }
mBytes, err = json.Marshal(m) mBytes, err := json.Marshal(m)
if err != nil { if err != nil {
return 0, nil, nil, fmt.Errorf("could not marshall manifest to bytes: %v", err) return 0, nil, fmt.Errorf("could not marshall manifest to bytes: %w", err)
} }
size, err = calculateTarballSize(refToImage, mBytes) size, err := calculateTarballSize(refToImage, mBytes)
if err != nil { if err != nil {
return 0, nil, nil, fmt.Errorf("error calculating tarball size: %v", err) return 0, nil, fmt.Errorf("error calculating tarball size: %w", err)
} }
return size, m, mBytes, nil return size, mBytes, nil
} }
// calculateTarballSize calculates the size of the tar file // calculateTarballSize calculates the size of the tar file
@ -318,7 +318,7 @@ func calculateTarballSize(refToImage map[name.Reference]v1.Image, mBytes []byte)
for img, name := range imageToTags { for img, name := range imageToTags {
manifest, err := img.Manifest() manifest, err := img.Manifest()
if err != nil { if err != nil {
return size, fmt.Errorf("unable to get manifest for img %s: %v", name, err) return size, fmt.Errorf("unable to get manifest for img %s: %w", name, err)
} }
size += calculateSingleFileInTarSize(manifest.Config.Size) size += calculateSingleFileInTarSize(manifest.Config.Size)
for _, l := range manifest.Layers { for _, l := range manifest.Layers {
@ -354,12 +354,10 @@ func dedupRefToImage(refToImage map[name.Reference]v1.Image) map[v1.Image][]stri
ts = fmt.Sprintf("%s:%s", ts, name.DefaultTag) ts = fmt.Sprintf("%s:%s", ts, name.DefaultTag)
} }
imageToTags[img] = append(imageToTags[img], ts) imageToTags[img] = append(imageToTags[img], ts)
} else { } else if _, ok := imageToTags[img]; !ok {
if _, ok := imageToTags[img]; !ok {
imageToTags[img] = nil imageToTags[img] = nil
} }
} }
}
return imageToTags return imageToTags
} }

View File

@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Copyright 2018 Google LLC All Rights Reserved. // Copyright 2018 Google LLC All Rights Reserved.

View File

@ -8,8 +8,6 @@
A high-performance 100% compatible drop-in replacement of "encoding/json" A high-performance 100% compatible drop-in replacement of "encoding/json"
You can also use thrift like JSON using [thrift-iterator](https://github.com/thrift-iterator/go)
# Benchmark # Benchmark
![benchmark](http://jsoniter.com/benchmarks/go-benchmark.png) ![benchmark](http://jsoniter.com/benchmarks/go-benchmark.png)

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