1
0
mirror of https://github.com/ko-build/ko.git synced 2024-12-12 08:54:09 +02:00

Update go-containerregistry (#140)

Pick up https://github.com/google/go-containerregistry/pull/690
This commit is contained in:
jonjohnsonjr 2020-03-10 09:36:12 -07:00 committed by GitHub
parent acf34edd61
commit ed28755e6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1396 additions and 1691 deletions

8
go.mod
View File

@ -3,19 +3,21 @@ module github.com/google/ko
go 1.12 go 1.12
require ( require (
github.com/docker/cli v0.0.0-20200303162255-7d407207c304 // indirect
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960
github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify v1.4.7
github.com/google/go-cmp v0.3.0 github.com/google/go-cmp v0.3.0
github.com/google/go-containerregistry v0.0.0-20200212224832-c629a66d7231 github.com/google/go-containerregistry v0.0.0-20200310013544-4fe717a9b4cb
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
github.com/mattmoor/dep-notify v0.0.0-20190205035814-a45dec370a17 github.com/mattmoor/dep-notify v0.0.0-20190205035814-a45dec370a17
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.3.2 github.com/spf13/viper v1.3.2
golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/tools v0.0.0-20200115192306-3ded1b734dda golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d
k8s.io/apimachinery v0.17.1 k8s.io/apimachinery v0.17.1
k8s.io/cli-runtime v0.17.0 k8s.io/cli-runtime v0.17.0

15
go.sum
View File

@ -65,6 +65,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017 h1:2HQmlpI3yI9deH18Q6xiSOIjXD4sLI55Y/gfpa8/558= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017 h1:2HQmlpI3yI9deH18Q6xiSOIjXD4sLI55Y/gfpa8/558=
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v0.0.0-20200303162255-7d407207c304 h1:A7SYzidcyuQ/yS4wezWGYeUioUFJQk8HYWY9aMYTF4I=
github.com/docker/cli v0.0.0-20200303162255-7d407207c304/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
@ -138,6 +140,10 @@ github.com/google/go-containerregistry v0.0.0-20200115190719-e8e9aa676278 h1:jzL
github.com/google/go-containerregistry v0.0.0-20200115190719-e8e9aa676278/go.mod h1:Wtl/v6YdQxv397EREtzwgd9+Ud7Q5D8XMbi3Zazgkrs= github.com/google/go-containerregistry v0.0.0-20200115190719-e8e9aa676278/go.mod h1:Wtl/v6YdQxv397EREtzwgd9+Ud7Q5D8XMbi3Zazgkrs=
github.com/google/go-containerregistry v0.0.0-20200212224832-c629a66d7231 h1:zoj6E1dzY9aeZw1CGJv1hffxgyunrLpjI0SZWK7ynzg= github.com/google/go-containerregistry v0.0.0-20200212224832-c629a66d7231 h1:zoj6E1dzY9aeZw1CGJv1hffxgyunrLpjI0SZWK7ynzg=
github.com/google/go-containerregistry v0.0.0-20200212224832-c629a66d7231/go.mod h1:Wtl/v6YdQxv397EREtzwgd9+Ud7Q5D8XMbi3Zazgkrs= github.com/google/go-containerregistry v0.0.0-20200212224832-c629a66d7231/go.mod h1:Wtl/v6YdQxv397EREtzwgd9+Ud7Q5D8XMbi3Zazgkrs=
github.com/google/go-containerregistry v0.0.0-20200304201134-fcc8ea80e26f h1:tc00QCOHwGF/IyStWQnJN7zmrPP9LBJTKyulXzApCfw=
github.com/google/go-containerregistry v0.0.0-20200304201134-fcc8ea80e26f/go.mod h1:m8YvHwSOuBCq25yrj1DaX/fIMrv6ec3CNg8jY8+5PEA=
github.com/google/go-containerregistry v0.0.0-20200310013544-4fe717a9b4cb h1:YZuYn5esRDuCPmdzqWupP4mM/CnOy/ZkSHEP0eXpiLA=
github.com/google/go-containerregistry v0.0.0-20200310013544-4fe717a9b4cb/go.mod h1:m8YvHwSOuBCq25yrj1DaX/fIMrv6ec3CNg8jY8+5PEA=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -250,6 +256,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -336,6 +344,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -368,6 +377,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -419,6 +430,9 @@ golang.org/x/tools v0.0.0-20200115165105-de0b1760071a h1:bEJ3JL2YUH3tt9KX9dsy0WU
golang.org/x/tools v0.0.0-20200115165105-de0b1760071a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200115165105-de0b1760071a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200115192306-3ded1b734dda h1:auEDF/YdrZGhWDfRdeHl7UpNtm5FtLF0X3NLqo4feXM= golang.org/x/tools v0.0.0-20200115192306-3ded1b734dda h1:auEDF/YdrZGhWDfRdeHl7UpNtm5FtLF0X3NLqo4feXM=
golang.org/x/tools v0.0.0-20200115192306-3ded1b734dda/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200115192306-3ded1b734dda/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17 h1:a/Fd23DJvg1CaeDH0dYHahE+hCI0v9rFgxSNIThoUcM=
golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
@ -490,6 +504,7 @@ k8s.io/client-go v0.17.1/go.mod h1:HZtHJSC/VuSHcETN9QA5QDZky1tXiYrkF/7t7vRpO1A=
k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE=
k8s.io/code-generator v0.17.1 h1:e3B1UqRzRUWygp7WD+QTRT3ZUahPIaRKF0OFa7duQwI= k8s.io/code-generator v0.17.1 h1:e3B1UqRzRUWygp7WD+QTRT3ZUahPIaRKF0OFa7duQwI=
k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=

View File

@ -112,7 +112,7 @@ func Load(configDir string) (*configfile.ConfigFile, error) {
} }
confFile := filepath.Join(homedir, oldConfigfile) confFile := filepath.Join(homedir, oldConfigfile)
if _, err := os.Stat(confFile); err != nil { if _, err := os.Stat(confFile); err != nil {
return configFile, nil //missing file is not an error return configFile, nil // missing file is not an error
} }
file, err := os.Open(confFile) file, err := os.Open(confFile)
if err != nil { if err != nil {

View File

@ -196,6 +196,9 @@ func (configFile *ConfigFile) Save() error {
os.Remove(temp.Name()) os.Remove(temp.Name())
return err return err
} }
// Try copying the current config file (if any) ownership and permissions
copyFilePermissions(configFile.Filename, temp.Name())
return os.Rename(temp.Name(), configFile.Filename) return os.Rename(temp.Name(), configFile.Filename)
} }

View File

@ -0,0 +1,35 @@
// +build !windows
package configfile
import (
"os"
"syscall"
)
// copyFilePermissions copies file ownership and permissions from "src" to "dst",
// ignoring any error during the process.
func copyFilePermissions(src, dst string) {
var (
mode os.FileMode = 0600
uid, gid int
)
fi, err := os.Stat(src)
if err != nil {
return
}
if fi.Mode().IsRegular() {
mode = fi.Mode()
}
if err := os.Chmod(dst, mode); err != nil {
return
}
uid = int(fi.Sys().(*syscall.Stat_t).Uid)
gid = int(fi.Sys().(*syscall.Stat_t).Gid)
if uid > 0 && gid > 0 {
_ = os.Chown(dst, uid, gid)
}
}

View File

@ -0,0 +1,5 @@
package configfile
func copyFilePermissions(src, dst string) {
// TODO implement for Windows
}

View File

@ -1,5 +1,7 @@
# `authn` # `authn`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/authn?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/authn)
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.

View File

@ -52,7 +52,9 @@ var (
) )
const ( const (
defaultAuthKey = "https://" + name.DefaultRegistry + "/v1/" // DefaultAuthKey is the key used for dockerhub in config files, which
// is hardcoded for historical reasons.
DefaultAuthKey = "https://" + name.DefaultRegistry + "/v1/"
) )
// Resolve implements Keychain. // Resolve implements Keychain.
@ -67,7 +69,7 @@ func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
// 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() key := target.RegistryStr()
if key == name.DefaultRegistry { if key == name.DefaultRegistry {
key = defaultAuthKey key = DefaultAuthKey
} }
cfg, err := cf.GetAuthConfig(key) cfg, err := cf.GetAuthConfig(key)

View File

@ -0,0 +1,3 @@
# `name`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/name?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/name)

View File

@ -23,6 +23,9 @@ import (
// ConfigFile is the configuration file that holds the metadata describing // ConfigFile is the configuration file that holds the metadata describing
// how to launch a container. See: // how to launch a container. See:
// https://github.com/opencontainers/image-spec/blob/master/config.md // https://github.com/opencontainers/image-spec/blob/master/config.md
//
// docker_version and os.version are not part of the spec but included
// for backwards compatibility.
type ConfigFile struct { type ConfigFile struct {
Architecture string `json:"architecture"` Architecture string `json:"architecture"`
Author string `json:"author,omitempty"` Author string `json:"author,omitempty"`
@ -33,7 +36,7 @@ type ConfigFile struct {
OS string `json:"os"` OS string `json:"os"`
RootFS RootFS `json:"rootfs"` RootFS RootFS `json:"rootfs"`
Config Config `json:"config"` Config Config `json:"config"`
OSVersion string `json:"osversion,omitempty"` OSVersion string `json:"os.version,omitempty"`
} }
// History is one entry of a list recording how this container image was built. // History is one entry of a list recording how this container image was built.

View File

@ -0,0 +1,11 @@
# `daemon`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/daemon?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/daemon)
The `daemon` package enables reading/writing images from/to the docker daemon.
It is not fully fleshed out, but is useful for interoperability, see various issues:
* https://github.com/google/go-containerregistry/issues/205
* https://github.com/google/go-containerregistry/issues/552
* https://github.com/google/go-containerregistry/issues/627

View File

@ -36,6 +36,7 @@ var _ v1.Image = (*image)(nil)
type imageOpener struct { type imageOpener struct {
ref name.Reference ref name.Reference
buffered bool buffered bool
client Client
} }
// ImageOption is a functional option for Image. // ImageOption is a functional option for Image.
@ -45,14 +46,16 @@ func (i *imageOpener) Open() (v1.Image, error) {
var opener tarball.Opener var opener tarball.Opener
var err error var err error
if i.buffered { if i.buffered {
opener, err = bufferedOpener(i.ref) opener, err = i.bufferedOpener(i.ref)
} else { } else {
opener, err = unbufferedOpener(i.ref) opener, err = i.unbufferedOpener(i.ref)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
i.client.NegotiateAPIVersion(context.Background())
tb, err := tarball.Image(opener, nil) tb, err := tarball.Image(opener, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -63,33 +66,13 @@ func (i *imageOpener) Open() (v1.Image, error) {
return img, nil return img, nil
} }
// ImageSaver is an interface for testing. func (i *imageOpener) saveImage(ref name.Reference) (io.ReadCloser, error) {
type ImageSaver interface { return i.client.ImageSave(context.Background(), []string{ref.Name()})
ImageSave(context.Context, []string) (io.ReadCloser, error)
} }
// This is a variable so we can override in tests. func (i *imageOpener) bufferedOpener(ref name.Reference) (tarball.Opener, error) {
var getImageSaver = func() (ImageSaver, error) {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, err
}
cli.NegotiateAPIVersion(context.Background())
return cli, nil
}
func saveImage(ref name.Reference) (io.ReadCloser, error) {
cli, err := getImageSaver()
if err != nil {
return nil, err
}
return cli.ImageSave(context.Background(), []string{ref.Name()})
}
func bufferedOpener(ref name.Reference) (tarball.Opener, error) {
// Store the tarball in memory and return a new reader into the bytes each time we need to access something. // Store the tarball in memory and return a new reader into the bytes each time we need to access something.
rc, err := saveImage(ref) rc, err := i.saveImage(ref)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -106,10 +89,10 @@ func bufferedOpener(ref name.Reference) (tarball.Opener, error) {
}, nil }, nil
} }
func unbufferedOpener(ref name.Reference) (tarball.Opener, error) { func (i *imageOpener) unbufferedOpener(ref name.Reference) (tarball.Opener, error) {
// To avoid storing the tarball in memory, do a save every time we need to access something. // To avoid storing the tarball in memory, do a save every time we need to access something.
return func() (io.ReadCloser, error) { return func() (io.ReadCloser, error) {
return saveImage(ref) return i.saveImage(ref)
}, nil }, nil
} }
@ -126,5 +109,14 @@ func Image(ref name.Reference, options ...ImageOption) (v1.Image, error) {
return nil, err return nil, err
} }
} }
if i.client == nil {
var err error
i.client, err = client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, err
}
}
return i.Open() return i.Open()
} }

View File

@ -14,6 +14,13 @@
package daemon package daemon
import (
"context"
"io"
"github.com/docker/docker/api/types"
)
// WithBufferedOpener buffers the image. // WithBufferedOpener buffers the image.
func WithBufferedOpener() ImageOption { func WithBufferedOpener() ImageOption {
return func(i *imageOpener) error { return func(i *imageOpener) error {
@ -32,3 +39,22 @@ func (i *imageOpener) setBuffered(buffer bool) error {
i.buffered = buffer i.buffered = buffer
return nil return nil
} }
// WithClient is a functional option to allow injecting a docker client.
//
// By default, github.com/docker/docker/client.FromEnv is used.
func WithClient(client Client) ImageOption {
return func(i *imageOpener) error {
i.client = client
return nil
}
}
// Client represents the subset of a docker client that the daemon
// package uses.
type Client interface {
NegotiateAPIVersion(ctx context.Context)
ImageSave(context.Context, []string) (io.ReadCloser, error)
ImageLoad(context.Context, io.Reader, bool) (types.ImageLoadResponse, error)
ImageTag(context.Context, string, string) error
}

View File

@ -0,0 +1,8 @@
# `empty`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/empty?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/empty)
The empty packages provides an empty base for constructing a `v1.Image` or `v1.ImageIndex`.
This is especially useful when paired with the [`mutate`](/pkg/v1/mutate) package,
see [`mutate.Append`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/mutate#Append)
and [`mutate.AppendManifests`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/mutate#AppendManifests).

View File

@ -31,13 +31,14 @@ type Image interface {
// Size returns the size of the manifest. // Size returns the size of the manifest.
Size() (int64, error) Size() (int64, error)
// ConfigName returns the hash of the image's config file. // ConfigName returns the hash of the image's config file, also known as
// the Image ID.
ConfigName() (Hash, error) ConfigName() (Hash, error)
// ConfigFile returns this image's config file. // ConfigFile returns this image's config file.
ConfigFile() (*ConfigFile, error) ConfigFile() (*ConfigFile, error)
// RawConfigFile returns the serialized bytes of ConfigFile() // RawConfigFile returns the serialized bytes of ConfigFile().
RawConfigFile() ([]byte, error) RawConfigFile() ([]byte, error)
// Digest returns the sha256 of this image's manifest. // Digest returns the sha256 of this image's manifest.

View File

@ -0,0 +1,5 @@
# `layout`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/layout?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/layout)
The `layout` package implements support for interacting with an [OCI Image Layout](https://github.com/opencontainers/image-spec/blob/master/image-layout.md).

View File

@ -124,15 +124,19 @@ func (l Path) AppendDescriptor(desc v1.Descriptor) error {
return err return err
} }
return l.writeFile("index.json", rawIndex) return l.WriteFile("index.json", rawIndex, os.ModePerm)
} }
func (l Path) writeFile(name string, data []byte) error { // WriteFile write a file with arbitrary data at an arbitrary location in a v1
// layout. Used mostly internally to write files like "oci-layout" and
// "index.json", also can be used to write other arbitrary files. Do *not* use
// this to write blobs. Use only WriteBlob() for that.
func (l Path) WriteFile(name string, data []byte, perm os.FileMode) error {
if err := os.MkdirAll(l.path(), os.ModePerm); err != nil && !os.IsExist(err) { if err := os.MkdirAll(l.path(), os.ModePerm); err != nil && !os.IsExist(err) {
return err return err
} }
return ioutil.WriteFile(l.path(name), data, os.ModePerm) return ioutil.WriteFile(l.path(name), data, perm)
} }
@ -259,12 +263,12 @@ func (l Path) writeIndexToFile(indexFile string, ii v1.ImageIndex) error {
return err return err
} }
return l.writeFile(indexFile, rawIndex) return l.WriteFile(indexFile, rawIndex, os.ModePerm)
} }
func (l Path) writeIndex(ii v1.ImageIndex) error { func (l Path) writeIndex(ii v1.ImageIndex) error {
// Always just write oci-layout file, since it's small. // Always just write oci-layout file, since it's small.
if err := l.writeFile("oci-layout", []byte(layoutFile)); err != nil { if err := l.WriteFile("oci-layout", []byte(layoutFile), os.ModePerm); err != nil {
return err return err
} }
@ -291,7 +295,7 @@ func (l Path) writeIndex(ii v1.ImageIndex) error {
func Write(path string, ii v1.ImageIndex) (Path, error) { func Write(path string, ii v1.ImageIndex) (Path, error) {
lp := Path(path) lp := Path(path)
// Always just write oci-layout file, since it's small. // Always just write oci-layout file, since it's small.
if err := lp.writeFile("oci-layout", []byte(layoutFile)); err != nil { if err := lp.WriteFile("oci-layout", []byte(layoutFile), os.ModePerm); err != nil {
return "", err return "", err
} }

View File

@ -0,0 +1,56 @@
# `mutate`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/mutate?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/mutate)
The `v1.Image`, `v1.ImageIndex`, and `v1.Layer` interfaces provide only
accessor methods, so they are essentially immutable. If you want to change
something about them, you need to produce a new instance of that interface.
A common use case for this library is to read an image from somewhere (a source),
change something about it, and write the image somewhere else (a sink).
Graphically, this looks something like:
<p align="center">
<img src="/images/mutate.dot.svg" />
</p>
## Mutations
This is obviously not a comprehensive set of useful transformations (PRs welcome!),
but a rough summary of what the `mutate` package currently does:
### `Config` and `ConfigFile`
These allow you to change the [image configuration](https://github.com/opencontainers/image-spec/blob/master/config.md#properties),
e.g. to change the entrypoint, environment, author, etc.
### `Time`, `Canonical`, and `CreatedAt`
These are useful in the context of [reproducible builds](https://reproducible-builds.org/),
where you may want to strip timestamps and other non-reproducible information.
### `Append`, `AppendLayers`, and `AppendManifests`
These functions allow the extension of a `v1.Image` or `v1.ImageIndex` with
new layers or manifests.
For constructing an image `FROM scratch`, see the [`empty`](/pkg/v1/empty) package.
### `MediaType` and `IndexMediaType`
Sometimes, it is necessary to change the media type of an image or index,
e.g. to appease a registry with strict validation of images (_looking at you, GCR_).
### `Rebase`
Rebase has [its own README](/cmd/crane/rebase.md).
This is the underlying implementation of [`crane rebase`](https://github.com/google/go-containerregistry/blob/master/cmd/crane/doc/crane_rebase.md).
### `Extract`
Extract will flatten an image filesystem into a single tar stream,
respecting whiteout files.
This is the underlying implementation of [`crane export`](https://github.com/google/go-containerregistry/blob/master/cmd/crane/doc/crane_export.md).

View File

@ -140,8 +140,6 @@ func CreatedAt(base v1.Image, created v1.Time) (v1.Image, error) {
// //
// If a caller doesn't read the full contents, they should Close it to free up // If a caller doesn't read the full contents, they should Close it to free up
// resources used during extraction. // resources used during extraction.
//
// Adapted from https://github.com/google/containerregistry/blob/master/client/v2_2/docker_image_.py#L731
func Extract(img v1.Image) io.ReadCloser { func Extract(img v1.Image) io.ReadCloser {
pr, pw := io.Pipe() pr, pw := io.Pipe()
@ -156,6 +154,7 @@ func Extract(img v1.Image) io.ReadCloser {
return pr return pr
} }
// Adapted from https://github.com/google/containerregistry/blob/da03b395ccdc4e149e34fbb540483efce962dc64/client/v2_2/docker_image_.py#L816
func extract(img v1.Image, w io.Writer) error { func extract(img v1.Image, w io.Writer) error {
tarWriter := tar.NewWriter(w) tarWriter := tar.NewWriter(w)
defer tarWriter.Close() defer tarWriter.Close()

View File

@ -0,0 +1,66 @@
# `partial`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/partial?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/partial)
## Partial Implementations
There are roughly two kinds of image representations: compressed and uncompressed.
The implementations for these kinds of images are almost identical, with the only
major difference being how blobs (config and layers) are fetched. This common
code lives in this package, where you provide a _partial_ implementation of a
compressed or uncompressed image, and you get back a full `v1.Image` implementation.
### Examples
In a registry, blobs are compressed, so it's easiest to implement a `v1.Image` in terms
of compressed layers. `remote.remoteImage` does this by implementing `CompressedImageCore`:
```go
type CompressedImageCore interface {
RawConfigFile() ([]byte, error)
MediaType() (types.MediaType, error)
RawManifest() ([]byte, error)
LayerByDigest(v1.Hash) (CompressedLayer, error)
}
```
In a tarball, blobs are (often) uncompressed, so it's easiest to implement a `v1.Image` in terms
of uncompressed layers. `tarball.uncompressedImage` does this by implementing `UncompressedImageCore`:
```go
type CompressedImageCore interface {
RawConfigFile() ([]byte, error)
MediaType() (types.MediaType, error)
LayerByDiffID(v1.Hash) (UncompressedLayer, error)
}
```
## Optional Methods
Where possible, we access some information via optional methods as an optimization.
### [`partial.Descriptor`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/partial#Descriptor)
There are some properties of a [`Descriptor`](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#properties) that aren't derivable from just image data:
* `MediaType`
* `Platform`
* `URLs`
* `Annotations`
For example, in a `tarball.Image`, there is a `LayerSources` field that contains
an entire layer descriptor with `URLs` information for foreign layers. This
information can be passed through to callers by implementing this optional
`Descriptor` method.
See [`#654`](https://github.com/google/go-containerregistry/pull/654).
### [`partial.UncompressedSize`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/partial#UncompressedSize)
Usually, you don't need to know the uncompressed size of a layer, since that
information isn't stored in a config file (just he sha256 is needed); however,
there are cases where it is very helpful to know the layer size, e.g. when
writing the uncompressed layer into a tarball.
See [`#655`](https://github.com/google/go-containerregistry/pull/655).

View File

@ -76,7 +76,7 @@ func CompressedToLayer(ul CompressedLayer) (v1.Layer, error) {
// CompressedImageCore represents the base minimum interface a natively // CompressedImageCore represents the base minimum interface a natively
// compressed image must implement for us to produce a v1.Image. // compressed image must implement for us to produce a v1.Image.
type CompressedImageCore interface { type CompressedImageCore interface {
imageCore ImageCore
// RawManifest returns the serialized bytes of the manifest. // RawManifest returns the serialized bytes of the manifest.
RawManifest() ([]byte, error) RawManifest() ([]byte, error)

View File

@ -18,8 +18,8 @@ import (
"github.com/google/go-containerregistry/pkg/v1/types" "github.com/google/go-containerregistry/pkg/v1/types"
) )
// imageCore is the core set of properties without which we cannot build a v1.Image // ImageCore is the core set of properties without which we cannot build a v1.Image
type imageCore interface { type ImageCore interface {
// RawConfigFile returns the serialized bytes of this image's config file. // RawConfigFile returns the serialized bytes of this image's config file.
RawConfigFile() ([]byte, error) RawConfigFile() ([]byte, error)

View File

@ -89,7 +89,7 @@ func UncompressedToLayer(ul UncompressedLayer) (v1.Layer, error) {
// UncompressedImageCore represents the bare minimum interface a natively // UncompressedImageCore represents the bare minimum interface a natively
// uncompressed image must implement for us to produce a v1.Image // uncompressed image must implement for us to produce a v1.Image
type UncompressedImageCore interface { type UncompressedImageCore interface {
imageCore ImageCore
// LayerByDiffID is a variation on the v1.Image method, which returns // LayerByDiffID is a variation on the v1.Image method, which returns
// an UncompressedLayer instead. // an UncompressedLayer instead.

View File

@ -201,22 +201,22 @@ func BlobSize(i WithManifest, h v1.Hash) (int64, error) {
} }
// BlobDescriptor is a helper for implementing v1.Image // BlobDescriptor is a helper for implementing v1.Image
func BlobDescriptor(i WithManifest, h v1.Hash) (v1.Descriptor, error) { func BlobDescriptor(i WithManifest, h v1.Hash) (*v1.Descriptor, error) {
m, err := i.Manifest() m, err := i.Manifest()
if err != nil { if err != nil {
return v1.Descriptor{}, err return nil, err
} }
if m.Config.Digest == h { if m.Config.Digest == h {
return m.Config, nil return &m.Config, nil
} }
for _, l := range m.Layers { for _, l := range m.Layers {
if l.Digest == h { if l.Digest == h {
return l, nil return &l, nil
} }
} }
return v1.Descriptor{}, fmt.Errorf("blob %v not found", h) return nil, fmt.Errorf("blob %v not found", h)
} }
// WithManifestAndConfigFile defines the subset of v1.Image used by these helper methods // WithManifestAndConfigFile defines the subset of v1.Image used by these helper methods

View File

@ -0,0 +1,117 @@
# `remote`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote)
The `remote` package implements a client for accessing a registry,
per the [OCI distribution spec](https://github.com/opencontainers/distribution-spec/blob/master/spec.md).
It leans heavily on the lower level [`transport`](/pkg/v1/remote/transport) package, which handles the
authentication handshake and structured errors.
## Usage
```go
package main
import (
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
func main() {
ref, err := name.ParseReference("gcr.io/google-containers/pause")
if err != nil {
panic(err)
}
img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
panic(err)
}
// do stuff with img
}
```
## Structure
<p align="center">
<img src="/images/remote.dot.svg" />
</p>
## Background
There are a lot of confusingly similar terms that come up when talking about images in registries.
### Anatomy of an image
In general...
* A tag refers to an image manifest.
* An image manifest references a config file and an orderered list of _compressed_ layers by sha256 digest.
* A config file references an ordered list of _uncompressed_ layers by sha256 digest and contains runtime configuration.
* The sha256 digest of the config file is the [image id](https://github.com/opencontainers/image-spec/blob/master/config.md#imageid) for the image.
For example, an image with two layers would look something like this:
![image anatomy](/images/image-anatomy.dot.svg)
### Anatomy of an index
In the normal case, an [index](https://github.com/opencontainers/image-spec/blob/master/image-index.md) is used to represent a multi-platform image.
This was the original use case for a [manifest
list](https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list).
![image index anatomy](/images/index-anatomy.dot.svg)
It is possible for an index to reference another index, per the OCI
[image-spec](https://github.com/opencontainers/image-spec/blob/master/media-types.md#compatibility-matrix).
In theory, both an image and image index can reference arbitrary things via
[descriptors](https://github.com/opencontainers/image-spec/blob/master/descriptor.md),
e.g. see the [image layout
example](https://github.com/opencontainers/image-spec/blob/master/image-layout.md#index-example),
which references an application/xml file from an image index.
That could look something like this:
![exotic image index anatomy](/images/index-anatomy-exotic.dot.svg)
Using a recursive index like this might not be possible with all registries,
but this flexibility allows for some interesting applications, e.g. the
[OCI Artifacts](https://github.com/opencontainers/artifacts) effort.
### Anatomy of an image upload
The structure of an image requires a delicate ordering when uploading an image to a registry.
Below is a (slightly simplified) figure that describes how an image is prepared for upload
to a registry and how the data flows between various artifacts:
![upload](/images/upload.dot.svg)
Note that:
* A config file references the uncompressed layer contents by sha256.
* A manifest references the compressed layer contents by sha256 and the size of the layer.
* A manifest references the config file contents by sha256 and the size of the file.
It follows that during an upload, we need to upload layers before the config file,
and we need to upload the config file before the manifest.
Sometimes, we know all of this information ahead of time, (e.g. when copying from remote.Image),
so the ordering is less important.
In other cases, e.g. when using a [`stream.Layer`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/stream#Layer),
we can't compute anything until we have already uploaded the layer, so we need to be careful about ordering.
## Caveats
### schema 1
This package does not support schema 1 images, see [`#377`](https://github.com/google/go-containerregistry/issues/377),
however, it's possible to do _something_ useful with them via [`remote.Get`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote#Get),
which doesn't try to interpret what is returned by the registry.
[`crane.Copy`](https://godoc.org/github.com/google/go-containerregistry/pkg/crane#Copy) takes advantage of this to implement support for copying schema 1 images,
see [here](https://github.com/google/go-containerregistry/blob/master/pkg/internal/legacy/copy.go).

View File

@ -77,14 +77,12 @@ func (d *Descriptor) RawManifest() ([]byte, error) {
// querying what kind of artifact a reference represents. // querying what kind of artifact a reference represents.
func Get(ref name.Reference, options ...Option) (*Descriptor, error) { func Get(ref name.Reference, options ...Option) (*Descriptor, error) {
acceptable := []types.MediaType{ acceptable := []types.MediaType{
types.DockerManifestSchema2,
types.OCIManifestSchema1,
types.DockerManifestList,
types.OCIImageIndex,
// Just to look at them. // Just to look at them.
types.DockerManifestSchema1, types.DockerManifestSchema1,
types.DockerManifestSchema1Signed, types.DockerManifestSchema1Signed,
} }
acceptable = append(acceptable, acceptableImageMediaTypes...)
acceptable = append(acceptable, acceptableIndexMediaTypes...)
return get(ref, acceptable, options...) return get(ref, acceptable, options...)
} }
@ -174,8 +172,9 @@ func (d *Descriptor) remoteImage() *remoteImage {
Ref: d.Ref, Ref: d.Ref,
Client: d.Client, Client: d.Client,
}, },
manifest: d.Manifest, manifest: d.Manifest,
mediaType: d.MediaType, mediaType: d.MediaType,
descriptor: &d.Descriptor,
} }
} }
@ -185,8 +184,9 @@ func (d *Descriptor) remoteIndex() *remoteIndex {
Ref: d.Ref, Ref: d.Ref,
Client: d.Client, Client: d.Client,
}, },
manifest: d.Manifest, manifest: d.Manifest,
mediaType: d.MediaType, mediaType: d.MediaType,
descriptor: &d.Descriptor,
} }
} }

View File

@ -29,6 +29,11 @@ import (
"github.com/google/go-containerregistry/pkg/v1/v1util" "github.com/google/go-containerregistry/pkg/v1/v1util"
) )
var acceptableImageMediaTypes = []types.MediaType{
types.DockerManifestSchema2,
types.OCIManifestSchema1,
}
// remoteImage accesses an image from a remote registry // remoteImage accesses an image from a remote registry
type remoteImage struct { type remoteImage struct {
fetcher fetcher
@ -37,6 +42,7 @@ type remoteImage struct {
configLock sync.Mutex // Protects config configLock sync.Mutex // Protects config
config []byte config []byte
mediaType types.MediaType mediaType types.MediaType
descriptor *v1.Descriptor
} }
var _ partial.CompressedImageCore = (*remoteImage)(nil) var _ partial.CompressedImageCore = (*remoteImage)(nil)
@ -68,15 +74,14 @@ func (r *remoteImage) RawManifest() ([]byte, error) {
// NOTE(jonjohnsonjr): We should never get here because the public entrypoints // NOTE(jonjohnsonjr): We should never get here because the public entrypoints
// do type-checking via remote.Descriptor. I've left this here for tests that // do type-checking via remote.Descriptor. I've left this here for tests that
// directly instantiate a remoteImage. // directly instantiate a remoteImage.
acceptable := []types.MediaType{ manifest, desc, err := r.fetchManifest(r.Ref, acceptableImageMediaTypes)
types.DockerManifestSchema2,
types.OCIManifestSchema1,
}
manifest, desc, err := r.fetchManifest(r.Ref, acceptable)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if r.descriptor == nil {
r.descriptor = desc
}
r.mediaType = desc.MediaType r.mediaType = desc.MediaType
r.manifest = manifest r.manifest = manifest
return r.manifest, nil return r.manifest, nil
@ -107,6 +112,15 @@ func (r *remoteImage) RawConfigFile() ([]byte, error) {
return r.config, nil return r.config, nil
} }
// Descriptor retains the original descriptor from an index manifest.
// See partial.Descriptor.
func (r *remoteImage) Descriptor() (*v1.Descriptor, error) {
// kind of a hack, but RawManifest does appropriate locking/memoization
// and makes sure r.descriptor is populated.
_, err := r.RawManifest()
return r.descriptor, err
}
// remoteImageLayer implements partial.CompressedLayer // remoteImageLayer implements partial.CompressedLayer
type remoteImageLayer struct { type remoteImageLayer struct {
ri *remoteImage ri *remoteImage
@ -192,6 +206,12 @@ func (rl *remoteImageLayer) DiffID() (v1.Hash, error) {
return partial.BlobToDiffID(rl, rl.digest) return partial.BlobToDiffID(rl, rl.digest)
} }
// Descriptor retains the original descriptor from an image manifest.
// See partial.Descriptor.
func (rl *remoteImageLayer) Descriptor() (*v1.Descriptor, error) {
return partial.BlobDescriptor(rl, rl.digest)
}
// LayerByDigest implements partial.CompressedLayer // LayerByDigest implements partial.CompressedLayer
func (r *remoteImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) { func (r *remoteImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {
return &remoteImageLayer{ return &remoteImageLayer{

View File

@ -25,22 +25,23 @@ import (
"github.com/google/go-containerregistry/pkg/v1/types" "github.com/google/go-containerregistry/pkg/v1/types"
) )
var acceptableIndexMediaTypes = []types.MediaType{
types.DockerManifestList,
types.OCIImageIndex,
}
// remoteIndex accesses an index from a remote registry // remoteIndex accesses an index from a remote registry
type remoteIndex struct { type remoteIndex struct {
fetcher fetcher
manifestLock sync.Mutex // Protects manifest manifestLock sync.Mutex // Protects manifest
manifest []byte manifest []byte
mediaType types.MediaType mediaType types.MediaType
descriptor *v1.Descriptor
} }
// Index provides access to a remote index reference. // Index provides access to a remote index reference.
func Index(ref name.Reference, options ...Option) (v1.ImageIndex, error) { func Index(ref name.Reference, options ...Option) (v1.ImageIndex, error) {
acceptable := []types.MediaType{ desc, err := get(ref, acceptableIndexMediaTypes, options...)
types.DockerManifestList,
types.OCIImageIndex,
}
desc, err := get(ref, acceptable, options...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -73,15 +74,14 @@ func (r *remoteIndex) RawManifest() ([]byte, error) {
// NOTE(jonjohnsonjr): We should never get here because the public entrypoints // NOTE(jonjohnsonjr): We should never get here because the public entrypoints
// do type-checking via remote.Descriptor. I've left this here for tests that // do type-checking via remote.Descriptor. I've left this here for tests that
// directly instantiate a remoteIndex. // directly instantiate a remoteIndex.
acceptable := []types.MediaType{ manifest, desc, err := r.fetchManifest(r.Ref, acceptableIndexMediaTypes)
types.DockerManifestList,
types.OCIImageIndex,
}
manifest, desc, err := r.fetchManifest(r.Ref, acceptable)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if r.descriptor == nil {
r.descriptor = desc
}
r.mediaType = desc.MediaType r.mediaType = desc.MediaType
r.manifest = manifest r.manifest = manifest
return r.manifest, nil return r.manifest, nil
@ -105,6 +105,15 @@ func (r *remoteIndex) Image(h v1.Hash) (v1.Image, error) {
return desc.Image() return desc.Image()
} }
// Descriptor retains the original descriptor from an index manifest.
// See partial.Descriptor.
func (r *remoteIndex) Descriptor() (*v1.Descriptor, error) {
// kind of a hack, but RawManifest does appropriate locking/memoization
// and makes sure r.descriptor is populated.
_, err := r.RawManifest()
return r.descriptor, err
}
func (r *remoteIndex) ImageIndex(h v1.Hash) (v1.ImageIndex, error) { func (r *remoteIndex) ImageIndex(h v1.Hash) (v1.ImageIndex, error) {
desc, err := r.childByHash(h) desc, err := r.childByHash(h)
if err != nil { if err != nil {
@ -165,7 +174,7 @@ func (r *remoteIndex) childByHash(h v1.Hash) (*Descriptor, error) {
// Convert one of this index's child's v1.Descriptor into a remote.Descriptor, with the given platform option. // Convert one of this index's child's v1.Descriptor into a remote.Descriptor, with the given platform option.
func (r *remoteIndex) childDescriptor(child v1.Descriptor, platform v1.Platform) (*Descriptor, error) { func (r *remoteIndex) childDescriptor(child v1.Descriptor, platform v1.Platform) (*Descriptor, error) {
ref := r.Ref.Context().Digest(child.Digest.String()) ref := r.Ref.Context().Digest(child.Digest.String())
manifest, desc, err := r.fetchManifest(ref, []types.MediaType{child.MediaType}) manifest, _, err := r.fetchManifest(ref, []types.MediaType{child.MediaType})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -175,7 +184,7 @@ func (r *remoteIndex) childDescriptor(child v1.Descriptor, platform v1.Platform)
Client: r.Client, Client: r.Client,
}, },
Manifest: manifest, Manifest: manifest,
Descriptor: *desc, Descriptor: child,
platform: platform, platform: platform,
}, nil }, nil
} }

View File

@ -50,10 +50,12 @@ func ListWithContext(ctx context.Context, repo name.Repository, options ...Optio
} }
uri := &url.URL{ uri := &url.URL{
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()),
RawQuery: "n=10000", // ECR returns an error if n > 1000:
// https://github.com/google/go-containerregistry/issues/681
RawQuery: "n=1000",
} }
client := http.Client{Transport: tr} client := http.Client{Transport: tr}

View File

@ -17,6 +17,7 @@ package remote
import ( 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/partial"
) )
// MountableLayer wraps a v1.Layer in a shim that enables the layer to be // MountableLayer wraps a v1.Layer in a shim that enables the layer to be
@ -27,6 +28,12 @@ type MountableLayer struct {
Reference name.Reference Reference name.Reference
} }
// Descriptor retains the original descriptor from an image manifest.
// See partial.Descriptor.
func (ml *MountableLayer) Descriptor() (*v1.Descriptor, error) {
return partial.Descriptor(ml.Layer)
}
// mountableImage wraps the v1.Layer references returned by the embedded v1.Image // mountableImage wraps the v1.Layer references returned by the embedded v1.Image
// in MountableLayer's so that remote.Write might attempt to mount them from their // in MountableLayer's so that remote.Write might attempt to mount them from their
// source repository. // source repository.
@ -75,3 +82,9 @@ func (mi *mountableImage) LayerByDiffID(d v1.Hash) (v1.Layer, error) {
Reference: mi.Reference, Reference: mi.Reference,
}, nil }, nil
} }
// Descriptor retains the original descriptor from an index manifest.
// See partial.Descriptor.
func (mi *mountableImage) Descriptor() (*v1.Descriptor, error) {
return partial.Descriptor(mi.Image)
}

View File

@ -1,5 +1,7 @@
# `transport` # `transport`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/transport?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/transport)
The [distribution protocol](https://github.com/opencontainers/distribution-spec) is fairly simple, but correctly [implementing authentication](../../../authn/README.md) is **hard**. The [distribution protocol](https://github.com/opencontainers/distribution-spec) is fairly simple, but correctly [implementing authentication](../../../authn/README.md) is **hard**.
This package [implements](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote/transport#New) an [`http.RoundTripper`](https://godoc.org/net/http#RoundTripper) This package [implements](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote/transport#New) an [`http.RoundTripper`](https://godoc.org/net/http#RoundTripper)

View File

@ -449,7 +449,7 @@ func scopesForUploadingImage(repo name.Repository, layers []v1.Layer) []string {
if ml, ok := l.(*MountableLayer); ok { if ml, ok := l.(*MountableLayer); ok {
// we will add push scope for ref.Context() after the loop. // we will add push scope for ref.Context() after the loop.
// for now we ask pull scope for references of the same registry // for now we ask pull scope for references of the same registry
if ml.Reference.Context() != repo && ml.Reference.Context().Registry == repo.Registry { if ml.Reference.Context().String() != repo.String() && ml.Reference.Context().Registry.String() == repo.Registry.String() {
scopeSet[ml.Reference.Scope(transport.PullScope)] = struct{}{} scopeSet[ml.Reference.Scope(transport.PullScope)] = struct{}{}
} }
} }

View File

@ -0,0 +1,68 @@
# `stream`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/stream?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/stream)
The `stream` package contains an implementation of
[`v1.Layer`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1#Layer)
that supports _streaming_ access, i.e. the layer contents are read once and not
buffered.
## Usage
```go
package main
import (
"os"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/stream"
)
// upload the contents of stdin as a layer to a local registry
func main() {
repo, err := name.NewRepository("localhost:5000/stream")
if err != nil {
panic(err)
}
layer := stream.NewLayer(os.Stdin)
if err := remote.WriteLayer(repo, layer); err != nil {
panic(err)
}
}
```
## Structure
This implements the layer portion of an [image
upload](/pkg/v1/remote#anatomy-of-an-image-upload). We launch a goroutine that
is responsible for hashing the uncompressed contents to compute the `DiffID`,
gzipping them to produce the `Compressed` contents, and hashing/counting the
bytes to produce the `Digest`/`Size`. This goroutine writes to an
`io.PipeWriter`, which blocks until `Compressed` reads the gzipped contents from
the corresponding `io.PipeReader`.
<p align="center">
<img src="/images/stream.dot.svg" />
</p>
## Caveats
This assumes that you have an uncompressed layer (i.e. a tarball) and would like
to compress it. Calling `Uncompressed` is always an error. Likewise, other
methods are invalid until the contents of `Compressed` have been completely
consumed and `Close`d.
Using a `stream.Layer` will likely not work without careful consideration. For
example, in the `mutate` package, we defer computing the manifest and config
file until they are actually called. This allows you to `mutate.Append` a
streaming layer to an image without accidentally consuming it. Similarly, in
`remote.Write`, if calling `Digest` on a layer fails, we attempt to upload the
layer anyway, understanding that we may be dealing with a `stream.Layer` whose
contents need to be uploaded before we can upload the config file.
Given the [structure](#structure) of how this is implemented, forgetting to
`Close` a `stream.Layer` will leak a goroutine.

View File

@ -0,0 +1,280 @@
# `tarball`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/tarball?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/tarball)
This package produces tarballs that can consumed via `docker load`. Note
that this is a _different_ format from the [`legacy`](/pkg/legacy/tarball)
tarballs that are produced by `docker save`, but this package is still able to
read the legacy tarballs produced by `docker save`.
## Usage
```go
package main
import (
"os"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/tarball"
)
func main() {
// Read a tarball from os.Args[1] that contains ubuntu.
tag, err := name.NewTag("ubuntu")
if err != nil {
panic(err)
}
img, err := tarball.ImageFromPath(os.Args[1], &tag)
if err != nil {
panic(err)
}
// Write that tarball to os.Args[2] with a different tag.
newTag, err := name.NewTag("ubuntu:newest")
if err != nil {
panic(err)
}
f, err := os.Create(os.Args[2])
if err != nil {
panic(err)
}
defer f.Close()
if err := tarball.Write(newTag, img, f); err != nil {
panic(err)
}
}
```
## Structure
<p align="center">
<img src="/images/tarball.dot.svg" />
</p>
Let's look at what happens when we write out a tarball:
### `ubuntu:latest`
```
$ crane pull ubuntu ubuntu.tar && mkdir ubuntu && tar xf ubuntu.tar -C ubuntu && rm ubuntu.tar
$ tree ubuntu/
ubuntu/
├── 423ae2b273f4c17ceee9e8482fa8d071d90c7d052ae208e1fe4963fceb3d6954.tar.gz
├── b6b53be908de2c0c78070fff0a9f04835211b3156c4e73785747af365e71a0d7.tar.gz
├── de83a2304fa1f7c4a13708a0d15b9704f5945c2be5cbb2b3ed9b2ccb718d0b3d.tar.gz
├── f9a83bce3af0648efaa60b9bb28225b09136d2d35d0bed25ac764297076dec1b.tar.gz
├── manifest.json
└── sha256:72300a873c2ca11c70d0c8642177ce76ff69ae04d61a5813ef58d40ff66e3e7c
0 directories, 6 files
```
There are a couple interesting files here.
`manifest.json` is the entrypoint: a list of [`tarball.Descriptor`s](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/tarball#Descriptor)
that describe the images containd in this tarball.
For each image, this has the `RepoTags` (how it was pulled), a `Config` file
that points to the image's config file, a list of `Layers`, and (optionally)
`LayerSources`.
```
$ jq < ubuntu/manifest.json
[
{
"Config": "sha256:72300a873c2ca11c70d0c8642177ce76ff69ae04d61a5813ef58d40ff66e3e7c",
"RepoTags": [
"ubuntu"
],
"Layers": [
"423ae2b273f4c17ceee9e8482fa8d071d90c7d052ae208e1fe4963fceb3d6954.tar.gz",
"de83a2304fa1f7c4a13708a0d15b9704f5945c2be5cbb2b3ed9b2ccb718d0b3d.tar.gz",
"f9a83bce3af0648efaa60b9bb28225b09136d2d35d0bed25ac764297076dec1b.tar.gz",
"b6b53be908de2c0c78070fff0a9f04835211b3156c4e73785747af365e71a0d7.tar.gz"
]
}
]
```
The config file and layers are exactly what you would expect, and match the
registry representations of the same artifacts. You'll notice that the
`manifest.json` contains similar information as the registry manifest, but isn't
quite the same:
```
$ crane manifest ubuntu@sha256:0925d086715714114c1988f7c947db94064fd385e171a63c07730f1fa014e6f9
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 3408,
"digest": "sha256:72300a873c2ca11c70d0c8642177ce76ff69ae04d61a5813ef58d40ff66e3e7c"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 26692096,
"digest": "sha256:423ae2b273f4c17ceee9e8482fa8d071d90c7d052ae208e1fe4963fceb3d6954"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 35365,
"digest": "sha256:de83a2304fa1f7c4a13708a0d15b9704f5945c2be5cbb2b3ed9b2ccb718d0b3d"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 852,
"digest": "sha256:f9a83bce3af0648efaa60b9bb28225b09136d2d35d0bed25ac764297076dec1b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 163,
"digest": "sha256:b6b53be908de2c0c78070fff0a9f04835211b3156c4e73785747af365e71a0d7"
}
]
}
```
This makes it difficult to maintain image digests when roundtripping images
through the tarball format, so it's not a great format if you care about
provenance.
The ubuntu example didn't have any `LayerSources` -- let's look at another image
that does.
### `hello-world:nanoserver`
```
$ crane pull hello-world:nanoserver@sha256:63c287625c2b0b72900e562de73c0e381472a83b1b39217aef3856cd398eca0b nanoserver.tar
$ mkdir nanoserver && tar xf nanoserver.tar -C nanoserver && rm nanoserver.tar
$ tree nanoserver/
nanoserver/
├── 10d1439be4eb8819987ec2e9c140d44d74d6b42a823d57fe1953bd99948e1bc0.tar.gz
├── a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053.tar.gz
├── be21f08f670160cbae227e3053205b91d6bfa3de750b90c7e00bd2c511ccb63a.tar.gz
├── manifest.json
└── sha256:bc5d255ea81f83c8c38a982a6d29a6f2198427d258aea5f166e49856896b2da6
0 directories, 5 files
$ jq < nanoserver/manifest.json
[
{
"Config": "sha256:bc5d255ea81f83c8c38a982a6d29a6f2198427d258aea5f166e49856896b2da6",
"RepoTags": [
"index.docker.io/library/hello-world:i-was-a-digest"
],
"Layers": [
"a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053.tar.gz",
"be21f08f670160cbae227e3053205b91d6bfa3de750b90c7e00bd2c511ccb63a.tar.gz",
"10d1439be4eb8819987ec2e9c140d44d74d6b42a823d57fe1953bd99948e1bc0.tar.gz"
],
"LayerSources": {
"sha256:26fd2d9d4c64a4f965bbc77939a454a31b607470f430b5d69fc21ded301fa55e": {
"mediaType": "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip",
"size": 101145811,
"digest": "sha256:a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053",
"urls": [
"https://mcr.microsoft.com/v2/windows/nanoserver/blobs/sha256:a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053"
]
}
}
}
]
```
A couple things to note about this `manifest.json` versus the other:
* The `RepoTags` field is a bit weird here. `hello-world` is a multi-platform
image, so We had to pull this image by digest, since we're (I'm) on
amd64/linux and wanted to grab a windows image. Since the tarball format
expects a tag under `RepoTags`, and we didn't pull by tag, we replace the
digest with a sentinel `i-was-a-digest` "tag" to appease docker.
* The `LayerSources` has enough information to reconstruct the foreign layers
pointer when pushing/pulling from the registry. For legal reasons, microsoft
doesn't want anyone but them to serve windows base images, so the mediaType
here indicates a "foreign" or "non-distributable" layer with an URL for where
you can download it from microsoft (see the [OCI
image-spec](https://github.com/opencontainers/image-spec/blob/master/layer.md#non-distributable-layers)).
We can look at what's in the registry to explain both of these things:
```
$ crane manifest hello-world:nanoserver | jq .
{
"manifests": [
{
"digest": "sha256:63c287625c2b0b72900e562de73c0e381472a83b1b39217aef3856cd398eca0b",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.17763.1040"
},
"size": 1124
}
],
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"schemaVersion": 2
}
# Note the media type and "urls" field.
$ crane manifest hello-world:nanoserver@sha256:63c287625c2b0b72900e562de73c0e381472a83b1b39217aef3856cd398eca0b | jq .
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1721,
"digest": "sha256:bc5d255ea81f83c8c38a982a6d29a6f2198427d258aea5f166e49856896b2da6"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip",
"size": 101145811,
"digest": "sha256:a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053",
"urls": [
"https://mcr.microsoft.com/v2/windows/nanoserver/blobs/sha256:a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053"
]
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 1669,
"digest": "sha256:be21f08f670160cbae227e3053205b91d6bfa3de750b90c7e00bd2c511ccb63a"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 949,
"digest": "sha256:10d1439be4eb8819987ec2e9c140d44d74d6b42a823d57fe1953bd99948e1bc0"
}
]
}
```
The `LayerSources` map is keyed by the diffid. Note that `sha256:26fd2d9d4c64a4f965bbc77939a454a31b607470f430b5d69fc21ded301fa55e` matches the first layer in the config file:
```
$ jq '.[0].LayerSources' < nanoserver/manifest.json
{
"sha256:26fd2d9d4c64a4f965bbc77939a454a31b607470f430b5d69fc21ded301fa55e": {
"mediaType": "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip",
"size": 101145811,
"digest": "sha256:a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053",
"urls": [
"https://mcr.microsoft.com/v2/windows/nanoserver/blobs/sha256:a35da61c356213336e646756218539950461ff2bf096badf307a23add6e70053"
]
}
}
$ jq < nanoserver/sha256\:bc5d255ea81f83c8c38a982a6d29a6f2198427d258aea5f166e49856896b2da6 | jq .rootfs
{
"type": "layers",
"diff_ids": [
"sha256:26fd2d9d4c64a4f965bbc77939a454a31b607470f430b5d69fc21ded301fa55e",
"sha256:601cf7d78c62e4b4d32a7bbf96a17606a9cea5bd9d22ffa6f34aa431d056b0e8",
"sha256:a1e1a3bf6529adcce4d91dce2cad86c2604a66b507ccbc4d2239f3da0ec5aab9"
]
}
```

View File

@ -146,7 +146,7 @@ func MultiRefWrite(refToImage map[name.Reference]v1.Image, w io.Writer) error {
if err != nil { if err != nil {
return err return err
} }
layerSources[diffid] = desc layerSources[diffid] = *desc
} }
r, err := l.Compressed() r, err := l.Compressed()

View File

@ -1,15 +1,10 @@
language: go language: go
go_import_path: github.com/pkg/errors go_import_path: github.com/pkg/errors
go: go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x - 1.11.x
- 1.12.x
- 1.13.x
- tip - tip
script: script:
- go test -v ./... - make check

44
vendor/github.com/pkg/errors/Makefile generated vendored Normal file
View File

@ -0,0 +1,44 @@
PKGS := github.com/pkg/errors
SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
GO := go
check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
test:
$(GO) test $(PKGS)
vet: | test
$(GO) vet $(PKGS)
staticcheck:
$(GO) get honnef.co/go/tools/cmd/staticcheck
staticcheck -checks all $(PKGS)
misspell:
$(GO) get github.com/client9/misspell/cmd/misspell
misspell \
-locale GB \
-error \
*.md *.go
unconvert:
$(GO) get github.com/mdempsky/unconvert
unconvert -v $(PKGS)
ineffassign:
$(GO) get github.com/gordonklaus/ineffassign
find $(SRCDIRS) -name '*.go' | xargs ineffassign
pedantic: check errcheck
unparam:
$(GO) get mvdan.cc/unparam
unparam ./...
errcheck:
$(GO) get github.com/kisielk/errcheck
errcheck $(PKGS)
gofmt:
@echo Checking code is gofmted
@test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"

View File

@ -41,11 +41,18 @@ default:
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
## Roadmap
With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
- 1.0. Final release.
## Contributing ## Contributing
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
Before proposing a change, please discuss your change by raising an issue. Before sending a PR, please discuss your change by raising an issue.
## License ## License

View File

@ -82,7 +82,7 @@
// //
// if err, ok := err.(stackTracer); ok { // if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() { // for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f) // fmt.Printf("%+s:%d\n", f, f)
// } // }
// } // }
// //
@ -159,6 +159,9 @@ type withStack struct {
func (w *withStack) Cause() error { return w.error } func (w *withStack) Cause() error { return w.error }
// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withStack) Unwrap() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) { func (w *withStack) Format(s fmt.State, verb rune) {
switch verb { switch verb {
case 'v': case 'v':
@ -241,6 +244,9 @@ type withMessage struct {
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause } func (w *withMessage) Cause() error { return w.cause }
// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withMessage) Unwrap() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) { func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb { switch verb {
case 'v': case 'v':

38
vendor/github.com/pkg/errors/go113.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
// +build go1.13
package errors
import (
stderrors "errors"
)
// Is reports whether any error in err's chain matches target.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func Is(err, target error) bool { return stderrors.Is(err, target) }
// As finds the first error in err's chain that matches target, and if so, sets
// target to that error value and returns true.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// As will panic if target is not a non-nil pointer to either a type that implements
// error, or to any interface type. As returns false if err is nil.
func As(err error, target interface{}) bool { return stderrors.As(err, target) }
// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
return stderrors.Unwrap(err)
}

View File

@ -5,10 +5,13 @@ import (
"io" "io"
"path" "path"
"runtime" "runtime"
"strconv"
"strings" "strings"
) )
// Frame represents a program counter inside a stack frame. // Frame represents a program counter inside a stack frame.
// For historical reasons if Frame is interpreted as a uintptr
// its value represents the program counter + 1.
type Frame uintptr type Frame uintptr
// pc returns the program counter for this frame; // pc returns the program counter for this frame;
@ -37,6 +40,15 @@ func (f Frame) line() int {
return line return line
} }
// name returns the name of this function, if known.
func (f Frame) name() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
return fn.Name()
}
// Format formats the frame according to the fmt.Formatter interface. // Format formats the frame according to the fmt.Formatter interface.
// //
// %s source file // %s source file
@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
case 's': case 's':
switch { switch {
case s.Flag('+'): case s.Flag('+'):
pc := f.pc() io.WriteString(s, f.name())
fn := runtime.FuncForPC(pc) io.WriteString(s, "\n\t")
if fn == nil { io.WriteString(s, f.file())
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default: default:
io.WriteString(s, path.Base(f.file())) io.WriteString(s, path.Base(f.file()))
} }
case 'd': case 'd':
fmt.Fprintf(s, "%d", f.line()) io.WriteString(s, strconv.Itoa(f.line()))
case 'n': case 'n':
name := runtime.FuncForPC(f.pc()).Name() io.WriteString(s, funcname(f.name()))
io.WriteString(s, funcname(name))
case 'v': case 'v':
f.Format(s, 's') f.Format(s, 's')
io.WriteString(s, ":") io.WriteString(s, ":")
@ -77,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
} }
} }
// MarshalText formats a stacktrace Frame as a text string. The output is the
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
func (f Frame) MarshalText() ([]byte, error) {
name := f.name()
if name == "unknown" {
return []byte(name), nil
}
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). // StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame type StackTrace []Frame
@ -94,18 +110,32 @@ func (st StackTrace) Format(s fmt.State, verb rune) {
switch { switch {
case s.Flag('+'): case s.Flag('+'):
for _, f := range st { for _, f := range st {
fmt.Fprintf(s, "\n%+v", f) io.WriteString(s, "\n")
f.Format(s, verb)
} }
case s.Flag('#'): case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st)) fmt.Fprintf(s, "%#v", []Frame(st))
default: default:
fmt.Fprintf(s, "%v", []Frame(st)) st.formatSlice(s, verb)
} }
case 's': case 's':
fmt.Fprintf(s, "%s", []Frame(st)) st.formatSlice(s, verb)
} }
} }
// formatSlice will format this StackTrace into the given buffer as a slice of
// Frame, only valid when called with '%s' or '%v'.
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
io.WriteString(s, "[")
for i, f := range st {
if i > 0 {
io.WriteString(s, " ")
}
f.Format(s, verb)
}
io.WriteString(s, "]")
}
// stack represents a stack of program counters. // stack represents a stack of program counters.
type stack []uintptr type stack []uintptr

View File

@ -344,7 +344,7 @@ func (p *parser) expectKeyword(keyword string) {
// PackageId = string_lit . // PackageId = string_lit .
// //
func (p *parser) parsePackageId() string { func (p *parser) parsePackageID() string {
id, err := strconv.Unquote(p.expect(scanner.String)) id, err := strconv.Unquote(p.expect(scanner.String))
if err != nil { if err != nil {
p.error(err) p.error(err)
@ -384,7 +384,7 @@ func (p *parser) parseDotIdent() string {
// //
func (p *parser) parseQualifiedName() (id, name string) { func (p *parser) parseQualifiedName() (id, name string) {
p.expect('@') p.expect('@')
id = p.parsePackageId() id = p.parsePackageID()
p.expect('.') p.expect('.')
// Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields. // Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields.
if p.tok == '?' { if p.tok == '?' {
@ -696,7 +696,7 @@ func (p *parser) parseInterfaceType(parent *types.Package) types.Type {
// Complete requires the type's embedded interfaces to be fully defined, // Complete requires the type's embedded interfaces to be fully defined,
// but we do not define any // but we do not define any
return types.NewInterface(methods, nil).Complete() return newInterface(methods, nil).Complete()
} }
// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . // ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type .
@ -785,7 +785,7 @@ func (p *parser) parseType(parent *types.Package) types.Type {
func (p *parser) parseImportDecl() { func (p *parser) parseImportDecl() {
p.expectKeyword("import") p.expectKeyword("import")
name := p.parsePackageName() name := p.parsePackageName()
p.getPkg(p.parsePackageId(), name) p.getPkg(p.parsePackageID(), name)
} }
// int_lit = [ "+" | "-" ] { "0" ... "9" } . // int_lit = [ "+" | "-" ] { "0" ... "9" } .

View File

@ -6,17 +6,17 @@ package packages
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"go/types" "go/types"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -24,8 +24,6 @@ import (
"unicode" "unicode"
"golang.org/x/tools/go/internal/packagesdriver" "golang.org/x/tools/go/internal/packagesdriver"
"golang.org/x/tools/internal/gopathwalk"
"golang.org/x/tools/internal/semver"
) )
// debug controls verbose logging. // debug controls verbose logging.
@ -44,16 +42,21 @@ type responseDeduper struct {
dr *driverResponse dr *driverResponse
} }
// init fills in r with a driverResponse. func newDeduper() *responseDeduper {
func (r *responseDeduper) init(dr *driverResponse) { return &responseDeduper{
r.dr = dr dr: &driverResponse{},
r.seenRoots = map[string]bool{} seenRoots: map[string]bool{},
r.seenPackages = map[string]*Package{} seenPackages: map[string]*Package{},
}
}
// addAll fills in r with a driverResponse.
func (r *responseDeduper) addAll(dr *driverResponse) {
for _, pkg := range dr.Packages { for _, pkg := range dr.Packages {
r.seenPackages[pkg.ID] = pkg r.addPackage(pkg)
} }
for _, root := range dr.Roots { for _, root := range dr.Roots {
r.seenRoots[root] = true r.addRoot(root)
} }
} }
@ -73,25 +76,47 @@ func (r *responseDeduper) addRoot(id string) {
r.dr.Roots = append(r.dr.Roots, id) r.dr.Roots = append(r.dr.Roots, id)
} }
// goInfo contains global information from the go tool. type golistState struct {
type goInfo struct { cfg *Config
rootDirs map[string]string ctx context.Context
env goEnv
envOnce sync.Once
goEnvError error
goEnv map[string]string
rootsOnce sync.Once
rootDirsError error
rootDirs map[string]string
// vendorDirs caches the (non)existence of vendor directories.
vendorDirs map[string]bool
} }
type goEnv struct { // getEnv returns Go environment variables. Only specific variables are
modulesOn bool // populated -- computing all of them is slow.
func (state *golistState) getEnv() (map[string]string, error) {
state.envOnce.Do(func() {
var b *bytes.Buffer
b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH")
if state.goEnvError != nil {
return
}
state.goEnv = make(map[string]string)
decoder := json.NewDecoder(b)
if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil {
return
}
})
return state.goEnv, state.goEnvError
} }
func determineEnv(cfg *Config) goEnv { // mustGetEnv is a convenience function that can be used if getEnv has already succeeded.
buf, err := invokeGo(cfg, "env", "GOMOD") func (state *golistState) mustGetEnv() map[string]string {
env, err := state.getEnv()
if err != nil { if err != nil {
return goEnv{} panic(fmt.Sprintf("mustGetEnv: %v", err))
} }
gomod := bytes.TrimSpace(buf.Bytes())
env := goEnv{}
env.modulesOn = len(gomod) > 0
return env return env
} }
@ -99,47 +124,38 @@ func determineEnv(cfg *Config) goEnv {
// the build system package structure. // the build system package structure.
// See driver for more details. // See driver for more details.
func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
var sizes types.Sizes // Make sure that any asynchronous go commands are killed when we return.
parentCtx := cfg.Context
if parentCtx == nil {
parentCtx = context.Background()
}
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()
response := newDeduper()
// Fill in response.Sizes asynchronously if necessary.
var sizeserr error var sizeserr error
var sizeswg sync.WaitGroup var sizeswg sync.WaitGroup
if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 {
sizeswg.Add(1) sizeswg.Add(1)
go func() { go func() {
sizes, sizeserr = getSizes(cfg) var sizes types.Sizes
sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg))
// types.SizesFor always returns nil or a *types.StdSizes.
response.dr.Sizes, _ = sizes.(*types.StdSizes)
sizeswg.Done() sizeswg.Done()
}() }()
} }
defer sizeswg.Wait()
// start fetching rootDirs state := &golistState{
var info goInfo cfg: cfg,
var rootDirsReady, envReady = make(chan struct{}), make(chan struct{}) ctx: ctx,
go func() { vendorDirs: map[string]bool{},
info.rootDirs = determineRootDirs(cfg)
close(rootDirsReady)
}()
go func() {
info.env = determineEnv(cfg)
close(envReady)
}()
getGoInfo := func() *goInfo {
<-rootDirsReady
<-envReady
return &info
}
// Ensure that we don't leak goroutines: Load is synchronous, so callers will
// not expect it to access the fields of cfg after the call returns.
defer getGoInfo()
// always pass getGoInfo to golistDriver
golistDriver := func(cfg *Config, patterns ...string) (*driverResponse, error) {
return golistDriver(cfg, getGoInfo, patterns...)
} }
// Determine files requested in contains patterns // Determine files requested in contains patterns
var containFiles []string var containFiles []string
var packagesNamed []string
restPatterns := make([]string, 0, len(patterns)) restPatterns := make([]string, 0, len(patterns))
// Extract file= and other [querytype]= patterns. Report an error if querytype // Extract file= and other [querytype]= patterns. Report an error if querytype
// doesn't exist. // doesn't exist.
@ -155,8 +171,6 @@ extractQueries:
containFiles = append(containFiles, value) containFiles = append(containFiles, value)
case "pattern": case "pattern":
restPatterns = append(restPatterns, value) restPatterns = append(restPatterns, value)
case "iamashamedtousethedisabledqueryname":
packagesNamed = append(packagesNamed, value)
case "": // not a reserved query case "": // not a reserved query
restPatterns = append(restPatterns, pattern) restPatterns = append(restPatterns, pattern)
default: default:
@ -172,52 +186,34 @@ extractQueries:
} }
} }
response := &responseDeduper{}
var err error
// See if we have any patterns to pass through to go list. Zero initial // See if we have any patterns to pass through to go list. Zero initial
// patterns also requires a go list call, since it's the equivalent of // patterns also requires a go list call, since it's the equivalent of
// ".". // ".".
if len(restPatterns) > 0 || len(patterns) == 0 { if len(restPatterns) > 0 || len(patterns) == 0 {
dr, err := golistDriver(cfg, restPatterns...) dr, err := state.createDriverResponse(restPatterns...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
response.init(dr) response.addAll(dr)
} else {
response.init(&driverResponse{})
} }
sizeswg.Wait()
if sizeserr != nil {
return nil, sizeserr
}
// types.SizesFor always returns nil or a *types.StdSizes
response.dr.Sizes, _ = sizes.(*types.StdSizes)
var containsCandidates []string
if len(containFiles) != 0 { if len(containFiles) != 0 {
if err := runContainsQueries(cfg, golistDriver, response, containFiles, getGoInfo); err != nil { if err := state.runContainsQueries(response, containFiles); err != nil {
return nil, err return nil, err
} }
} }
if len(packagesNamed) != 0 { modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
if err := runNamedQueries(cfg, golistDriver, response, packagesNamed); err != nil {
return nil, err
}
}
modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var containsCandidates []string
if len(containFiles) > 0 { if len(containFiles) > 0 {
containsCandidates = append(containsCandidates, modifiedPkgs...) containsCandidates = append(containsCandidates, modifiedPkgs...)
containsCandidates = append(containsCandidates, needPkgs...) containsCandidates = append(containsCandidates, needPkgs...)
} }
if err := addNeededOverlayPackages(cfg, golistDriver, response, needPkgs, getGoInfo); err != nil { if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
return nil, err return nil, err
} }
// Check candidate packages for containFiles. // Check candidate packages for containFiles.
@ -246,28 +242,32 @@ extractQueries:
} }
} }
sizeswg.Wait()
if sizeserr != nil {
return nil, sizeserr
}
return response.dr, nil return response.dr, nil
} }
func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string, getGoInfo func() *goInfo) error { func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error {
if len(pkgs) == 0 { if len(pkgs) == 0 {
return nil return nil
} }
dr, err := driver(cfg, pkgs...) dr, err := state.createDriverResponse(pkgs...)
if err != nil { if err != nil {
return err return err
} }
for _, pkg := range dr.Packages { for _, pkg := range dr.Packages {
response.addPackage(pkg) response.addPackage(pkg)
} }
_, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo) _, needPkgs, err := state.processGolistOverlay(response)
if err != nil { if err != nil {
return err return err
} }
return addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo) return state.addNeededOverlayPackages(response, needPkgs)
} }
func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string, goInfo func() *goInfo) error { func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error {
for _, query := range queries { for _, query := range queries {
// TODO(matloob): Do only one query per directory. // TODO(matloob): Do only one query per directory.
fdir := filepath.Dir(query) fdir := filepath.Dir(query)
@ -277,44 +277,17 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
if err != nil { if err != nil {
return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
} }
dirResponse, err := driver(cfg, pattern) dirResponse, err := state.createDriverResponse(pattern)
if err != nil || (len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1) {
// There was an error loading the package. Try to load the file as an ad-hoc package. // If there was an error loading the package, or the package is returned
// Usually the error will appear in a returned package, but may not if we're in modules mode // with errors, try to load the file as an ad-hoc package.
// and the ad-hoc is located outside a module. // Usually the error will appear in a returned package, but may not if we're
// in module mode and the ad-hoc is located outside a module.
if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 &&
len(dirResponse.Packages[0].Errors) == 1 {
var queryErr error var queryErr error
dirResponse, queryErr = driver(cfg, query) if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil {
if queryErr != nil { return err // return the original error
// Return the original error if the attempt to fall back failed.
return err
}
// If we get nothing back from `go list`, try to make this file into its own ad-hoc package.
if len(dirResponse.Packages) == 0 && queryErr == nil {
dirResponse.Packages = append(dirResponse.Packages, &Package{
ID: "command-line-arguments",
PkgPath: query,
GoFiles: []string{query},
CompiledGoFiles: []string{query},
Imports: make(map[string]*Package),
})
dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments")
}
// Special case to handle issue #33482:
// If this is a file= query for ad-hoc packages where the file only exists on an overlay,
// and exists outside of a module, add the file in for the package.
if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" ||
filepath.ToSlash(dirResponse.Packages[0].PkgPath) == filepath.ToSlash(query)) {
if len(dirResponse.Packages[0].GoFiles) == 0 {
filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
// TODO(matloob): check if the file is outside of a root dir?
for path := range cfg.Overlay {
if path == filename {
dirResponse.Packages[0].Errors = nil
dirResponse.Packages[0].GoFiles = []string{path}
dirResponse.Packages[0].CompiledGoFiles = []string{path}
}
}
}
} }
} }
isRoot := make(map[string]bool, len(dirResponse.Roots)) isRoot := make(map[string]bool, len(dirResponse.Roots))
@ -342,276 +315,47 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
return nil return nil
} }
// modCacheRegexp splits a path in a module cache into module, module version, and package. // adhocPackage attempts to load or construct an ad-hoc package for a given
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) // query, if the original call to the driver produced inadequate results.
func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) {
func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error { response, err := state.createDriverResponse(query)
// calling `go env` isn't free; bail out if there's nothing to do.
if len(queries) == 0 {
return nil
}
// Determine which directories are relevant to scan.
roots, modRoot, err := roots(cfg)
if err != nil { if err != nil {
return err return nil, err
} }
// If we get nothing back from `go list`,
// Scan the selected directories. Simple matches, from GOPATH/GOROOT // try to make this file into its own ad-hoc package.
// or the local module, can simply be "go list"ed. Matches from the // TODO(rstambler): Should this check against the original response?
// module cache need special treatment. if len(response.Packages) == 0 {
var matchesMu sync.Mutex response.Packages = append(response.Packages, &Package{
var simpleMatches, modCacheMatches []string ID: "command-line-arguments",
add := func(root gopathwalk.Root, dir string) { PkgPath: query,
// Walk calls this concurrently; protect the result slices. GoFiles: []string{query},
matchesMu.Lock() CompiledGoFiles: []string{query},
defer matchesMu.Unlock() Imports: make(map[string]*Package),
path := dir
if dir != root.Path {
path = dir[len(root.Path)+1:]
}
if pathMatchesQueries(path, queries) {
switch root.Type {
case gopathwalk.RootModuleCache:
modCacheMatches = append(modCacheMatches, path)
case gopathwalk.RootCurrentModule:
// We'd need to read go.mod to find the full
// import path. Relative's easier.
rel, err := filepath.Rel(cfg.Dir, dir)
if err != nil {
// This ought to be impossible, since
// we found dir in the current module.
panic(err)
}
simpleMatches = append(simpleMatches, "./"+rel)
case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT:
simpleMatches = append(simpleMatches, path)
}
}
}
startWalk := time.Now()
gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug})
cfg.Logf("%v for walk", time.Since(startWalk))
// Weird special case: the top-level package in a module will be in
// whatever directory the user checked the repository out into. It's
// more reasonable for that to not match the package name. So, if there
// are any Go files in the mod root, query it just to be safe.
if modRoot != "" {
rel, err := filepath.Rel(cfg.Dir, modRoot)
if err != nil {
panic(err) // See above.
}
files, err := ioutil.ReadDir(modRoot)
if err != nil {
panic(err) // See above.
}
for _, f := range files {
if strings.HasSuffix(f.Name(), ".go") {
simpleMatches = append(simpleMatches, rel)
break
}
}
}
addResponse := func(r *driverResponse) {
for _, pkg := range r.Packages {
response.addPackage(pkg)
for _, name := range queries {
if pkg.Name == name {
response.addRoot(pkg.ID)
break
}
}
}
}
if len(simpleMatches) != 0 {
resp, err := driver(cfg, simpleMatches...)
if err != nil {
return err
}
addResponse(resp)
}
// Module cache matches are tricky. We want to avoid downloading new
// versions of things, so we need to use the ones present in the cache.
// go list doesn't accept version specifiers, so we have to write out a
// temporary module, and do the list in that module.
if len(modCacheMatches) != 0 {
// Collect all the matches, deduplicating by major version
// and preferring the newest.
type modInfo struct {
mod string
major string
}
mods := make(map[modInfo]string)
var imports []string
for _, modPath := range modCacheMatches {
matches := modCacheRegexp.FindStringSubmatch(modPath)
mod, ver := filepath.ToSlash(matches[1]), matches[2]
importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3]))
major := semver.Major(ver)
if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 {
mods[modInfo{mod, major}] = ver
}
imports = append(imports, importPath)
}
// Build the temporary module.
var gomod bytes.Buffer
gomod.WriteString("module modquery\nrequire (\n")
for mod, version := range mods {
gomod.WriteString("\t" + mod.mod + " " + version + "\n")
}
gomod.WriteString(")\n")
tmpCfg := *cfg
// We're only trying to look at stuff in the module cache, so
// disable the network. This should speed things up, and has
// prevented errors in at least one case, #28518.
tmpCfg.Env = append([]string{"GOPROXY=off"}, cfg.Env...)
var err error
tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery")
if err != nil {
return err
}
defer os.RemoveAll(tmpCfg.Dir)
if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil {
return fmt.Errorf("writing go.mod for module cache query: %v", err)
}
// Run the query, using the import paths calculated from the matches above.
resp, err := driver(&tmpCfg, imports...)
if err != nil {
return fmt.Errorf("querying module cache matches: %v", err)
}
addResponse(resp)
}
return nil
}
func getSizes(cfg *Config) (types.Sizes, error) {
return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg))
}
// roots selects the appropriate paths to walk based on the passed-in configuration,
// particularly the environment and the presence of a go.mod in cfg.Dir's parents.
func roots(cfg *Config) ([]gopathwalk.Root, string, error) {
stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD")
if err != nil {
return nil, "", err
}
fields := strings.Split(stdout.String(), "\n")
if len(fields) != 4 || len(fields[3]) != 0 {
return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String())
}
goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2]
var modDir string
if gomod != "" {
modDir = filepath.Dir(gomod)
}
var roots []gopathwalk.Root
// Always add GOROOT.
roots = append(roots, gopathwalk.Root{
Path: filepath.Join(goroot, "/src"),
Type: gopathwalk.RootGOROOT,
})
// If modules are enabled, scan the module dir.
if modDir != "" {
roots = append(roots, gopathwalk.Root{
Path: modDir,
Type: gopathwalk.RootCurrentModule,
}) })
response.Roots = append(response.Roots, "command-line-arguments")
} }
// Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode. // Handle special cases.
for _, p := range gopath { if len(response.Packages) == 1 {
if modDir != "" { // golang/go#33482: If this is a file= query for ad-hoc packages where
roots = append(roots, gopathwalk.Root{ // the file only exists on an overlay, and exists outside of a module,
Path: filepath.Join(p, "/pkg/mod"), // add the file to the package and remove the errors.
Type: gopathwalk.RootModuleCache, if response.Packages[0].ID == "command-line-arguments" ||
}) filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) {
} else { if len(response.Packages[0].GoFiles) == 0 {
roots = append(roots, gopathwalk.Root{ filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
Path: filepath.Join(p, "/src"), // TODO(matloob): check if the file is outside of a root dir?
Type: gopathwalk.RootGOPATH, for path := range state.cfg.Overlay {
}) if path == filename {
} response.Packages[0].Errors = nil
} response.Packages[0].GoFiles = []string{path}
response.Packages[0].CompiledGoFiles = []string{path}
return roots, modDir, nil }
} }
// These functions were copied from goimports. See further documentation there.
// pathMatchesQueries is adapted from pkgIsCandidate.
// TODO: is it reasonable to do Contains here, rather than an exact match on a path component?
func pathMatchesQueries(path string, queries []string) bool {
lastTwo := lastTwoComponents(path)
for _, query := range queries {
if strings.Contains(lastTwo, query) {
return true
}
if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) {
lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
if strings.Contains(lastTwo, query) {
return true
} }
} }
} }
return false return response, nil
}
// lastTwoComponents returns at most the last two path components
// of v, using either / or \ as the path separator.
func lastTwoComponents(v string) string {
nslash := 0
for i := len(v) - 1; i >= 0; i-- {
if v[i] == '/' || v[i] == '\\' {
nslash++
if nslash == 2 {
return v[i:]
}
}
}
return v
}
func hasHyphenOrUpperASCII(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b == '-' || ('A' <= b && b <= 'Z') {
return true
}
}
return false
}
func lowerASCIIAndRemoveHyphen(s string) (ret string) {
buf := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
b := s[i]
switch {
case b == '-':
continue
case 'A' <= b && b <= 'Z':
buf = append(buf, b+('a'-'A'))
default:
buf = append(buf, b)
}
}
return string(buf)
} }
// Fields must match go list; // Fields must match go list;
@ -656,10 +400,9 @@ func otherFiles(p *jsonPackage) [][]string {
return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
} }
// golistDriver uses the "go list" command to expand the pattern // createDriverResponse uses the "go list" command to expand the pattern
// words and return metadata for the specified packages. dir may be // words and return a response for the specified packages.
// "" and env may be nil, as per os/exec.Command. func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) {
func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driverResponse, error) {
// go list uses the following identifiers in ImportPath and Imports: // go list uses the following identifiers in ImportPath and Imports:
// //
// "p" -- importable package or main (command) // "p" -- importable package or main (command)
@ -673,11 +416,13 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
// Run "go list" for complete // Run "go list" for complete
// information on the specified packages. // information on the specified packages.
buf, err := invokeGo(cfg, "list", golistargs(cfg, words)...) buf, err := state.invokeGo("list", golistargs(state.cfg, words)...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
seen := make(map[string]*jsonPackage) seen := make(map[string]*jsonPackage)
pkgs := make(map[string]*Package)
additionalErrors := make(map[string][]Error)
// Decode the JSON and convert it to Package form. // Decode the JSON and convert it to Package form.
var response driverResponse var response driverResponse
for dec := json.NewDecoder(buf); dec.More(); { for dec := json.NewDecoder(buf); dec.More(); {
@ -708,18 +453,72 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
// contained in a known module or GOPATH entry. This will allow the package to be // contained in a known module or GOPATH entry. This will allow the package to be
// properly "reclaimed" when overlays are processed. // properly "reclaimed" when overlays are processed.
if filepath.IsAbs(p.ImportPath) && p.Error != nil { if filepath.IsAbs(p.ImportPath) && p.Error != nil {
pkgPath, ok := getPkgPath(cfg, p.ImportPath, rootsDirs) pkgPath, ok, err := state.getPkgPath(p.ImportPath)
if err != nil {
return nil, err
}
if ok { if ok {
p.ImportPath = pkgPath p.ImportPath = pkgPath
} }
} }
if old, found := seen[p.ImportPath]; found { if old, found := seen[p.ImportPath]; found {
if !reflect.DeepEqual(p, old) { // If one version of the package has an error, and the other doesn't, assume
return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) // that this is a case where go list is reporting a fake dependency variant
// of the imported package: When a package tries to invalidly import another
// package, go list emits a variant of the imported package (with the same
// import path, but with an error on it, and the package will have a
// DepError set on it). An example of when this can happen is for imports of
// main packages: main packages can not be imported, but they may be
// separately matched and listed by another pattern.
// See golang.org/issue/36188 for more details.
// The plan is that eventually, hopefully in Go 1.15, the error will be
// reported on the importing package rather than the duplicate "fake"
// version of the imported package. Once all supported versions of Go
// have the new behavior this logic can be deleted.
// TODO(matloob): delete the workaround logic once all supported versions of
// Go return the errors on the proper package.
// There should be exactly one version of a package that doesn't have an
// error.
if old.Error == nil && p.Error == nil {
if !reflect.DeepEqual(p, old) {
return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath)
}
continue
} }
// skip the duplicate
continue // Determine if this package's error needs to be bubbled up.
// This is a hack, and we expect for go list to eventually set the error
// on the package.
if old.Error != nil {
var errkind string
if strings.Contains(old.Error.Err, "not an importable package") {
errkind = "not an importable package"
} else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") {
errkind = "use of internal package not allowed"
}
if errkind != "" {
if len(old.Error.ImportStack) < 2 {
return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack with fewer than two elements`, errkind)
}
importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-2]
additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{
Pos: old.Error.Pos,
Msg: old.Error.Err,
Kind: ListError,
})
}
}
// Make sure that if there's a version of the package without an error,
// that's the one reported to the user.
if old.Error == nil {
continue
}
// This package will replace the old one at the end of the loop.
} }
seen[p.ImportPath] = p seen[p.ImportPath] = p
@ -729,6 +528,7 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
OtherFiles: absJoin(p.Dir, otherFiles(p)...), OtherFiles: absJoin(p.Dir, otherFiles(p)...),
forTest: p.ForTest,
} }
// Work around https://golang.org/issue/28749: // Work around https://golang.org/issue/28749:
@ -817,29 +617,37 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
}) })
} }
pkgs[pkg.ID] = pkg
}
for id, errs := range additionalErrors {
if p, ok := pkgs[id]; ok {
p.Errors = append(p.Errors, errs...)
}
}
for _, pkg := range pkgs {
response.Packages = append(response.Packages, pkg) response.Packages = append(response.Packages, pkg)
} }
sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID })
return &response, nil return &response, nil
} }
// getPkgPath finds the package path of a directory if it's relative to a root directory. // getPkgPath finds the package path of a directory if it's relative to a root directory.
func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) { func (state *golistState) getPkgPath(dir string) (string, bool, error) {
absDir, err := filepath.Abs(dir) absDir, err := filepath.Abs(dir)
if err != nil { if err != nil {
cfg.Logf("error getting absolute path of %s: %v", dir, err) return "", false, err
return "", false
} }
for rdir, rpath := range goInfo().rootDirs { roots, err := state.determineRootDirs()
absRdir, err := filepath.Abs(rdir) if err != nil {
if err != nil { return "", false, err
cfg.Logf("error getting absolute path of %s: %v", rdir, err) }
continue
} for rdir, rpath := range roots {
// Make sure that the directory is in the module, // Make sure that the directory is in the module,
// to avoid creating a path relative to another module. // to avoid creating a path relative to another module.
if !strings.HasPrefix(absDir, absRdir) { if !strings.HasPrefix(absDir, rdir) {
cfg.Logf("%s does not have prefix %s", absDir, absRdir)
continue continue
} }
// TODO(matloob): This doesn't properly handle symlinks. // TODO(matloob): This doesn't properly handle symlinks.
@ -854,11 +662,11 @@ func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) {
// Once the file is saved, gopls, or the next invocation of the tool will get the correct // Once the file is saved, gopls, or the next invocation of the tool will get the correct
// result straight from golist. // result straight from golist.
// TODO(matloob): Implement module tiebreaking? // TODO(matloob): Implement module tiebreaking?
return path.Join(rpath, filepath.ToSlash(r)), true return path.Join(rpath, filepath.ToSlash(r)), true, nil
} }
return filepath.ToSlash(r), true return filepath.ToSlash(r), true, nil
} }
return "", false return "", false, nil
} }
// absJoin absolutizes and flattens the lists of files. // absJoin absolutizes and flattens the lists of files.
@ -878,7 +686,7 @@ func golistargs(cfg *Config, words []string) []string {
const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo
fullargs := []string{ fullargs := []string{
"-e", "-json", "-e", "-json",
fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0), fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0),
fmt.Sprintf("-test=%t", cfg.Tests), fmt.Sprintf("-test=%t", cfg.Tests),
fmt.Sprintf("-export=%t", usesExportData(cfg)), fmt.Sprintf("-export=%t", usesExportData(cfg)),
fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0),
@ -893,13 +701,17 @@ func golistargs(cfg *Config, words []string) []string {
} }
// invokeGo returns the stdout of a go command invocation. // invokeGo returns the stdout of a go command invocation.
func invokeGo(cfg *Config, verb string, args ...string) (*bytes.Buffer, error) { func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
cfg := state.cfg
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
goArgs := []string{verb} goArgs := []string{verb}
goArgs = append(goArgs, cfg.BuildFlags...) if verb != "env" {
goArgs = append(goArgs, cfg.BuildFlags...)
}
goArgs = append(goArgs, args...) goArgs = append(goArgs, args...)
cmd := exec.CommandContext(cfg.Context, "go", goArgs...) cmd := exec.CommandContext(state.ctx, "go", goArgs...)
// On darwin the cwd gets resolved to the real path, which breaks anything that // On darwin the cwd gets resolved to the real path, which breaks anything that
// expects the working directory to keep the original path, including the // expects the working directory to keep the original path, including the
// go command when dealing with modules. // go command when dealing with modules.
@ -911,7 +723,7 @@ func invokeGo(cfg *Config, verb string, args ...string) (*bytes.Buffer, error) {
cmd.Stdout = stdout cmd.Stdout = stdout
cmd.Stderr = stderr cmd.Stderr = stderr
defer func(start time.Time) { defer func(start time.Time) {
cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr, stdout) cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, goArgs...), stderr, stdout)
}(time.Now()) }(time.Now())
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
@ -951,7 +763,12 @@ func invokeGo(cfg *Config, verb string, args ...string) (*bytes.Buffer, error) {
!strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r)
} }
if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") {
if strings.HasPrefix(strings.TrimLeftFunc(stderr.String()[len("# "):], isPkgPathRune), "\n") { msg := stderr.String()[len("# "):]
if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") {
return stdout, nil
}
// Treat pkg-config errors as a special case (golang.org/issue/36770).
if strings.HasPrefix(msg, "pkg-config") {
return stdout, nil return stdout, nil
} }
} }

View File

@ -1,12 +1,13 @@
package packages package packages
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"go/parser" "go/parser"
"go/token" "go/token"
"os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
) )
@ -16,7 +17,7 @@ import (
// sometimes incorrect. // sometimes incorrect.
// TODO(matloob): Handle unsupported cases, including the following: // TODO(matloob): Handle unsupported cases, including the following:
// - determining the correct package to add given a new import path // - determining the correct package to add given a new import path
func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func() *goInfo) (modifiedPkgs, needPkgs []string, err error) { func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) {
havePkgs := make(map[string]string) // importPath -> non-test package ID havePkgs := make(map[string]string) // importPath -> non-test package ID
needPkgsSet := make(map[string]bool) needPkgsSet := make(map[string]bool)
modifiedPkgsSet := make(map[string]bool) modifiedPkgsSet := make(map[string]bool)
@ -34,7 +35,23 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
// potentially modifying the transitive set of dependencies). // potentially modifying the transitive set of dependencies).
var overlayAddsImports bool var overlayAddsImports bool
for opath, contents := range cfg.Overlay { // If both a package and its test package are created by the overlay, we
// need the real package first. Process all non-test files before test
// files, and make the whole process deterministic while we're at it.
var overlayFiles []string
for opath := range state.cfg.Overlay {
overlayFiles = append(overlayFiles, opath)
}
sort.Slice(overlayFiles, func(i, j int) bool {
iTest := strings.HasSuffix(overlayFiles[i], "_test.go")
jTest := strings.HasSuffix(overlayFiles[j], "_test.go")
if iTest != jTest {
return !iTest // non-tests are before tests.
}
return overlayFiles[i] < overlayFiles[j]
})
for _, opath := range overlayFiles {
contents := state.cfg.Overlay[opath]
base := filepath.Base(opath) base := filepath.Base(opath)
dir := filepath.Dir(opath) dir := filepath.Dir(opath)
var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant
@ -64,14 +81,8 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
testVariantOf = p testVariantOf = p
continue nextPackage continue nextPackage
} }
// We must have already seen the package of which this is a test variant.
if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath { if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath {
// If we've already seen the test variant,
// make sure to label which package it is a test variant of.
if hasTestFiles(pkg) {
testVariantOf = p
continue nextPackage
}
// If we have already seen the package of which this is a test variant.
if hasTestFiles(p) { if hasTestFiles(p) {
testVariantOf = pkg testVariantOf = pkg
} }
@ -86,7 +97,10 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
if pkg == nil { if pkg == nil {
// Try to find the module or gopath dir the file is contained in. // Try to find the module or gopath dir the file is contained in.
// Then for modules, add the module opath to the beginning. // Then for modules, add the module opath to the beginning.
pkgPath, ok := getPkgPath(cfg, dir, rootDirs) pkgPath, ok, err := state.getPkgPath(dir)
if err != nil {
return nil, nil, err
}
if !ok { if !ok {
break break
} }
@ -114,6 +128,11 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
if isTestFile && !isXTest && testVariantOf != nil { if isTestFile && !isXTest && testVariantOf != nil {
pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
// Add the package under test and its imports to the test variant.
pkg.forTest = testVariantOf.PkgPath
for k, v := range testVariantOf.Imports {
pkg.Imports[k] = &Package{ID: v.ID}
}
} }
} }
} }
@ -130,42 +149,45 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
continue continue
} }
for _, imp := range imports { for _, imp := range imports {
_, found := pkg.Imports[imp] if _, found := pkg.Imports[imp]; found {
if !found { continue
overlayAddsImports = true }
// TODO(matloob): Handle cases when the following block isn't correct. overlayAddsImports = true
// These include imports of vendored packages, etc. id, ok := havePkgs[imp]
id, ok := havePkgs[imp] if !ok {
if !ok { var err error
id = imp id, err = state.resolveImport(dir, imp)
} if err != nil {
pkg.Imports[imp] = &Package{ID: id} return nil, nil, err
// Add dependencies to the non-test variant version of this package as wel.
if testVariantOf != nil {
testVariantOf.Imports[imp] = &Package{ID: id}
} }
} }
pkg.Imports[imp] = &Package{ID: id}
// Add dependencies to the non-test variant version of this package as well.
if testVariantOf != nil {
testVariantOf.Imports[imp] = &Package{ID: id}
}
} }
continue
} }
// toPkgPath tries to guess the package path given the id. // toPkgPath guesses the package path given the id.
// This isn't always correct -- it's certainly wrong for toPkgPath := func(sourceDir, id string) (string, error) {
// vendored packages' paths. if i := strings.IndexByte(id, ' '); i >= 0 {
toPkgPath := func(id string) string { return state.resolveImport(sourceDir, id[:i])
// TODO(matloob): Handle vendor paths.
i := strings.IndexByte(id, ' ')
if i >= 0 {
return id[:i]
} }
return id return state.resolveImport(sourceDir, id)
} }
// Do another pass now that new packages have been created to determine the // Now that new packages have been created, do another pass to determine
// set of missing packages. // the new set of missing packages.
for _, pkg := range response.dr.Packages { for _, pkg := range response.dr.Packages {
for _, imp := range pkg.Imports { for _, imp := range pkg.Imports {
pkgPath := toPkgPath(imp.ID) if len(pkg.GoFiles) == 0 {
return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath)
}
pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID)
if err != nil {
return nil, nil, err
}
if _, ok := havePkgs[pkgPath]; !ok { if _, ok := havePkgs[pkgPath]; !ok {
needPkgsSet[pkgPath] = true needPkgsSet[pkgPath] = true
} }
@ -185,6 +207,52 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
return modifiedPkgs, needPkgs, err return modifiedPkgs, needPkgs, err
} }
// resolveImport finds the the ID of a package given its import path.
// In particular, it will find the right vendored copy when in GOPATH mode.
func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) {
env, err := state.getEnv()
if err != nil {
return "", err
}
if env["GOMOD"] != "" {
return importPath, nil
}
searchDir := sourceDir
for {
vendorDir := filepath.Join(searchDir, "vendor")
exists, ok := state.vendorDirs[vendorDir]
if !ok {
info, err := os.Stat(vendorDir)
exists = err == nil && info.IsDir()
state.vendorDirs[vendorDir] = exists
}
if exists {
vendoredPath := filepath.Join(vendorDir, importPath)
if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() {
// We should probably check for .go files here, but shame on anyone who fools us.
path, ok, err := state.getPkgPath(vendoredPath)
if err != nil {
return "", err
}
if ok {
return path, nil
}
}
}
// We know we've hit the top of the filesystem when we Dir / and get /,
// or C:\ and get C:\, etc.
next := filepath.Dir(searchDir)
if next == searchDir {
break
}
searchDir = next
}
return importPath, nil
}
func hasTestFiles(p *Package) bool { func hasTestFiles(p *Package) bool {
for _, f := range p.GoFiles { for _, f := range p.GoFiles {
if strings.HasSuffix(f, "_test.go") { if strings.HasSuffix(f, "_test.go") {
@ -194,44 +262,59 @@ func hasTestFiles(p *Package) bool {
return false return false
} }
// determineRootDirs returns a mapping from directories code can be contained in to the // determineRootDirs returns a mapping from absolute directories that could
// corresponding import path prefixes of those directories. // contain code to their corresponding import path prefixes.
// Its result is used to try to determine the import path for a package containing func (state *golistState) determineRootDirs() (map[string]string, error) {
// an overlay file. env, err := state.getEnv()
func determineRootDirs(cfg *Config) map[string]string {
// Assume modules first:
out, err := invokeGo(cfg, "list", "-m", "-json", "all")
if err != nil { if err != nil {
return determineRootDirsGOPATH(cfg) return nil, err
}
if env["GOMOD"] != "" {
state.rootsOnce.Do(func() {
state.rootDirs, state.rootDirsError = state.determineRootDirsModules()
})
} else {
state.rootsOnce.Do(func() {
state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH()
})
}
return state.rootDirs, state.rootDirsError
}
func (state *golistState) determineRootDirsModules() (map[string]string, error) {
out, err := state.invokeGo("list", "-m", "-json", "all")
if err != nil {
return nil, err
} }
m := map[string]string{} m := map[string]string{}
type jsonMod struct{ Path, Dir string } type jsonMod struct{ Path, Dir string }
for dec := json.NewDecoder(out); dec.More(); { for dec := json.NewDecoder(out); dec.More(); {
mod := new(jsonMod) mod := new(jsonMod)
if err := dec.Decode(mod); err != nil { if err := dec.Decode(mod); err != nil {
return m // Give up and return an empty map. Package won't be found for overlay. return nil, err
} }
if mod.Dir != "" && mod.Path != "" { if mod.Dir != "" && mod.Path != "" {
// This is a valid module; add it to the map. // This is a valid module; add it to the map.
m[mod.Dir] = mod.Path absDir, err := filepath.Abs(mod.Dir)
if err != nil {
return nil, err
}
m[absDir] = mod.Path
} }
} }
return m return m, nil
} }
func determineRootDirsGOPATH(cfg *Config) map[string]string { func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
m := map[string]string{} m := map[string]string{}
out, err := invokeGo(cfg, "env", "GOPATH") for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) {
if err != nil { absDir, err := filepath.Abs(dir)
// Could not determine root dir mapping. Everything is best-effort, so just return an empty map. if err != nil {
// When we try to find the import path for a directory, there will be no root-dir match and return nil, err
// we'll give up. }
return m m[filepath.Join(absDir, "src")] = ""
} }
for _, p := range filepath.SplitList(string(bytes.TrimSpace(out.Bytes()))) { return m, nil
m[filepath.Join(p, "src")] = ""
}
return m
} }
func extractImports(filename string, contents []byte) ([]string, error) { func extractImports(filename string, contents []byte) ([]string, error) {

View File

@ -23,6 +23,7 @@ import (
"sync" "sync"
"golang.org/x/tools/go/gcexportdata" "golang.org/x/tools/go/gcexportdata"
"golang.org/x/tools/internal/packagesinternal"
) )
// A LoadMode controls the amount of detail to return when loading. // A LoadMode controls the amount of detail to return when loading.
@ -34,6 +35,9 @@ import (
// Load may return more information than requested. // Load may return more information than requested.
type LoadMode int type LoadMode int
// TODO(matloob): When a V2 of go/packages is released, rename NeedExportsFile to
// NeedExportFile to make it consistent with the Package field it's adding.
const ( const (
// NeedName adds Name and PkgPath. // NeedName adds Name and PkgPath.
NeedName LoadMode = 1 << iota NeedName LoadMode = 1 << iota
@ -51,7 +55,7 @@ const (
// NeedDeps adds the fields requested by the LoadMode in the packages in Imports. // NeedDeps adds the fields requested by the LoadMode in the packages in Imports.
NeedDeps NeedDeps
// NeedExportsFile adds ExportsFile. // NeedExportsFile adds ExportFile.
NeedExportsFile NeedExportsFile
// NeedTypes adds Types, Fset, and IllTyped. // NeedTypes adds Types, Fset, and IllTyped.
@ -292,6 +296,15 @@ type Package struct {
// TypesSizes provides the effective size function for types in TypesInfo. // TypesSizes provides the effective size function for types in TypesInfo.
TypesSizes types.Sizes TypesSizes types.Sizes
// forTest is the package under test, if any.
forTest string
}
func init() {
packagesinternal.GetForTest = func(p interface{}) string {
return p.(*Package).forTest
}
} }
// An Error describes a problem with a package's metadata, syntax, or types. // An Error describes a problem with a package's metadata, syntax, or types.
@ -500,12 +513,23 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
if i, found := rootMap[pkg.ID]; found { if i, found := rootMap[pkg.ID]; found {
rootIndex = i rootIndex = i
} }
// Overlays can invalidate export data.
// TODO(matloob): make this check fine-grained based on dependencies on overlaid files
exportDataInvalid := len(ld.Overlay) > 0 || pkg.ExportFile == "" && pkg.PkgPath != "unsafe"
// This package needs type information if the caller requested types and the package is
// either a root, or it's a non-root and the user requested dependencies ...
needtypes := (ld.Mode&NeedTypes|NeedTypesInfo != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0))
// This package needs source if the call requested source (or types info, which implies source)
// and the package is either a root, or itas a non- root and the user requested dependencies...
needsrc := ((ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) ||
// ... or if we need types and the exportData is invalid. We fall back to (incompletely)
// typechecking packages from source if they fail to compile.
(ld.Mode&NeedTypes|NeedTypesInfo != 0 && exportDataInvalid)) && pkg.PkgPath != "unsafe"
lpkg := &loaderPackage{ lpkg := &loaderPackage{
Package: pkg, Package: pkg,
needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0, needtypes: needtypes,
needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0 || needsrc: needsrc,
len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files
pkg.ExportFile == "" && pkg.PkgPath != "unsafe",
} }
ld.pkgs[lpkg.ID] = lpkg ld.pkgs[lpkg.ID] = lpkg
if rootIndex >= 0 { if rootIndex >= 0 {

View File

@ -1,196 +0,0 @@
// Copyright 2016 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 fastwalk provides a faster version of filepath.Walk for file system
// scanning tools.
package fastwalk
import (
"errors"
"os"
"path/filepath"
"runtime"
"sync"
)
// TraverseLink is used as a return value from WalkFuncs to indicate that the
// symlink named in the call may be traversed.
var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory")
// SkipFiles is a used as a return value from WalkFuncs to indicate that the
// callback should not be called for any other files in the current directory.
// Child directories will still be traversed.
var SkipFiles = errors.New("fastwalk: skip remaining files in directory")
// Walk is a faster implementation of filepath.Walk.
//
// filepath.Walk's design necessarily calls os.Lstat on each file,
// even if the caller needs less info.
// Many tools need only the type of each file.
// On some platforms, this information is provided directly by the readdir
// system call, avoiding the need to stat each file individually.
// fastwalk_unix.go contains a fork of the syscall routines.
//
// See golang.org/issue/16399
//
// Walk walks the file tree rooted at root, calling walkFn for
// each file or directory in the tree, including root.
//
// If fastWalk returns filepath.SkipDir, the directory is skipped.
//
// Unlike filepath.Walk:
// * file stat calls must be done by the user.
// The only provided metadata is the file type, which does not include
// any permission bits.
// * multiple goroutines stat the filesystem concurrently. The provided
// walkFn must be safe for concurrent use.
// * fastWalk can follow symlinks if walkFn returns the TraverseLink
// sentinel error. It is the walkFn's responsibility to prevent
// fastWalk from going into symlink cycles.
func Walk(root string, walkFn func(path string, typ os.FileMode) error) error {
// TODO(bradfitz): make numWorkers configurable? We used a
// minimum of 4 to give the kernel more info about multiple
// things we want, in hopes its I/O scheduling can take
// advantage of that. Hopefully most are in cache. Maybe 4 is
// even too low of a minimum. Profile more.
numWorkers := 4
if n := runtime.NumCPU(); n > numWorkers {
numWorkers = n
}
// Make sure to wait for all workers to finish, otherwise
// walkFn could still be called after returning. This Wait call
// runs after close(e.donec) below.
var wg sync.WaitGroup
defer wg.Wait()
w := &walker{
fn: walkFn,
enqueuec: make(chan walkItem, numWorkers), // buffered for performance
workc: make(chan walkItem, numWorkers), // buffered for performance
donec: make(chan struct{}),
// buffered for correctness & not leaking goroutines:
resc: make(chan error, numWorkers),
}
defer close(w.donec)
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go w.doWork(&wg)
}
todo := []walkItem{{dir: root}}
out := 0
for {
workc := w.workc
var workItem walkItem
if len(todo) == 0 {
workc = nil
} else {
workItem = todo[len(todo)-1]
}
select {
case workc <- workItem:
todo = todo[:len(todo)-1]
out++
case it := <-w.enqueuec:
todo = append(todo, it)
case err := <-w.resc:
out--
if err != nil {
return err
}
if out == 0 && len(todo) == 0 {
// It's safe to quit here, as long as the buffered
// enqueue channel isn't also readable, which might
// happen if the worker sends both another unit of
// work and its result before the other select was
// scheduled and both w.resc and w.enqueuec were
// readable.
select {
case it := <-w.enqueuec:
todo = append(todo, it)
default:
return nil
}
}
}
}
}
// doWork reads directories as instructed (via workc) and runs the
// user's callback function.
func (w *walker) doWork(wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-w.donec:
return
case it := <-w.workc:
select {
case <-w.donec:
return
case w.resc <- w.walk(it.dir, !it.callbackDone):
}
}
}
}
type walker struct {
fn func(path string, typ os.FileMode) error
donec chan struct{} // closed on fastWalk's return
workc chan walkItem // to workers
enqueuec chan walkItem // from workers
resc chan error // from workers
}
type walkItem struct {
dir string
callbackDone bool // callback already called; don't do it again
}
func (w *walker) enqueue(it walkItem) {
select {
case w.enqueuec <- it:
case <-w.donec:
}
}
func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error {
joined := dirName + string(os.PathSeparator) + baseName
if typ == os.ModeDir {
w.enqueue(walkItem{dir: joined})
return nil
}
err := w.fn(joined, typ)
if typ == os.ModeSymlink {
if err == TraverseLink {
// Set callbackDone so we don't call it twice for both the
// symlink-as-symlink and the symlink-as-directory later:
w.enqueue(walkItem{dir: joined, callbackDone: true})
return nil
}
if err == filepath.SkipDir {
// Permit SkipDir on symlinks too.
return nil
}
}
return err
}
func (w *walker) walk(root string, runUserCallback bool) error {
if runUserCallback {
err := w.fn(root, os.ModeDir)
if err == filepath.SkipDir {
return nil
}
if err != nil {
return err
}
}
return readDir(root, w.onDirEnt)
}

View File

@ -1,13 +0,0 @@
// Copyright 2016 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 freebsd openbsd netbsd
package fastwalk
import "syscall"
func direntInode(dirent *syscall.Dirent) uint64 {
return uint64(dirent.Fileno)
}

View File

@ -1,14 +0,0 @@
// Copyright 2016 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 linux darwin
// +build !appengine
package fastwalk
import "syscall"
func direntInode(dirent *syscall.Dirent) uint64 {
return uint64(dirent.Ino)
}

View File

@ -1,13 +0,0 @@
// Copyright 2018 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 darwin freebsd openbsd netbsd
package fastwalk
import "syscall"
func direntNamlen(dirent *syscall.Dirent) uint64 {
return uint64(dirent.Namlen)
}

View File

@ -1,29 +0,0 @@
// Copyright 2018 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 linux
// +build !appengine
package fastwalk
import (
"bytes"
"syscall"
"unsafe"
)
func direntNamlen(dirent *syscall.Dirent) uint64 {
const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name))
nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
const nameBufLen = uint16(len(nameBuf))
limit := dirent.Reclen - fixedHdr
if limit > nameBufLen {
limit = nameBufLen
}
nameLen := bytes.IndexByte(nameBuf[:limit], 0)
if nameLen < 0 {
panic("failed to find terminating 0 byte in dirent")
}
return uint64(nameLen)
}

View File

@ -1,37 +0,0 @@
// Copyright 2016 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 appengine !linux,!darwin,!freebsd,!openbsd,!netbsd
package fastwalk
import (
"io/ioutil"
"os"
)
// readDir calls fn for each directory entry in dirName.
// It does not descend into directories or follow symlinks.
// If fn returns a non-nil error, readDir returns with that error
// immediately.
func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
fis, err := ioutil.ReadDir(dirName)
if err != nil {
return err
}
skipFiles := false
for _, fi := range fis {
if fi.Mode().IsRegular() && skipFiles {
continue
}
if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil {
if err == SkipFiles {
skipFiles = true
continue
}
return err
}
}
return nil
}

View File

@ -1,127 +0,0 @@
// Copyright 2016 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 linux darwin freebsd openbsd netbsd
// +build !appengine
package fastwalk
import (
"fmt"
"os"
"syscall"
"unsafe"
)
const blockSize = 8 << 10
// unknownFileMode is a sentinel (and bogus) os.FileMode
// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
fd, err := syscall.Open(dirName, 0, 0)
if err != nil {
return &os.PathError{Op: "open", Path: dirName, Err: err}
}
defer syscall.Close(fd)
// The buffer must be at least a block long.
buf := make([]byte, blockSize) // stack-allocated; doesn't escape
bufp := 0 // starting read position in buf
nbuf := 0 // end valid data in buf
skipFiles := false
for {
if bufp >= nbuf {
bufp = 0
nbuf, err = syscall.ReadDirent(fd, buf)
if err != nil {
return os.NewSyscallError("readdirent", err)
}
if nbuf <= 0 {
return nil
}
}
consumed, name, typ := parseDirEnt(buf[bufp:nbuf])
bufp += consumed
if name == "" || name == "." || name == ".." {
continue
}
// Fallback for filesystems (like old XFS) that don't
// support Dirent.Type and have DT_UNKNOWN (0) there
// instead.
if typ == unknownFileMode {
fi, err := os.Lstat(dirName + "/" + name)
if err != nil {
// It got deleted in the meantime.
if os.IsNotExist(err) {
continue
}
return err
}
typ = fi.Mode() & os.ModeType
}
if skipFiles && typ.IsRegular() {
continue
}
if err := fn(dirName, name, typ); err != nil {
if err == SkipFiles {
skipFiles = true
continue
}
return err
}
}
}
func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) {
// golang.org/issue/15653
dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
}
if len(buf) < int(dirent.Reclen) {
panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
}
consumed = int(dirent.Reclen)
if direntInode(dirent) == 0 { // File absent in directory.
return
}
switch dirent.Type {
case syscall.DT_REG:
typ = 0
case syscall.DT_DIR:
typ = os.ModeDir
case syscall.DT_LNK:
typ = os.ModeSymlink
case syscall.DT_BLK:
typ = os.ModeDevice
case syscall.DT_FIFO:
typ = os.ModeNamedPipe
case syscall.DT_SOCK:
typ = os.ModeSocket
case syscall.DT_UNKNOWN:
typ = unknownFileMode
default:
// Skip weird things.
// It's probably a DT_WHT (http://lwn.net/Articles/325369/)
// or something. Revisit if/when this package is moved outside
// of goimports. goimports only cares about regular files,
// symlinks, and directories.
return
}
nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
nameLen := direntNamlen(dirent)
// Special cases for common things:
if nameLen == 1 && nameBuf[0] == '.' {
name = "."
} else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
name = ".."
} else {
name = string(nameBuf[:nameLen])
}
return
}

View File

@ -1,273 +0,0 @@
// Copyright 2018 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 gopathwalk is like filepath.Walk but specialized for finding Go
// packages, particularly in $GOPATH and $GOROOT.
package gopathwalk
import (
"bufio"
"bytes"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/tools/internal/fastwalk"
)
// Options controls the behavior of a Walk call.
type Options struct {
Debug bool // Enable debug logging
ModulesEnabled bool // Search module caches. Also disables legacy goimports ignore rules.
}
// RootType indicates the type of a Root.
type RootType int
const (
RootUnknown RootType = iota
RootGOROOT
RootGOPATH
RootCurrentModule
RootModuleCache
RootOther
)
// A Root is a starting point for a Walk.
type Root struct {
Path string
Type RootType
}
// SrcDirsRoots returns the roots from build.Default.SrcDirs(). Not modules-compatible.
func SrcDirsRoots(ctx *build.Context) []Root {
var roots []Root
roots = append(roots, Root{filepath.Join(ctx.GOROOT, "src"), RootGOROOT})
for _, p := range filepath.SplitList(ctx.GOPATH) {
roots = append(roots, Root{filepath.Join(p, "src"), RootGOPATH})
}
return roots
}
// Walk walks Go source directories ($GOROOT, $GOPATH, etc) to find packages.
// For each package found, add will be called (concurrently) with the absolute
// paths of the containing source directory and the package directory.
// add will be called concurrently.
func Walk(roots []Root, add func(root Root, dir string), opts Options) {
WalkSkip(roots, add, func(Root, string) bool { return false }, opts)
}
// WalkSkip walks Go source directories ($GOROOT, $GOPATH, etc) to find packages.
// For each package found, add will be called (concurrently) with the absolute
// paths of the containing source directory and the package directory.
// For each directory that will be scanned, skip will be called (concurrently)
// with the absolute paths of the containing source directory and the directory.
// If skip returns false on a directory it will be processed.
// add will be called concurrently.
// skip will be called concurrently.
func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root, dir string) bool, opts Options) {
for _, root := range roots {
walkDir(root, add, skip, opts)
}
}
// walkDir creates a walker and starts fastwalk with this walker.
func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) {
if _, err := os.Stat(root.Path); os.IsNotExist(err) {
if opts.Debug {
log.Printf("skipping nonexistent directory: %v", root.Path)
}
return
}
start := time.Now()
if opts.Debug {
log.Printf("gopathwalk: scanning %s", root.Path)
}
w := &walker{
root: root,
add: add,
skip: skip,
opts: opts,
}
w.init()
if err := fastwalk.Walk(root.Path, w.walk); err != nil {
log.Printf("gopathwalk: scanning directory %v: %v", root.Path, err)
}
if opts.Debug {
log.Printf("gopathwalk: scanned %s in %v", root.Path, time.Since(start))
}
}
// walker is the callback for fastwalk.Walk.
type walker struct {
root Root // The source directory to scan.
add func(Root, string) // The callback that will be invoked for every possible Go package dir.
skip func(Root, string) bool // The callback that will be invoked for every dir. dir is skipped if it returns true.
opts Options // Options passed to Walk by the user.
ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files.
}
// init initializes the walker based on its Options
func (w *walker) init() {
var ignoredPaths []string
if w.root.Type == RootModuleCache {
ignoredPaths = []string{"cache"}
}
if !w.opts.ModulesEnabled && w.root.Type == RootGOPATH {
ignoredPaths = w.getIgnoredDirs(w.root.Path)
ignoredPaths = append(ignoredPaths, "v", "mod")
}
for _, p := range ignoredPaths {
full := filepath.Join(w.root.Path, p)
if fi, err := os.Stat(full); err == nil {
w.ignoredDirs = append(w.ignoredDirs, fi)
if w.opts.Debug {
log.Printf("Directory added to ignore list: %s", full)
}
} else if w.opts.Debug {
log.Printf("Error statting ignored directory: %v", err)
}
}
}
// getIgnoredDirs reads an optional config file at <path>/.goimportsignore
// of relative directories to ignore when scanning for go files.
// The provided path is one of the $GOPATH entries with "src" appended.
func (w *walker) getIgnoredDirs(path string) []string {
file := filepath.Join(path, ".goimportsignore")
slurp, err := ioutil.ReadFile(file)
if w.opts.Debug {
if err != nil {
log.Print(err)
} else {
log.Printf("Read %s", file)
}
}
if err != nil {
return nil
}
var ignoredDirs []string
bs := bufio.NewScanner(bytes.NewReader(slurp))
for bs.Scan() {
line := strings.TrimSpace(bs.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
ignoredDirs = append(ignoredDirs, line)
}
return ignoredDirs
}
// shouldSkipDir reports whether the file should be skipped or not.
func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool {
for _, ignoredDir := range w.ignoredDirs {
if os.SameFile(fi, ignoredDir) {
return true
}
}
if w.skip != nil {
// Check with the user specified callback.
return w.skip(w.root, dir)
}
return false
}
// walk walks through the given path.
func (w *walker) walk(path string, typ os.FileMode) error {
dir := filepath.Dir(path)
if typ.IsRegular() {
if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) {
// Doesn't make sense to have regular files
// directly in your $GOPATH/src or $GOROOT/src.
return fastwalk.SkipFiles
}
if !strings.HasSuffix(path, ".go") {
return nil
}
w.add(w.root, dir)
return fastwalk.SkipFiles
}
if typ == os.ModeDir {
base := filepath.Base(path)
if base == "" || base[0] == '.' || base[0] == '_' ||
base == "testdata" ||
(w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") ||
(!w.opts.ModulesEnabled && base == "node_modules") {
return filepath.SkipDir
}
fi, err := os.Lstat(path)
if err == nil && w.shouldSkipDir(fi, path) {
return filepath.SkipDir
}
return nil
}
if typ == os.ModeSymlink {
base := filepath.Base(path)
if strings.HasPrefix(base, ".#") {
// Emacs noise.
return nil
}
fi, err := os.Lstat(path)
if err != nil {
// Just ignore it.
return nil
}
if w.shouldTraverse(dir, fi) {
return fastwalk.TraverseLink
}
}
return nil
}
// shouldTraverse reports whether the symlink fi, found in dir,
// should be followed. It makes sure symlinks were never visited
// before to avoid symlink loops.
func (w *walker) shouldTraverse(dir string, fi os.FileInfo) bool {
path := filepath.Join(dir, fi.Name())
target, err := filepath.EvalSymlinks(path)
if err != nil {
return false
}
ts, err := os.Stat(target)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return false
}
if !ts.IsDir() {
return false
}
if w.shouldSkipDir(ts, dir) {
return false
}
// Check for symlink loops by statting each directory component
// and seeing if any are the same file as ts.
for {
parent := filepath.Dir(path)
if parent == path {
// Made it to the root without seeing a cycle.
// Use this symlink.
return true
}
parentInfo, err := os.Stat(parent)
if err != nil {
return false
}
if os.SameFile(ts, parentInfo) {
// Cycle. Don't traverse.
return false
}
path = parent
}
}

View File

@ -0,0 +1,4 @@
// Package packagesinternal exposes internal-only fields from go/packages.
package packagesinternal
var GetForTest = func(p interface{}) string { return "" }

View File

@ -1,388 +0,0 @@
// Copyright 2018 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 semver implements comparison of semantic version strings.
// In this package, semantic version strings must begin with a leading "v",
// as in "v1.0.0".
//
// The general form of a semantic version string accepted by this package is
//
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
//
// where square brackets indicate optional parts of the syntax;
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
// using only alphanumeric characters and hyphens; and
// all-numeric PRERELEASE identifiers must not have leading zeros.
//
// This package follows Semantic Versioning 2.0.0 (see semver.org)
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
package semver
// parsed returns the parsed form of a semantic version string.
type parsed struct {
major string
minor string
patch string
short string
prerelease string
build string
err string
}
// IsValid reports whether v is a valid semantic version string.
func IsValid(v string) bool {
_, ok := parse(v)
return ok
}
// Canonical returns the canonical formatting of the semantic version v.
// It fills in any missing .MINOR or .PATCH and discards build metadata.
// Two semantic versions compare equal only if their canonical formattings
// are identical strings.
// The canonical invalid semantic version is the empty string.
func Canonical(v string) string {
p, ok := parse(v)
if !ok {
return ""
}
if p.build != "" {
return v[:len(v)-len(p.build)]
}
if p.short != "" {
return v + p.short
}
return v
}
// Major returns the major version prefix of the semantic version v.
// For example, Major("v2.1.0") == "v2".
// If v is an invalid semantic version string, Major returns the empty string.
func Major(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return v[:1+len(pv.major)]
}
// MajorMinor returns the major.minor version prefix of the semantic version v.
// For example, MajorMinor("v2.1.0") == "v2.1".
// If v is an invalid semantic version string, MajorMinor returns the empty string.
func MajorMinor(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
i := 1 + len(pv.major)
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
return v[:j]
}
return v[:i] + "." + pv.minor
}
// Prerelease returns the prerelease suffix of the semantic version v.
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
// If v is an invalid semantic version string, Prerelease returns the empty string.
func Prerelease(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.prerelease
}
// Build returns the build suffix of the semantic version v.
// For example, Build("v2.1.0+meta") == "+meta".
// If v is an invalid semantic version string, Build returns the empty string.
func Build(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.build
}
// Compare returns an integer comparing two versions according to
// according to semantic version precedence.
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
//
// An invalid semantic version string is considered less than a valid one.
// All invalid semantic version strings compare equal to each other.
func Compare(v, w string) int {
pv, ok1 := parse(v)
pw, ok2 := parse(w)
if !ok1 && !ok2 {
return 0
}
if !ok1 {
return -1
}
if !ok2 {
return +1
}
if c := compareInt(pv.major, pw.major); c != 0 {
return c
}
if c := compareInt(pv.minor, pw.minor); c != 0 {
return c
}
if c := compareInt(pv.patch, pw.patch); c != 0 {
return c
}
return comparePrerelease(pv.prerelease, pw.prerelease)
}
// Max canonicalizes its arguments and then returns the version string
// that compares greater.
func Max(v, w string) string {
v = Canonical(v)
w = Canonical(w)
if Compare(v, w) > 0 {
return v
}
return w
}
func parse(v string) (p parsed, ok bool) {
if v == "" || v[0] != 'v' {
p.err = "missing v prefix"
return
}
p.major, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad major version"
return
}
if v == "" {
p.minor = "0"
p.patch = "0"
p.short = ".0.0"
return
}
if v[0] != '.' {
p.err = "bad minor prefix"
ok = false
return
}
p.minor, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad minor version"
return
}
if v == "" {
p.patch = "0"
p.short = ".0"
return
}
if v[0] != '.' {
p.err = "bad patch prefix"
ok = false
return
}
p.patch, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad patch version"
return
}
if len(v) > 0 && v[0] == '-' {
p.prerelease, v, ok = parsePrerelease(v)
if !ok {
p.err = "bad prerelease"
return
}
}
if len(v) > 0 && v[0] == '+' {
p.build, v, ok = parseBuild(v)
if !ok {
p.err = "bad build"
return
}
}
if v != "" {
p.err = "junk on end"
ok = false
return
}
ok = true
return
}
func parseInt(v string) (t, rest string, ok bool) {
if v == "" {
return
}
if v[0] < '0' || '9' < v[0] {
return
}
i := 1
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
if v[0] == '0' && i != 1 {
return
}
return v[:i], v[i:], true
}
func parsePrerelease(v string) (t, rest string, ok bool) {
// "A pre-release version MAY be denoted by appending a hyphen and
// a series of dot separated identifiers immediately following the patch version.
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
if v == "" || v[0] != '-' {
return
}
i := 1
start := 1
for i < len(v) && v[i] != '+' {
if !isIdentChar(v[i]) && v[i] != '.' {
return
}
if v[i] == '.' {
if start == i || isBadNum(v[start:i]) {
return
}
start = i + 1
}
i++
}
if start == i || isBadNum(v[start:i]) {
return
}
return v[:i], v[i:], true
}
func parseBuild(v string) (t, rest string, ok bool) {
if v == "" || v[0] != '+' {
return
}
i := 1
start := 1
for i < len(v) {
if !isIdentChar(v[i]) {
return
}
if v[i] == '.' {
if start == i {
return
}
start = i + 1
}
i++
}
if start == i {
return
}
return v[:i], v[i:], true
}
func isIdentChar(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
}
func isBadNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v) && i > 1 && v[0] == '0'
}
func isNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v)
}
func compareInt(x, y string) int {
if x == y {
return 0
}
if len(x) < len(y) {
return -1
}
if len(x) > len(y) {
return +1
}
if x < y {
return -1
} else {
return +1
}
}
func comparePrerelease(x, y string) int {
// "When major, minor, and patch are equal, a pre-release version has
// lower precedence than a normal version.
// Example: 1.0.0-alpha < 1.0.0.
// Precedence for two pre-release versions with the same major, minor,
// and patch version MUST be determined by comparing each dot separated
// identifier from left to right until a difference is found as follows:
// identifiers consisting of only digits are compared numerically and
// identifiers with letters or hyphens are compared lexically in ASCII
// sort order. Numeric identifiers always have lower precedence than
// non-numeric identifiers. A larger set of pre-release fields has a
// higher precedence than a smaller set, if all of the preceding
// identifiers are equal.
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
if x == y {
return 0
}
if x == "" {
return +1
}
if y == "" {
return -1
}
for x != "" && y != "" {
x = x[1:] // skip - or .
y = y[1:] // skip - or .
var dx, dy string
dx, x = nextIdent(x)
dy, y = nextIdent(y)
if dx != dy {
ix := isNum(dx)
iy := isNum(dy)
if ix != iy {
if ix {
return -1
} else {
return +1
}
}
if ix {
if len(dx) < len(dy) {
return -1
}
if len(dx) > len(dy) {
return +1
}
}
if dx < dy {
return -1
} else {
return +1
}
}
}
if x == "" {
return -1
} else {
return +1
}
}
func nextIdent(x string) (dx, rest string) {
i := 0
for i < len(x) && x[i] != '.' {
i++
}
return x[:i], x[i:]
}

14
vendor/modules.txt vendored
View File

@ -9,7 +9,7 @@ github.com/PuerkitoBio/urlesc
github.com/containerd/containerd/errdefs github.com/containerd/containerd/errdefs
# github.com/davecgh/go-spew v1.1.1 # github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew github.com/davecgh/go-spew/spew
# github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017 # github.com/docker/cli v0.0.0-20200303162255-7d407207c304
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/configfile
github.com/docker/cli/cli/config/credentials github.com/docker/cli/cli/config/credentials
@ -84,7 +84,7 @@ github.com/google/go-cmp/cmp/internal/diff
github.com/google/go-cmp/cmp/internal/flags 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
# github.com/google/go-containerregistry v0.0.0-20200212224832-c629a66d7231 # github.com/google/go-containerregistry v0.0.0-20200310013544-4fe717a9b4cb
github.com/google/go-containerregistry/pkg/authn github.com/google/go-containerregistry/pkg/authn
github.com/google/go-containerregistry/pkg/internal/retry github.com/google/go-containerregistry/pkg/internal/retry
github.com/google/go-containerregistry/pkg/internal/retry/wait github.com/google/go-containerregistry/pkg/internal/retry/wait
@ -156,7 +156,7 @@ github.com/opencontainers/image-spec/specs-go/v1
github.com/pelletier/go-toml github.com/pelletier/go-toml
# github.com/peterbourgon/diskv v2.0.1+incompatible # github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/peterbourgon/diskv github.com/peterbourgon/diskv
# github.com/pkg/errors v0.8.1 # github.com/pkg/errors v0.9.1
github.com/pkg/errors github.com/pkg/errors
# github.com/sirupsen/logrus v1.4.2 # github.com/sirupsen/logrus v1.4.2
github.com/sirupsen/logrus github.com/sirupsen/logrus
@ -187,7 +187,7 @@ golang.org/x/net/proxy
# golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 # golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/oauth2 golang.org/x/oauth2
golang.org/x/oauth2/internal golang.org/x/oauth2/internal
# golang.org/x/sync v0.0.0-20190423024810-112230192c58 # golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sync/errgroup golang.org/x/sync/errgroup
golang.org/x/sync/semaphore golang.org/x/sync/semaphore
# golang.org/x/sys v0.0.0-20191010194322-b09406accb47 # golang.org/x/sys v0.0.0-20191010194322-b09406accb47
@ -207,14 +207,12 @@ golang.org/x/text/unicode/norm
golang.org/x/text/width golang.org/x/text/width
# golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 # golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
golang.org/x/time/rate golang.org/x/time/rate
# golang.org/x/tools v0.0.0-20200115192306-3ded1b734dda # golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17
golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/gcexportdata
golang.org/x/tools/go/internal/gcimporter golang.org/x/tools/go/internal/gcimporter
golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/go/internal/packagesdriver
golang.org/x/tools/go/packages golang.org/x/tools/go/packages
golang.org/x/tools/internal/fastwalk golang.org/x/tools/internal/packagesinternal
golang.org/x/tools/internal/gopathwalk
golang.org/x/tools/internal/semver
# google.golang.org/appengine v1.5.0 # google.golang.org/appengine v1.5.0
google.golang.org/appengine/internal google.golang.org/appengine/internal
google.golang.org/appengine/internal/base google.golang.org/appengine/internal/base