forked from toolshed/abra
Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bb1aea435 | ||
|
|
ed1203c4ab | ||
|
|
5b42b3d306 | ||
|
|
d99e86fb0e | ||
|
|
ade646abbc | ||
|
|
80b3b90905 | ||
|
|
fab3f0b97b | ||
|
|
3819ee38f5 | ||
|
|
74261a7a01 | ||
|
|
ca39ff6e29 | ||
|
|
5d00b3c932 | ||
|
|
a380b5e8ae | ||
|
|
9ff5024a58 | ||
|
|
d6dd082e59 | ||
|
|
0531f30590 | ||
|
|
27ca0cd21e | ||
|
|
920a898955 | ||
|
|
7cbc974763 | ||
|
|
c71b764546 | ||
|
|
5db8430a73 | ||
|
|
091c4a2ac1 | ||
|
|
8e935d08a6 | ||
|
|
b959c55852 | ||
|
|
bab9b21882 | ||
|
|
703fbf5c8a | ||
|
|
aaaf855a6e | ||
|
|
a6666be477 | ||
|
|
ba90b04624 | ||
|
|
debbdde5d1 | ||
|
|
48f62f541f | ||
|
|
e6296e4ce4 | ||
|
|
423650e8b5 | ||
|
|
d0c42e37a7 | ||
|
|
6afd17f1ed | ||
|
|
cb0eec2203 | ||
|
|
f5900b726b | ||
|
|
0d9e777c18 | ||
|
|
f5cc61c211 | ||
|
|
786ba43b83 | ||
|
|
ee80cfa27f | ||
|
|
bbaca64d36 | ||
|
|
b608ee3985 | ||
|
|
5e72e28ba6 | ||
|
|
84b18c1a7b | ||
|
|
249851a556 | ||
|
|
344b4c8d3d |
@@ -0,0 +1 @@
|
||||
vendor/** linguist-generated=true
|
||||
@@ -17,7 +17,7 @@ export GOPRIVATE=coopcloud.tech
|
||||
all: format check build
|
||||
|
||||
run:
|
||||
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
||||
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA) $(ARGS)
|
||||
|
||||
install:
|
||||
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
||||
@@ -40,11 +40,17 @@ check:
|
||||
(echo "gofmt: formatting issue - run 'make format' to resolve" && exit 1)
|
||||
|
||||
test:
|
||||
@go test ./... -cover -v -p 1
|
||||
@go test ./... -short -cover -v -p 1
|
||||
|
||||
integration-test:
|
||||
@go test ./... -short=false -failfast -cover -v -p 1 -count=1
|
||||
|
||||
find-tests:
|
||||
@find . -name "*_test.go"
|
||||
|
||||
find-integration-test:
|
||||
@find . -iname "*_e2e_test.go"
|
||||
|
||||
loc:
|
||||
@find . -name "*.go" | xargs wc -l
|
||||
|
||||
|
||||
@@ -19,6 +19,5 @@ var (
|
||||
Minor bool
|
||||
NoDomainChecks bool
|
||||
Patch bool
|
||||
PinDigests bool
|
||||
ShowUnchanged bool
|
||||
)
|
||||
|
||||
+2
-29
@@ -293,31 +293,12 @@ interface.`),
|
||||
}
|
||||
}
|
||||
if upgradeTag != "skip" {
|
||||
var ok bool
|
||||
var err error
|
||||
var resolvedDigest string
|
||||
|
||||
if internal.PinDigests {
|
||||
digest, dErr := client.GetImageDigest(img, upgradeTag)
|
||||
if dErr != nil {
|
||||
log.Fatal(i18n.G("failed to resolve digest for %s:%s: %s", image, upgradeTag, dErr.Error()))
|
||||
}
|
||||
resolvedDigest = digest
|
||||
|
||||
ok, err = recipe.UpdatePinnedTag(image, upgradeTag, digest)
|
||||
} else {
|
||||
ok, err = recipe.UpdateTag(image, upgradeTag)
|
||||
}
|
||||
|
||||
ok, err := recipe.UpdateTag(image, upgradeTag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if ok {
|
||||
if internal.PinDigests {
|
||||
log.Info(i18n.G("tag upgraded and pinned from %s to %s@%s for %s", tag.String(), upgradeTag, resolvedDigest, image))
|
||||
} else {
|
||||
log.Info(i18n.G("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image))
|
||||
}
|
||||
log.Info(i18n.G("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image))
|
||||
}
|
||||
} else {
|
||||
if !internal.NoInput {
|
||||
@@ -436,12 +417,4 @@ func init() {
|
||||
false,
|
||||
i18n.G("commit changes"),
|
||||
)
|
||||
|
||||
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||
&internal.PinDigests,
|
||||
i18n.G("pindigests"),
|
||||
i18n.GC("p", "pin container image by digest"),
|
||||
false,
|
||||
i18n.G("pin the container image version by manifest digest (image:tag@sha256:...)"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
packages = with pkgs; [
|
||||
go_1_26
|
||||
gnumake
|
||||
(bats.withLibraries (libraries: [
|
||||
libraries.bats-support
|
||||
libraries.bats-assert
|
||||
libraries.bats-file
|
||||
]))
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,15 +13,14 @@ require (
|
||||
github.com/docker/cli v28.4.0+incompatible
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/go-git/go-git/v5 v5.19.2
|
||||
github.com/go-git/go-git/v5 v5.19.3
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/moby/sys/signal v0.7.1
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/regclient/regclient v0.11.5
|
||||
github.com/schollz/progressbar/v3 v3.19.1
|
||||
golang.org/x/term v0.45.0
|
||||
golang.org/x/term v0.47.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
)
|
||||
@@ -60,7 +59,7 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.2 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
@@ -73,8 +72,7 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
@@ -96,7 +94,7 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.13 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.7.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
@@ -105,7 +103,6 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
@@ -124,10 +121,10 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/crypto v0.56.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
@@ -151,11 +148,11 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/sys v0.49.0
|
||||
)
|
||||
|
||||
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
|
||||
|
||||
@@ -388,12 +388,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||
github.com/go-git/go-billy/v5 v5.9.2 h1:OXFSRyz4g20upsGDJgQG9Bak1l/ZEv8GHVYB52O71sE=
|
||||
github.com/go-git/go-billy/v5 v5.9.2/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
||||
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
||||
github.com/go-git/go-git/v5 v5.19.3 h1:qHttbZ+Am7wEjdTpR1XMQ4rl42FK9SIXzZ4o9x7s6M4=
|
||||
github.com/go-git/go-git/v5 v5.19.3/go.mod h1:Ye4C8dVigkqrv9UsB4NMdSLV4yAIkLiIhW61gcOyhl4=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -426,8 +426,6 @@ github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
@@ -585,10 +583,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -700,8 +696,6 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
|
||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/olareg/olareg v0.2.1 h1:RPHGIaqlVWPbKAsOYUj7e2WfEEW5M7F8I6hKMrwd4jU=
|
||||
github.com/olareg/olareg v0.2.1/go.mod h1:dhr8QetC7U7jJ2m93oxDhEEOKCRbPgOK1oGyKfB4QNo=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -756,8 +750,8 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pjbgf/sha1cd v0.7.0 h1:ZRNPKHj+gfkLBf0KJv/p1Hmz+7bqCT5o311YX0nq+DA=
|
||||
github.com/pjbgf/sha1cd v0.7.0/go.mod h1:pKR5Li+qTCo+ebqITZfwPlJGoCFCvSO00qxjbNtTUFQ=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -798,8 +792,6 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/regclient/regclient v0.11.5 h1:OHRsXO0F3qHGfa4HEUv+EkMH9NXNcCTBKjNzyC/UhIA=
|
||||
github.com/regclient/regclient v0.11.5/go.mod h1:DZUOfIT14WFTK2Pj4vjd93avy9O4Fdpjrf9ir23TbRE=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
@@ -863,8 +855,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
github.com/sudo-bmitch/oci-digest v0.1.2 h1:are0qzWTsFZGZ3Uvdi9OSztJszSWaab6iqquMEEB7rw=
|
||||
github.com/sudo-bmitch/oci-digest v0.1.2/go.mod h1:SH6l5OIe0islKBZBedjiPOeET/0QwGL+/oYfQt51uQo=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
@@ -876,8 +866,6 @@ github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnH
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
@@ -975,8 +963,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1052,8 +1040,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1148,13 +1136,13 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
|
||||
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1164,8 +1152,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -9,12 +9,8 @@ import (
|
||||
"github.com/containers/image/docker"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/regclient/regclient"
|
||||
"github.com/regclient/regclient/types/ref"
|
||||
)
|
||||
|
||||
var rc = regclient.New()
|
||||
|
||||
// GetRegistryTags retrieves all tags of an image from a container registry.
|
||||
func GetRegistryTags(img reference.Named) ([]string, error) {
|
||||
var tags []string
|
||||
@@ -32,23 +28,3 @@ func GetRegistryTags(img reference.Named) ([]string, error) {
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// GetImageDigest resolves the content digest (sha256:...) for a specific
|
||||
// image:tag reference by querying the registry manifest. This gives us a
|
||||
// digest to pin to while keeping support for multi-arch deployments: when
|
||||
// the tag points at a manifest list/OCI index, the digest returned is the
|
||||
// digest of that top-level index, so the pin still resolves correctly
|
||||
// regardless of the deploying host's architecture.
|
||||
func GetImageDigest(img reference.Named, tag string) (string, error) {
|
||||
r, err := ref.New(fmt.Sprintf("%s:%s", img.Name(), tag))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing reference %s:%s: %w", img.Name(), tag, err)
|
||||
}
|
||||
|
||||
m, err := rc.ManifestHead(context.Background(), r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fetching manifest for %s:%s: %w", img.Name(), tag, err)
|
||||
}
|
||||
|
||||
return m.GetDescriptor().Digest.String(), nil
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ func SmallSHA(hash string) string {
|
||||
return hash[:8]
|
||||
}
|
||||
|
||||
// RemoveSha remove image sha (digest suffix) from a string that are added in some docker outputs
|
||||
// e.g., "image:tag@sha256:..." -> "image:tag"
|
||||
// RemoveSha remove image sha from a string that are added in some docker outputs
|
||||
func RemoveSha(str string) string {
|
||||
return strings.Split(str, "@")[0]
|
||||
}
|
||||
|
||||
@@ -6,13 +6,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemoveSha(t *testing.T) {
|
||||
assert.Equal(t, "ubuntu:latest", RemoveSha("ubuntu:latest@sha256:1234567890abcdef"))
|
||||
assert.Equal(t, "ubuntu:latest", RemoveSha("ubuntu:latest"))
|
||||
assert.Equal(t, "my-repo/my-image:v1", RemoveSha("my-repo/my-image:v1@sha256:abcdef1234567890"))
|
||||
assert.Equal(t, "my-repo/my-image", RemoveSha("my-repo/my-image@sha256:abcdef1234567890"))
|
||||
}
|
||||
|
||||
func TestBoldDirtyDefault(t *testing.T) {
|
||||
assert.Equal(t, "foo", BoldDirtyDefault("foo"))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ func Clone(dir, url string) error {
|
||||
URL: url,
|
||||
Tags: git.AllTags,
|
||||
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
|
||||
SingleBranch: true,
|
||||
})
|
||||
|
||||
if err != nil && gitCloneIgnoreErr(err) {
|
||||
@@ -71,7 +70,6 @@ func Clone(dir, url string) error {
|
||||
URL: url,
|
||||
Tags: git.AllTags,
|
||||
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
|
||||
SingleBranch: true,
|
||||
})
|
||||
|
||||
if err != nil && gitCloneIgnoreErr(err) {
|
||||
|
||||
+36
-36
@@ -7,7 +7,7 @@
|
||||
msgid ""
|
||||
msgstr "Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-06-14 17:56+0200\n"
|
||||
"POT-Creation-Date: 2026-08-31 19:21+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -189,7 +189,7 @@ msgstr ""
|
||||
msgid "%d volumes removed successfully"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:197
|
||||
#: ./pkg/recipe/git.go:198
|
||||
#, c-format
|
||||
msgid "%s (%s) has locally unstaged changes?"
|
||||
msgstr ""
|
||||
@@ -419,7 +419,7 @@ msgstr ""
|
||||
msgid "%s sanitised as %s for new app"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:445
|
||||
#: ./pkg/recipe/git.go:451
|
||||
#, c-format
|
||||
msgid "%s service is missing image tag?"
|
||||
msgstr ""
|
||||
@@ -902,7 +902,7 @@ msgstr ""
|
||||
msgid "Generate the recipe catalogue"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:459
|
||||
#: ./pkg/recipe/git.go:465
|
||||
#, c-format
|
||||
msgid "GetRecipeVersions encountered error for %s: %s (collected %d versions)"
|
||||
msgstr ""
|
||||
@@ -1815,7 +1815,7 @@ msgstr ""
|
||||
msgid "are you sure?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:162
|
||||
#: ./pkg/recipe/git.go:163
|
||||
#, c-format
|
||||
msgid "attempting to checkout '%s' as chaos commit"
|
||||
msgstr ""
|
||||
@@ -1951,7 +1951,7 @@ msgstr ""
|
||||
msgid "cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/catalogue/catalogue.go:59 ./pkg/recipe/git.go:251
|
||||
#: ./pkg/catalogue/catalogue.go:59 ./pkg/recipe/git.go:252
|
||||
#, c-format
|
||||
msgid "cannot ensure %s is up-to-date, no git remotes configured"
|
||||
msgstr ""
|
||||
@@ -1966,7 +1966,7 @@ msgstr ""
|
||||
msgid "cannot get label %s for %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:58
|
||||
#: ./pkg/recipe/git.go:59
|
||||
#, c-format
|
||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||
msgstr ""
|
||||
@@ -2118,7 +2118,7 @@ msgstr ""
|
||||
msgid "cmd"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:470
|
||||
#: ./pkg/recipe/git.go:476
|
||||
#, c-format
|
||||
msgid "collected %s for %s"
|
||||
msgstr ""
|
||||
@@ -2456,7 +2456,7 @@ msgstr ""
|
||||
msgid "destination directory does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:373
|
||||
#: ./pkg/recipe/git.go:379
|
||||
#, c-format
|
||||
msgid "detected %s as tags for recipe %s"
|
||||
msgstr ""
|
||||
@@ -2653,7 +2653,7 @@ msgstr ""
|
||||
msgid "ensure recipe: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:56
|
||||
#: ./pkg/recipe/git.go:57
|
||||
#, c-format
|
||||
msgid "ensuring env version %s"
|
||||
msgstr ""
|
||||
@@ -2751,12 +2751,12 @@ msgstr ""
|
||||
msgid "failed to check git status of %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/branch.go:95 ./pkg/recipe/git.go:231
|
||||
#: ./pkg/git/branch.go:95 ./pkg/recipe/git.go:232
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:412
|
||||
#: ./pkg/recipe/git.go:418
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s: %s"
|
||||
msgstr ""
|
||||
@@ -2806,7 +2806,7 @@ msgstr ""
|
||||
msgid "failed to generate random bytes: %w"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:421
|
||||
#: ./pkg/recipe/git.go:427
|
||||
#, c-format
|
||||
msgid "failed to get compose config for %s: %s"
|
||||
msgstr ""
|
||||
@@ -2844,7 +2844,7 @@ msgstr ""
|
||||
msgid "failed to parse image %s, saw: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:431
|
||||
#: ./pkg/recipe/git.go:437
|
||||
#, c-format
|
||||
msgid "failed to parse image for %s in %s: %s"
|
||||
msgstr ""
|
||||
@@ -2952,7 +2952,7 @@ msgstr ""
|
||||
msgid "fetch all recipes"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/catalogue/catalogue.go:84 ./pkg/recipe/git.go:284
|
||||
#: ./pkg/catalogue/catalogue.go:84 ./pkg/recipe/git.go:290
|
||||
#, c-format
|
||||
msgid "fetched latest git changes for %s"
|
||||
msgstr ""
|
||||
@@ -3070,12 +3070,12 @@ msgstr ""
|
||||
msgid "git changes pushed"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:417
|
||||
#: ./pkg/recipe/git.go:423
|
||||
#, c-format
|
||||
msgid "git checkout: %s in %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:64 ./pkg/git/clone.go:102
|
||||
#: ./pkg/git/clone.go:63 ./pkg/git/clone.go:100
|
||||
#, c-format
|
||||
msgid "git clone %s: cancelled due to interrupt"
|
||||
msgstr ""
|
||||
@@ -3085,17 +3085,17 @@ msgstr ""
|
||||
msgid "git clone: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:89
|
||||
#: ./pkg/git/clone.go:87
|
||||
#, c-format
|
||||
msgid "git clone: %s already exists"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:59 ./pkg/git/clone.go:78 ./pkg/git/clone.go:87
|
||||
#: ./pkg/git/clone.go:58 ./pkg/git/clone.go:76 ./pkg/git/clone.go:85
|
||||
#, c-format
|
||||
msgid "git clone: %s cloned successfully"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:68
|
||||
#: ./pkg/git/clone.go:67
|
||||
msgid "git clone: main branch failed, attempting master branch"
|
||||
msgstr ""
|
||||
|
||||
@@ -3150,7 +3150,7 @@ msgstr ""
|
||||
msgid "git.coopcloud.tech repo exists"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:384
|
||||
#: ./pkg/recipe/git.go:390
|
||||
#, c-format
|
||||
msgid "git: opening repository in %s"
|
||||
msgstr ""
|
||||
@@ -4061,7 +4061,7 @@ msgstr ""
|
||||
msgid "proceed?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:404
|
||||
#: ./pkg/recipe/git.go:410
|
||||
#, c-format
|
||||
msgid "processing %s for %s"
|
||||
msgstr ""
|
||||
@@ -4129,7 +4129,7 @@ msgstr ""
|
||||
msgid "re"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:157
|
||||
#: ./pkg/recipe/git.go:158
|
||||
#, c-format
|
||||
msgid "read %s as tags for recipe %s"
|
||||
msgstr ""
|
||||
@@ -4865,17 +4865,17 @@ msgstr ""
|
||||
msgid "skipping secret (because it already exists) on %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:413
|
||||
#: ./pkg/recipe/git.go:419
|
||||
#, c-format
|
||||
msgid "skipping tag %s: checkout failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:422
|
||||
#: ./pkg/recipe/git.go:428
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid compose config: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:432
|
||||
#: ./pkg/recipe/git.go:438
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid image reference in service %s: %s"
|
||||
msgstr ""
|
||||
@@ -4978,7 +4978,7 @@ msgstr ""
|
||||
msgid "succeeded"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:184
|
||||
#: ./pkg/recipe/git.go:185
|
||||
#, c-format
|
||||
msgid "successfully checked %s out to %s in %s"
|
||||
msgstr ""
|
||||
@@ -5138,17 +5138,17 @@ msgstr ""
|
||||
msgid "un"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:193
|
||||
#: ./pkg/recipe/git.go:194
|
||||
#, c-format
|
||||
msgid "unable to check git clean status in %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:262
|
||||
#: ./pkg/recipe/git.go:263
|
||||
#, c-format
|
||||
msgid "unable to check out default branch in %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:100
|
||||
#: ./pkg/git/clone.go:98
|
||||
#, c-format
|
||||
msgid "unable to clean up git clone of %s: %s"
|
||||
msgstr ""
|
||||
@@ -5222,7 +5222,7 @@ msgstr ""
|
||||
msgid "unable to discover SSH remote for %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:268
|
||||
#: ./pkg/recipe/git.go:274
|
||||
#, c-format
|
||||
msgid "unable to fetch tags in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5232,7 +5232,7 @@ msgstr ""
|
||||
msgid "unable to get container matching %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:280
|
||||
#: ./pkg/recipe/git.go:286
|
||||
#, c-format
|
||||
msgid "unable to git pull in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5261,12 +5261,12 @@ msgstr ""
|
||||
msgid "unable to look up server context for %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/recipe/fetch.go:77 ./pkg/git/read.go:26 ./pkg/lint/recipe.go:491 ./pkg/recipe/git.go:242
|
||||
#: ./cli/recipe/fetch.go:77 ./pkg/git/read.go:26 ./pkg/lint/recipe.go:491 ./pkg/recipe/git.go:243
|
||||
#, c-format
|
||||
msgid "unable to open %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:257
|
||||
#: ./pkg/recipe/git.go:258
|
||||
#, c-format
|
||||
msgid "unable to open git work tree in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5326,7 +5326,7 @@ msgstr ""
|
||||
msgid "unable to read new env %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:247
|
||||
#: ./pkg/recipe/git.go:248
|
||||
#, c-format
|
||||
msgid "unable to read remotes in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5361,7 +5361,7 @@ msgstr ""
|
||||
msgid "unable to reset commit after failed release attempt: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:166
|
||||
#: ./pkg/recipe/git.go:167
|
||||
#, c-format
|
||||
msgid "unable to resolve '%s': %s"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
+167
-53
@@ -1,5 +1,17 @@
|
||||
msgid ""
|
||||
msgstr "Project-Id-Version: \nReport-Msgid-Bugs-To: EMAIL\nPOT-Creation-Date: 2026-06-14 17:56+0200\nPO-Revision-Date: 2026-08-11 06:51+0000\nLast-Translator: chasqui <chasqui@cryptolab.net>\nLanguage-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\nLanguage: es\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 5.12.2\n"
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-08-31 19:21+0000\n"
|
||||
"PO-Revision-Date: 2026-09-01 22:35+0000\n"
|
||||
"Last-Translator: chasqui <chasqui@cryptolab.net>\n"
|
||||
"Language-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
|
||||
#: cli/app/cp.go:38
|
||||
msgid ""
|
||||
@@ -100,7 +112,26 @@ msgid ""
|
||||
"\n"
|
||||
" # list apps of all servers which match a specific recipe\n"
|
||||
" abra app ls -r gitea"
|
||||
msgstr " # Listar todas las aplicaciones instaladas con mayor detalle\n abra aplicacion listar --estado\n\n # Listar todas las aplicaciones \"wordpress\" (puedes probar otras recetas)\n abra aplicacion listar --receta wordpress\n\n # Listar todas las aplicaciones \"wordpress\" con mayor detalle\n abra aplicacion listar --receta wordpress --estado\n\n # Listar todas las aplicaciones instaladas con mayor detalle usando los comandos abreviados\n abra app ls -S"
|
||||
msgstr ""
|
||||
" OPCIONES Y FLAGS\n"
|
||||
" ────────────────\n"
|
||||
" ejemplos de uso:\n"
|
||||
"\n"
|
||||
"┌─ Información detallada ─────────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion listar --estado │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Aplicaciones de una receta ────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion listar --receta wordpress │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Receta + información detallada ────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion listar --receta wordpress --estado │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Forma abreviada ───────────────────────────────────────────┐\n"
|
||||
"│ $ abra app ls -S │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘"
|
||||
|
||||
#: cli/app/move.go:59
|
||||
msgid ""
|
||||
@@ -213,7 +244,21 @@ msgstr " abra aplicacion entorno listar \"nombre_app.dominio\""
|
||||
|
||||
#: cli/app/remove.go:45
|
||||
msgid " abra app remove 1312.net"
|
||||
msgstr " abra aplicacion borrar \"nombre_app.dominio\""
|
||||
msgstr ""
|
||||
" EJEMPLOS DE USO\n"
|
||||
" ────────────────\n"
|
||||
"\n"
|
||||
"┌─ Borrar una aplicación ─────────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion borrar \"nombre_app.dominio\" │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Borrar sin confirmación ───────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion borrar \"nombre_app.dominio\" --forzar │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Borrar sin confirmación usando comando abreviado ──────────┐\n"
|
||||
"│ $ abra app rm \"nombre_app.dominio\" -f │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘"
|
||||
|
||||
#: cli/app/secret.go:346
|
||||
msgid " abra app secret rm 1312.net oauth_key"
|
||||
@@ -269,7 +314,7 @@ msgstr "%d volumen(es) eliminados con éxito"
|
||||
msgid "%d volumes removed successfully"
|
||||
msgstr "%d volúmenes eliminados correctamente"
|
||||
|
||||
#: pkg/recipe/git.go:197
|
||||
#: pkg/recipe/git.go:198
|
||||
#, c-format
|
||||
msgid "%s (%s) has locally unstaged changes?"
|
||||
msgstr "%s (%s) ¿tiene cambios locales?"
|
||||
@@ -502,7 +547,7 @@ msgstr "%s requiere inserción de secretos antes del despliegue (#generate=false
|
||||
msgid "%s sanitised as %s for new app"
|
||||
msgstr "%s sanitisado como %s para nueva aplicación"
|
||||
|
||||
#: pkg/recipe/git.go:445
|
||||
#: pkg/recipe/git.go:451
|
||||
#, c-format
|
||||
msgid "%s service is missing image tag?"
|
||||
msgstr "¿falta la etiqueta de imagen del servicio %s?"
|
||||
@@ -1084,7 +1129,26 @@ msgid ""
|
||||
"Generate a report of all managed apps.\n"
|
||||
"\n"
|
||||
"Use \"--status/-S\" flag to query all servers for the live deployment status."
|
||||
msgstr "BIENVENIDE AL MANUAL DE USO COMANDO \"LISTAR\"\n\nEl comando \"abra aplicacion listar\" permite consultar de forma rápida qué aplicaciones están siendo gestionadas. \nAl ejecutarlo, Abra genera un informe con la lista de aplicaciones disponibles y la información básica de cada una.\nPuedes imaginar el comando listar como una forma de decirle a Abra: «muéstrame qué aplicaciones tengo»\n\nLos comandos pueden aceptar opciones y flags que permiten modificar o ampliar la información que muestran, \nen este caso es bastante útil el uso de \"--estado\". Estas opciones se escriben después del comando siguiendo \nla sintaxis detallada más adelante."
|
||||
msgstr ""
|
||||
"┌──────────────────────────────────────────────────────────────┐\n"
|
||||
"│ MANUAL DE USO · COMANDO «LISTAR» │\n"
|
||||
"└──────────────────────────────────────────────────────────────┘\n"
|
||||
" DESCRIPCIÓN\n"
|
||||
" ───────────\n"
|
||||
" El comando:\n"
|
||||
"\n"
|
||||
" abra aplicacion listar\n"
|
||||
"\n"
|
||||
"Permite consultar rápidamente qué aplicaciones están siendo\n"
|
||||
"gestionadas por Abra.\n"
|
||||
"\n"
|
||||
"Al ejecutarlo, Abra genera un informe con la lista de\n"
|
||||
"aplicaciones disponibles y la información básica de cada una.\n"
|
||||
"\n"
|
||||
"En pocas palabras, puedes imaginar `listar` como una forma\n"
|
||||
"de decirle a Abra:\n"
|
||||
"\n"
|
||||
" «muéstrame qué aplicaciones tengo»"
|
||||
|
||||
#: cli/app/new.go:321
|
||||
msgid "Generate app secrets?"
|
||||
@@ -1110,7 +1174,7 @@ msgstr "Genera 🥷 secretos (contraseñas) automáticamente"
|
||||
msgid "Generate the recipe catalogue"
|
||||
msgstr "📋 Muestra el catálogo de 📜 recetas"
|
||||
|
||||
#: pkg/recipe/git.go:459
|
||||
#: pkg/recipe/git.go:465
|
||||
#, c-format
|
||||
msgid "GetRecipeVersions encountered error for %s: %s (collected %d versions)"
|
||||
msgstr "GetRecipeVersions encontró un error para %s: %s (recopiladas %d versiones)"
|
||||
@@ -1452,24 +1516,48 @@ msgid ""
|
||||
"To delete everything without prompt, use the \"--force/-f\" or the \"--no-input/n\"\n"
|
||||
"flag."
|
||||
msgstr ""
|
||||
"Elimina todo lo relacionado con una aplicación que ya está plegada.\n"
|
||||
"┌──────────────────────────────────────────────────────────────┐\n"
|
||||
"│ MANUAL DE USO · COMANDO «BORRAR» │\n"
|
||||
"└──────────────────────────────────────────────────────────────┘\n"
|
||||
" DESCRIPCIÓN\n"
|
||||
" ───────────\n"
|
||||
" El comando:\n"
|
||||
"\n"
|
||||
"Por defecto, pedirá confirmación antes de continuar. Se borrarán \n"
|
||||
"todos los secretos, volúmenes y el archivo .env local de la aplicación.\n"
|
||||
" abra aplicacion borrar \"nombre_app.dominio\"\n"
|
||||
"\n"
|
||||
"Ejecuta este comando solo si estás seguro de querer quitar por \n"
|
||||
"completo la aplicación y todos sus datos asociados. Esta acción \n"
|
||||
"es destructiva. ¡Ten cuidado!\n"
|
||||
"elimina todo lo relacionado con una aplicación que ya está\n"
|
||||
"desplegada.\n"
|
||||
"\n"
|
||||
"Si quieres borrar volúmenes o secretos específicos, \n"
|
||||
"usa los subcomandos de eliminación bajo \"aplicación volumen\" \n"
|
||||
"y \"aplicación secreto\" en su lugar.\n"
|
||||
"Por defecto, Abra pedirá confirmación antes de continuar.\n"
|
||||
"\n"
|
||||
"Ten en cuenta que si borras el archivo .env local sin eliminar primero \n"
|
||||
"volúmenes y secretos, Abra *no* podrá ayudarte a eliminarlos después.\n"
|
||||
"La eliminación incluye:\n"
|
||||
"\n"
|
||||
"Para borrar todo sin confirmación, usa la opción \"--forzar/-f\" \n"
|
||||
"o la opción \"--no-input/-n\"."
|
||||
" ├── secretos\n"
|
||||
" ├── volúmenes\n"
|
||||
" └── archivo .env local\n"
|
||||
"\n"
|
||||
"\n"
|
||||
" ¡ATENCIÓN!\n"
|
||||
" ──────────\n"
|
||||
"Esta es una acción destructiva.\n"
|
||||
"\n"
|
||||
"La aplicación y todos los datos asociados serán eliminados.\n"
|
||||
"Ejecuta este comando únicamente si estás seguro de querer\n"
|
||||
"quitar la aplicación por completo.\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"Si quieres eliminar volúmenes o secretos específicos, utiliza\n"
|
||||
"los subcomandos correspondientes:\n"
|
||||
"\n"
|
||||
" abra aplicacion volumen\n"
|
||||
" abra aplicacion secreto\n"
|
||||
"\n"
|
||||
"\n"
|
||||
" IMPORTANTE\n"
|
||||
" ──────────\n"
|
||||
"Si eliminas el archivo .env local sin eliminar primero los\n"
|
||||
"volúmenes y secretos, Abra no podrá ayudarte a eliminarlos\n"
|
||||
"después."
|
||||
|
||||
#. translators: Short description for `app volume remove` command
|
||||
#: cli/app/volume.go:90
|
||||
@@ -1998,7 +2086,33 @@ msgid ""
|
||||
" {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n"
|
||||
"\n"
|
||||
"Use \"{{.CommandPath}} [command] --help\" for more information about a command.{{end}}\n"
|
||||
msgstr "Sintaxis del comando:{{if .Runnable}}\n {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}\n\nComando y comando abreviado:\n {{.NameAndAliases}}{{end}}{{if .HasExample}}\n\nUSO DE\"OPCIONES\" y \"FLAGS\":\nImportante: 1312.net es únicamente un ejemplo utilizado para mostrar cómo debe configurarse este campo. No es necesario utilizar este nombre.\nAl ejecutar el comando, reemplaza 1312.net por el nombre o dominio real de tu aplicación.\n\n{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n\nComandos disponibles:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\n\nOpciones:\n{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\n\nOpciones globales:\n{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}\n\nTemas de ayuda adicionales:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n\nUse \"{{.CommandPath}} [command] --help\" para más información sobre un comando.{{end}}\n"
|
||||
msgstr ""
|
||||
"COMANDO ABREVIADO\n"
|
||||
"─────────────────\n"
|
||||
" {{range .Aliases}}{{.}}{{end}}\n"
|
||||
"\n"
|
||||
"{{if .HasExample}}\n"
|
||||
"{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n"
|
||||
"\n"
|
||||
"Comandos disponibles:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n"
|
||||
" {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasHelpSubCommands}}\n"
|
||||
"\n"
|
||||
"Temas de ayuda adicionales:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n"
|
||||
" {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}\n"
|
||||
"\n"
|
||||
" SINTAXIS\n"
|
||||
" ────────{{if .Runnable}}\n"
|
||||
" {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n"
|
||||
" {{.CommandPath}} [command]{{end}}\n"
|
||||
"\n"
|
||||
"Opciones:{{if .HasAvailableLocalFlags}}\n"
|
||||
"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\n"
|
||||
"\n"
|
||||
"Opciones globales:{{if .HasAvailableInheritedFlags}}\n"
|
||||
"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\n"
|
||||
"\n"
|
||||
"{{if .HasAvailableSubCommands}}\n"
|
||||
"Use \"{{.CommandPath}} [comando] --help\" para más información sobre un comando.{{end}}\n"
|
||||
|
||||
#: cli/recipe/fetch.go:28
|
||||
msgid "Using \"--force/-f\" Git syncs an existing recipe. It does not erase unstaged changes."
|
||||
@@ -2296,7 +2410,7 @@ msgstr "¿A la aplicación le falta la etiqueta de imagen?"
|
||||
msgid "are you sure?"
|
||||
msgstr "¿Estás segurx?"
|
||||
|
||||
#: pkg/recipe/git.go:162
|
||||
#: pkg/recipe/git.go:163
|
||||
#, c-format
|
||||
msgid "attempting to checkout '%s' as chaos commit"
|
||||
msgstr "Intentando extraer '%s' como el commit de caos"
|
||||
@@ -2439,7 +2553,7 @@ msgstr "no se pueden leer las recetas locales: %s"
|
||||
msgid "cancelled"
|
||||
msgstr "cancelado"
|
||||
|
||||
#: pkg/catalogue/catalogue.go:59 pkg/recipe/git.go:251
|
||||
#: pkg/catalogue/catalogue.go:59 pkg/recipe/git.go:252
|
||||
#, c-format
|
||||
msgid "cannot ensure %s is up-to-date, no git remotes configured"
|
||||
msgstr "no se puede asegurar que %s esté actualizado, no hay remotos de git configurados"
|
||||
@@ -2454,7 +2568,7 @@ msgstr "no se puede encontrar la aplicación con el nombre %s"
|
||||
msgid "cannot get label %s for %s"
|
||||
msgstr "no se puede obtener la etiqueta %s para %s"
|
||||
|
||||
#: pkg/recipe/git.go:58
|
||||
#: pkg/recipe/git.go:59
|
||||
#, c-format
|
||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||
msgstr "no se puede redeplegar la versión anterior de caos (%s), ¿Era tu intención usar \"--caos\"?"
|
||||
@@ -2614,7 +2728,7 @@ msgstr "tarea: Actualizar tags de una imagen"
|
||||
msgid "cmd"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/recipe/git.go:470
|
||||
#: pkg/recipe/git.go:476
|
||||
#, c-format
|
||||
msgid "collected %s for %s"
|
||||
msgstr "colectado %s para %s"
|
||||
@@ -2953,7 +3067,7 @@ msgstr "tiempo de espera agotado 🟠"
|
||||
msgid "destination directory does not exist"
|
||||
msgstr "el directorio de destino no existe"
|
||||
|
||||
#: pkg/recipe/git.go:373
|
||||
#: pkg/recipe/git.go:379
|
||||
#, c-format
|
||||
msgid "detected %s as tags for recipe %s"
|
||||
msgstr "detectado(s) %s como etiquetas para la receta %s"
|
||||
@@ -3150,7 +3264,7 @@ msgstr "asegúrate que \"image: ...\" esté establecido en todos los servicios"
|
||||
msgid "ensure recipe: %s"
|
||||
msgstr "asegura receta: %s"
|
||||
|
||||
#: pkg/recipe/git.go:56
|
||||
#: pkg/recipe/git.go:57
|
||||
#, c-format
|
||||
msgid "ensuring env version %s"
|
||||
msgstr "asegurando la versión del entorno (env) %s"
|
||||
@@ -3252,12 +3366,12 @@ msgstr "🛑 error al agregar notas de la versión: %s"
|
||||
msgid "failed to check git status of %s: %s"
|
||||
msgstr "🛑 error al comprobar el estado de git de %s: %s"
|
||||
|
||||
#: pkg/git/branch.go:95 pkg/recipe/git.go:231
|
||||
#: pkg/git/branch.go:95 pkg/recipe/git.go:232
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s"
|
||||
msgstr "🛑 error al cambiar a %s en %s"
|
||||
|
||||
#: pkg/recipe/git.go:412
|
||||
#: pkg/recipe/git.go:418
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s: %s"
|
||||
msgstr "🛑 error al cambiar a %s en %s: %s"
|
||||
@@ -3307,7 +3421,7 @@ msgstr "🛑 error al crear el volumen %s en %s: %s"
|
||||
msgid "failed to generate random bytes: %w"
|
||||
msgstr "🛑 error al generar bytes aleatorios: %w"
|
||||
|
||||
#: pkg/recipe/git.go:421
|
||||
#: pkg/recipe/git.go:427
|
||||
#, c-format
|
||||
msgid "failed to get compose config for %s: %s"
|
||||
msgstr "🛑 error al obtener la configuración para %s: %s"
|
||||
@@ -3345,7 +3459,7 @@ msgstr "🛑 error al migrar la configuración de la aplicación: %s"
|
||||
msgid "failed to parse image %s, saw: %s"
|
||||
msgstr "🛑 error al analizar la imagen %s; detectado: %s"
|
||||
|
||||
#: pkg/recipe/git.go:431
|
||||
#: pkg/recipe/git.go:437
|
||||
#, c-format
|
||||
msgid "failed to parse image for %s in %s: %s"
|
||||
msgstr "🛑 error al analizar la imagen %s; en: %s: %s"
|
||||
@@ -3453,7 +3567,7 @@ msgstr "obtener [receta | --todos] [opciones]"
|
||||
msgid "fetch all recipes"
|
||||
msgstr "Obtener todas las recetas"
|
||||
|
||||
#: pkg/catalogue/catalogue.go:84 pkg/recipe/git.go:284
|
||||
#: pkg/catalogue/catalogue.go:84 pkg/recipe/git.go:290
|
||||
#, c-format
|
||||
msgid "fetched latest git changes for %s"
|
||||
msgstr "Se obtuvieron los últimos cambios de Git para %s"
|
||||
@@ -3573,12 +3687,12 @@ msgstr "cambios de git confirmados"
|
||||
msgid "git changes pushed"
|
||||
msgstr "cambios de git enviados (push)"
|
||||
|
||||
#: pkg/recipe/git.go:417
|
||||
#: pkg/recipe/git.go:423
|
||||
#, c-format
|
||||
msgid "git checkout: %s in %s"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/git/clone.go:64 pkg/git/clone.go:102
|
||||
#: pkg/git/clone.go:63 pkg/git/clone.go:100
|
||||
#, c-format
|
||||
msgid "git clone %s: cancelled due to interrupt"
|
||||
msgstr "git clone %s: cancelado por interrupción"
|
||||
@@ -3588,17 +3702,17 @@ msgstr "git clone %s: cancelado por interrupción"
|
||||
msgid "git clone: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/git/clone.go:89
|
||||
#: pkg/git/clone.go:87
|
||||
#, c-format
|
||||
msgid "git clone: %s already exists"
|
||||
msgstr "git clone: %s ya existe"
|
||||
|
||||
#: pkg/git/clone.go:59 pkg/git/clone.go:78 pkg/git/clone.go:87
|
||||
#: pkg/git/clone.go:58 pkg/git/clone.go:76 pkg/git/clone.go:85
|
||||
#, c-format
|
||||
msgid "git clone: %s cloned successfully"
|
||||
msgstr "git clone: %s clonado correctamente"
|
||||
|
||||
#: pkg/git/clone.go:68
|
||||
#: pkg/git/clone.go:67
|
||||
msgid "git clone: main branch failed, attempting master branch"
|
||||
msgstr "git clone: error en la rama principal; intentando la rama master"
|
||||
|
||||
@@ -3653,7 +3767,7 @@ msgstr "git-nombre"
|
||||
msgid "git.coopcloud.tech repo exists"
|
||||
msgstr "el repositorio git.coopcloud.tech existe"
|
||||
|
||||
#: pkg/recipe/git.go:384
|
||||
#: pkg/recipe/git.go:390
|
||||
#, c-format
|
||||
msgid "git: opening repository in %s"
|
||||
msgstr "git: abriendo repositorio en %s"
|
||||
@@ -4591,7 +4705,7 @@ msgstr "mostrar salida legible por máquina"
|
||||
msgid "proceed?"
|
||||
msgstr "¿procedemos?"
|
||||
|
||||
#: pkg/recipe/git.go:404
|
||||
#: pkg/recipe/git.go:410
|
||||
#, c-format
|
||||
msgid "processing %s for %s"
|
||||
msgstr "procesando %s para %s"
|
||||
@@ -4661,7 +4775,7 @@ msgstr ""
|
||||
msgid "re"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/recipe/git.go:157
|
||||
#: pkg/recipe/git.go:158
|
||||
#, c-format
|
||||
msgid "read %s as tags for recipe %s"
|
||||
msgstr "leer %s como etiquetas para la receta %s"
|
||||
@@ -5407,17 +5521,17 @@ msgstr "omitiendo generación de %s (generate=false)"
|
||||
msgid "skipping secret (because it already exists) on %s: %s"
|
||||
msgstr "omitiendo secreto (porque ya existe) en %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:413
|
||||
#: pkg/recipe/git.go:419
|
||||
#, c-format
|
||||
msgid "skipping tag %s: checkout failed: %s"
|
||||
msgstr "omitiendo la etiqueta %s: error en el checkout: %s"
|
||||
|
||||
#: pkg/recipe/git.go:422
|
||||
#: pkg/recipe/git.go:428
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid compose config: %s"
|
||||
msgstr "omitiendo la etiqueta %s: configuración de compose inválida: %s"
|
||||
|
||||
#: pkg/recipe/git.go:432
|
||||
#: pkg/recipe/git.go:438
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid image reference in service %s: %s"
|
||||
msgstr "omitiendo la etiqueta %s: referencia de imagen inválida en el servicio %s: %s"
|
||||
@@ -5520,7 +5634,7 @@ msgstr "recortado %s a %s para el análisis"
|
||||
msgid "succeeded"
|
||||
msgstr "exitoso"
|
||||
|
||||
#: pkg/recipe/git.go:184
|
||||
#: pkg/recipe/git.go:185
|
||||
#, c-format
|
||||
msgid "successfully checked %s out to %s in %s"
|
||||
msgstr "se comprobó correctamente %s de %s en %s"
|
||||
@@ -5682,17 +5796,17 @@ msgstr ""
|
||||
msgid "un"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/recipe/git.go:193
|
||||
#: pkg/recipe/git.go:194
|
||||
#, c-format
|
||||
msgid "unable to check git clean status in %s: %s"
|
||||
msgstr "no se pudo comprobar el estado de git clean en %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:262
|
||||
#: pkg/recipe/git.go:263
|
||||
#, c-format
|
||||
msgid "unable to check out default branch in %s: %s"
|
||||
msgstr "no se pudo cambiar a la rama predeterminada en %s: %s"
|
||||
|
||||
#: pkg/git/clone.go:100
|
||||
#: pkg/git/clone.go:98
|
||||
#, c-format
|
||||
msgid "unable to clean up git clone of %s: %s"
|
||||
msgstr "no se pudo limpiar la clonación de Git de %s: %s"
|
||||
@@ -5766,7 +5880,7 @@ msgstr "no se pudieron determinar las reglas de versionado de %s; listando todas
|
||||
msgid "unable to discover SSH remote for %s"
|
||||
msgstr "no se pudo detectar el remoto SSH para %s"
|
||||
|
||||
#: pkg/recipe/git.go:268
|
||||
#: pkg/recipe/git.go:274
|
||||
#, c-format
|
||||
msgid "unable to fetch tags in %s: %s"
|
||||
msgstr "no se pudieron obtener las etiquetas en %s: %s"
|
||||
@@ -5776,7 +5890,7 @@ msgstr "no se pudieron obtener las etiquetas en %s: %s"
|
||||
msgid "unable to get container matching %s: %s"
|
||||
msgstr "no se pudo obtener el contenedor que coincide con %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:280
|
||||
#: pkg/recipe/git.go:286
|
||||
#, c-format
|
||||
msgid "unable to git pull in %s: %s"
|
||||
msgstr "no se pudo hacer git pull en %s: %s"
|
||||
@@ -5806,12 +5920,12 @@ msgid "unable to look up server context for %s: %s"
|
||||
msgstr "no se pudo obtener el contexto del servidor para %s: %s"
|
||||
|
||||
#: cli/recipe/fetch.go:77 pkg/git/read.go:26 pkg/lint/recipe.go:491
|
||||
#: pkg/recipe/git.go:242
|
||||
#: pkg/recipe/git.go:243
|
||||
#, c-format
|
||||
msgid "unable to open %s: %s"
|
||||
msgstr "no se pudo abrir %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:257
|
||||
#: pkg/recipe/git.go:258
|
||||
#, c-format
|
||||
msgid "unable to open git work tree in %s: %s"
|
||||
msgstr "no se pudo abrir el git work tree en %s: %s"
|
||||
@@ -5871,7 +5985,7 @@ msgstr "no se pudo consultar el estado de %s: %s"
|
||||
msgid "unable to read new env %s: %s"
|
||||
msgstr "no se pudo leer el nuevo env %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:247
|
||||
#: pkg/recipe/git.go:248
|
||||
#, c-format
|
||||
msgid "unable to read remotes in %s: %s"
|
||||
msgstr "no se pudieron leer los remotos en %s: %s"
|
||||
@@ -5907,7 +6021,7 @@ msgstr "no se pudo renderizar a JSON: %s"
|
||||
msgid "unable to reset commit after failed release attempt: %s"
|
||||
msgstr "no se pudo establecer los streams de IO como terminal raw: %s"
|
||||
|
||||
#: pkg/recipe/git.go:166
|
||||
#: pkg/recipe/git.go:167
|
||||
#, c-format
|
||||
msgid "unable to resolve '%s': %s"
|
||||
msgstr "no se pudo resolver '%s': %s"
|
||||
|
||||
+1
-67
@@ -122,7 +122,6 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
|
||||
log.Debug(i18n.G("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")))
|
||||
|
||||
updated := false
|
||||
for _, composeFile := range composeFiles {
|
||||
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
||||
|
||||
@@ -167,10 +166,6 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
|
||||
old := fmt.Sprintf("%s:%s", composeImage, composeTag)
|
||||
new := fmt.Sprintf("%s:%s", composeImage, tag)
|
||||
if old == new {
|
||||
continue
|
||||
}
|
||||
|
||||
replacedBytes := strings.Replace(string(bytes), old, new, -1)
|
||||
|
||||
log.Debug(i18n.G("updating %s to %s in %s", old, new, compose.Filename))
|
||||
@@ -178,72 +173,11 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
if err := os.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil {
|
||||
return false, err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// UpdatePinnedTag updates an image reference to a pinned version (image:tag@sha256:...) in-place on local compose files.
|
||||
func (r Recipe) UpdatePinnedTag(image, tag, digest string) (bool, error) {
|
||||
glob := fmt.Sprintf("%s/compose**yml", r.Dir)
|
||||
image = formatter.StripTagMeta(image)
|
||||
|
||||
composeFiles, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
updated := false
|
||||
for _, composeFile := range composeFiles {
|
||||
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
||||
|
||||
sampleEnv, err := r.SampleEnv()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
compose, err := loader.LoadComposefile(opts, sampleEnv)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, service := range compose.Services {
|
||||
if service.Image == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
img, _ := reference.ParseNormalizedNamed(formatter.StripDigest(service.Image))
|
||||
composeImage := formatter.StripTagMeta(reference.Path(img))
|
||||
|
||||
if image == composeImage {
|
||||
bytes, err := ioutil.ReadFile(composeFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
old := service.Image
|
||||
new := fmt.Sprintf("%s:%s@%s", composeImage, tag, digest)
|
||||
|
||||
if old == new {
|
||||
continue
|
||||
}
|
||||
|
||||
replacedBytes := strings.Replace(string(bytes), old, new, -1)
|
||||
|
||||
log.Debug(i18n.G("updating pinned reference %s to %s in %s", old, new, compose.Filename))
|
||||
|
||||
if err := os.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil {
|
||||
return false, err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// UpdateLabel updates a label in-place on file system local compose files.
|
||||
|
||||
+7
-1
@@ -16,6 +16,7 @@ import (
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/go-git/go-git/v5"
|
||||
gitCfg "github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
@@ -262,7 +263,12 @@ func (r Recipe) EnsureUpToDate() error {
|
||||
return errors.New(i18n.G("unable to check out default branch in %s: %s", r.Dir, err))
|
||||
}
|
||||
|
||||
fetchOpts := &git.FetchOptions{Tags: git.AllTags}
|
||||
// the refspec is passed explicitly, because a repository cloned by an older abra has a
|
||||
// single-branch refspec stored in its config and would otherwise never see other branches
|
||||
fetchOpts := &git.FetchOptions{
|
||||
Tags: git.AllTags,
|
||||
RefSpecs: []gitCfg.RefSpec{"+refs/heads/*:refs/remotes/origin/*"},
|
||||
}
|
||||
if err := repo.Fetch(fetchOpts); err != nil {
|
||||
if !strings.Contains(err.Error(), "already up-to-date") {
|
||||
return errors.New(i18n.G("unable to fetch tags in %s: %s", r.Dir, err))
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: nginx:1.31.4
|
||||
image: nginx:1.31.6
|
||||
secrets:
|
||||
- test_pass_one
|
||||
- test_pass_two
|
||||
|
||||
@@ -122,6 +122,39 @@ teardown(){
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "create new app with commit from another branch" {
|
||||
branchHash=$(_get_other_branch_hash)
|
||||
if [[ -z "$branchHash" ]]; then
|
||||
skip "$TEST_RECIPE has no branch besides main"
|
||||
fi
|
||||
|
||||
# re-clone the recipe the way an older abra did, with a single-branch refspec. --no-local
|
||||
# forces a real transfer, a local clone would hardlink the whole object database and leave
|
||||
# the commit reachable
|
||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE"
|
||||
assert_success
|
||||
|
||||
run git clone -q --no-local --single-branch --branch main \
|
||||
"$ABRA_DIR/origin-recipes/$TEST_RECIPE.git" "$ABRA_DIR/recipes/$TEST_RECIPE"
|
||||
assert_success
|
||||
|
||||
# the commit has to be genuinely missing, otherwise this test passes for the wrong reason
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --verify "$branchHash^{commit}"
|
||||
assert_failure
|
||||
|
||||
run $ABRA app new "$TEST_RECIPE" "$branchHash" \
|
||||
--no-input \
|
||||
--server "$TEST_SERVER" \
|
||||
--domain "$TEST_APP_DOMAIN"
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
|
||||
# the recipe names itself in TYPE, which differs per branch, so only the version is checked
|
||||
run grep -q "TYPE=.*:${branchHash}$" \
|
||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "does not overwrite existing env files" {
|
||||
run $ABRA app new "$TEST_RECIPE" \
|
||||
--no-input \
|
||||
|
||||
@@ -56,6 +56,13 @@ _get_tag_hash() {
|
||||
echo $(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-list -n 1 "$1")
|
||||
}
|
||||
|
||||
_get_other_branch_hash() {
|
||||
# asked from the origin mirror, not from the recipe checkout, so that the result does not
|
||||
# depend on what has been fetched. empty when the recipe only has a default branch
|
||||
echo $(git ls-remote "$ABRA_DIR/origin-recipes/$TEST_RECIPE.git" \
|
||||
| grep 'refs/heads/' | grep -v 'refs/heads/main$' | head -1 | cut -f1)
|
||||
}
|
||||
|
||||
_get_head_hash() {
|
||||
echo $(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H" HEAD)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: nginx:1.31.4
|
||||
image: nginx:1.31.6
|
||||
networks:
|
||||
- proxy
|
||||
deploy:
|
||||
|
||||
+4
@@ -329,6 +329,10 @@ func (c *Config) unmarshalCore() {
|
||||
if parsed := parseConfigBool(s.Options.Get(protectHFSKey)); parsed.IsSet() {
|
||||
c.Core.ProtectHFS = parsed
|
||||
}
|
||||
|
||||
if s.Options.Get(repositoryFormatVersionKey) == string(format.Version_1) {
|
||||
c.Core.RepositoryFormatVersion = format.Version_1
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) unmarshalUser() {
|
||||
|
||||
+25
-3
@@ -139,8 +139,12 @@ func (dw *deltaSelector) fixAndBreakChains(objectsToPack []*ObjectToPack) error
|
||||
m[otp.Hash()] = otp
|
||||
}
|
||||
|
||||
// visiting holds the objects on the current resolution path, so that a
|
||||
// delta chain looping back on itself can be detected and broken.
|
||||
visiting := make(map[plumbing.Hash]bool)
|
||||
|
||||
for _, otp := range objectsToPack {
|
||||
if err := dw.fixAndBreakChainsOne(m, otp); err != nil {
|
||||
if err := dw.fixAndBreakChainsOne(m, otp, visiting); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -148,7 +152,11 @@ func (dw *deltaSelector) fixAndBreakChains(objectsToPack []*ObjectToPack) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dw *deltaSelector) fixAndBreakChainsOne(objectsToPack map[plumbing.Hash]*ObjectToPack, otp *ObjectToPack) error {
|
||||
func (dw *deltaSelector) fixAndBreakChainsOne(
|
||||
objectsToPack map[plumbing.Hash]*ObjectToPack,
|
||||
otp *ObjectToPack,
|
||||
visiting map[plumbing.Hash]bool,
|
||||
) error {
|
||||
if !otp.Object.Type().IsDelta() {
|
||||
return nil
|
||||
}
|
||||
@@ -174,7 +182,21 @@ func (dw *deltaSelector) fixAndBreakChainsOne(objectsToPack map[plumbing.Hash]*O
|
||||
return dw.undeltify(otp)
|
||||
}
|
||||
|
||||
if err := dw.fixAndBreakChainsOne(objectsToPack, base); err != nil {
|
||||
// Mark this object as being resolved before looking at its base, so that
|
||||
// a delta based on itself is caught by the check below.
|
||||
h := otp.Hash()
|
||||
visiting[h] = true
|
||||
defer delete(visiting, h)
|
||||
|
||||
// A delta chain that loops back onto an object we are already resolving
|
||||
// cannot be written: every delta needs its base written first. Break the
|
||||
// chain here instead of following the cycle, which would recurse until
|
||||
// the goroutine stack is exhausted.
|
||||
if visiting[do.BaseHash()] {
|
||||
return dw.undeltify(otp)
|
||||
}
|
||||
|
||||
if err := dw.fixAndBreakChainsOne(objectsToPack, base, visiting); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+34
-2
@@ -474,6 +474,14 @@ type TreeWalker struct {
|
||||
recursive bool
|
||||
seen map[plumbing.Hash]bool
|
||||
|
||||
// skipPathValidation disables the pathutil.ValidTreePath check in Next.
|
||||
// It is set by inspection-only callers (e.g. the revlist object walk)
|
||||
// that never funnel entry names into the filesystem and must enumerate
|
||||
// trees faithfully, including entries with names upstream Git accepts
|
||||
// but that are unsafe to materialise (control characters, `.git`-shaped
|
||||
// names).
|
||||
skipPathValidation bool
|
||||
|
||||
s storer.EncodedObjectStorer
|
||||
t *Tree
|
||||
}
|
||||
@@ -496,10 +504,32 @@ func NewTreeWalker(t *Tree, recursive bool, seen map[plumbing.Hash]bool) *TreeWa
|
||||
}
|
||||
}
|
||||
|
||||
// SkipPathValidation disables the pathutil.ValidTreePath check performed by
|
||||
// Next, and must be called before the first Next.
|
||||
//
|
||||
// It is for inspection-only walks that never funnel an entry name into the
|
||||
// filesystem — enumerating which objects exist, rather than materialising
|
||||
// them — and that must therefore see the tree faithfully, including entries
|
||||
// whose names upstream Git accepts but that are unsafe to check out. Callers
|
||||
// that hand the returned name to filesystem or archive output must not use
|
||||
// it; path safety for those is enforced at the materialisation boundaries
|
||||
// (FindEntry, TreeEntryFile, archive, FileIter).
|
||||
func (w *TreeWalker) SkipPathValidation() {
|
||||
w.skipPathValidation = true
|
||||
}
|
||||
|
||||
// Next returns the next object from the tree. Objects are returned in order
|
||||
// and subtrees are included. After the last object has been returned further
|
||||
// calls to Next() will return io.EOF.
|
||||
//
|
||||
// Each entry's name is validated against pathutil.ValidTreePath as it
|
||||
// surfaces, so callers that funnel the returned name into filesystem
|
||||
// or archive output can trust it is free of `.git`-shaped components,
|
||||
// HFS+/NTFS variants, Windows reserved names, and traversal sequences.
|
||||
// A malformed entry stops the walk with the validator's error;
|
||||
// inspection-only callers that need to enumerate raw, unvalidated
|
||||
// names can read Tree.Entries directly or call SkipPathValidation.
|
||||
//
|
||||
// In the current implementation any objects which cannot be found in the
|
||||
// underlying repository will be skipped automatically. It is possible that this
|
||||
// may change in future versions.
|
||||
@@ -536,8 +566,10 @@ func (w *TreeWalker) Next() (name string, entry TreeEntry, err error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := pathutil.ValidTreePath(entry.Name); err != nil {
|
||||
return name, entry, err
|
||||
if !w.skipPathValidation {
|
||||
if err := pathutil.ValidTreePath(entry.Name); err != nil {
|
||||
return name, entry, err
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Mode == filemode.Dir {
|
||||
|
||||
+6
@@ -106,6 +106,12 @@ func transformChildren(t *Tree) ([]noder.Noder, error) {
|
||||
ret := make([]noder.Noder, 0, len(t.Entries))
|
||||
|
||||
walker := NewTreeWalker(t, false, nil) // don't recurse
|
||||
// The diff walk is read-only and never materialises entry names into the
|
||||
// filesystem, so it must enumerate the tree faithfully — including entries
|
||||
// with names that are unsafe to check out but valid per upstream Git (e.g.
|
||||
// control characters). Path safety is enforced at materialisation
|
||||
// boundaries (FindEntry, TreeEntryFile, archive, FileIter), not here.
|
||||
walker.SkipPathValidation()
|
||||
// don't defer walker.Close() for efficiency reasons.
|
||||
for {
|
||||
_, e, err = walker.Next()
|
||||
|
||||
+7
@@ -187,6 +187,13 @@ func iterateCommitTrees(
|
||||
cb(tree.Hash)
|
||||
|
||||
treeWalker := object.NewTreeWalker(tree, true, seen)
|
||||
// This walk only enumerates which objects are reachable, to decide what
|
||||
// to send; it never materialises an entry name into the filesystem. It
|
||||
// must therefore enumerate the tree faithfully, including entries with
|
||||
// names that are unsafe to check out but valid per upstream Git (e.g.
|
||||
// control characters). Path safety is enforced at materialisation
|
||||
// boundaries (FindEntry, TreeEntryFile, archive, FileIter), not here.
|
||||
treeWalker.SkipPathValidation()
|
||||
|
||||
for {
|
||||
_, e, err := treeWalker.Next()
|
||||
|
||||
+379
-22
@@ -6,8 +6,12 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -73,7 +77,7 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) (
|
||||
s.endpoint.String(), infoRefsPath, serviceName,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
req, err := newRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,6 +181,24 @@ func NewClient(c *http.Client) transport.Transport {
|
||||
// and other custom options specific to the client.
|
||||
// If the net/http client is nil or empty, it will use a net/http client configured
|
||||
// with http.DefaultTransport.
|
||||
//
|
||||
// Credentials this client adds where the transport cannot see them are not
|
||||
// subject to the redirect stripping described on AuthMethod: a RoundTripper
|
||||
// injects after the hop is decided, and Client.Jar is consulted after
|
||||
// CheckRedirect, so a domain cookie still follows a redirect to a subdomain
|
||||
// the transport counts as another origin. Apply them in an AuthMethod instead
|
||||
// if that is not wanted. A CheckRedirect hook set on this client runs
|
||||
// alongside the transport's own, but any header it adds when a redirect
|
||||
// leaves the repository's origin is discarded the same way.
|
||||
//
|
||||
// A RoundTripper is therefore also how to authenticate to a new origin a
|
||||
// redirect has moved the repository to: match on the request URL and inject
|
||||
// the credential only for that origin, so it is not sent anywhere else. The
|
||||
// transport keeps its own CheckRedirect on the copy it makes of this client,
|
||||
// so the policy and the origin checks still apply.
|
||||
//
|
||||
// None of this applies to a RoundTripper that follows redirects itself:
|
||||
// CheckRedirect is not consulted then, so no stripping happens at all.
|
||||
func NewClientWithOptions(c *http.Client, opts *ClientOptions) transport.Transport {
|
||||
if c == nil {
|
||||
c = &http.Client{
|
||||
@@ -370,11 +392,20 @@ func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
|
||||
if !strings.HasSuffix(r.URL.Path, infoRefsPath) {
|
||||
return fmt.Errorf("http redirect: target %q does not end with %s", r.URL.Path, infoRefsPath)
|
||||
}
|
||||
if r.URL.Scheme != "http" && r.URL.Scheme != "https" {
|
||||
// A scheme is case-insensitive per RFC 3986, and checkRedirect folds case
|
||||
// when it reads the same hop, so fold here too rather than reject a
|
||||
// spelling that check let through. url.Parse and transport.NewEndpoint
|
||||
// both lowercase what they parse, so only a hand-built URL or Endpoint
|
||||
// arrives uppercased. The folded form is what gets stored below, so every
|
||||
// later request built from the endpoint carries the canonical spelling.
|
||||
scheme := strings.ToLower(r.URL.Scheme)
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return fmt.Errorf("http redirect: unsupported scheme %q", r.URL.Scheme)
|
||||
}
|
||||
if r.URL.Scheme != s.endpoint.Protocol &&
|
||||
!(s.endpoint.Protocol == "http" && r.URL.Scheme == "https") {
|
||||
// schemeUpgrade rather than an inline comparison, so the one cross-scheme
|
||||
// change go-git permits has a single definition shared with
|
||||
// credentialsMayFollow.
|
||||
if !strings.EqualFold(scheme, s.endpoint.Protocol) && !schemeUpgrade(s.endpoint.Protocol, scheme) {
|
||||
return fmt.Errorf("http redirect: changes scheme from %q to %q", s.endpoint.Protocol, r.URL.Scheme)
|
||||
}
|
||||
|
||||
@@ -384,7 +415,20 @@ func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if host != s.endpoint.Host || effectivePort(r.URL.Scheme, port) != effectivePort(s.endpoint.Protocol, s.endpoint.Port) {
|
||||
// The session stores the endpoint and re-applies its credentials on every
|
||||
// later request, so clear them once the redirect has left the origin they
|
||||
// were issued for. This uses the same predicate as stripCredentials, so
|
||||
// both halves share one definition of an origin.
|
||||
//
|
||||
// The two are deliberately asymmetric in one respect: stripCredentials is
|
||||
// sticky over the whole chain, so an origin -> evil -> origin redirect
|
||||
// leaves the discovery GET's later hops unauthenticated even though the
|
||||
// chain returned home. This compares the endpoint only against the final
|
||||
// URL, so the same round trip leaves the session authenticated. That is
|
||||
// not a leak - the final URL's origin is the original one - but it means
|
||||
// such a chain can make the discovery GET anonymous while the session's
|
||||
// POSTs are authenticated, which can surface as a confusing 401.
|
||||
if !credentialsMayFollow(endpointURL(s.endpoint), r.URL) {
|
||||
s.endpoint.User = ""
|
||||
s.endpoint.Password = ""
|
||||
s.auth = nil
|
||||
@@ -393,7 +437,7 @@ func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
|
||||
s.endpoint.Host = host
|
||||
s.endpoint.Port = port
|
||||
|
||||
s.endpoint.Protocol = r.URL.Scheme
|
||||
s.endpoint.Protocol = scheme
|
||||
s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)]
|
||||
return nil
|
||||
}
|
||||
@@ -419,19 +463,132 @@ func endpointPort(port string) (int, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func effectivePort(scheme string, port int) int {
|
||||
if port != 0 {
|
||||
return port
|
||||
}
|
||||
// schemeUpgrade reports whether the scheme transition from one URL to another
|
||||
// is the one cross-scheme change go-git permits: a plain-http origin upgrading
|
||||
// to https. It strictly improves confidentiality and is how servers steer
|
||||
// clients off cleartext.
|
||||
//
|
||||
// Permitting it at all is a deliberate deviation: curl, git and the Fetch
|
||||
// standard all count scheme as part of host identity and drop credentials on
|
||||
// the upgrade. Auth is sent pre-emptively here, so an http origin has already
|
||||
// spent its credential in cleartext on the first request and refusing the
|
||||
// upgrade would break the clone without unspending it. The host is unchanged,
|
||||
// where an on-path attacker needs a valid certificate to receive anything.
|
||||
func schemeUpgrade(from, to string) bool {
|
||||
return strings.EqualFold(from, "http") && strings.EqualFold(to, "https")
|
||||
}
|
||||
|
||||
switch strings.ToLower(scheme) {
|
||||
case "http":
|
||||
return 80
|
||||
case "https":
|
||||
return 443
|
||||
default:
|
||||
return 0
|
||||
// canonicalHost returns u's hostname in the form origins are compared in.
|
||||
//
|
||||
// An address literal is normalised by netip, so the many spellings of one
|
||||
// address are one origin. Two literals are the same origin exactly when netip
|
||||
// parses them to the same Addr, which is also how the WHATWG URL Standard
|
||||
// compares hosts. An IPv4-mapped literal is deliberately not unmapped onto
|
||||
// the IPv4 it dials: reaching the same endpoint is not the same authority,
|
||||
// since net/http sends the literal as written in Host and a server may route
|
||||
// the two spellings to different virtual hosts.
|
||||
//
|
||||
// netip also keeps a scope zone verbatim, which is what origin comparison
|
||||
// needs: net resolves a zone to an interface by exact name, so folding %eth0
|
||||
// onto %ETH0 would call two hosts the same origin that net dials down
|
||||
// different interfaces.
|
||||
//
|
||||
// A registered name is ASCII-lowercased. That fold is the only liberty taken;
|
||||
// every other difference in spelling is a different origin.
|
||||
//
|
||||
// A trailing root dot is one such difference and is kept, for the same reason
|
||||
// as the IPv4-mapped literal: curl and the WHATWG URL Standard both hold
|
||||
// "example.com." and "example.com" to be distinct hosts, and although
|
||||
// crypto/tls and crypto/x509 fold the dot when they authenticate the peer,
|
||||
// net/http sends the name as written in Host.
|
||||
//
|
||||
// The fold is deliberately ASCII-only. strings.ToLower and strings.EqualFold
|
||||
// apply Unicode case mapping, which folds U+03C2 onto U+03C3 and so would
|
||||
// call two hosts the same origin when they resolve to different servers. An
|
||||
// ASCII-only fold cannot merge two names DNS keeps apart.
|
||||
//
|
||||
// No IDNA mapping is applied either, so a unicode hostname is a different
|
||||
// origin from the punycode encoding of it, and from another Unicode case of
|
||||
// itself, even though all three reach the same server. Mapping through
|
||||
// golang.org/x/net/idna would join them, but it can only widen this equality,
|
||||
// never narrow it, so leaving it out can cost a credential across such a
|
||||
// redirect and cannot forward one. Against that cost, go-git pins x/net while
|
||||
// net/http uses the copy vendored into the toolchain: the two are versioned
|
||||
// separately, so a release that moves the Unicode tables under one and not
|
||||
// the other would have this merge origins net/http still dials apart. That is
|
||||
// the failure this comparison exists to prevent, and comparing bytes has no
|
||||
// such mode.
|
||||
func canonicalHost(u *url.URL) string {
|
||||
host := u.Hostname()
|
||||
if addr, err := netip.ParseAddr(host); err == nil {
|
||||
return addr.String()
|
||||
}
|
||||
b := []byte(host)
|
||||
for i := range b {
|
||||
if b[i] >= 'A' && b[i] <= 'Z' {
|
||||
b[i] += 'a' - 'A'
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// effectivePort returns u's port as the connection will use it: the scheme's
|
||||
// well-known port when the URL does not spell one out, and without leading
|
||||
// zeroes, so "https://x", "https://x:443" and "https://x:0443" all agree.
|
||||
func effectivePort(u *url.URL) string {
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http":
|
||||
return "80"
|
||||
case "https":
|
||||
return "443"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
if trimmed := strings.TrimLeft(port, "0"); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// credentialsMayFollow reports whether credentials issued for one URL may be
|
||||
// sent to another.
|
||||
//
|
||||
// The relation is deliberately asymmetric: scheme, host and effective port
|
||||
// must all match, except that a plain http origin may upgrade to https on the
|
||||
// same host (see schemeUpgrade). That exception is confined to the two
|
||||
// default ports: 80 to 443 is the upgrade servers actually steer clients
|
||||
// through, whereas a non-default port carries no such convention, so
|
||||
// http://host:8080 to https://host:8443 is a move to another origin like any
|
||||
// other port change.
|
||||
//
|
||||
// Host matching is exact. Unlike Go's http.Client, which forwards credentials
|
||||
// from a host to any subdomain of it, a subdomain is a different origin here —
|
||||
// matching canonical git and libcurl.
|
||||
func credentialsMayFollow(from, to *url.URL) bool {
|
||||
if canonicalHost(from) != canonicalHost(to) {
|
||||
return false
|
||||
}
|
||||
if strings.EqualFold(from.Scheme, to.Scheme) {
|
||||
return effectivePort(from) == effectivePort(to)
|
||||
}
|
||||
return schemeUpgrade(from.Scheme, to.Scheme) &&
|
||||
effectivePort(from) == "80" && effectivePort(to) == "443"
|
||||
}
|
||||
|
||||
// endpointURL renders an Endpoint's origin as a URL, so that the session's
|
||||
// credential clearing and the per-hop stripping share one definition of an
|
||||
// origin and cannot drift apart.
|
||||
func endpointURL(ep *transport.Endpoint) *url.URL {
|
||||
host := strings.Trim(ep.Host, "[]")
|
||||
if ep.Port != 0 {
|
||||
host = net.JoinHostPort(host, strconv.Itoa(ep.Port))
|
||||
} else if strings.Contains(host, ":") {
|
||||
host = "[" + host + "]"
|
||||
}
|
||||
return &url.URL{Scheme: ep.Protocol, Host: host}
|
||||
}
|
||||
|
||||
func (c *client) cloneHTTPClient(transport http.RoundTripper) *http.Client {
|
||||
@@ -448,26 +605,215 @@ func wrapCheckRedirect(policy RedirectPolicy, next func(*http.Request, []*http.R
|
||||
if err := checkRedirect(req, via, policy); err != nil {
|
||||
return err
|
||||
}
|
||||
// Strip before the caller's hook so it observes what will actually
|
||||
// be sent, and again afterwards so a hook of the common "preserve
|
||||
// my headers across redirects" shape - which copies from via[0],
|
||||
// the original unsanitized request - cannot reinstate them.
|
||||
// Carrying credentials across an origin boundary is deliberately
|
||||
// unsupported.
|
||||
stripCredentials(req, via)
|
||||
if next != nil {
|
||||
return next(req, via)
|
||||
if err := next(req, via); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stripCredentials(req, via)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// safeHeaders lists the headers go-git sets itself, none of which can carry a
|
||||
// caller credential. stripCredentials keeps only these when a redirect leaves
|
||||
// the credential's origin. Adding a name here makes it forwardable across an
|
||||
// origin boundary - do not add anything a caller can put a secret in.
|
||||
//
|
||||
// This narrows rather than eliminates the exposure: an AuthMethod that writes
|
||||
// a credential into one of these names directly - for example
|
||||
// Header.Set("User-Agent", "token "+secret) - still survives a cross-origin
|
||||
// redirect. Such a value is also sent to the origin and to any proxy in path,
|
||||
// so it should not be placed there whether or not a redirect follows.
|
||||
var safeHeaders = map[string]struct{}{
|
||||
"User-Agent": {},
|
||||
"Host": {},
|
||||
"Accept": {},
|
||||
"Content-Type": {},
|
||||
"Content-Length": {},
|
||||
}
|
||||
|
||||
func filterHeaders(h http.Header) http.Header {
|
||||
filtered := make(http.Header)
|
||||
for key, values := range h {
|
||||
if _, ok := safeHeaders[http.CanonicalHeaderKey(key)]; ok {
|
||||
filtered[key] = values
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// stripCredentials removes credentials from req once the redirect chain has
|
||||
// left the origin of the original, credential-bearing request.
|
||||
//
|
||||
// CheckRedirect is the only hook that runs while a redirected request's
|
||||
// headers are still mutable: http.Client.Do performs the entire chain
|
||||
// internally, so anything the transport does after Do returns - including
|
||||
// ModifyEndpointIfRedirect - is too late for the hops themselves.
|
||||
//
|
||||
// Two subtleties:
|
||||
//
|
||||
// - net/http rebuilds every redirect request from the original request's
|
||||
// headers before calling this, so a header removed at one hop reappears
|
||||
// at the next. The decision is therefore recomputed per hop.
|
||||
// - The decision is sticky: once the chain has left the origin, credentials
|
||||
// stay gone even if a later hop returns to it. Stickiness is derived from
|
||||
// via rather than stored, because this closure is shared across a
|
||||
// session's requests.
|
||||
//
|
||||
// Stripping keeps only the headers go-git sets itself (safeHeaders). An
|
||||
// allowlist is used rather than a list of credential header names because
|
||||
// caller credentials arrive under names that cannot be enumerated -
|
||||
// PRIVATE-TOKEN, X-Api-Key, gateway headers - which is exactly what
|
||||
// net/http's fixed list of sensitive header names gets wrong. It is also
|
||||
// immune to header-name canonicalisation: an AuthMethod that writes a raw map
|
||||
// key is still removed.
|
||||
func stripCredentials(req *http.Request, via []*http.Request) {
|
||||
if len(via) == 0 {
|
||||
return
|
||||
}
|
||||
// net/http sets a URL on every request it builds, and req.URL is non-nil
|
||||
// by construction: checkRedirect dereferences req.URL.Scheme on each path
|
||||
// that returns nil, so it runs first or not at all. This nil check and
|
||||
// the two in crossedOrigin are defensive, against a synthetic caller.
|
||||
// Each treats a URL it cannot read as an origin crossing; removing one
|
||||
// panics in canonicalHost rather than leaking.
|
||||
if origin := via[0].URL; origin != nil && !crossedOrigin(origin, req, via) {
|
||||
return
|
||||
}
|
||||
req.Header = filterHeaders(req.Header)
|
||||
if req.URL != nil {
|
||||
req.URL.User = nil
|
||||
}
|
||||
}
|
||||
|
||||
// crossedOrigin reports whether any hop so far, including the pending one, has
|
||||
// left origin.
|
||||
func crossedOrigin(origin *url.URL, req *http.Request, via []*http.Request) bool {
|
||||
if req.URL == nil || !credentialsMayFollow(origin, req.URL) {
|
||||
return true
|
||||
}
|
||||
for _, prev := range via[1:] {
|
||||
if prev.URL == nil || !credentialsMayFollow(origin, prev.URL) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// redactedURL returns the string form of u with the userinfo password
|
||||
// replaced, for use in error messages. (*url.URL).String() renders the
|
||||
// password verbatim, and request URLs are built from the endpoint, which
|
||||
// carries whatever credentials the caller put in the clone URL.
|
||||
func redactedURL(u *url.URL) string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
if u.User == nil {
|
||||
return u.String()
|
||||
}
|
||||
if _, hasPassword := u.User.Password(); !hasPassword {
|
||||
return u.String()
|
||||
}
|
||||
redacted := *u
|
||||
redacted.User = url.UserPassword(u.User.Username(), "REDACTED")
|
||||
return redacted.String()
|
||||
}
|
||||
|
||||
// redactedRawURL is redactedURL for a string that may not parse. Request URLs
|
||||
// are assembled from Endpoint.String(), which re-emits Endpoint.Path raw, so a
|
||||
// path holding a stray percent produces a string url.Parse rejects. url.Parse
|
||||
// reports the input verbatim and applies no redaction of its own; only
|
||||
// http.Client strips a password, and only from errors it raises itself.
|
||||
func redactedRawURL(raw string) string {
|
||||
i := strings.Index(raw, "://")
|
||||
if i < 0 {
|
||||
return raw
|
||||
}
|
||||
authority := raw[i+3:]
|
||||
if end := strings.IndexByte(authority, '/'); end >= 0 {
|
||||
authority = authority[:end]
|
||||
}
|
||||
at := strings.LastIndexByte(authority, '@')
|
||||
if at < 0 {
|
||||
return raw
|
||||
}
|
||||
colon := strings.IndexByte(authority[:at], ':')
|
||||
if colon < 0 {
|
||||
// Username only, left alone, as redactedURL leaves it.
|
||||
return raw
|
||||
}
|
||||
return raw[:i+3+colon+1] + "REDACTED" + raw[i+3+at:]
|
||||
}
|
||||
|
||||
// newRequest wraps http.NewRequest so that a URL it cannot parse does not
|
||||
// reach the caller with the endpoint's credentials still in it.
|
||||
func newRequest(method, rawURL string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, rawURL, body)
|
||||
if err != nil {
|
||||
var uerr *url.Error
|
||||
if errors.As(err, &uerr) {
|
||||
uerr.URL = redactedRawURL(uerr.URL)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func checkRedirect(req *http.Request, via []*http.Request, policy RedirectPolicy) error {
|
||||
// CheckRedirect is the only hook that runs before the next hop leaves
|
||||
// the client. ModifyEndpointIfRedirect inspects the chain after
|
||||
// client.Do has followed all of it, so a hop rejected there has already
|
||||
// carried the request headers to its server.
|
||||
//
|
||||
// The wording matches the message ModifyEndpointIfRedirect produces for
|
||||
// the same hop, which this check reaches first.
|
||||
if len(via) != 0 {
|
||||
// A hop whose scheme cannot be read cannot be shown not to have been
|
||||
// https, so it is assumed to have been, and a cleartext target is
|
||||
// rejected. Skipping the comparison instead would let an
|
||||
// undeterminable hop turn the check off, which is the wrong default
|
||||
// for a credential control; crossedOrigin fails closed the same way.
|
||||
// An empty scheme is as unreadable as a nil URL, so both take the
|
||||
// assumed-https default.
|
||||
//
|
||||
// The comparisons fold case because a scheme is case-insensitive per
|
||||
// RFC 3986. net/url lowercases what it parses, so only a hand-built
|
||||
// URL reaches here uppercased - the same synthetic caller the nil
|
||||
// checks guard against - and for that caller "HTTPS" to "http" is
|
||||
// still a downgrade.
|
||||
prevScheme := "https"
|
||||
if prev := via[len(via)-1]; prev.URL != nil && prev.URL.Scheme != "" {
|
||||
prevScheme = prev.URL.Scheme
|
||||
}
|
||||
if strings.EqualFold(prevScheme, "https") && strings.EqualFold(req.URL.Scheme, "http") {
|
||||
return fmt.Errorf("http redirect: changes scheme from %q to %q: %s",
|
||||
prevScheme, req.URL.Scheme, redactedURL(req.URL))
|
||||
}
|
||||
}
|
||||
|
||||
switch policy {
|
||||
case FollowRedirects:
|
||||
case NoFollowRedirects:
|
||||
return fmt.Errorf("http redirect: redirects disabled to %s", req.URL)
|
||||
return fmt.Errorf("http redirect: redirects disabled to %s", redactedURL(req.URL))
|
||||
case "", FollowInitialRedirects:
|
||||
if !isInitialRequest(req) {
|
||||
return fmt.Errorf("http redirect: redirect on non-initial request to %s", req.URL)
|
||||
return fmt.Errorf("http redirect: redirect on non-initial request to %s", redactedURL(req.URL))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("http redirect: invalid redirect policy %q", policy)
|
||||
}
|
||||
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
|
||||
// Folded for the same reason as the guard above: a scheme is
|
||||
// case-insensitive per RFC 3986, so a spelling the downgrade check read
|
||||
// as https must not be rejected here as a scheme go-git cannot speak.
|
||||
if !strings.EqualFold(req.URL.Scheme, "http") && !strings.EqualFold(req.URL.Scheme, "https") {
|
||||
return fmt.Errorf("http redirect: unsupported scheme %q", req.URL.Scheme)
|
||||
}
|
||||
if len(via) >= 10 {
|
||||
@@ -481,6 +827,17 @@ func (*session) Close() error {
|
||||
}
|
||||
|
||||
// AuthMethod is concrete implementation of common.AuthMethod for HTTP services
|
||||
//
|
||||
// Headers SetAuth adds are dropped when a redirect leaves the repository's
|
||||
// origin: only the headers the transport sets itself survive that boundary.
|
||||
// This applies to non-credential headers too, so an implementation that adds a
|
||||
// trace or tenant header loses it on such a hop.
|
||||
//
|
||||
// That filter matches header names, not values. An implementation that writes
|
||||
// a credential into a name the transport also uses - User-Agent, Host, Accept,
|
||||
// Content-Type, Content-Length - has that value carried across the boundary
|
||||
// with the name. Such a credential is also sent to the origin and to any proxy
|
||||
// in path, so it should not be placed there whether or not a redirect follows.
|
||||
type AuthMethod interface {
|
||||
transport.AuthMethod
|
||||
SetAuth(r *http.Request)
|
||||
@@ -598,6 +955,6 @@ func (e *Err) StatusCode() int {
|
||||
|
||||
func (e *Err) Error() string {
|
||||
return fmt.Sprintf("unexpected requesting %q status code: %d",
|
||||
e.Response.Request.URL, e.Response.StatusCode,
|
||||
redactedURL(e.Response.Request.URL), e.Response.StatusCode,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ func (s *rpSession) doRequest(
|
||||
body = content
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
req, err := newRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, plumbing.NewPermanentError(err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ func (s *upSession) doRequest(
|
||||
body = content
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
req, err := newRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, plumbing.NewPermanentError(err)
|
||||
}
|
||||
|
||||
+1
@@ -433,6 +433,7 @@ func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ioutil.CheckClose(f, &err)
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
* -text
|
||||
*.bin -text -diff
|
||||
*.md text eol=lf
|
||||
|
||||
+700
-700
File diff suppressed because it is too large
Load Diff
+78
-78
@@ -1,79 +1,79 @@
|
||||
# Finite State Entropy
|
||||
|
||||
This package provides Finite State Entropy encoding and decoding.
|
||||
|
||||
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
|
||||
encoding provides a fast near-optimal symbol encoding/decoding
|
||||
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
|
||||
|
||||
## News
|
||||
|
||||
* Feb 2018: First implementation released. Consider this beta software for now.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
|
||||
|
||||
# Performance
|
||||
|
||||
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
|
||||
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
|
||||
|
||||
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
|
||||
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
|
||||
beneficial to transpose all your input values down by 64.
|
||||
|
||||
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
|
||||
around 300MB/s decompression speed.
|
||||
|
||||
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
|
||||
|
||||
# Plans
|
||||
|
||||
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
|
||||
|
||||
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
# Finite State Entropy
|
||||
|
||||
This package provides Finite State Entropy encoding and decoding.
|
||||
|
||||
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
|
||||
encoding provides a fast near-optimal symbol encoding/decoding
|
||||
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
|
||||
|
||||
## News
|
||||
|
||||
* Feb 2018: First implementation released. Consider this beta software for now.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
|
||||
|
||||
# Performance
|
||||
|
||||
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
|
||||
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
|
||||
|
||||
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
|
||||
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
|
||||
beneficial to transpose all your input values down by 64.
|
||||
|
||||
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
|
||||
around 300MB/s decompression speed.
|
||||
|
||||
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
|
||||
|
||||
# Plans
|
||||
|
||||
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
|
||||
|
||||
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
+89
-89
@@ -1,89 +1,89 @@
|
||||
# Huff0 entropy compression
|
||||
|
||||
This package provides Huff0 encoding and decoding as used in zstd.
|
||||
|
||||
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
|
||||
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
|
||||
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
|
||||
|
||||
## News
|
||||
|
||||
This is used as part of the [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd) compression and decompression package.
|
||||
|
||||
This ensures that most functionality is well tested.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
|
||||
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
|
||||
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
|
||||
|
||||
## Tables and re-use
|
||||
|
||||
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
|
||||
|
||||
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
|
||||
that controls this behaviour. See the documentation for details. This can be altered between each block.
|
||||
|
||||
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
|
||||
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
|
||||
based on the boolean reported back from the CompressXX call.
|
||||
|
||||
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
|
||||
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
|
||||
|
||||
## Decompressing
|
||||
|
||||
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
|
||||
This will initialize the decoding tables.
|
||||
You can supply the complete block to `ReadTable` and it will return the data part of the block
|
||||
which can be given to the decompressor.
|
||||
|
||||
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
|
||||
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
|
||||
|
||||
For concurrently decompressing content with a fixed table a stateless [`Decoder`](https://godoc.org/github.com/klauspost/compress/huff0#Decoder) can be requested which will remain correct as long as the scratch is unchanged. The capacity of the provided slice indicates the expected output size.
|
||||
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
# Huff0 entropy compression
|
||||
|
||||
This package provides Huff0 encoding and decoding as used in zstd.
|
||||
|
||||
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
|
||||
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
|
||||
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
|
||||
|
||||
## News
|
||||
|
||||
This is used as part of the [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd) compression and decompression package.
|
||||
|
||||
This ensures that most functionality is well tested.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
|
||||
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
|
||||
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
|
||||
|
||||
## Tables and re-use
|
||||
|
||||
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
|
||||
|
||||
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
|
||||
that controls this behaviour. See the documentation for details. This can be altered between each block.
|
||||
|
||||
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
|
||||
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
|
||||
based on the boolean reported back from the CompressXX call.
|
||||
|
||||
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
|
||||
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
|
||||
|
||||
## Decompressing
|
||||
|
||||
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
|
||||
This will initialize the decoding tables.
|
||||
You can supply the complete block to `ReadTable` and it will return the data part of the block
|
||||
which can be given to the decompressor.
|
||||
|
||||
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
|
||||
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
|
||||
|
||||
For concurrently decompressing content with a fixed table a stateless [`Decoder`](https://godoc.org/github.com/klauspost/compress/huff0#Decoder) can be requested which will remain correct as long as the scratch is unchanged. The capacity of the provided slice indicates the expected output size.
|
||||
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
-
|
||||
id: "cpuid"
|
||||
binary: cpuid
|
||||
main: ./cmd/cpuid/main.go
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -ldflags=-s -w
|
||||
goos:
|
||||
- aix
|
||||
- linux
|
||||
- freebsd
|
||||
- netbsd
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm64
|
||||
goarm:
|
||||
- 7
|
||||
|
||||
archives:
|
||||
-
|
||||
id: cpuid
|
||||
name_template: "cpuid-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^doc:'
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^tests:'
|
||||
- '^Update\sREADME.md'
|
||||
|
||||
nfpms:
|
||||
-
|
||||
file_name_template: "cpuid_package_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
vendor: Klaus Post
|
||||
homepage: https://github.com/klauspost/cpuid
|
||||
maintainer: Klaus Post <klauspost@gmail.com>
|
||||
description: CPUID Tool
|
||||
license: BSD 3-Clause
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2015- Klaus Post & Contributors.
|
||||
Email: klauspost@gmail.com
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Klaus Post
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-512
@@ -1,512 +0,0 @@
|
||||
# cpuid
|
||||
Package cpuid provides information about the CPU running the current program.
|
||||
|
||||
CPU features are detected on startup, and kept for fast access through the life of the application.
|
||||
Currently x86 / x64 (AMD64/i386) and ARM (ARM64) is supported, and no external C (cgo) code is used, which should make the library very easy to use.
|
||||
|
||||
You can access the CPU information by accessing the shared CPU variable of the cpuid library.
|
||||
|
||||
Package home: https://github.com/klauspost/cpuid
|
||||
|
||||
[](https://pkg.go.dev/github.com/klauspost/cpuid/v2)
|
||||
[](https://github.com/klauspost/cpuid/actions/workflows/go.yml)
|
||||
|
||||
## installing
|
||||
|
||||
`go get -u github.com/klauspost/cpuid/v2` using modules.
|
||||
Drop `v2` for others.
|
||||
|
||||
Installing binary:
|
||||
|
||||
`go install github.com/klauspost/cpuid/v2/cmd/cpuid@latest`
|
||||
|
||||
Or download binaries from release page: https://github.com/klauspost/cpuid/releases
|
||||
|
||||
### Homebrew
|
||||
|
||||
For macOS/Linux users, you can install via [brew](https://brew.sh/)
|
||||
|
||||
```sh
|
||||
$ brew install cpuid
|
||||
```
|
||||
|
||||
## example
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Print basic CPU information:
|
||||
fmt.Println("Name:", CPU.BrandName)
|
||||
fmt.Println("PhysicalCores:", CPU.PhysicalCores)
|
||||
fmt.Println("ThreadsPerCore:", CPU.ThreadsPerCore)
|
||||
fmt.Println("LogicalCores:", CPU.LogicalCores)
|
||||
fmt.Println("Family", CPU.Family, "Model:", CPU.Model, "Vendor ID:", CPU.VendorID)
|
||||
fmt.Println("Features:", strings.Join(CPU.FeatureSet(), ","))
|
||||
fmt.Println("Cacheline bytes:", CPU.CacheLine)
|
||||
fmt.Println("L1 Data Cache:", CPU.Cache.L1D, "bytes")
|
||||
fmt.Println("L1 Instruction Cache:", CPU.Cache.L1I, "bytes")
|
||||
fmt.Println("L2 Cache:", CPU.Cache.L2, "bytes")
|
||||
fmt.Println("L3 Cache:", CPU.Cache.L3, "bytes")
|
||||
fmt.Println("Frequency", CPU.Hz, "hz")
|
||||
|
||||
// Test if we have these specific features:
|
||||
if CPU.Supports(SSE, SSE2) {
|
||||
fmt.Println("We have Streaming SIMD 2 Extensions")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sample output:
|
||||
```
|
||||
>go run main.go
|
||||
Name: AMD Ryzen 9 3950X 16-Core Processor
|
||||
PhysicalCores: 16
|
||||
ThreadsPerCore: 2
|
||||
LogicalCores: 32
|
||||
Family 23 Model: 113 Vendor ID: AMD
|
||||
Features: ADX,AESNI,AVX,AVX2,BMI1,BMI2,CLMUL,CMOV,CX16,F16C,FMA3,HTT,HYPERVISOR,LZCNT,MMX,MMXEXT,NX,POPCNT,RDRAND,RDSEED,RDTSCP,SHA,SSE,SSE2,SSE3,SSE4,SSE42,SSE4A,SSSE3
|
||||
Cacheline bytes: 64
|
||||
L1 Data Cache: 32768 bytes
|
||||
L1 Instruction Cache: 32768 bytes
|
||||
L2 Cache: 524288 bytes
|
||||
L3 Cache: 16777216 bytes
|
||||
Frequency 0 hz
|
||||
We have Streaming SIMD 2 Extensions
|
||||
```
|
||||
|
||||
# usage
|
||||
|
||||
The `cpuid.CPU` provides access to CPU features. Use `cpuid.CPU.Supports()` to check for CPU features.
|
||||
A faster `cpuid.CPU.Has()` is provided which will usually be inlined by the gc compiler.
|
||||
|
||||
To test a larger number of features, they can be combined using `f := CombineFeatures(CMOV, CMPXCHG8, X87, FXSR, MMX, SYSCALL, SSE, SSE2)`, etc.
|
||||
This can be using with `cpuid.CPU.HasAll(f)` to quickly test if all features are supported.
|
||||
|
||||
Note that for some cpu/os combinations some features will not be detected.
|
||||
`amd64` has rather good support and should work reliably on all platforms.
|
||||
|
||||
Note that hypervisors may not pass through all CPU features through to the guest OS,
|
||||
so even if your host supports a feature it may not be visible on guests.
|
||||
|
||||
## arm64 feature detection
|
||||
|
||||
Not all operating systems provide ARM features directly
|
||||
and there is no safe way to do so for the rest.
|
||||
|
||||
Currently `arm64/linux` and `arm64/freebsd` should be quite reliable.
|
||||
`arm64/darwin` adds features expected from the M1 processor, but a lot remains undetected.
|
||||
|
||||
A `DetectARM()` can be used if you are able to control your deployment,
|
||||
it will detect CPU features, but may crash if the OS doesn't intercept the calls.
|
||||
A `-cpu.arm` flag for detecting unsafe ARM features can be added. See below.
|
||||
|
||||
Note that currently only features are detected on ARM,
|
||||
no additional information is currently available.
|
||||
|
||||
## flags
|
||||
|
||||
It is possible to add flags that affects cpu detection.
|
||||
|
||||
For this the `Flags()` command is provided.
|
||||
|
||||
This must be called *before* `flag.Parse()` AND after the flags have been parsed `Detect()` must be called.
|
||||
|
||||
This means that any detection used in `init()` functions will not contain these flags.
|
||||
|
||||
Example:
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cpuid.Flags()
|
||||
flag.Parse()
|
||||
cpuid.Detect()
|
||||
|
||||
// Test if we have these specific features:
|
||||
if cpuid.CPU.Supports(cpuid.SSE, cpuid.SSE2) {
|
||||
fmt.Println("We have Streaming SIMD 2 Extensions")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## commandline
|
||||
|
||||
Download as binary from: https://github.com/klauspost/cpuid/releases
|
||||
|
||||
Install from source:
|
||||
|
||||
`go install github.com/klauspost/cpuid/v2/cmd/cpuid@latest`
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
λ cpuid
|
||||
Name: AMD Ryzen 9 3950X 16-Core Processor
|
||||
Vendor String: AuthenticAMD
|
||||
Vendor ID: AMD
|
||||
PhysicalCores: 16
|
||||
Threads Per Core: 2
|
||||
Logical Cores: 32
|
||||
CPU Family 23 Model: 113
|
||||
Features: ADX,AESNI,AVX,AVX2,BMI1,BMI2,CLMUL,CLZERO,CMOV,CMPXCHG8,CPBOOST,CX16,F16C,FMA3,FXSR,FXSROPT,HTT,HYPERVISOR,LAHF,LZCNT,MCAOVERFLOW,MMX,MMXEXT,MOVBE,NX,OSXSAVE,POPCNT,RDRAND,RDSEED,RDTSCP,SCE,SHA,SSE,SSE2,SSE3,SSE4,SSE42,SSE4A,SSSE3,SUCCOR,X87,XSAVE
|
||||
Microarchitecture level: 3
|
||||
Cacheline bytes: 64
|
||||
L1 Instruction Cache: 32768 bytes
|
||||
L1 Data Cache: 32768 bytes
|
||||
L2 Cache: 524288 bytes
|
||||
L3 Cache: 16777216 bytes
|
||||
|
||||
```
|
||||
### JSON Output:
|
||||
|
||||
```
|
||||
λ cpuid --json
|
||||
{
|
||||
"BrandName": "AMD Ryzen 9 3950X 16-Core Processor",
|
||||
"VendorID": 2,
|
||||
"VendorString": "AuthenticAMD",
|
||||
"PhysicalCores": 16,
|
||||
"ThreadsPerCore": 2,
|
||||
"LogicalCores": 32,
|
||||
"Family": 23,
|
||||
"Model": 113,
|
||||
"CacheLine": 64,
|
||||
"Hz": 0,
|
||||
"BoostFreq": 0,
|
||||
"Cache": {
|
||||
"L1I": 32768,
|
||||
"L1D": 32768,
|
||||
"L2": 524288,
|
||||
"L3": 16777216
|
||||
},
|
||||
"SGX": {
|
||||
"Available": false,
|
||||
"LaunchControl": false,
|
||||
"SGX1Supported": false,
|
||||
"SGX2Supported": false,
|
||||
"MaxEnclaveSizeNot64": 0,
|
||||
"MaxEnclaveSize64": 0,
|
||||
"EPCSections": null
|
||||
},
|
||||
"Features": [
|
||||
"ADX",
|
||||
"AESNI",
|
||||
"AVX",
|
||||
"AVX2",
|
||||
"BMI1",
|
||||
"BMI2",
|
||||
"CLMUL",
|
||||
"CLZERO",
|
||||
"CMOV",
|
||||
"CMPXCHG8",
|
||||
"CPBOOST",
|
||||
"CX16",
|
||||
"F16C",
|
||||
"FMA3",
|
||||
"FXSR",
|
||||
"FXSROPT",
|
||||
"HTT",
|
||||
"HYPERVISOR",
|
||||
"LAHF",
|
||||
"LZCNT",
|
||||
"MCAOVERFLOW",
|
||||
"MMX",
|
||||
"MMXEXT",
|
||||
"MOVBE",
|
||||
"NX",
|
||||
"OSXSAVE",
|
||||
"POPCNT",
|
||||
"RDRAND",
|
||||
"RDSEED",
|
||||
"RDTSCP",
|
||||
"SCE",
|
||||
"SHA",
|
||||
"SSE",
|
||||
"SSE2",
|
||||
"SSE3",
|
||||
"SSE4",
|
||||
"SSE42",
|
||||
"SSE4A",
|
||||
"SSSE3",
|
||||
"SUCCOR",
|
||||
"X87",
|
||||
"XSAVE"
|
||||
],
|
||||
"X64Level": 3
|
||||
}
|
||||
```
|
||||
|
||||
### Check CPU microarch level
|
||||
|
||||
```
|
||||
λ cpuid --check-level=3
|
||||
2022/03/18 17:04:40 AMD Ryzen 9 3950X 16-Core Processor
|
||||
2022/03/18 17:04:40 Microarchitecture level 3 is supported. Max level is 3.
|
||||
Exit Code 0
|
||||
|
||||
λ cpuid --check-level=4
|
||||
2022/03/18 17:06:18 AMD Ryzen 9 3950X 16-Core Processor
|
||||
2022/03/18 17:06:18 Microarchitecture level 4 not supported. Max level is 3.
|
||||
Exit Code 1
|
||||
```
|
||||
|
||||
|
||||
## Available flags
|
||||
|
||||
### x86 & amd64
|
||||
|
||||
| Feature Flag | Description |
|
||||
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ADX | Intel ADX (Multi-Precision Add-Carry Instruction Extensions) |
|
||||
| AESNI | Advanced Encryption Standard New Instructions |
|
||||
| AMD3DNOW | AMD 3DNOW |
|
||||
| AMD3DNOWEXT | AMD 3DNowExt |
|
||||
| AMXBF16 | Tile computational operations on BFLOAT16 numbers |
|
||||
| AMXINT8 | Tile computational operations on 8-bit integers |
|
||||
| AMXFP16 | Tile computational operations on FP16 numbers |
|
||||
| AMXFP8 | Tile computational operations on FP8 numbers |
|
||||
| AMXCOMPLEX | Tile computational operations on complex numbers |
|
||||
| AMXTILE | Tile architecture |
|
||||
| AMXTF32 | Matrix Multiplication of TF32 Tiles into Packed Single Precision Tile |
|
||||
| AMXTRANSPOSE | Tile multiply where the first operand is transposed |
|
||||
| APX_F | Intel APX |
|
||||
| AVX | AVX functions |
|
||||
| AVX10 | If set the Intel AVX10 Converged Vector ISA is supported |
|
||||
| AVX10_128 | If set indicates that AVX10 128-bit vector support is present |
|
||||
| AVX10_256 | If set indicates that AVX10 256-bit vector support is present |
|
||||
| AVX10_512 | If set indicates that AVX10 512-bit vector support is present |
|
||||
| AVX2 | AVX2 functions |
|
||||
| AVX512BF16 | AVX-512 BFLOAT16 Instructions |
|
||||
| AVX512BITALG | AVX-512 Bit Algorithms |
|
||||
| AVX512BW | AVX-512 Byte and Word Instructions |
|
||||
| AVX512CD | AVX-512 Conflict Detection Instructions |
|
||||
| AVX512DQ | AVX-512 Doubleword and Quadword Instructions |
|
||||
| AVX512ER | AVX-512 Exponential and Reciprocal Instructions |
|
||||
| AVX512F | AVX-512 Foundation |
|
||||
| AVX512FP16 | AVX-512 FP16 Instructions |
|
||||
| AVX512IFMA | AVX-512 Integer Fused Multiply-Add Instructions |
|
||||
| AVX512PF | AVX-512 Prefetch Instructions |
|
||||
| AVX512VBMI | AVX-512 Vector Bit Manipulation Instructions |
|
||||
| AVX512VBMI2 | AVX-512 Vector Bit Manipulation Instructions, Version 2 |
|
||||
| AVX512VL | AVX-512 Vector Length Extensions |
|
||||
| AVX512VNNI | AVX-512 Vector Neural Network Instructions |
|
||||
| AVX512VP2INTERSECT | AVX-512 Intersect for D/Q |
|
||||
| AVX512VPOPCNTDQ | AVX-512 Vector Population Count Doubleword and Quadword |
|
||||
| AVXIFMA | AVX-IFMA instructions |
|
||||
| AVXNECONVERT | AVX-NE-CONVERT instructions |
|
||||
| AVXSLOW | Indicates the CPU performs 2 128 bit operations instead of one |
|
||||
| AVXVNNI | AVX (VEX encoded) VNNI neural network instructions |
|
||||
| AVXVNNIINT8 | AVX-VNNI-INT8 instructions |
|
||||
| AVXVNNIINT16 | AVX-VNNI-INT16 instructions |
|
||||
| BHI_CTRL | Branch History Injection and Intra-mode Branch Target Injection / CVE-2022-0001, CVE-2022-0002 / INTEL-SA-00598 |
|
||||
| BMI1 | Bit Manipulation Instruction Set 1 |
|
||||
| BMI2 | Bit Manipulation Instruction Set 2 |
|
||||
| CETIBT | Intel CET Indirect Branch Tracking |
|
||||
| CETSS | Intel CET Shadow Stack |
|
||||
| CLDEMOTE | Cache Line Demote |
|
||||
| CLMUL | Carry-less Multiplication |
|
||||
| CLZERO | CLZERO instruction supported |
|
||||
| CMOV | i686 CMOV |
|
||||
| CMPCCXADD | CMPCCXADD instructions |
|
||||
| CMPSB_SCADBS_SHORT | Fast short CMPSB and SCASB |
|
||||
| CMPXCHG8 | CMPXCHG8 instruction |
|
||||
| CPBOOST | Core Performance Boost |
|
||||
| CPPC | AMD: Collaborative Processor Performance Control |
|
||||
| CX16 | CMPXCHG16B Instruction |
|
||||
| EFER_LMSLE_UNS | AMD: =Core::X86::Msr::EFER[LMSLE] is not supported, and MBZ |
|
||||
| ENQCMD | Enqueue Command |
|
||||
| ERMS | Enhanced REP MOVSB/STOSB |
|
||||
| F16C | Half-precision floating-point conversion |
|
||||
| FLUSH_L1D | Flush L1D cache |
|
||||
| FMA3 | Intel FMA 3. Does not imply AVX. |
|
||||
| FMA4 | Bulldozer FMA4 functions |
|
||||
| FP128 | AMD: When set, the internal FP/SIMD execution datapath is 128-bits wide |
|
||||
| FP256 | AMD: When set, the internal FP/SIMD execution datapath is 256-bits wide |
|
||||
| FSRM | Fast Short Rep Mov |
|
||||
| FXSR | FXSAVE, FXRESTOR instructions, CR4 bit 9 |
|
||||
| FXSROPT | FXSAVE/FXRSTOR optimizations |
|
||||
| GFNI | Galois Field New Instructions. May require other features (AVX, AVX512VL,AVX512F) based on usage. |
|
||||
| HLE | Hardware Lock Elision |
|
||||
| HRESET | If set CPU supports history reset and the IA32_HRESET_ENABLE MSR |
|
||||
| HTT | Hyperthreading (enabled) |
|
||||
| HWA | Hardware assert supported. Indicates support for MSRC001_10 |
|
||||
| HYBRID_CPU | This part has CPUs of more than one type. |
|
||||
| HYPERVISOR | This bit has been reserved by Intel & AMD for use by hypervisors |
|
||||
| IA32_ARCH_CAP | IA32_ARCH_CAPABILITIES MSR (Intel) |
|
||||
| IA32_CORE_CAP | IA32_CORE_CAPABILITIES MSR |
|
||||
| IBPB | Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB) |
|
||||
| IBRS | AMD: Indirect Branch Restricted Speculation |
|
||||
| IBRS_PREFERRED | AMD: IBRS is preferred over software solution |
|
||||
| IBRS_PROVIDES_SMP | AMD: IBRS provides Same Mode Protection |
|
||||
| IBS | Instruction Based Sampling (AMD) |
|
||||
| IBSBRNTRGT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSFETCHSAM | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSFFV | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSOPCNT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSOPCNTEXT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSOPSAM | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSRDWROPCNT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSRIPINVALIDCHK | Instruction Based Sampling Feature (AMD) |
|
||||
| IBS_FETCH_CTLX | AMD: IBS fetch control extended MSR supported |
|
||||
| IBS_OPDATA4 | AMD: IBS op data 4 MSR supported |
|
||||
| IBS_OPFUSE | AMD: Indicates support for IbsOpFuse |
|
||||
| IBS_PREVENTHOST | Disallowing IBS use by the host supported |
|
||||
| IBS_ZEN4 | Fetch and Op IBS support IBS extensions added with Zen4 |
|
||||
| IDPRED_CTRL | IPRED_DIS |
|
||||
| INT_WBINVD | WBINVD/WBNOINVD are interruptible. |
|
||||
| INVLPGB | NVLPGB and TLBSYNC instruction supported |
|
||||
| KEYLOCKER | Key locker |
|
||||
| KEYLOCKERW | Key locker wide |
|
||||
| LAHF | LAHF/SAHF in long mode |
|
||||
| LAM | If set, CPU supports Linear Address Masking |
|
||||
| LBRVIRT | LBR virtualization |
|
||||
| LZCNT | LZCNT instruction |
|
||||
| MCAOVERFLOW | MCA overflow recovery support. |
|
||||
| MCDT_NO | Processor do not exhibit MXCSR Configuration Dependent Timing behavior and do not need to mitigate it. |
|
||||
| MCOMMIT | MCOMMIT instruction supported |
|
||||
| MD_CLEAR | VERW clears CPU buffers |
|
||||
| MMX | standard MMX |
|
||||
| MMXEXT | SSE integer functions or AMD MMX ext |
|
||||
| MOVBE | MOVBE instruction (big-endian) |
|
||||
| MOVDIR64B | Move 64 Bytes as Direct Store |
|
||||
| MOVDIRI | Move Doubleword as Direct Store |
|
||||
| MOVSB_ZL | Fast Zero-Length MOVSB |
|
||||
| MPX | Intel MPX (Memory Protection Extensions) |
|
||||
| MOVU | MOVU SSE instructions are more efficient and should be preferred to SSE MOVL/MOVH. MOVUPS is more efficient than MOVLPS/MOVHPS. MOVUPD is more efficient than MOVLPD/MOVHPD |
|
||||
| MSRIRC | Instruction Retired Counter MSR available |
|
||||
| MSRLIST | Read/Write List of Model Specific Registers |
|
||||
| MSR_PAGEFLUSH | Page Flush MSR available |
|
||||
| NRIPS | Indicates support for NRIP save on VMEXIT |
|
||||
| NX | NX (No-Execute) bit |
|
||||
| OSXSAVE | XSAVE enabled by OS |
|
||||
| PCONFIG | PCONFIG for Intel Multi-Key Total Memory Encryption |
|
||||
| POPCNT | POPCNT instruction |
|
||||
| PPIN | AMD: Protected Processor Inventory Number support. Indicates that Protected Processor Inventory Number (PPIN) capability can be enabled |
|
||||
| PREFETCHI | PREFETCHIT0/1 instructions |
|
||||
| PSFD | Predictive Store Forward Disable |
|
||||
| RDPRU | RDPRU instruction supported |
|
||||
| RDRAND | RDRAND instruction is available |
|
||||
| RDSEED | RDSEED instruction is available |
|
||||
| RDTSCP | RDTSCP Instruction |
|
||||
| RRSBA_CTRL | Restricted RSB Alternate |
|
||||
| RTM | Restricted Transactional Memory |
|
||||
| RTM_ALWAYS_ABORT | Indicates that the loaded microcode is forcing RTM abort. |
|
||||
| SERIALIZE | Serialize Instruction Execution |
|
||||
| SEV | AMD Secure Encrypted Virtualization supported |
|
||||
| SEV_64BIT | AMD SEV guest execution only allowed from a 64-bit host |
|
||||
| SEV_ALTERNATIVE | AMD SEV Alternate Injection supported |
|
||||
| SEV_DEBUGSWAP | Full debug state swap supported for SEV-ES guests |
|
||||
| SEV_ES | AMD SEV Encrypted State supported |
|
||||
| SEV_RESTRICTED | AMD SEV Restricted Injection supported |
|
||||
| SEV_SNP | AMD SEV Secure Nested Paging supported |
|
||||
| SGX | Software Guard Extensions |
|
||||
| SGXLC | Software Guard Extensions Launch Control |
|
||||
| SGXPQC | Software Guard Extensions 256-bit Encryption |
|
||||
| SHA | Intel SHA Extensions |
|
||||
| SME | AMD Secure Memory Encryption supported |
|
||||
| SME_COHERENT | AMD Hardware cache coherency across encryption domains enforced |
|
||||
| SM3_X86 | SM3 instructions |
|
||||
| SM4_X86 | SM4 instructions |
|
||||
| SPEC_CTRL_SSBD | Speculative Store Bypass Disable |
|
||||
| SRBDS_CTRL | SRBDS mitigation MSR available |
|
||||
| SSE | SSE functions |
|
||||
| SSE2 | P4 SSE functions |
|
||||
| SSE3 | Prescott SSE3 functions |
|
||||
| SSE4 | Penryn SSE4.1 functions |
|
||||
| SSE42 | Nehalem SSE4.2 functions |
|
||||
| SSE4A | AMD Barcelona microarchitecture SSE4a instructions |
|
||||
| SSSE3 | Conroe SSSE3 functions |
|
||||
| STIBP | Single Thread Indirect Branch Predictors |
|
||||
| STIBP_ALWAYSON | AMD: Single Thread Indirect Branch Prediction Mode has Enhanced Performance and may be left Always On |
|
||||
| STOSB_SHORT | Fast short STOSB |
|
||||
| SUCCOR | Software uncorrectable error containment and recovery capability. |
|
||||
| SVM | AMD Secure Virtual Machine |
|
||||
| SVMDA | Indicates support for the SVM decode assists. |
|
||||
| SVMFBASID | SVM, Indicates that TLB flush events, including CR3 writes and CR4.PGE toggles, flush only the current ASID's TLB entries. Also indicates support for the extended VMCBTLB_Control |
|
||||
| SVML | AMD SVM lock. Indicates support for SVM-Lock. |
|
||||
| SVMNP | AMD SVM nested paging |
|
||||
| SVMPF | SVM pause intercept filter. Indicates support for the pause intercept filter |
|
||||
| SVMPFT | SVM PAUSE filter threshold. Indicates support for the PAUSE filter cycle count threshold |
|
||||
| SYSCALL | System-Call Extension (SCE): SYSCALL and SYSRET instructions. |
|
||||
| SYSEE | SYSENTER and SYSEXIT instructions |
|
||||
| TBM | AMD Trailing Bit Manipulation |
|
||||
| TDX_GUEST | Intel Trust Domain Extensions Guest |
|
||||
| TLB_FLUSH_NESTED | AMD: Flushing includes all the nested translations for guest translations |
|
||||
| TME | Intel Total Memory Encryption. The following MSRs are supported: IA32_TME_CAPABILITY, IA32_TME_ACTIVATE, IA32_TME_EXCLUDE_MASK, and IA32_TME_EXCLUDE_BASE. |
|
||||
| TOPEXT | TopologyExtensions: topology extensions support. Indicates support for CPUID Fn8000_001D_EAX_x[N:0]-CPUID Fn8000_001E_EDX. |
|
||||
| TSA_L1_NO | AMD only: Not vulnerable to TSA-L1 |
|
||||
| TSA_SQ_NO | AMD only: Not vulnerable to TSA-SQ |
|
||||
| TSA_VERW_CLEAR | AMD: If set, the memory form of the VERW instruction may be used to help mitigate TSA |
|
||||
| TSCRATEMSR | MSR based TSC rate control. Indicates support for MSR TSC ratio MSRC000_0104 |
|
||||
| TSXLDTRK | Intel TSX Suspend Load Address Tracking |
|
||||
| VAES | Vector AES. AVX(512) versions requires additional checks. |
|
||||
| VMCBCLEAN | VMCB clean bits. Indicates support for VMCB clean bits. |
|
||||
| VMPL | AMD VM Permission Levels supported |
|
||||
| VMSA_REGPROT | AMD VMSA Register Protection supported |
|
||||
| VMX | Virtual Machine Extensions |
|
||||
| VPCLMULQDQ | Carry-Less Multiplication Quadword. Requires AVX for 3 register versions. |
|
||||
| VTE | AMD Virtual Transparent Encryption supported |
|
||||
| WAITPKG | TPAUSE, UMONITOR, UMWAIT |
|
||||
| WBNOINVD | Write Back and Do Not Invalidate Cache |
|
||||
| WRMSRNS | Non-Serializing Write to Model Specific Register |
|
||||
| X87 | FPU |
|
||||
| XGETBV1 | Supports XGETBV with ECX = 1 |
|
||||
| XOP | Bulldozer XOP functions |
|
||||
| XSAVE | XSAVE, XRESTOR, XSETBV, XGETBV |
|
||||
| XSAVEC | Supports XSAVEC and the compacted form of XRSTOR. |
|
||||
| XSAVEOPT | XSAVEOPT available |
|
||||
| XSAVES | Supports XSAVES/XRSTORS and IA32_XSS |
|
||||
|
||||
# ARM features:
|
||||
|
||||
| Feature Flag | Description |
|
||||
|--------------|------------------------------------------------------------------|
|
||||
| AESARM | AES instructions |
|
||||
| ARMCPUID | Some CPU ID registers readable at user-level |
|
||||
| ASIMD | Advanced SIMD |
|
||||
| ASIMDDP | SIMD Dot Product |
|
||||
| ASIMDHP | Advanced SIMD half-precision floating point |
|
||||
| ASIMDRDM | Rounding Double Multiply Accumulate/Subtract (SQRDMLAH/SQRDMLSH) |
|
||||
| ATOMICS | Large System Extensions (LSE) |
|
||||
| CRC32 | CRC32/CRC32C instructions |
|
||||
| DCPOP | Data cache clean to Point of Persistence (DC CVAP) |
|
||||
| EVTSTRM | Generic timer |
|
||||
| FCMA | Floatin point complex number addition and multiplication |
|
||||
| FHM | FMLAL and FMLSL instructions |
|
||||
| FP | Single-precision and double-precision floating point |
|
||||
| FPHP | Half-precision floating point |
|
||||
| GPA | Generic Pointer Authentication |
|
||||
| JSCVT | Javascript-style double->int convert (FJCVTZS) |
|
||||
| LRCPC | Weaker release consistency (LDAPR, etc) |
|
||||
| PMULL | Polynomial Multiply instructions (PMULL/PMULL2) |
|
||||
| RNDR | Random Number instructions |
|
||||
| TLB | Outer Shareable and TLB range maintenance instructions |
|
||||
| TS | Flag manipulation instructions |
|
||||
| SHA1 | SHA-1 instructions (SHA1C, etc) |
|
||||
| SHA2 | SHA-2 instructions (SHA256H, etc) |
|
||||
| SHA3 | SHA-3 instructions (EOR3, RAXI, XAR, BCAX) |
|
||||
| SHA512 | SHA512 instructions |
|
||||
| SM3 | SM3 instructions |
|
||||
| SM4 | SM4 instructions |
|
||||
| SVE | Scalable Vector Extension |
|
||||
|
||||
# license
|
||||
|
||||
This code is published under an MIT license. See LICENSE file for more information.
|
||||
-1679
File diff suppressed because it is too large
Load Diff
-47
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build 386,!gccgo,!noasm,!appengine
|
||||
|
||||
// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuid(SB), 7, $0
|
||||
XORL CX, CX
|
||||
MOVL op+0(FP), AX
|
||||
CPUID
|
||||
MOVL AX, eax+4(FP)
|
||||
MOVL BX, ebx+8(FP)
|
||||
MOVL CX, ecx+12(FP)
|
||||
MOVL DX, edx+16(FP)
|
||||
RET
|
||||
|
||||
// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuidex(SB), 7, $0
|
||||
MOVL op+0(FP), AX
|
||||
MOVL op2+4(FP), CX
|
||||
CPUID
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL BX, ebx+12(FP)
|
||||
MOVL CX, ecx+16(FP)
|
||||
MOVL DX, edx+20(FP)
|
||||
RET
|
||||
|
||||
// func xgetbv(index uint32) (eax, edx uint32)
|
||||
TEXT ·asmXgetbv(SB), 7, $0
|
||||
MOVL index+0(FP), CX
|
||||
BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
|
||||
MOVL AX, eax+4(FP)
|
||||
MOVL DX, edx+8(FP)
|
||||
RET
|
||||
|
||||
// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmRdtscpAsm(SB), 7, $0
|
||||
BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
|
||||
MOVL AX, eax+0(FP)
|
||||
MOVL BX, ebx+4(FP)
|
||||
MOVL CX, ecx+8(FP)
|
||||
MOVL DX, edx+12(FP)
|
||||
RET
|
||||
|
||||
// func asmDarwinHasAVX512() bool
|
||||
TEXT ·asmDarwinHasAVX512(SB), 7, $0
|
||||
MOVL $0, eax+0(FP)
|
||||
RET
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build amd64,!gccgo,!noasm,!appengine
|
||||
|
||||
// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuid(SB), 7, $0
|
||||
XORQ CX, CX
|
||||
MOVL op+0(FP), AX
|
||||
CPUID
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL BX, ebx+12(FP)
|
||||
MOVL CX, ecx+16(FP)
|
||||
MOVL DX, edx+20(FP)
|
||||
RET
|
||||
|
||||
// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuidex(SB), 7, $0
|
||||
MOVL op+0(FP), AX
|
||||
MOVL op2+4(FP), CX
|
||||
CPUID
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL BX, ebx+12(FP)
|
||||
MOVL CX, ecx+16(FP)
|
||||
MOVL DX, edx+20(FP)
|
||||
RET
|
||||
|
||||
// func asmXgetbv(index uint32) (eax, edx uint32)
|
||||
TEXT ·asmXgetbv(SB), 7, $0
|
||||
MOVL index+0(FP), CX
|
||||
BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL DX, edx+12(FP)
|
||||
RET
|
||||
|
||||
// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmRdtscpAsm(SB), 7, $0
|
||||
BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
|
||||
MOVL AX, eax+0(FP)
|
||||
MOVL BX, ebx+4(FP)
|
||||
MOVL CX, ecx+8(FP)
|
||||
MOVL DX, edx+12(FP)
|
||||
RET
|
||||
|
||||
// From https://go-review.googlesource.com/c/sys/+/285572/
|
||||
// func asmDarwinHasAVX512() bool
|
||||
TEXT ·asmDarwinHasAVX512(SB), 7, $0-1
|
||||
MOVB $0, ret+0(FP) // default to false
|
||||
|
||||
#ifdef GOOS_darwin // return if not darwin
|
||||
#ifdef GOARCH_amd64 // return if not amd64
|
||||
// These values from:
|
||||
// https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/osfmk/i386/cpu_capabilities.h
|
||||
#define commpage64_base_address 0x00007fffffe00000
|
||||
#define commpage64_cpu_capabilities64 (commpage64_base_address+0x010)
|
||||
#define commpage64_version (commpage64_base_address+0x01E)
|
||||
#define hasAVX512F 0x0000004000000000
|
||||
MOVQ $commpage64_version, BX
|
||||
MOVW (BX), AX
|
||||
CMPW AX, $13 // versions < 13 do not support AVX512
|
||||
JL no_avx512
|
||||
MOVQ $commpage64_cpu_capabilities64, BX
|
||||
MOVQ (BX), AX
|
||||
MOVQ $hasAVX512F, CX
|
||||
ANDQ CX, AX
|
||||
JZ no_avx512
|
||||
MOVB $1, ret+0(FP)
|
||||
|
||||
no_avx512:
|
||||
#endif
|
||||
#endif
|
||||
RET
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build arm64,!gccgo,!noasm,!appengine
|
||||
|
||||
// See https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
|
||||
|
||||
// func getMidr
|
||||
TEXT ·getMidr(SB), 7, $0
|
||||
WORD $0xd5380000 // mrs x0, midr_el1 /* Main ID Register */
|
||||
MOVD R0, midr+0(FP)
|
||||
RET
|
||||
|
||||
// func getProcFeatures
|
||||
TEXT ·getProcFeatures(SB), 7, $0
|
||||
WORD $0xd5380400 // mrs x0, id_aa64pfr0_el1 /* Processor Feature Register 0 */
|
||||
MOVD R0, procFeatures+0(FP)
|
||||
RET
|
||||
|
||||
// func getInstAttributes
|
||||
TEXT ·getInstAttributes(SB), 7, $0
|
||||
WORD $0xd5380600 // mrs x0, id_aa64isar0_el1 /* Instruction Set Attribute Register 0 */
|
||||
WORD $0xd5380621 // mrs x1, id_aa64isar1_el1 /* Instruction Set Attribute Register 1 */
|
||||
MOVD R0, instAttrReg0+0(FP)
|
||||
MOVD R1, instAttrReg1+8(FP)
|
||||
RET
|
||||
|
||||
TEXT ·getVectorLength(SB), 7, $0
|
||||
WORD $0xd2800002 // mov x2, #0
|
||||
WORD $0x04225022 // addvl x2, x2, #1
|
||||
WORD $0xd37df042 // lsl x2, x2, #3
|
||||
WORD $0xd2800003 // mov x3, #0
|
||||
WORD $0x04635023 // addpl x3, x3, #1
|
||||
WORD $0xd37df063 // lsl x3, x3, #3
|
||||
MOVD R2, vl+0(FP)
|
||||
MOVD R3, pl+8(FP)
|
||||
RET
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build arm64 && !gccgo && !noasm && !appengine
|
||||
// +build arm64,!gccgo,!noasm,!appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
import "runtime"
|
||||
|
||||
func getMidr() (midr uint64)
|
||||
func getProcFeatures() (procFeatures uint64)
|
||||
func getInstAttributes() (instAttrReg0, instAttrReg1 uint64)
|
||||
func getVectorLength() (vl, pl uint64)
|
||||
|
||||
func initCPU() {
|
||||
cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
|
||||
rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
}
|
||||
|
||||
func addInfo(c *CPUInfo, safe bool) {
|
||||
// Seems to be safe to assume on ARM64
|
||||
c.CacheLine = 64
|
||||
detectOS(c)
|
||||
|
||||
// ARM64 disabled since it may crash if interrupt is not intercepted by OS.
|
||||
if safe && !c.Has(ARMCPUID) && runtime.GOOS != "freebsd" {
|
||||
return
|
||||
}
|
||||
midr := getMidr()
|
||||
|
||||
// MIDR_EL1 - Main ID Register
|
||||
// https://developer.arm.com/docs/ddi0595/h/aarch64-system-registers/midr_el1
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | Implementer | [31-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Variant | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Architecture | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | PartNum | [15-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Revision | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
switch (midr >> 24) & 0xff {
|
||||
case 0xC0:
|
||||
c.VendorString = "Ampere Computing"
|
||||
c.VendorID = Ampere
|
||||
case 0x41:
|
||||
c.VendorString = "Arm Limited"
|
||||
c.VendorID = ARM
|
||||
case 0x42:
|
||||
c.VendorString = "Broadcom Corporation"
|
||||
c.VendorID = Broadcom
|
||||
case 0x43:
|
||||
c.VendorString = "Cavium Inc"
|
||||
c.VendorID = Cavium
|
||||
case 0x44:
|
||||
c.VendorString = "Digital Equipment Corporation"
|
||||
c.VendorID = DEC
|
||||
case 0x46:
|
||||
c.VendorString = "Fujitsu Ltd"
|
||||
c.VendorID = Fujitsu
|
||||
case 0x49:
|
||||
c.VendorString = "Infineon Technologies AG"
|
||||
c.VendorID = Infineon
|
||||
case 0x4D:
|
||||
c.VendorString = "Motorola or Freescale Semiconductor Inc"
|
||||
c.VendorID = Motorola
|
||||
case 0x4E:
|
||||
c.VendorString = "NVIDIA Corporation"
|
||||
c.VendorID = NVIDIA
|
||||
case 0x50:
|
||||
c.VendorString = "Applied Micro Circuits Corporation"
|
||||
c.VendorID = AMCC
|
||||
case 0x51:
|
||||
c.VendorString = "Qualcomm Inc"
|
||||
c.VendorID = Qualcomm
|
||||
case 0x56:
|
||||
c.VendorString = "Marvell International Ltd"
|
||||
c.VendorID = Marvell
|
||||
case 0x69:
|
||||
c.VendorString = "Intel Corporation"
|
||||
c.VendorID = Intel
|
||||
}
|
||||
|
||||
// Lower 4 bits: Architecture
|
||||
// Architecture Meaning
|
||||
// 0b0001 Armv4.
|
||||
// 0b0010 Armv4T.
|
||||
// 0b0011 Armv5 (obsolete).
|
||||
// 0b0100 Armv5T.
|
||||
// 0b0101 Armv5TE.
|
||||
// 0b0110 Armv5TEJ.
|
||||
// 0b0111 Armv6.
|
||||
// 0b1111 Architectural features are individually identified in the ID_* registers, see 'ID registers'.
|
||||
// Upper 4 bit: Variant
|
||||
// An IMPLEMENTATION DEFINED variant number.
|
||||
// Typically, this field is used to distinguish between different product variants, or major revisions of a product.
|
||||
c.Family = int(midr>>16) & 0xff
|
||||
|
||||
// PartNum, bits [15:4]
|
||||
// An IMPLEMENTATION DEFINED primary part number for the device.
|
||||
// On processors implemented by Arm, if the top four bits of the primary
|
||||
// part number are 0x0 or 0x7, the variant and architecture are encoded differently.
|
||||
// Revision, bits [3:0]
|
||||
// An IMPLEMENTATION DEFINED revision number for the device.
|
||||
c.Model = int(midr) & 0xffff
|
||||
|
||||
procFeatures := getProcFeatures()
|
||||
|
||||
// ID_AA64PFR0_EL1 - Processor Feature Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | DIT | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SVE | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GIC | [27-24] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | AdvSIMD | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FP | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | EL3 | [15-12] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL2 | [11-8] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL1 | [7-4] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL0 | [3-0] | n |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
var f flagSet
|
||||
// if procFeatures&(0xf<<48) != 0 {
|
||||
// fmt.Println("DIT")
|
||||
// }
|
||||
f.setIf(procFeatures&(0xf<<32) != 0, SVE)
|
||||
if procFeatures&(0xf<<20) != 15<<20 {
|
||||
f.set(ASIMD)
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64pfr0_el1
|
||||
// 0b0001 --> As for 0b0000, and also includes support for half-precision floating-point arithmetic.
|
||||
f.setIf(procFeatures&(0xf<<20) == 1<<20, FPHP, ASIMDHP)
|
||||
}
|
||||
f.setIf(procFeatures&(0xf<<16) != 0, FP)
|
||||
|
||||
instAttrReg0, instAttrReg1 := getInstAttributes()
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
//
|
||||
// ID_AA64ISAR0_EL1 - Instruction Set Attribute Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | RNDR | [63-60] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | TLB | [59-56] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | TS | [55-52] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FHM | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DP | [47-44] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM4 | [43-40] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM3 | [39-36] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA3 | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | RDM | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | ATOMICS | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | CRC32 | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA2 | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA1 | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | AES | [7-4] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
f.setIf(instAttrReg0&(0xf<<60) != 0, RNDR)
|
||||
f.setIf(instAttrReg0&(0xf<<56) != 0, TLB)
|
||||
f.setIf(instAttrReg0&(0xf<<52) != 0, TS)
|
||||
f.setIf(instAttrReg0&(0xf<<48) != 0, FHM)
|
||||
f.setIf(instAttrReg0&(0xf<<44) != 0, ASIMDDP)
|
||||
f.setIf(instAttrReg0&(0xf<<40) != 0, SM4)
|
||||
f.setIf(instAttrReg0&(0xf<<36) != 0, SM3)
|
||||
f.setIf(instAttrReg0&(0xf<<32) != 0, SHA3)
|
||||
f.setIf(instAttrReg0&(0xf<<28) != 0, ASIMDRDM)
|
||||
f.setIf(instAttrReg0&(0xf<<20) != 0, ATOMICS)
|
||||
f.setIf(instAttrReg0&(0xf<<16) != 0, CRC32)
|
||||
f.setIf(instAttrReg0&(0xf<<12) != 0, SHA2)
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As 0b0001, plus SHA512H, SHA512H2, SHA512SU0, and SHA512SU1 instructions implemented.
|
||||
f.setIf(instAttrReg0&(0xf<<12) == 2<<12, SHA512)
|
||||
f.setIf(instAttrReg0&(0xf<<8) != 0, SHA1)
|
||||
f.setIf(instAttrReg0&(0xf<<4) != 0, AESARM)
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As for 0b0001, plus PMULL/PMULL2 instructions operating on 64-bit data quantities.
|
||||
f.setIf(instAttrReg0&(0xf<<4) == 2<<4, PMULL)
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar1_el1
|
||||
//
|
||||
// ID_AA64ISAR1_EL1 - Instruction set attribute register 1
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | GPI | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GPA | [27-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | LRCPC | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FCMA | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | JSCVT | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | API | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | APA | [7-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DPB | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// if instAttrReg1&(0xf<<28) != 0 {
|
||||
// fmt.Println("GPI")
|
||||
// }
|
||||
f.setIf(instAttrReg1&(0xf<<28) != 24, GPA)
|
||||
f.setIf(instAttrReg1&(0xf<<20) != 0, LRCPC)
|
||||
f.setIf(instAttrReg1&(0xf<<16) != 0, FCMA)
|
||||
f.setIf(instAttrReg1&(0xf<<12) != 0, JSCVT)
|
||||
// if instAttrReg1&(0xf<<8) != 0 {
|
||||
// fmt.Println("API")
|
||||
// }
|
||||
// if instAttrReg1&(0xf<<4) != 0 {
|
||||
// fmt.Println("APA")
|
||||
// }
|
||||
f.setIf(instAttrReg1&(0xf<<0) != 0, DCPOP)
|
||||
|
||||
// Store
|
||||
c.featureSet.or(f)
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build (!amd64 && !386 && !arm64) || gccgo || noasm || appengine
|
||||
// +build !amd64,!386,!arm64 gccgo noasm appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
func initCPU() {
|
||||
cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
|
||||
rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
|
||||
}
|
||||
|
||||
func addInfo(info *CPUInfo, safe bool) {}
|
||||
func getVectorLength() (vl, pl uint64) { return 0, 0 }
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build (386 && !gccgo && !noasm && !appengine) || (amd64 && !gccgo && !noasm && !appengine)
|
||||
// +build 386,!gccgo,!noasm,!appengine amd64,!gccgo,!noasm,!appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||
func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||
func asmXgetbv(index uint32) (eax, edx uint32)
|
||||
func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||
func asmDarwinHasAVX512() bool
|
||||
|
||||
func initCPU() {
|
||||
cpuid = asmCpuid
|
||||
cpuidex = asmCpuidex
|
||||
xgetbv = asmXgetbv
|
||||
rdtscpAsm = asmRdtscpAsm
|
||||
darwinHasAVX512 = asmDarwinHasAVX512
|
||||
}
|
||||
|
||||
func addInfo(c *CPUInfo, safe bool) {
|
||||
c.maxFunc = maxFunctionID()
|
||||
c.maxExFunc = maxExtendedFunction()
|
||||
c.BrandName = brandName()
|
||||
c.CacheLine = cacheLine()
|
||||
c.Family, c.Model, c.Stepping = familyModel()
|
||||
c.featureSet = support()
|
||||
c.SGX = hasSGX(c.featureSet.inSet(SGX), c.featureSet.inSet(SGXLC))
|
||||
c.AMDMemEncryption = hasAMDMemEncryption(c.featureSet.inSet(SME) || c.featureSet.inSet(SEV))
|
||||
c.ThreadsPerCore = threadsPerCore()
|
||||
c.LogicalCores = logicalCores()
|
||||
c.PhysicalCores = physicalCores()
|
||||
c.VendorID, c.VendorString = vendorID()
|
||||
c.HypervisorVendorID, c.HypervisorVendorString = hypervisorVendorID()
|
||||
c.AVX10Level = c.supportAVX10()
|
||||
c.cacheSize()
|
||||
c.frequencies()
|
||||
if c.maxFunc >= 0x0A {
|
||||
eax, ebx, _, edx := cpuid(0x0A)
|
||||
c.PMU = parseLeaf0AH(c, eax, ebx, edx)
|
||||
}
|
||||
}
|
||||
|
||||
func getVectorLength() (vl, pl uint64) { return 0, 0 }
|
||||
-308
@@ -1,308 +0,0 @@
|
||||
// Code generated by "stringer -type=FeatureID,Vendor"; DO NOT EDIT.
|
||||
|
||||
package cpuid
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ADX-1]
|
||||
_ = x[AESNI-2]
|
||||
_ = x[AMD3DNOW-3]
|
||||
_ = x[AMD3DNOWEXT-4]
|
||||
_ = x[AMXBF16-5]
|
||||
_ = x[AMXFP16-6]
|
||||
_ = x[AMXINT8-7]
|
||||
_ = x[AMXFP8-8]
|
||||
_ = x[AMXTILE-9]
|
||||
_ = x[AMXTF32-10]
|
||||
_ = x[AMXCOMPLEX-11]
|
||||
_ = x[AMXTRANSPOSE-12]
|
||||
_ = x[APX_F-13]
|
||||
_ = x[AVX-14]
|
||||
_ = x[AVX10-15]
|
||||
_ = x[AVX10_128-16]
|
||||
_ = x[AVX10_256-17]
|
||||
_ = x[AVX10_512-18]
|
||||
_ = x[AVX2-19]
|
||||
_ = x[AVX512BF16-20]
|
||||
_ = x[AVX512BITALG-21]
|
||||
_ = x[AVX512BW-22]
|
||||
_ = x[AVX512CD-23]
|
||||
_ = x[AVX512DQ-24]
|
||||
_ = x[AVX512ER-25]
|
||||
_ = x[AVX512F-26]
|
||||
_ = x[AVX512FP16-27]
|
||||
_ = x[AVX512IFMA-28]
|
||||
_ = x[AVX512PF-29]
|
||||
_ = x[AVX512VBMI-30]
|
||||
_ = x[AVX512VBMI2-31]
|
||||
_ = x[AVX512VL-32]
|
||||
_ = x[AVX512VNNI-33]
|
||||
_ = x[AVX512VP2INTERSECT-34]
|
||||
_ = x[AVX512VPOPCNTDQ-35]
|
||||
_ = x[AVXIFMA-36]
|
||||
_ = x[AVXNECONVERT-37]
|
||||
_ = x[AVXSLOW-38]
|
||||
_ = x[AVXVNNI-39]
|
||||
_ = x[AVXVNNIINT8-40]
|
||||
_ = x[AVXVNNIINT16-41]
|
||||
_ = x[BHI_CTRL-42]
|
||||
_ = x[BMI1-43]
|
||||
_ = x[BMI2-44]
|
||||
_ = x[CETIBT-45]
|
||||
_ = x[CETSS-46]
|
||||
_ = x[CLDEMOTE-47]
|
||||
_ = x[CLMUL-48]
|
||||
_ = x[CLZERO-49]
|
||||
_ = x[CMOV-50]
|
||||
_ = x[CMPCCXADD-51]
|
||||
_ = x[CMPSB_SCADBS_SHORT-52]
|
||||
_ = x[CMPXCHG8-53]
|
||||
_ = x[CPBOOST-54]
|
||||
_ = x[CPPC-55]
|
||||
_ = x[CX16-56]
|
||||
_ = x[EFER_LMSLE_UNS-57]
|
||||
_ = x[ENQCMD-58]
|
||||
_ = x[ERMS-59]
|
||||
_ = x[F16C-60]
|
||||
_ = x[FLUSH_L1D-61]
|
||||
_ = x[FMA3-62]
|
||||
_ = x[FMA4-63]
|
||||
_ = x[FP128-64]
|
||||
_ = x[FP256-65]
|
||||
_ = x[FSRM-66]
|
||||
_ = x[FXSR-67]
|
||||
_ = x[FXSROPT-68]
|
||||
_ = x[GFNI-69]
|
||||
_ = x[HLE-70]
|
||||
_ = x[HRESET-71]
|
||||
_ = x[HTT-72]
|
||||
_ = x[HWA-73]
|
||||
_ = x[HYBRID_CPU-74]
|
||||
_ = x[HYPERVISOR-75]
|
||||
_ = x[IA32_ARCH_CAP-76]
|
||||
_ = x[IA32_CORE_CAP-77]
|
||||
_ = x[IBPB-78]
|
||||
_ = x[IBPB_BRTYPE-79]
|
||||
_ = x[IBRS-80]
|
||||
_ = x[IBRS_PREFERRED-81]
|
||||
_ = x[IBRS_PROVIDES_SMP-82]
|
||||
_ = x[IBS-83]
|
||||
_ = x[IBSBRNTRGT-84]
|
||||
_ = x[IBSFETCHSAM-85]
|
||||
_ = x[IBSFFV-86]
|
||||
_ = x[IBSOPCNT-87]
|
||||
_ = x[IBSOPCNTEXT-88]
|
||||
_ = x[IBSOPSAM-89]
|
||||
_ = x[IBSRDWROPCNT-90]
|
||||
_ = x[IBSRIPINVALIDCHK-91]
|
||||
_ = x[IBS_FETCH_CTLX-92]
|
||||
_ = x[IBS_OPDATA4-93]
|
||||
_ = x[IBS_OPFUSE-94]
|
||||
_ = x[IBS_PREVENTHOST-95]
|
||||
_ = x[IBS_ZEN4-96]
|
||||
_ = x[IDPRED_CTRL-97]
|
||||
_ = x[INT_WBINVD-98]
|
||||
_ = x[INVLPGB-99]
|
||||
_ = x[KEYLOCKER-100]
|
||||
_ = x[KEYLOCKERW-101]
|
||||
_ = x[LAHF-102]
|
||||
_ = x[LAM-103]
|
||||
_ = x[LBRVIRT-104]
|
||||
_ = x[LZCNT-105]
|
||||
_ = x[MCAOVERFLOW-106]
|
||||
_ = x[MCDT_NO-107]
|
||||
_ = x[MCOMMIT-108]
|
||||
_ = x[MD_CLEAR-109]
|
||||
_ = x[MMX-110]
|
||||
_ = x[MMXEXT-111]
|
||||
_ = x[MOVBE-112]
|
||||
_ = x[MOVDIR64B-113]
|
||||
_ = x[MOVDIRI-114]
|
||||
_ = x[MOVSB_ZL-115]
|
||||
_ = x[MOVU-116]
|
||||
_ = x[MPX-117]
|
||||
_ = x[MSRIRC-118]
|
||||
_ = x[MSRLIST-119]
|
||||
_ = x[MSR_PAGEFLUSH-120]
|
||||
_ = x[NRIPS-121]
|
||||
_ = x[NX-122]
|
||||
_ = x[OSXSAVE-123]
|
||||
_ = x[PCONFIG-124]
|
||||
_ = x[POPCNT-125]
|
||||
_ = x[PPIN-126]
|
||||
_ = x[PREFETCHI-127]
|
||||
_ = x[PSFD-128]
|
||||
_ = x[RDPRU-129]
|
||||
_ = x[RDRAND-130]
|
||||
_ = x[RDSEED-131]
|
||||
_ = x[RDTSCP-132]
|
||||
_ = x[RRSBA_CTRL-133]
|
||||
_ = x[RTM-134]
|
||||
_ = x[RTM_ALWAYS_ABORT-135]
|
||||
_ = x[SBPB-136]
|
||||
_ = x[SERIALIZE-137]
|
||||
_ = x[SEV-138]
|
||||
_ = x[SEV_64BIT-139]
|
||||
_ = x[SEV_ALTERNATIVE-140]
|
||||
_ = x[SEV_DEBUGSWAP-141]
|
||||
_ = x[SEV_ES-142]
|
||||
_ = x[SEV_RESTRICTED-143]
|
||||
_ = x[SEV_SNP-144]
|
||||
_ = x[SGX-145]
|
||||
_ = x[SGXLC-146]
|
||||
_ = x[SGXPQC-147]
|
||||
_ = x[SHA-148]
|
||||
_ = x[SME-149]
|
||||
_ = x[SME_COHERENT-150]
|
||||
_ = x[SM3_X86-151]
|
||||
_ = x[SM4_X86-152]
|
||||
_ = x[SPEC_CTRL_SSBD-153]
|
||||
_ = x[SRBDS_CTRL-154]
|
||||
_ = x[SRSO_MSR_FIX-155]
|
||||
_ = x[SRSO_NO-156]
|
||||
_ = x[SRSO_USER_KERNEL_NO-157]
|
||||
_ = x[SSE-158]
|
||||
_ = x[SSE2-159]
|
||||
_ = x[SSE3-160]
|
||||
_ = x[SSE4-161]
|
||||
_ = x[SSE42-162]
|
||||
_ = x[SSE4A-163]
|
||||
_ = x[SSSE3-164]
|
||||
_ = x[STIBP-165]
|
||||
_ = x[STIBP_ALWAYSON-166]
|
||||
_ = x[STOSB_SHORT-167]
|
||||
_ = x[SUCCOR-168]
|
||||
_ = x[SVM-169]
|
||||
_ = x[SVMDA-170]
|
||||
_ = x[SVMFBASID-171]
|
||||
_ = x[SVML-172]
|
||||
_ = x[SVMNP-173]
|
||||
_ = x[SVMPF-174]
|
||||
_ = x[SVMPFT-175]
|
||||
_ = x[SYSCALL-176]
|
||||
_ = x[SYSEE-177]
|
||||
_ = x[TBM-178]
|
||||
_ = x[TDX_GUEST-179]
|
||||
_ = x[TLB_FLUSH_NESTED-180]
|
||||
_ = x[TME-181]
|
||||
_ = x[TOPEXT-182]
|
||||
_ = x[TSA_L1_NO-183]
|
||||
_ = x[TSA_SQ_NO-184]
|
||||
_ = x[TSA_VERW_CLEAR-185]
|
||||
_ = x[TSCRATEMSR-186]
|
||||
_ = x[TSXLDTRK-187]
|
||||
_ = x[VAES-188]
|
||||
_ = x[VMCBCLEAN-189]
|
||||
_ = x[VMPL-190]
|
||||
_ = x[VMSA_REGPROT-191]
|
||||
_ = x[VMX-192]
|
||||
_ = x[VPCLMULQDQ-193]
|
||||
_ = x[VTE-194]
|
||||
_ = x[WAITPKG-195]
|
||||
_ = x[WBNOINVD-196]
|
||||
_ = x[WRMSRNS-197]
|
||||
_ = x[X87-198]
|
||||
_ = x[XGETBV1-199]
|
||||
_ = x[XOP-200]
|
||||
_ = x[XSAVE-201]
|
||||
_ = x[XSAVEC-202]
|
||||
_ = x[XSAVEOPT-203]
|
||||
_ = x[XSAVES-204]
|
||||
_ = x[AESARM-205]
|
||||
_ = x[ARMCPUID-206]
|
||||
_ = x[ASIMD-207]
|
||||
_ = x[ASIMDDP-208]
|
||||
_ = x[ASIMDHP-209]
|
||||
_ = x[ASIMDRDM-210]
|
||||
_ = x[ATOMICS-211]
|
||||
_ = x[CRC32-212]
|
||||
_ = x[DCPOP-213]
|
||||
_ = x[EVTSTRM-214]
|
||||
_ = x[FCMA-215]
|
||||
_ = x[FHM-216]
|
||||
_ = x[FP-217]
|
||||
_ = x[FPHP-218]
|
||||
_ = x[GPA-219]
|
||||
_ = x[JSCVT-220]
|
||||
_ = x[LRCPC-221]
|
||||
_ = x[PMULL-222]
|
||||
_ = x[RNDR-223]
|
||||
_ = x[TLB-224]
|
||||
_ = x[TS-225]
|
||||
_ = x[SHA1-226]
|
||||
_ = x[SHA2-227]
|
||||
_ = x[SHA3-228]
|
||||
_ = x[SHA512-229]
|
||||
_ = x[SM3-230]
|
||||
_ = x[SM4-231]
|
||||
_ = x[SVE-232]
|
||||
_ = x[PMU_FIXEDCOUNTER_CYCLES-233]
|
||||
_ = x[PMU_FIXEDCOUNTER_REFCYCLES-234]
|
||||
_ = x[PMU_FIXEDCOUNTER_INSTRUCTIONS-235]
|
||||
_ = x[PMU_FIXEDCOUNTER_TOPDOWN_SLOTS-236]
|
||||
_ = x[lastID-237]
|
||||
_ = x[firstID-0]
|
||||
}
|
||||
|
||||
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXFP16AMXINT8AMXFP8AMXTILEAMXTF32AMXCOMPLEXAMXTRANSPOSEAPX_FAVXAVX10AVX10_128AVX10_256AVX10_512AVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXIFMAAVXNECONVERTAVXSLOWAVXVNNIAVXVNNIINT8AVXVNNIINT16BHI_CTRLBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPCCXADDCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCPPCCX16EFER_LMSLE_UNSENQCMDERMSF16CFLUSH_L1DFMA3FMA4FP128FP256FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBPB_BRTYPEIBRSIBRS_PREFERREDIBRS_PROVIDES_SMPIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_FETCH_CTLXIBS_OPDATA4IBS_OPFUSEIBS_PREVENTHOSTIBS_ZEN4IDPRED_CTRLINT_WBINVDINVLPGBKEYLOCKERKEYLOCKERWLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMOVUMPXMSRIRCMSRLISTMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTPPINPREFETCHIPSFDRDPRURDRANDRDSEEDRDTSCPRRSBA_CTRLRTMRTM_ALWAYS_ABORTSBPBSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSGXPQCSHASMESME_COHERENTSM3_X86SM4_X86SPEC_CTRL_SSBDSRBDS_CTRLSRSO_MSR_FIXSRSO_NOSRSO_USER_KERNEL_NOSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTIBP_ALWAYSONSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTDX_GUESTTLB_FLUSH_NESTEDTMETOPEXTTSA_L1_NOTSA_SQ_NOTSA_VERW_CLEARTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDWRMSRNSX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFHMFPFPHPGPAJSCVTLRCPCPMULLRNDRTLBTSSHA1SHA2SHA3SHA512SM3SM4SVEPMU_FIXEDCOUNTER_CYCLESPMU_FIXEDCOUNTER_REFCYCLESPMU_FIXEDCOUNTER_INSTRUCTIONSPMU_FIXEDCOUNTER_TOPDOWN_SLOTSlastID"
|
||||
|
||||
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 61, 68, 75, 85, 97, 102, 105, 110, 119, 128, 137, 141, 151, 163, 171, 179, 187, 195, 202, 212, 222, 230, 240, 251, 259, 269, 287, 302, 309, 321, 328, 335, 346, 358, 366, 370, 374, 380, 385, 393, 398, 404, 408, 417, 435, 443, 450, 454, 458, 472, 478, 482, 486, 495, 499, 503, 508, 513, 517, 521, 528, 532, 535, 541, 544, 547, 557, 567, 580, 593, 597, 608, 612, 626, 643, 646, 656, 667, 673, 681, 692, 700, 712, 728, 742, 753, 763, 778, 786, 797, 807, 814, 823, 833, 837, 840, 847, 852, 863, 870, 877, 885, 888, 894, 899, 908, 915, 923, 927, 930, 936, 943, 956, 961, 963, 970, 977, 983, 987, 996, 1000, 1005, 1011, 1017, 1023, 1033, 1036, 1052, 1056, 1065, 1068, 1077, 1092, 1105, 1111, 1125, 1132, 1135, 1140, 1146, 1149, 1152, 1164, 1171, 1178, 1192, 1202, 1214, 1221, 1240, 1243, 1247, 1251, 1255, 1260, 1265, 1270, 1275, 1289, 1300, 1306, 1309, 1314, 1323, 1327, 1332, 1337, 1343, 1350, 1355, 1358, 1367, 1383, 1386, 1392, 1401, 1410, 1424, 1434, 1442, 1446, 1455, 1459, 1471, 1474, 1484, 1487, 1494, 1502, 1509, 1512, 1519, 1522, 1527, 1533, 1541, 1547, 1553, 1561, 1566, 1573, 1580, 1588, 1595, 1600, 1605, 1612, 1616, 1619, 1621, 1625, 1628, 1633, 1638, 1643, 1647, 1650, 1652, 1656, 1660, 1664, 1670, 1673, 1676, 1679, 1702, 1728, 1757, 1787, 1793}
|
||||
|
||||
func (i FeatureID) String() string {
|
||||
if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {
|
||||
return "FeatureID(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _FeatureID_name[_FeatureID_index[i]:_FeatureID_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[VendorUnknown-0]
|
||||
_ = x[Intel-1]
|
||||
_ = x[AMD-2]
|
||||
_ = x[VIA-3]
|
||||
_ = x[Transmeta-4]
|
||||
_ = x[NSC-5]
|
||||
_ = x[KVM-6]
|
||||
_ = x[MSVM-7]
|
||||
_ = x[VMware-8]
|
||||
_ = x[XenHVM-9]
|
||||
_ = x[Bhyve-10]
|
||||
_ = x[Hygon-11]
|
||||
_ = x[SiS-12]
|
||||
_ = x[RDC-13]
|
||||
_ = x[Ampere-14]
|
||||
_ = x[ARM-15]
|
||||
_ = x[Broadcom-16]
|
||||
_ = x[Cavium-17]
|
||||
_ = x[DEC-18]
|
||||
_ = x[Fujitsu-19]
|
||||
_ = x[Infineon-20]
|
||||
_ = x[Motorola-21]
|
||||
_ = x[NVIDIA-22]
|
||||
_ = x[AMCC-23]
|
||||
_ = x[Qualcomm-24]
|
||||
_ = x[Marvell-25]
|
||||
_ = x[QEMU-26]
|
||||
_ = x[QNX-27]
|
||||
_ = x[ACRN-28]
|
||||
_ = x[SRE-29]
|
||||
_ = x[Apple-30]
|
||||
_ = x[lastVendor-31]
|
||||
}
|
||||
|
||||
const _Vendor_name = "VendorUnknownIntelAMDVIATransmetaNSCKVMMSVMVMwareXenHVMBhyveHygonSiSRDCAmpereARMBroadcomCaviumDECFujitsuInfineonMotorolaNVIDIAAMCCQualcommMarvellQEMUQNXACRNSREApplelastVendor"
|
||||
|
||||
var _Vendor_index = [...]uint8{0, 13, 18, 21, 24, 33, 36, 39, 43, 49, 55, 60, 65, 68, 71, 77, 80, 88, 94, 97, 104, 112, 120, 126, 130, 138, 145, 149, 152, 156, 159, 164, 174}
|
||||
|
||||
func (i Vendor) String() string {
|
||||
if i < 0 || i >= Vendor(len(_Vendor_index)-1) {
|
||||
return "Vendor(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Vendor_name[_Vendor_index[i]:_Vendor_index[i+1]]
|
||||
}
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
package cpuid
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func detectOS(c *CPUInfo) bool {
|
||||
if runtime.GOOS != "ios" {
|
||||
tryToFillCPUInfoFomSysctl(c)
|
||||
}
|
||||
// There are no hw.optional sysctl values for the below features on Mac OS 11.0
|
||||
// to detect their supported state dynamically. Assume the CPU features that
|
||||
// Apple Silicon M1 supports to be available as a minimal set of features
|
||||
// to all Go programs running on darwin/arm64.
|
||||
// TODO: Add more if we know them.
|
||||
c.featureSet.setIf(runtime.GOOS != "ios", AESARM, PMULL, SHA1, SHA2)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func sysctlGetBool(name string) bool {
|
||||
value, err := unix.SysctlUint32(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return value != 0
|
||||
}
|
||||
|
||||
func sysctlGetString(name string) string {
|
||||
value, err := unix.Sysctl(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func sysctlGetInt(unknown int, names ...string) int {
|
||||
for _, name := range names {
|
||||
value, err := unix.SysctlUint32(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if value != 0 {
|
||||
return int(value)
|
||||
}
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
func sysctlGetInt64(unknown int, names ...string) int {
|
||||
for _, name := range names {
|
||||
value64, err := unix.SysctlUint64(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if int(value64) != unknown {
|
||||
return int(value64)
|
||||
}
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
func setFeature(c *CPUInfo, feature FeatureID, aliases ...string) {
|
||||
for _, alias := range aliases {
|
||||
set := sysctlGetBool(alias)
|
||||
c.featureSet.setIf(set, feature)
|
||||
if set {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tryToFillCPUInfoFomSysctl(c *CPUInfo) {
|
||||
c.BrandName = sysctlGetString("machdep.cpu.brand_string")
|
||||
|
||||
if len(c.BrandName) != 0 {
|
||||
c.VendorString = strings.Fields(c.BrandName)[0]
|
||||
}
|
||||
|
||||
c.PhysicalCores = sysctlGetInt(runtime.NumCPU(), "hw.physicalcpu")
|
||||
c.ThreadsPerCore = sysctlGetInt(1, "machdep.cpu.thread_count", "kern.num_threads") /
|
||||
sysctlGetInt(1, "hw.physicalcpu")
|
||||
c.LogicalCores = sysctlGetInt(runtime.NumCPU(), "machdep.cpu.core_count")
|
||||
c.Family = sysctlGetInt(0, "machdep.cpu.family", "hw.cpufamily")
|
||||
c.Model = sysctlGetInt(0, "machdep.cpu.model")
|
||||
c.CacheLine = sysctlGetInt64(0, "hw.cachelinesize")
|
||||
c.Cache.L1I = sysctlGetInt64(-1, "hw.l1icachesize")
|
||||
c.Cache.L1D = sysctlGetInt64(-1, "hw.l1dcachesize")
|
||||
c.Cache.L2 = sysctlGetInt64(-1, "hw.l2cachesize")
|
||||
c.Cache.L3 = sysctlGetInt64(-1, "hw.l3cachesize")
|
||||
|
||||
// ARM features:
|
||||
//
|
||||
// Note: On some Apple Silicon system, some feats have aliases. See:
|
||||
// https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics
|
||||
// When so, we look at all aliases and consider a feature available when at least one identifier matches.
|
||||
setFeature(c, AESARM, "hw.optional.arm.FEAT_AES") // AES instructions
|
||||
setFeature(c, ASIMD, "hw.optional.arm.AdvSIMD", "hw.optional.neon") // Advanced SIMD
|
||||
setFeature(c, ASIMDDP, "hw.optional.arm.FEAT_DotProd") // SIMD Dot Product
|
||||
setFeature(c, ASIMDHP, "hw.optional.arm.AdvSIMD_HPFPCvt", "hw.optional.neon_hpfp") // Advanced SIMD half-precision floating point
|
||||
setFeature(c, ASIMDRDM, "hw.optional.arm.FEAT_RDM") // Rounding Double Multiply Accumulate/Subtract
|
||||
setFeature(c, ATOMICS, "hw.optional.arm.FEAT_LSE", "hw.optional.armv8_1_atomics") // Large System Extensions (LSE)
|
||||
setFeature(c, CRC32, "hw.optional.arm.FEAT_CRC32", "hw.optional.armv8_crc32") // CRC32/CRC32C instructions
|
||||
setFeature(c, DCPOP, "hw.optional.arm.FEAT_DPB") // Data cache clean to Point of Persistence (DC CVAP)
|
||||
setFeature(c, EVTSTRM, "hw.optional.arm.FEAT_ECV") // Generic timer
|
||||
setFeature(c, FCMA, "hw.optional.arm.FEAT_FCMA", "hw.optional.armv8_3_compnum") // Floating point complex number addition and multiplication
|
||||
setFeature(c, FHM, "hw.optional.armv8_2_fhm", "hw.optional.arm.FEAT_FHM") // FMLAL and FMLSL instructions
|
||||
setFeature(c, FP, "hw.optional.floatingpoint") // Single-precision and double-precision floating point
|
||||
setFeature(c, FPHP, "hw.optional.arm.FEAT_FP16", "hw.optional.neon_fp16") // Half-precision floating point
|
||||
setFeature(c, GPA, "hw.optional.arm.FEAT_PAuth") // Generic Pointer Authentication
|
||||
setFeature(c, JSCVT, "hw.optional.arm.FEAT_JSCVT") // Javascript-style double->int convert (FJCVTZS)
|
||||
setFeature(c, LRCPC, "hw.optional.arm.FEAT_LRCPC") // Weaker release consistency (LDAPR, etc)
|
||||
setFeature(c, PMULL, "hw.optional.arm.FEAT_PMULL") // Polynomial Multiply instructions (PMULL/PMULL2)
|
||||
setFeature(c, RNDR, "hw.optional.arm.FEAT_RNG") // Random Number instructions
|
||||
setFeature(c, TLB, "hw.optional.arm.FEAT_TLBIOS", "hw.optional.arm.FEAT_TLBIRANGE") // Outer Shareable and TLB range maintenance instructions
|
||||
setFeature(c, TS, "hw.optional.arm.FEAT_FlagM", "hw.optional.arm.FEAT_FlagM2") // Flag manipulation instructions
|
||||
setFeature(c, SHA1, "hw.optional.arm.FEAT_SHA1") // SHA-1 instructions (SHA1C, etc)
|
||||
setFeature(c, SHA2, "hw.optional.arm.FEAT_SHA256") // SHA-2 instructions (SHA256H, etc)
|
||||
setFeature(c, SHA3, "hw.optional.arm.FEAT_SHA3") // SHA-3 instructions (EOR3, RAXI, XAR, BCAX)
|
||||
setFeature(c, SHA512, "hw.optional.arm.FEAT_SHA512") // SHA512 instructions
|
||||
setFeature(c, SM3, "hw.optional.arm.FEAT_SM3") // SM3 instructions
|
||||
setFeature(c, SM4, "hw.optional.arm.FEAT_SM4") // SM4 instructions
|
||||
setFeature(c, SVE, "hw.optional.arm.FEAT_SVE") // Scalable Vector Extension
|
||||
}
|
||||
-208
@@ -1,208 +0,0 @@
|
||||
// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
// 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 located
|
||||
// here https://github.com/golang/sys/blob/master/LICENSE
|
||||
|
||||
package cpuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// HWCAP bits.
|
||||
const (
|
||||
hwcap_FP = 1 << 0
|
||||
hwcap_ASIMD = 1 << 1
|
||||
hwcap_EVTSTRM = 1 << 2
|
||||
hwcap_AES = 1 << 3
|
||||
hwcap_PMULL = 1 << 4
|
||||
hwcap_SHA1 = 1 << 5
|
||||
hwcap_SHA2 = 1 << 6
|
||||
hwcap_CRC32 = 1 << 7
|
||||
hwcap_ATOMICS = 1 << 8
|
||||
hwcap_FPHP = 1 << 9
|
||||
hwcap_ASIMDHP = 1 << 10
|
||||
hwcap_CPUID = 1 << 11
|
||||
hwcap_ASIMDRDM = 1 << 12
|
||||
hwcap_JSCVT = 1 << 13
|
||||
hwcap_FCMA = 1 << 14
|
||||
hwcap_LRCPC = 1 << 15
|
||||
hwcap_DCPOP = 1 << 16
|
||||
hwcap_SHA3 = 1 << 17
|
||||
hwcap_SM3 = 1 << 18
|
||||
hwcap_SM4 = 1 << 19
|
||||
hwcap_ASIMDDP = 1 << 20
|
||||
hwcap_SHA512 = 1 << 21
|
||||
hwcap_SVE = 1 << 22
|
||||
hwcap_ASIMDFHM = 1 << 23
|
||||
hwcap_DIT = 1 << 24
|
||||
hwcap_USCAT = 1 << 25
|
||||
hwcap_ILRCPC = 1 << 26
|
||||
hwcap_FLAGM = 1 << 27
|
||||
hwcap_SSBS = 1 << 28
|
||||
hwcap_SB = 1 << 29
|
||||
hwcap_PACA = 1 << 30
|
||||
hwcap_PACG = 1 << 31
|
||||
hwcap_GCS = 1 << 32
|
||||
|
||||
hwcap2_DCPODP = 1 << 0
|
||||
hwcap2_SVE2 = 1 << 1
|
||||
hwcap2_SVEAES = 1 << 2
|
||||
hwcap2_SVEPMULL = 1 << 3
|
||||
hwcap2_SVEBITPERM = 1 << 4
|
||||
hwcap2_SVESHA3 = 1 << 5
|
||||
hwcap2_SVESM4 = 1 << 6
|
||||
hwcap2_FLAGM2 = 1 << 7
|
||||
hwcap2_FRINT = 1 << 8
|
||||
hwcap2_SVEI8MM = 1 << 9
|
||||
hwcap2_SVEF32MM = 1 << 10
|
||||
hwcap2_SVEF64MM = 1 << 11
|
||||
hwcap2_SVEBF16 = 1 << 12
|
||||
hwcap2_I8MM = 1 << 13
|
||||
hwcap2_BF16 = 1 << 14
|
||||
hwcap2_DGH = 1 << 15
|
||||
hwcap2_RNG = 1 << 16
|
||||
hwcap2_BTI = 1 << 17
|
||||
hwcap2_MTE = 1 << 18
|
||||
hwcap2_ECV = 1 << 19
|
||||
hwcap2_AFP = 1 << 20
|
||||
hwcap2_RPRES = 1 << 21
|
||||
hwcap2_MTE3 = 1 << 22
|
||||
hwcap2_SME = 1 << 23
|
||||
hwcap2_SME_I16I64 = 1 << 24
|
||||
hwcap2_SME_F64F64 = 1 << 25
|
||||
hwcap2_SME_I8I32 = 1 << 26
|
||||
hwcap2_SME_F16F32 = 1 << 27
|
||||
hwcap2_SME_B16F32 = 1 << 28
|
||||
hwcap2_SME_F32F32 = 1 << 29
|
||||
hwcap2_SME_FA64 = 1 << 30
|
||||
hwcap2_WFXT = 1 << 31
|
||||
hwcap2_EBF16 = 1 << 32
|
||||
hwcap2_SVE_EBF16 = 1 << 33
|
||||
hwcap2_CSSC = 1 << 34
|
||||
hwcap2_RPRFM = 1 << 35
|
||||
hwcap2_SVE2P1 = 1 << 36
|
||||
hwcap2_SME2 = 1 << 37
|
||||
hwcap2_SME2P1 = 1 << 38
|
||||
hwcap2_SME_I16I32 = 1 << 39
|
||||
hwcap2_SME_BI32I32 = 1 << 40
|
||||
hwcap2_SME_B16B16 = 1 << 41
|
||||
hwcap2_SME_F16F16 = 1 << 42
|
||||
hwcap2_MOPS = 1 << 43
|
||||
hwcap2_HBC = 1 << 44
|
||||
hwcap2_SVE_B16B16 = 1 << 45
|
||||
hwcap2_LRCPC3 = 1 << 46
|
||||
hwcap2_LSE128 = 1 << 47
|
||||
hwcap2_FPMR = 1 << 48
|
||||
hwcap2_LUT = 1 << 49
|
||||
hwcap2_FAMINMAX = 1 << 50
|
||||
hwcap2_F8CVT = 1 << 51
|
||||
hwcap2_F8FMA = 1 << 52
|
||||
hwcap2_F8DP4 = 1 << 53
|
||||
hwcap2_F8DP2 = 1 << 54
|
||||
hwcap2_F8E4M3 = 1 << 55
|
||||
hwcap2_F8E5M2 = 1 << 56
|
||||
hwcap2_SME_LUTV2 = 1 << 57
|
||||
hwcap2_SME_F8F16 = 1 << 58
|
||||
hwcap2_SME_F8F32 = 1 << 59
|
||||
hwcap2_SME_SF8FMA = 1 << 60
|
||||
hwcap2_SME_SF8DP4 = 1 << 61
|
||||
hwcap2_SME_SF8DP2 = 1 << 62
|
||||
hwcap2_POE = 1 << 63
|
||||
)
|
||||
|
||||
func detectOS(c *CPUInfo) bool {
|
||||
// For now assuming no hyperthreading is reasonable.
|
||||
c.LogicalCores = runtime.NumCPU()
|
||||
c.PhysicalCores = c.LogicalCores
|
||||
c.ThreadsPerCore = 1
|
||||
if hwcap == 0 {
|
||||
// We did not get values from the runtime.
|
||||
// Try reading /proc/self/auxv
|
||||
|
||||
// From https://github.com/golang/sys
|
||||
const (
|
||||
_AT_HWCAP = 16
|
||||
_AT_HWCAP2 = 26
|
||||
|
||||
uintSize = int(32 << (^uint(0) >> 63))
|
||||
)
|
||||
|
||||
buf, err := ioutil.ReadFile("/proc/self/auxv")
|
||||
if err != nil {
|
||||
// e.g. on android /proc/self/auxv is not accessible, so silently
|
||||
// ignore the error and leave Initialized = false. On some
|
||||
// architectures (e.g. arm64) doinit() implements a fallback
|
||||
// readout and will set Initialized = true again.
|
||||
return false
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
for len(buf) >= 2*(uintSize/8) {
|
||||
var tag, val uint
|
||||
switch uintSize {
|
||||
case 32:
|
||||
tag = uint(bo.Uint32(buf[0:]))
|
||||
val = uint(bo.Uint32(buf[4:]))
|
||||
buf = buf[8:]
|
||||
case 64:
|
||||
tag = uint(bo.Uint64(buf[0:]))
|
||||
val = uint(bo.Uint64(buf[8:]))
|
||||
buf = buf[16:]
|
||||
}
|
||||
switch tag {
|
||||
case _AT_HWCAP:
|
||||
hwcap = val
|
||||
case _AT_HWCAP2:
|
||||
// Not used
|
||||
}
|
||||
}
|
||||
if hwcap == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// HWCap was populated by the runtime from the auxiliary vector.
|
||||
// Use HWCap information since reading aarch64 system registers
|
||||
// is not supported in user space on older linux kernels.
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_AES), AESARM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMD), ASIMD)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDDP), ASIMDDP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDHP), ASIMDHP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDRDM), ASIMDRDM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_CPUID), ARMCPUID)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_CRC32), CRC32)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_DCPOP), DCPOP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_EVTSTRM), EVTSTRM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_FCMA), FCMA)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDFHM), FHM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_FP), FP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_FPHP), FPHP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_JSCVT), JSCVT)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_LRCPC), LRCPC)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_PMULL), PMULL)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap2_RNG), RNDR)
|
||||
// c.featureSet.setIf(isSet(hwcap, hwcap_), TLB)
|
||||
// c.featureSet.setIf(isSet(hwcap, hwcap_), TS)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA1), SHA1)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA2), SHA2)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA3), SHA3)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA512), SHA512)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SM3), SM3)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SM4), SM4)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SVE), SVE)
|
||||
|
||||
// The Samsung S9+ kernel reports support for atomics, but not all cores
|
||||
// actually support them, resulting in SIGILL. See issue #28431.
|
||||
// TODO(elias.naur): Only disable the optimization on bad chipsets on android.
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ATOMICS) && runtime.GOOS != "android", ATOMICS)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isSet(hwc uint, value uint) bool {
|
||||
return hwc&value != 0
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build arm64 && !linux && !darwin
|
||||
// +build arm64,!linux,!darwin
|
||||
|
||||
package cpuid
|
||||
|
||||
import "runtime"
|
||||
|
||||
func detectOS(c *CPUInfo) bool {
|
||||
c.PhysicalCores = runtime.NumCPU()
|
||||
// For now assuming 1 thread per core...
|
||||
c.ThreadsPerCore = 1
|
||||
c.LogicalCores = c.PhysicalCores
|
||||
return false
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2021 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build nounsafe
|
||||
// +build nounsafe
|
||||
|
||||
package cpuid
|
||||
|
||||
var hwcap uint
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) 2021 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build !nounsafe
|
||||
// +build !nounsafe
|
||||
|
||||
package cpuid
|
||||
|
||||
import _ "unsafe" // needed for go:linkname
|
||||
|
||||
//go:linkname hwcap internal/cpu.HWCap
|
||||
var hwcap uint
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
go tool dist list | while IFS=/ read os arch; do
|
||||
echo "Checking $os/$arch..."
|
||||
echo " normal"
|
||||
GOARCH=$arch GOOS=$os go build -o /dev/null .
|
||||
echo " noasm"
|
||||
GOARCH=$arch GOOS=$os go build -tags noasm -o /dev/null .
|
||||
echo " appengine"
|
||||
GOARCH=$arch GOOS=$os go build -tags appengine -o /dev/null .
|
||||
echo " noasm,appengine"
|
||||
GOARCH=$arch GOOS=$os go build -tags 'appengine noasm' -o /dev/null .
|
||||
done
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.27@sha256:eb37f58646a901dc7727cf448cae36daaefaba79de33b5058dab79aa4c04aefb
|
||||
FROM golang:1.27@sha256:e432b43af23a9328d56a7c499be0476810aa344acbcf65fc7c455d4ff5a40602
|
||||
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=arm
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.27@sha256:eb37f58646a901dc7727cf448cae36daaefaba79de33b5058dab79aa4c04aefb
|
||||
FROM golang:1.27@sha256:e432b43af23a9328d56a7c499be0476810aa344acbcf65fc7c455d4ff5a40602
|
||||
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=arm64
|
||||
|
||||
+23
-4
@@ -1,18 +1,35 @@
|
||||
FUZZ_TIME ?= 1m
|
||||
FUZZ_FLAGS ?=
|
||||
|
||||
# The fuzz targets of each package, along with the tags needed to build them.
|
||||
FUZZ_TARGETS = ./test/:gofuzz .:sha1cd_asmtest
|
||||
|
||||
export CGO_ENABLED := 1
|
||||
|
||||
# The hashing tests run a second time with SHA-NI off, so that CPUs with it
|
||||
# also cover the AVX2 fallback that CPUs without it use.
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -race -timeout 15s ./...
|
||||
go test -race -timeout 15s -tags sha1cd_asmtest ./...
|
||||
SHA1CD_TEST_NOSHANI=1 go test -race -timeout 15s -tags sha1cd_asmtest ./test/
|
||||
|
||||
.PHONY: bench
|
||||
bench:
|
||||
go test -benchmem -run=^$$ -bench ^Benchmark ./...
|
||||
|
||||
# go test only fuzzes a single target per invocation, so each one is run in
|
||||
# turn for FUZZ_TIME.
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
go test -tags gofuzz -fuzz=. -fuzztime=$(FUZZ_TIME) ./test/
|
||||
@set -e; for entry in $(FUZZ_TARGETS); do \
|
||||
pkg="$${entry%%:*}"; tags="$${entry##*:}"; \
|
||||
listed="$$(go test -tags "$$tags" -list '^Fuzz' "$$pkg")"; \
|
||||
for target in $$(echo "$$listed" | grep '^Fuzz'); do \
|
||||
echo "fuzzing $$target in $$pkg for $(FUZZ_TIME)"; \
|
||||
go test $(FUZZ_FLAGS) -tags "$$tags" -run '^$$' -fuzz "^$$target"'$$' \
|
||||
-fuzztime=$(FUZZ_TIME) "$$pkg"; \
|
||||
done; \
|
||||
done
|
||||
|
||||
# Cross build project in arm/v7.
|
||||
build-arm:
|
||||
@@ -24,9 +41,11 @@ build-arm64:
|
||||
docker build -t sha1cd-arm64 -f Dockerfile.arm64 .
|
||||
docker run --rm sha1cd-arm64
|
||||
|
||||
# Build with cgo disabled.
|
||||
# Build with cgo disabled. Vetting every package (not just ./cgo) is what
|
||||
# catches the cgo and non-cgo builds drifting apart in their exported API.
|
||||
build-nocgo:
|
||||
CGO_ENABLED=0 go build ./cgo
|
||||
CGO_ENABLED=0 go build ./...
|
||||
CGO_ENABLED=0 go vet ./...
|
||||
|
||||
# Run cross-compilation to assure supported architectures.
|
||||
cross-build: build-arm build-arm64 build-nocgo
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// Package cpu detects the CPU features that sha1cd dispatches on.
|
||||
//
|
||||
// It covers only what the assembly implementations need, which keeps
|
||||
// start up cheap and avoids an external dependency. Every flag is false
|
||||
// where a feature cannot be detected safely, which selects the generic
|
||||
// implementation.
|
||||
package cpu
|
||||
|
||||
// X86 holds the features of the current amd64 CPU. All flags are false on
|
||||
// other architectures.
|
||||
var X86 struct {
|
||||
// HasAVX and HasAVX2 are set only when the OS also preserves the YMM
|
||||
// state, and HasAVX512F only when it preserves the ZMM and opmask state.
|
||||
HasAVX bool
|
||||
HasAVX2 bool
|
||||
HasAVX512F bool
|
||||
HasBMI1 bool
|
||||
HasBMI2 bool
|
||||
HasSHA bool
|
||||
HasSSSE3 bool
|
||||
HasSSE41 bool
|
||||
}
|
||||
|
||||
// ARM64 holds the features of the current arm64 CPU. All flags are false on
|
||||
// other architectures.
|
||||
var ARM64 struct {
|
||||
HasSHA1 bool
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package cpu
|
||||
|
||||
// cpuid and xgetbv are implemented in cpu_amd64.s.
|
||||
func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32)
|
||||
func xgetbv() (eax, edx uint32)
|
||||
|
||||
func init() {
|
||||
const (
|
||||
// CPUID EAX=1: ECX
|
||||
ssse3 = 1 << 9
|
||||
sse41 = 1 << 19
|
||||
osxsave = 1 << 27
|
||||
avx = 1 << 28
|
||||
|
||||
// CPUID EAX=7, ECX=0: EBX
|
||||
bmi1 = 1 << 3
|
||||
avx2 = 1 << 5
|
||||
bmi2 = 1 << 8
|
||||
avx512f = 1 << 16
|
||||
sha = 1 << 29
|
||||
|
||||
// XCR0
|
||||
xmmState = 1 << 1
|
||||
ymmState = 1 << 2
|
||||
opmaskState = 1 << 5
|
||||
zmmHi256State = 1 << 6
|
||||
hi16ZMMState = 1 << 7
|
||||
zmmState = opmaskState | zmmHi256State | hi16ZMMState
|
||||
)
|
||||
|
||||
maxID, _, _, _ := cpuid(0, 0)
|
||||
if maxID < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, ecx1, _ := cpuid(1, 0)
|
||||
X86.HasSSSE3 = ecx1&ssse3 != 0
|
||||
X86.HasSSE41 = ecx1&sse41 != 0
|
||||
|
||||
// VEX encoded instructions also need the OS to preserve the YMM state,
|
||||
// which XGETBV reports once OSXSAVE says it is available.
|
||||
// macOS enables the ZMM state lazily, so XCR0 may not report it and
|
||||
// AVX-512 is then left unused, which is safe.
|
||||
var osYMM, osZMM bool
|
||||
if ecx1&(osxsave|avx) == osxsave|avx {
|
||||
xcr0, _ := xgetbv()
|
||||
osYMM = xcr0&(xmmState|ymmState) == xmmState|ymmState
|
||||
osZMM = osYMM && xcr0&zmmState == zmmState
|
||||
}
|
||||
X86.HasAVX = osYMM
|
||||
|
||||
if maxID < 7 {
|
||||
return
|
||||
}
|
||||
_, ebx7, _, _ := cpuid(7, 0)
|
||||
X86.HasAVX2 = osYMM && ebx7&avx2 != 0
|
||||
X86.HasAVX512F = osZMM && ebx7&avx512f != 0
|
||||
X86.HasBMI1 = ebx7&bmi1 != 0
|
||||
X86.HasBMI2 = ebx7&bmi2 != 0
|
||||
X86.HasSHA = ebx7&sha != 0
|
||||
}
|
||||
Generated
Vendored
+2
-6
@@ -1,8 +1,4 @@
|
||||
// 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.
|
||||
|
||||
//go:build (386 || amd64 || amd64p32) && gc
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
@@ -18,7 +14,7 @@ TEXT ·cpuid(SB), NOSPLIT, $0-24
|
||||
RET
|
||||
|
||||
// func xgetbv() (eax, edx uint32)
|
||||
TEXT ·xgetbv(SB),NOSPLIT,$0-8
|
||||
TEXT ·xgetbv(SB), NOSPLIT, $0-8
|
||||
MOVL $0, CX
|
||||
XGETBV
|
||||
MOVL AX, eax+0(FP)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
//go:build !noasm && gc && arm64 && darwin
|
||||
|
||||
package cpu
|
||||
|
||||
// Every Apple arm64 chip implements the ARMv8 Cryptographic Extension. The Go
|
||||
// runtime makes the same assumption for crypto/sha1 on darwin/arm64.
|
||||
func init() {
|
||||
ARM64.HasSHA1 = true
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
//go:build !noasm && gc && arm64 && freebsd
|
||||
|
||||
package cpu
|
||||
|
||||
// getisar0 is implemented in cpu_arm64_freebsd.s.
|
||||
func getisar0() uint64
|
||||
|
||||
// FreeBSD emulates user space reads of ID_AA64ISAR0_EL1. Its SHA1 field, bits
|
||||
// [11:8], is non zero when the SHA1 instructions are implemented.
|
||||
func init() {
|
||||
ARM64.HasSHA1 = (getisar0()>>8)&0xf != 0
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
//go:build !noasm && gc && arm64 && freebsd
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func getisar0() uint64
|
||||
TEXT ·getisar0(SB), NOSPLIT, $0-8
|
||||
MRS ID_AA64ISAR0_EL1, R0
|
||||
MOVD R0, ret+0(FP)
|
||||
RET
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
//go:build !noasm && gc && arm64 && linux
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"os"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
// runtime_getAuxv returns the auxiliary vector the kernel passed to the
|
||||
// process. The runtime keeps it reachable for golang.org/x/sys/cpu and
|
||||
// others, see go.dev/issue/57336 and go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname runtime_getAuxv runtime.getAuxv
|
||||
func runtime_getAuxv() []uintptr
|
||||
|
||||
// Linux, and so Android, reports the SHA1 instructions through HWCAP, as
|
||||
// not every kernel lets user space read the ID registers.
|
||||
func init() {
|
||||
hwcap, ok := hwcapFromAuxv(runtime_getAuxv())
|
||||
if !ok {
|
||||
// The procfs copy may not be readable in restricted environments,
|
||||
// in which case the generic implementation is used.
|
||||
if buf, err := os.ReadFile("/proc/self/auxv"); err == nil {
|
||||
hwcap, _ = hwcapFromProcAuxv(buf)
|
||||
}
|
||||
}
|
||||
ARM64.HasSHA1 = hwcap&hwcapSHA1 != 0
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
//go:build !noasm && gc && arm64 && windows
|
||||
|
||||
package cpu
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Windows reports the ARMv8 Cryptographic Extension, which includes the SHA1
|
||||
// instructions, through IsProcessorFeaturePresent.
|
||||
func init() {
|
||||
const _PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30
|
||||
|
||||
proc := syscall.NewLazyDLL("kernel32.dll").NewProc("IsProcessorFeaturePresent")
|
||||
if proc.Find() != nil {
|
||||
return
|
||||
}
|
||||
ret, _, _ := proc.Call(_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasSHA1 = ret != 0
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cpu
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
const (
|
||||
_AT_HWCAP = 16
|
||||
|
||||
// hwcapSHA1 is HWCAP_SHA1 on linux/arm64.
|
||||
hwcapSHA1 = 1 << 5
|
||||
)
|
||||
|
||||
// hwcapFromAuxv returns AT_HWCAP from an auxiliary vector of tag and value
|
||||
// pairs, and whether it was present.
|
||||
func hwcapFromAuxv(auxv []uintptr) (uint64, bool) {
|
||||
for i := 0; i+1 < len(auxv); i += 2 {
|
||||
if auxv[i] == _AT_HWCAP {
|
||||
return uint64(auxv[i+1]), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// hwcapFromProcAuxv does the same for the contents of /proc/self/auxv on a
|
||||
// 64-bit little endian system.
|
||||
func hwcapFromProcAuxv(buf []byte) (uint64, bool) {
|
||||
for ; len(buf) >= 16; buf = buf[16:] {
|
||||
if binary.LittleEndian.Uint64(buf) == _AT_HWCAP {
|
||||
return binary.LittleEndian.Uint64(buf[8:]), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
+28
-18
@@ -4,30 +4,39 @@
|
||||
package sha1cd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
shared "github.com/pjbgf/sha1cd/internal"
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
"github.com/pjbgf/sha1cd/ubc"
|
||||
)
|
||||
|
||||
var hasSHANI = (runtime.GOARCH == "amd64" &&
|
||||
cpuid.CPU.Supports(cpuid.AVX) &&
|
||||
cpuid.CPU.Supports(cpuid.SHA) &&
|
||||
cpuid.CPU.Supports(cpuid.SSE3) &&
|
||||
cpuid.CPU.Supports(cpuid.SSE4))
|
||||
// hasSHANI reports whether blockAMD64 can run. It uses legacy SSE encodings
|
||||
// only, so it does not need AVX and also runs on the Goldmont, Goldmont Plus
|
||||
// and Tremont Atoms, which implement SHA-NI without AVX.
|
||||
var hasSHANI = cpu.X86.HasSHA && cpu.X86.HasSSSE3 && cpu.X86.HasSSE41
|
||||
|
||||
// blockAMD64 hashes the message p into the current state in h.
|
||||
// blockAMD64 hashes a single chunk of p into the current state in h.
|
||||
// p must hold at least one whole chunk. Anything beyond the first chunk is
|
||||
// ignored, as the collision detection the caller runs afterwards inspects m1
|
||||
// and cs for one chunk only.
|
||||
// Both m1 and cs are used to store intermediate results which are used by the collision detection logic.
|
||||
//
|
||||
//go:noescape
|
||||
func blockAMD64(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
|
||||
|
||||
func block(dig *digest, p []byte) {
|
||||
if forceGeneric || !hasSHANI {
|
||||
switch {
|
||||
case forceGeneric:
|
||||
blockGeneric(dig, p)
|
||||
case hasSHANI:
|
||||
blockSHANI(dig, p)
|
||||
case hasAVX2:
|
||||
blockAVX2(dig, p)
|
||||
default:
|
||||
blockGeneric(dig, p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func blockSHANI(dig *digest, p []byte) {
|
||||
m1 := [shared.Rounds]uint32{}
|
||||
cs := [shared.PreStepState][shared.WordBuffers]uint32{}
|
||||
|
||||
@@ -37,14 +46,15 @@ func block(dig *digest, p []byte) {
|
||||
chunk := p[:shared.Chunk]
|
||||
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
// Assembly states need repair only when a disturbance vector survives.
|
||||
if mask := ubc.CalculateDvMask(&m1); mask != 0 {
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
if checkCollision(&m1, &cs, &dig.h, mask) {
|
||||
dig.col = true
|
||||
|
||||
col := checkCollision(&m1, &cs, &dig.h)
|
||||
if col {
|
||||
dig.col = true
|
||||
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
}
|
||||
}
|
||||
|
||||
p = p[shared.Chunk:]
|
||||
|
||||
+44
-49
@@ -12,27 +12,30 @@
|
||||
// - https://github.com/golang/go/blob/master/src/crypto/sha1/sha1block_amd64.s
|
||||
|
||||
// Reverse the dword order in abcd via PSHUFD then store the 16 bytes in one
|
||||
// move, instead of issuing four VPEXTRD's that each go through the store port.
|
||||
// move, instead of issuing four PEXTRD's that each go through the store port.
|
||||
#define LOADCS(abcd, e, index, target) \
|
||||
VPSHUFD $0x1B, abcd, X8; \
|
||||
VMOVDQU X8, ((index*20)+0)(target); \
|
||||
PSHUFD $0x1B, abcd, X8; \
|
||||
MOVOU X8, ((index*20)+0)(target); \
|
||||
MOVL e, ((index*20)+16)(target);
|
||||
|
||||
#define LOADM1(m1, index, target) \
|
||||
VPSHUFD $0x1B, m1, X8; \
|
||||
VMOVDQU X8, ((index*16)+0)(target);
|
||||
PSHUFD $0x1B, m1, X8; \
|
||||
MOVOU X8, ((index*16)+0)(target);
|
||||
|
||||
// func blockAMD64(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
|
||||
// Requires: AVX, SHA, SSE2, SSE4.1, SSSE3
|
||||
// Requires: SHA, SSE2, SSE4.1, SSSE3
|
||||
TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
MOVQ h_base+0(FP), DI
|
||||
MOVQ p_base+24(FP), SI
|
||||
MOVQ p_len+32(FP), DX
|
||||
MOVQ m1_base+48(FP), R13
|
||||
MOVQ cs_base+72(FP), R15
|
||||
CMPQ DX, $0x00
|
||||
JEQ done
|
||||
ADDQ SI, DX
|
||||
|
||||
// Truncate the length to whole chunks and skip the block if none is left.
|
||||
// The caller compresses exactly one chunk per call, so that the collision
|
||||
// detection it runs afterwards sees this chunk's m1 and cs.
|
||||
ANDQ $-64, DX
|
||||
JZ done
|
||||
|
||||
// Allocate space on the stack for saving ABCD and E0, and align it to 16 bytes
|
||||
LEAQ 15(SP), AX
|
||||
@@ -42,52 +45,51 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Load initial hash state
|
||||
PINSRD $0x03, 16(DI), X5
|
||||
VMOVDQU (DI), X0
|
||||
MOVOU (DI), X0
|
||||
PAND upper_mask<>+0(SB), X5
|
||||
PSHUFD $0x1b, X0, X0
|
||||
VMOVDQA shuffle_mask<>+0(SB), X7
|
||||
MOVO shuffle_mask<>+0(SB), X7
|
||||
|
||||
loop:
|
||||
// Save ABCD and E working values
|
||||
VMOVDQA X5, (AX)
|
||||
VMOVDQA X0, 16(AX)
|
||||
MOVO X5, (AX)
|
||||
MOVO X0, 16(AX)
|
||||
|
||||
// LOAD CS 0
|
||||
VPEXTRD $3, X5, R12
|
||||
PEXTRD $3, X5, R12
|
||||
LOADCS(X0, R12, 0, R15)
|
||||
|
||||
// Rounds 0-3
|
||||
VMOVDQU (SI), X1
|
||||
MOVOU (SI), X1
|
||||
PSHUFB X7, X1
|
||||
PADDD X1, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1RNDS4 $0x00, X5, X0
|
||||
LOADM1(X1, 0, R13)
|
||||
|
||||
// Rounds 4-7
|
||||
VMOVDQU 16(SI), X2
|
||||
MOVOU 16(SI), X2
|
||||
PSHUFB X7, X2
|
||||
SHA1NEXTE X2, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1RNDS4 $0x00, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
LOADM1(X2, 1, R13)
|
||||
|
||||
// Rounds 8-11
|
||||
VMOVDQU 32(SI), X3
|
||||
MOVOU 32(SI), X3
|
||||
PSHUFB X7, X3
|
||||
SHA1NEXTE X3, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1RNDS4 $0x00, X5, X0
|
||||
SHA1MSG1 X3, X2
|
||||
PXOR X3, X1
|
||||
LOADM1(X3, 2, R13)
|
||||
|
||||
// Rounds 12-15
|
||||
VMOVDQU 48(SI), X4
|
||||
MOVOU 48(SI), X4
|
||||
PSHUFB X7, X4
|
||||
SHA1NEXTE X4, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x00, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -96,7 +98,7 @@ loop:
|
||||
|
||||
// Rounds 16-19
|
||||
SHA1NEXTE X1, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x00, X5, X0
|
||||
SHA1MSG1 X1, X4
|
||||
@@ -105,7 +107,7 @@ loop:
|
||||
|
||||
// Rounds 20-23
|
||||
SHA1NEXTE X2, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x01, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
@@ -114,7 +116,7 @@ loop:
|
||||
|
||||
// Rounds 24-27
|
||||
SHA1NEXTE X3, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x01, X5, X0
|
||||
SHA1MSG1 X3, X2
|
||||
@@ -123,7 +125,7 @@ loop:
|
||||
|
||||
// Rounds 28-31
|
||||
SHA1NEXTE X4, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x01, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -132,7 +134,7 @@ loop:
|
||||
|
||||
// Rounds 32-35
|
||||
SHA1NEXTE X1, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x01, X5, X0
|
||||
SHA1MSG1 X1, X4
|
||||
@@ -141,7 +143,7 @@ loop:
|
||||
|
||||
// Rounds 36-39
|
||||
SHA1NEXTE X2, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x01, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
@@ -150,7 +152,7 @@ loop:
|
||||
|
||||
// Rounds 40-43
|
||||
SHA1NEXTE X3, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x02, X5, X0
|
||||
SHA1MSG1 X3, X2
|
||||
@@ -159,7 +161,7 @@ loop:
|
||||
|
||||
// Rounds 44-47
|
||||
SHA1NEXTE X4, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x02, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -168,21 +170,20 @@ loop:
|
||||
|
||||
// Rounds 48-51
|
||||
SHA1NEXTE X1, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x02, X5, X0
|
||||
VPEXTRD $0, X5, R12
|
||||
SHA1MSG1 X1, X4
|
||||
PXOR X1, X3
|
||||
LOADM1(X1, 12, R13)
|
||||
|
||||
// derive pre-round 56's E out of round 51's A.
|
||||
VPEXTRD $3, X0, R12
|
||||
PEXTRD $3, X0, R12
|
||||
ROLL $30, R12
|
||||
|
||||
// Rounds 52-55
|
||||
SHA1NEXTE X2, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x02, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
@@ -194,21 +195,20 @@ loop:
|
||||
|
||||
// Rounds 56-59
|
||||
SHA1NEXTE X3, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x02, X5, X0
|
||||
VPEXTRD $0, X5, R12
|
||||
SHA1MSG1 X3, X2
|
||||
PXOR X3, X1
|
||||
LOADM1(X3, 14, R13)
|
||||
|
||||
// derive pre-round 64's E out of round 59's A.
|
||||
VPEXTRD $3, X0, R12
|
||||
PEXTRD $3, X0, R12
|
||||
ROLL $30, R12
|
||||
|
||||
// Rounds 60-63
|
||||
SHA1NEXTE X4, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x03, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -220,7 +220,7 @@ loop:
|
||||
|
||||
// Rounds 64-67
|
||||
SHA1NEXTE X1, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x03, X5, X0
|
||||
SHA1MSG1 X1, X4
|
||||
@@ -229,7 +229,7 @@ loop:
|
||||
|
||||
// Rounds 68-71
|
||||
SHA1NEXTE X2, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x03, X6, X0
|
||||
PXOR X2, X4
|
||||
@@ -237,14 +237,14 @@ loop:
|
||||
|
||||
// Rounds 72-75
|
||||
SHA1NEXTE X3, X5
|
||||
VMOVDQA X0, X6
|
||||
MOVO X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x03, X5, X0
|
||||
LOADM1(X3, 18, R13)
|
||||
|
||||
// Rounds 76-79
|
||||
SHA1NEXTE X4, X6
|
||||
VMOVDQA X0, X5
|
||||
MOVO X0, X5
|
||||
SHA1RNDS4 $0x03, X6, X0
|
||||
LOADM1(X4, 19, R13)
|
||||
|
||||
@@ -252,14 +252,9 @@ loop:
|
||||
SHA1NEXTE (AX), X5
|
||||
PADDD 16(AX), X0
|
||||
|
||||
// Check if we are done, if not return to the loop
|
||||
ADDQ $0x40, SI
|
||||
CMPQ SI, DX
|
||||
JNE loop
|
||||
|
||||
// Write the hash state back to digest
|
||||
PSHUFD $0x1b, X0, X0
|
||||
VMOVDQU X0, (DI)
|
||||
MOVOU X0, (DI)
|
||||
PEXTRD $0x03, X5, 16(DI)
|
||||
|
||||
done:
|
||||
|
||||
+15
-11
@@ -4,15 +4,17 @@
|
||||
package sha1cd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
shared "github.com/pjbgf/sha1cd/internal"
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
"github.com/pjbgf/sha1cd/ubc"
|
||||
)
|
||||
|
||||
var hasSHA1 = (runtime.GOARCH == "arm64" && cpuid.CPU.Supports(cpuid.SHA1))
|
||||
var hasSHA1 = cpu.ARM64.HasSHA1
|
||||
|
||||
// blockARM64 hashes the message p into the current state in h.
|
||||
// blockARM64 hashes a single chunk of p into the current state in h.
|
||||
// p must hold at least one whole chunk. Anything beyond the first chunk is
|
||||
// ignored, as the collision detection the caller runs afterwards inspects m1
|
||||
// and cs for one chunk only.
|
||||
// Both m1 and cs are used to store intermediate results which are used by the collision detection logic.
|
||||
//
|
||||
//go:noescape
|
||||
@@ -34,13 +36,15 @@ func block(dig *digest, p []byte) {
|
||||
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
col := checkCollision(&m1, &cs, &dig.h)
|
||||
if col {
|
||||
dig.col = true
|
||||
// Assembly states need repair only when a disturbance vector survives.
|
||||
if mask := ubc.CalculateDvMask(&m1); mask != 0 {
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
if checkCollision(&m1, &cs, &dig.h, mask) {
|
||||
dig.col = true
|
||||
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
}
|
||||
}
|
||||
|
||||
p = p[shared.Chunk:]
|
||||
|
||||
+2
-4
@@ -37,15 +37,13 @@ TEXT ·blockARM64(SB), NOSPLIT, $80-96
|
||||
|
||||
LSR $6, R2, R2
|
||||
LSL $6, R2, R2
|
||||
ADD R16, R2, R21
|
||||
|
||||
VLD1.P 16(R0), [V0.S4]
|
||||
FMOVS (R0), F20
|
||||
SUB $16, R0, R0
|
||||
|
||||
loop:
|
||||
CMP R16, R21
|
||||
BLS end
|
||||
// The caller passes exactly one block; skip hashing only if p is shorter.
|
||||
CBZ R2, end
|
||||
|
||||
// Load block (p) into 16-bytes vectors.
|
||||
VLD1.P 16(R1), [V4.B16]
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
//go:build !noasm && gc && arm64 && !amd64 && sha1cd_asmtest
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// callBlockARM64DirtyRegs calls blockARM64 with every general-purpose and
|
||||
// vector register the caller does not own set to all ones. It is only built
|
||||
// with the sha1cd_asmtest tag, to catch the assembly reading a register before
|
||||
// writing it.
|
||||
//
|
||||
// func callBlockARM64DirtyRegs(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
|
||||
TEXT ·callBlockARM64DirtyRegs(SB), NOSPLIT, $104-96
|
||||
MOVD h_base+0(FP), R0
|
||||
MOVD R0, 8(RSP)
|
||||
MOVD h_len+8(FP), R0
|
||||
MOVD R0, 16(RSP)
|
||||
MOVD h_cap+16(FP), R0
|
||||
MOVD R0, 24(RSP)
|
||||
MOVD p_base+24(FP), R0
|
||||
MOVD R0, 32(RSP)
|
||||
MOVD p_len+32(FP), R0
|
||||
MOVD R0, 40(RSP)
|
||||
MOVD p_cap+40(FP), R0
|
||||
MOVD R0, 48(RSP)
|
||||
MOVD m1_base+48(FP), R0
|
||||
MOVD R0, 56(RSP)
|
||||
MOVD m1_len+56(FP), R0
|
||||
MOVD R0, 64(RSP)
|
||||
MOVD m1_cap+64(FP), R0
|
||||
MOVD R0, 72(RSP)
|
||||
MOVD cs_base+72(FP), R0
|
||||
MOVD R0, 80(RSP)
|
||||
MOVD cs_len+80(FP), R0
|
||||
MOVD R0, 88(RSP)
|
||||
MOVD cs_cap+88(FP), R0
|
||||
MOVD R0, 96(RSP)
|
||||
|
||||
// R18 is reserved by the platform, R27 by the assembler, R28 holds g,
|
||||
// R29 is the frame pointer and R30 the link register.
|
||||
MOVD $-1, R0
|
||||
MOVD R0, R1
|
||||
MOVD R0, R2
|
||||
MOVD R0, R3
|
||||
MOVD R0, R4
|
||||
MOVD R0, R5
|
||||
MOVD R0, R6
|
||||
MOVD R0, R7
|
||||
MOVD R0, R8
|
||||
MOVD R0, R9
|
||||
MOVD R0, R10
|
||||
MOVD R0, R11
|
||||
MOVD R0, R12
|
||||
MOVD R0, R13
|
||||
MOVD R0, R14
|
||||
MOVD R0, R15
|
||||
MOVD R0, R16
|
||||
MOVD R0, R17
|
||||
MOVD R0, R19
|
||||
MOVD R0, R20
|
||||
MOVD R0, R21
|
||||
MOVD R0, R22
|
||||
MOVD R0, R23
|
||||
MOVD R0, R24
|
||||
MOVD R0, R25
|
||||
MOVD R0, R26
|
||||
|
||||
VMOVI $0xff, V0.B16
|
||||
VMOVI $0xff, V1.B16
|
||||
VMOVI $0xff, V2.B16
|
||||
VMOVI $0xff, V3.B16
|
||||
VMOVI $0xff, V4.B16
|
||||
VMOVI $0xff, V5.B16
|
||||
VMOVI $0xff, V6.B16
|
||||
VMOVI $0xff, V7.B16
|
||||
VMOVI $0xff, V8.B16
|
||||
VMOVI $0xff, V9.B16
|
||||
VMOVI $0xff, V10.B16
|
||||
VMOVI $0xff, V11.B16
|
||||
VMOVI $0xff, V12.B16
|
||||
VMOVI $0xff, V13.B16
|
||||
VMOVI $0xff, V14.B16
|
||||
VMOVI $0xff, V15.B16
|
||||
VMOVI $0xff, V16.B16
|
||||
VMOVI $0xff, V17.B16
|
||||
VMOVI $0xff, V18.B16
|
||||
VMOVI $0xff, V19.B16
|
||||
VMOVI $0xff, V20.B16
|
||||
VMOVI $0xff, V21.B16
|
||||
VMOVI $0xff, V22.B16
|
||||
VMOVI $0xff, V23.B16
|
||||
VMOVI $0xff, V24.B16
|
||||
VMOVI $0xff, V25.B16
|
||||
VMOVI $0xff, V26.B16
|
||||
VMOVI $0xff, V27.B16
|
||||
VMOVI $0xff, V28.B16
|
||||
VMOVI $0xff, V29.B16
|
||||
VMOVI $0xff, V30.B16
|
||||
VMOVI $0xff, V31.B16
|
||||
|
||||
BL ·blockARM64(SB)
|
||||
RET
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package sha1cd
|
||||
|
||||
import (
|
||||
shared "github.com/pjbgf/sha1cd/internal"
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
"github.com/pjbgf/sha1cd/ubc"
|
||||
)
|
||||
|
||||
// hasAVX2 reports whether blockAVX2 can run. It is the fallback for CPUs
|
||||
// without SHA-NI, such as Intel's big cores before Ice Lake.
|
||||
var hasAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI1 && cpu.X86.HasBMI2
|
||||
|
||||
// scheduleAVX2 expands the message schedules of the blocks at pa and pb, which
|
||||
// may be the same, into m1a and m1b.
|
||||
//
|
||||
//go:noescape
|
||||
func scheduleAVX2(pa, pb *byte, m1a, m1b *[shared.Rounds]uint32)
|
||||
|
||||
// roundsBMI2 compresses the block whose schedule is in m1 into h, and stores
|
||||
// the states before steps 0, 58 and 65 into cs.
|
||||
//
|
||||
//go:noescape
|
||||
func roundsBMI2(h *[shared.WordBuffers]uint32, m1 *[shared.Rounds]uint32,
|
||||
cs *[shared.PreStepState][shared.WordBuffers]uint32)
|
||||
|
||||
func blockAVX2(dig *digest, p []byte) {
|
||||
var m1 [2][shared.Rounds]uint32
|
||||
cs := [shared.PreStepState][shared.WordBuffers]uint32{}
|
||||
|
||||
for len(p) >= shared.Chunk {
|
||||
// The schedules do not depend on the chaining state, so expand two
|
||||
// blocks at once. The rounds must still run one block at a time, as a
|
||||
// detected collision changes the state the next block starts from.
|
||||
n, pb := 1, p
|
||||
if len(p) >= 2*shared.Chunk {
|
||||
n, pb = 2, p[shared.Chunk:]
|
||||
}
|
||||
scheduleAVX2(&p[0], &pb[0], &m1[0], &m1[1])
|
||||
|
||||
for j := 0; j < n; j++ {
|
||||
w := &m1[j]
|
||||
roundsBMI2(&dig.h, w, &cs)
|
||||
if mask := ubc.CalculateDvMask(w); mask != 0 && checkCollision(w, &cs, &dig.h, mask) {
|
||||
dig.col = true
|
||||
|
||||
roundsBMI2(&dig.h, w, &cs)
|
||||
roundsBMI2(&dig.h, w, &cs)
|
||||
}
|
||||
}
|
||||
|
||||
p = p[n*shared.Chunk:]
|
||||
}
|
||||
}
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// The fallback for CPUs without SHA-NI. The message schedule is expanded with
|
||||
// AVX2 for two blocks at a time, one per 128-bit lane, as it does not depend
|
||||
// on the chaining state. The rounds then run one block at a time with scalar
|
||||
// BMI instructions, so that the collision detection can run between blocks
|
||||
// and gets the exact states before steps 58 and 65, with no repair needed.
|
||||
|
||||
// func scheduleAVX2(pa, pb *byte, m1a, m1b *[80]uint32)
|
||||
// Requires: AVX, AVX2
|
||||
//
|
||||
// Expands the blocks at pa and pb, which may be the same, into m1a and m1b.
|
||||
// Y0-Y7 hold the last eight groups of four words, group g in Y(g%8).
|
||||
TEXT ·scheduleAVX2(SB), NOSPLIT, $0-32
|
||||
MOVQ pa+0(FP), AX
|
||||
MOVQ pb+8(FP), BX
|
||||
MOVQ m1a+16(FP), CX
|
||||
MOVQ m1b+24(FP), DX
|
||||
VBROADCASTI128 bswap_mask<>(SB), Y13
|
||||
|
||||
// W[0..3] loaded from the blocks
|
||||
VMOVDQU 0(AX), X0
|
||||
VINSERTI128 $1, 0(BX), Y0, Y0
|
||||
VPSHUFB Y13, Y0, Y0
|
||||
VMOVDQU X0, 0(CX)
|
||||
VEXTRACTI128 $1, Y0, 0(DX)
|
||||
|
||||
// W[4..7] loaded from the blocks
|
||||
VMOVDQU 16(AX), X1
|
||||
VINSERTI128 $1, 16(BX), Y1, Y1
|
||||
VPSHUFB Y13, Y1, Y1
|
||||
VMOVDQU X1, 16(CX)
|
||||
VEXTRACTI128 $1, Y1, 16(DX)
|
||||
|
||||
// W[8..11] loaded from the blocks
|
||||
VMOVDQU 32(AX), X2
|
||||
VINSERTI128 $1, 32(BX), Y2, Y2
|
||||
VPSHUFB Y13, Y2, Y2
|
||||
VMOVDQU X2, 32(CX)
|
||||
VEXTRACTI128 $1, Y2, 32(DX)
|
||||
|
||||
// W[12..15] loaded from the blocks
|
||||
VMOVDQU 48(AX), X3
|
||||
VINSERTI128 $1, 48(BX), Y3, Y3
|
||||
VPSHUFB Y13, Y3, Y3
|
||||
VMOVDQU X3, 48(CX)
|
||||
VEXTRACTI128 $1, Y3, 48(DX)
|
||||
|
||||
// W[16..19] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y0, Y1, Y8 // W[i-14..i-11]
|
||||
VPXOR Y0, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y2, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y3, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y4
|
||||
VPOR Y9, Y4, Y4
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y4, Y4
|
||||
VMOVDQU X4, 64(CX)
|
||||
VEXTRACTI128 $1, Y4, 64(DX)
|
||||
|
||||
// W[20..23] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y1, Y2, Y8 // W[i-14..i-11]
|
||||
VPXOR Y1, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y3, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y4, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y5
|
||||
VPOR Y9, Y5, Y5
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y5, Y5
|
||||
VMOVDQU X5, 80(CX)
|
||||
VEXTRACTI128 $1, Y5, 80(DX)
|
||||
|
||||
// W[24..27] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y2, Y3, Y8 // W[i-14..i-11]
|
||||
VPXOR Y2, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y4, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y5, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y6
|
||||
VPOR Y9, Y6, Y6
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y6, Y6
|
||||
VMOVDQU X6, 96(CX)
|
||||
VEXTRACTI128 $1, Y6, 96(DX)
|
||||
|
||||
// W[28..31] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y3, Y4, Y8 // W[i-14..i-11]
|
||||
VPXOR Y3, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y5, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y6, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y7
|
||||
VPOR Y9, Y7, Y7
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y7, Y7
|
||||
VMOVDQU X7, 112(CX)
|
||||
VEXTRACTI128 $1, Y7, 112(DX)
|
||||
|
||||
// W[32..35] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y6, Y7, Y8 // W[i-6..i-3]
|
||||
VPXOR Y4, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y1, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y0, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y0
|
||||
VPOR Y9, Y0, Y0
|
||||
VMOVDQU X0, 128(CX)
|
||||
VEXTRACTI128 $1, Y0, 128(DX)
|
||||
|
||||
// W[36..39] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y7, Y0, Y8 // W[i-6..i-3]
|
||||
VPXOR Y5, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y2, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y1, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y1
|
||||
VPOR Y9, Y1, Y1
|
||||
VMOVDQU X1, 144(CX)
|
||||
VEXTRACTI128 $1, Y1, 144(DX)
|
||||
|
||||
// W[40..43] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y0, Y1, Y8 // W[i-6..i-3]
|
||||
VPXOR Y6, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y3, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y2, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y2
|
||||
VPOR Y9, Y2, Y2
|
||||
VMOVDQU X2, 160(CX)
|
||||
VEXTRACTI128 $1, Y2, 160(DX)
|
||||
|
||||
// W[44..47] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y1, Y2, Y8 // W[i-6..i-3]
|
||||
VPXOR Y7, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y4, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y3, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y3
|
||||
VPOR Y9, Y3, Y3
|
||||
VMOVDQU X3, 176(CX)
|
||||
VEXTRACTI128 $1, Y3, 176(DX)
|
||||
|
||||
// W[48..51] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y2, Y3, Y8 // W[i-6..i-3]
|
||||
VPXOR Y0, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y5, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y4, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y4
|
||||
VPOR Y9, Y4, Y4
|
||||
VMOVDQU X4, 192(CX)
|
||||
VEXTRACTI128 $1, Y4, 192(DX)
|
||||
|
||||
// W[52..55] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y3, Y4, Y8 // W[i-6..i-3]
|
||||
VPXOR Y1, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y6, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y5, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y5
|
||||
VPOR Y9, Y5, Y5
|
||||
VMOVDQU X5, 208(CX)
|
||||
VEXTRACTI128 $1, Y5, 208(DX)
|
||||
|
||||
// W[56..59] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y4, Y5, Y8 // W[i-6..i-3]
|
||||
VPXOR Y2, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y7, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y6, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y6
|
||||
VPOR Y9, Y6, Y6
|
||||
VMOVDQU X6, 224(CX)
|
||||
VEXTRACTI128 $1, Y6, 224(DX)
|
||||
|
||||
// W[60..63] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y5, Y6, Y8 // W[i-6..i-3]
|
||||
VPXOR Y3, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y0, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y7, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y7
|
||||
VPOR Y9, Y7, Y7
|
||||
VMOVDQU X7, 240(CX)
|
||||
VEXTRACTI128 $1, Y7, 240(DX)
|
||||
|
||||
// W[64..67] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y6, Y7, Y8 // W[i-6..i-3]
|
||||
VPXOR Y4, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y1, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y0, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y0
|
||||
VPOR Y9, Y0, Y0
|
||||
VMOVDQU X0, 256(CX)
|
||||
VEXTRACTI128 $1, Y0, 256(DX)
|
||||
|
||||
// W[68..71] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y7, Y0, Y8 // W[i-6..i-3]
|
||||
VPXOR Y5, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y2, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y1, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y1
|
||||
VPOR Y9, Y1, Y1
|
||||
VMOVDQU X1, 272(CX)
|
||||
VEXTRACTI128 $1, Y1, 272(DX)
|
||||
|
||||
// W[72..75] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y0, Y1, Y8 // W[i-6..i-3]
|
||||
VPXOR Y6, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y3, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y2, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y2
|
||||
VPOR Y9, Y2, Y2
|
||||
VMOVDQU X2, 288(CX)
|
||||
VEXTRACTI128 $1, Y2, 288(DX)
|
||||
|
||||
// W[76..79] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y1, Y2, Y8 // W[i-6..i-3]
|
||||
VPXOR Y7, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y4, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y3, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y3
|
||||
VPOR Y9, Y3, Y3
|
||||
VMOVDQU X3, 304(CX)
|
||||
VEXTRACTI128 $1, Y3, 304(DX)
|
||||
|
||||
VZEROUPPER
|
||||
RET
|
||||
|
||||
// Each round adds W[i], K, f(b, c, d) and rol5(a) into e and rotates b by 30.
|
||||
// The callers rotate the register names instead of moving values around, and
|
||||
// rol5(a) goes in last, as a is the only input the previous round produced.
|
||||
#define ROUND_CH(a, b, c, d, e, i) \
|
||||
ADDL ((i)*4)(SI), e; \
|
||||
ADDL $0x5a827999, e; \
|
||||
ANDNL d, b, AX; \
|
||||
MOVL c, BX; \
|
||||
ANDL b, BX; \
|
||||
ADDL AX, e; \
|
||||
ADDL BX, e; \
|
||||
RORXL $27, a, CX; \
|
||||
RORXL $2, b, b; \
|
||||
ADDL CX, e
|
||||
|
||||
#define ROUND_PARITY(a, b, c, d, e, i, k) \
|
||||
ADDL ((i)*4)(SI), e; \
|
||||
ADDL k, e; \
|
||||
MOVL b, AX; \
|
||||
XORL c, AX; \
|
||||
XORL d, AX; \
|
||||
ADDL AX, e; \
|
||||
RORXL $27, a, CX; \
|
||||
RORXL $2, b, b; \
|
||||
ADDL CX, e
|
||||
|
||||
// maj(b, c, d) = (b & c) + ((b ^ c) & d), as the two never share a bit.
|
||||
#define ROUND_MAJ(a, b, c, d, e, i) \
|
||||
ADDL ((i)*4)(SI), e; \
|
||||
ADDL $0x8f1bbcdc, e; \
|
||||
MOVL b, AX; \
|
||||
XORL c, AX; \
|
||||
ANDL d, AX; \
|
||||
MOVL b, BX; \
|
||||
ANDL c, BX; \
|
||||
ADDL AX, e; \
|
||||
ADDL BX, e; \
|
||||
RORXL $27, a, CX; \
|
||||
RORXL $2, b, b; \
|
||||
ADDL CX, e
|
||||
|
||||
#define SAVECS(a, b, c, d, e, index) \
|
||||
MOVL a, ((index)*20+0)(DX); \
|
||||
MOVL b, ((index)*20+4)(DX); \
|
||||
MOVL c, ((index)*20+8)(DX); \
|
||||
MOVL d, ((index)*20+12)(DX); \
|
||||
MOVL e, ((index)*20+16)(DX)
|
||||
|
||||
// func roundsBMI2(h *[5]uint32, m1 *[80]uint32, cs *[3][5]uint32)
|
||||
// Requires: BMI1, BMI2
|
||||
//
|
||||
// Compresses the block whose schedule is in m1 into h, storing the states
|
||||
// before steps 0, 58 and 65 into cs.
|
||||
TEXT ·roundsBMI2(SB), NOSPLIT, $0-24
|
||||
MOVQ h+0(FP), DI
|
||||
MOVQ m1+8(FP), SI
|
||||
MOVQ cs+16(FP), DX
|
||||
MOVL 0(DI), R8
|
||||
MOVL 4(DI), R9
|
||||
MOVL 8(DI), R10
|
||||
MOVL 12(DI), R11
|
||||
MOVL 16(DI), R12
|
||||
|
||||
SAVECS(R8, R9, R10, R11, R12, 0)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 0)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 1)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 2)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 3)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 4)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 5)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 6)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 7)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 8)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 9)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 10)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 11)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 12)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 13)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 14)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 15)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 16)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 17)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 18)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 19)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 20, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 21, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 22, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 23, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 24, $0x6ed9eba1)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 25, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 26, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 27, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 28, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 29, $0x6ed9eba1)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 30, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 31, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 32, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 33, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 34, $0x6ed9eba1)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 35, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 36, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 37, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 38, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 39, $0x6ed9eba1)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 40)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 41)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 42)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 43)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 44)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 45)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 46)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 47)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 48)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 49)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 50)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 51)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 52)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 53)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 54)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 55)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 56)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 57)
|
||||
SAVECS(R10, R11, R12, R8, R9, 1)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 58)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 59)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 60, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 61, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 62, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 63, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 64, $0xca62c1d6)
|
||||
SAVECS(R8, R9, R10, R11, R12, 2)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 65, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 66, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 67, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 68, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 69, $0xca62c1d6)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 70, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 71, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 72, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 73, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 74, $0xca62c1d6)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 75, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 76, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 77, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 78, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 79, $0xca62c1d6)
|
||||
|
||||
ADDL R8, 0(DI)
|
||||
ADDL R9, 4(DI)
|
||||
ADDL R10, 8(DI)
|
||||
ADDL R11, 12(DI)
|
||||
ADDL R12, 16(DI)
|
||||
RET
|
||||
|
||||
// Swaps the bytes of each word, as SHA-1 reads the block big endian.
|
||||
DATA bswap_mask<>+0(SB)/8, $0x0405060700010203
|
||||
DATA bswap_mask<>+8(SB)/8, $0x0c0d0e0f08090a0b
|
||||
GLOBL bswap_mask<>(SB), RODATA|NOPTR, $16
|
||||
+32
-46
@@ -20,7 +20,9 @@ var forceGeneric bool
|
||||
// blockGeneric is a portable, pure Go version of the SHA-1 block step.
|
||||
// It's used by sha1block_generic.go and tests.
|
||||
func blockGeneric(dig *digest, p []byte) {
|
||||
var w [16]uint32
|
||||
// Expand directly into the schedule retained for collision detection.
|
||||
// Every word is overwritten for each block, including rehashes.
|
||||
var m1 [shared.Rounds]uint32
|
||||
|
||||
// cs stores the pre-step compression state for only the steps required for the
|
||||
// collision detection, which are 0, 58 and 65.
|
||||
@@ -29,7 +31,6 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
|
||||
h0, h1, h2, h3, h4 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4]
|
||||
for len(p) >= shared.Chunk {
|
||||
m1 := [shared.Rounds]uint32{}
|
||||
hi := 1
|
||||
|
||||
// Collision attacks are thwarted by hashing a detected near-collision block 3 times.
|
||||
@@ -51,36 +52,27 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
for ; i < 16; i++ {
|
||||
// load step
|
||||
j := i * 4
|
||||
w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3])
|
||||
m1[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3])
|
||||
|
||||
f := b&c | (^b)&d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K0
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K0
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 20; i++ {
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := b&c | (^b)&d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K0
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K0
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 40; i++ {
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K1
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K1
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 60; i++ {
|
||||
if i == 58 {
|
||||
@@ -88,15 +80,12 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
cs[1] = [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := ((b | c) & d) | (b & c)
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K2
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K2
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 80; i++ {
|
||||
if i == 65 {
|
||||
@@ -104,15 +93,12 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
cs[2] = [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K3
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K3
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
|
||||
h0 += a
|
||||
@@ -128,7 +114,7 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
|
||||
if hi == 1 {
|
||||
h := [shared.WordBuffers]uint32{h0, h1, h2, h3, h4}
|
||||
col := checkCollision(&m1, &cs, &h)
|
||||
col := checkCollision(&m1, &cs, &h, ubc.CalculateDvMask(&m1))
|
||||
if col {
|
||||
dig.col = true
|
||||
hi++
|
||||
@@ -147,8 +133,9 @@ func checkCollision(
|
||||
m1 *[shared.Rounds]uint32,
|
||||
cs *[shared.PreStepState][shared.WordBuffers]uint32,
|
||||
h *[shared.WordBuffers]uint32,
|
||||
mask uint32,
|
||||
) bool {
|
||||
if mask := ubc.CalculateDvMask(m1); mask != 0 {
|
||||
if mask != 0 {
|
||||
dvs := ubc.SHA1_dvs()
|
||||
|
||||
for i := 0; dvs[i].DvType != 0; i++ {
|
||||
@@ -290,23 +277,22 @@ func rectifyCompressionState(
|
||||
return
|
||||
}
|
||||
|
||||
func3 := func(state [shared.WordBuffers]uint32, i int) [shared.WordBuffers]uint32 {
|
||||
a, b, c, d, e := state[0], state[1], state[2], state[3], state[4]
|
||||
// The words are loaded and stored one at a time. Passing [5]uint32 values
|
||||
// around made the compiler reload them with wider moves than they were
|
||||
// stored with, which stalled on store forwarding each time.
|
||||
|
||||
// Advance cs[1] from the state before step 56 to the one before step 58.
|
||||
a, b, c, d, e := cs[1][0], cs[1][1], cs[1][2], cs[1][3], cs[1][4]
|
||||
for i := 56; i < 58; i++ {
|
||||
f := ((b | c) & d) | (b & c)
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K2
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
return [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
func4 := func(state [shared.WordBuffers]uint32, i int) [shared.WordBuffers]uint32 {
|
||||
a, b, c, d, e := state[0], state[1], state[2], state[3], state[4]
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K3
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
return [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
cs[1][0], cs[1][1], cs[1][2], cs[1][3], cs[1][4] = a, b, c, d, e
|
||||
|
||||
cs57 := func3(cs[1], 56)
|
||||
cs[1] = func3(cs57, 57)
|
||||
cs[2] = func4(cs[2], 64)
|
||||
// Advance cs[2] from the state before step 64 to the one before step 65.
|
||||
a, b, c, d, e = cs[2][0], cs[2][1], cs[2][2], cs[2][3], cs[2][4]
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[64] + shared.K3
|
||||
cs[2][0], cs[2][1], cs[2][2], cs[2][3], cs[2][4] = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package ubc
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
)
|
||||
|
||||
// useAVX512 reports whether calculateDvMaskAVX512 can run. Its only
|
||||
// instructions outside AVX-512F are VMOVD and VZEROUPPER, which need AVX.
|
||||
// HasAVX512F is set only when the CPU reports AVX as well.
|
||||
var useAVX512 = cpu.X86.HasAVX512F
|
||||
|
||||
//go:noescape
|
||||
func calculateDvMaskAVX512(W *[80]uint32, terms *uint32, groups int) uint32
|
||||
|
||||
// avx512Lanes is the number of terms a group of avx512Terms describes, and
|
||||
// avx512Fields the vectors it holds for them.
|
||||
const (
|
||||
avx512Lanes = 16
|
||||
avx512Fields = 6
|
||||
)
|
||||
|
||||
// avx512Groups is how many passes of the kernel loop the table needs.
|
||||
const avx512Groups = len(avx512Terms) / (avx512Fields * avx512Lanes)
|
||||
|
||||
// alignedTerms is a copy of avx512Terms that starts on a cache line. The
|
||||
// linker aligns data to 32 bytes at most, and a 64 byte load that straddles
|
||||
// two cache lines costs about twice as much.
|
||||
var alignedTerms = func() *uint32 {
|
||||
buf := make([]uint32, len(avx512Terms)+16)
|
||||
off := (64 - uintptr(unsafe.Pointer(&buf[0]))%64) % 64 / 4
|
||||
copy(buf[off:], avx512Terms[:])
|
||||
return &buf[off]
|
||||
}()
|
||||
|
||||
// CalculateDvMask takes as input an expanded message block and
|
||||
// verifies the unavoidable bitconditions for all listed DVs. It returns
|
||||
// a dvmask where each bit belonging to a DV is set if all unavoidable
|
||||
// bitconditions for that DV have been met.
|
||||
func CalculateDvMask(W *[80]uint32) uint32 {
|
||||
if W == nil {
|
||||
return 0
|
||||
}
|
||||
if useAVX512 {
|
||||
return calculateDvMaskAVX512(W, alignedTerms, avx512Groups)
|
||||
}
|
||||
return calculateDvMaskGeneric(W)
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func calculateDvMaskAVX512(W *[80]uint32, terms *uint32, groups int) uint32
|
||||
// Requires: AVX, AVX512F
|
||||
//
|
||||
// Each group of avx512Terms describes 16 of the bit tests of CalculateDvMask,
|
||||
// as vectors of word indices, shift counts, expected parities and the masks
|
||||
// that clear the DVs of a failing test. One pass of the loop runs a group.
|
||||
//
|
||||
// terms only has to be 64 byte aligned for speed, as otherwise every load of
|
||||
// it straddles two cache lines. The word indices are biased by 34, matching
|
||||
// the two vectors of W loaded below.
|
||||
TEXT ·calculateDvMaskAVX512(SB), NOSPLIT, $0-28
|
||||
MOVQ W+0(FP), AX
|
||||
MOVQ terms+8(FP), BX
|
||||
MOVQ groups+16(FP), CX
|
||||
VMOVDQU32 136(AX), Z0 // W[34..49]
|
||||
VMOVDQU32 200(AX), Z1 // W[50..65]
|
||||
VPTERNLOGD $0xff, Z2, Z2, Z2 // mask = all ones
|
||||
MOVL $1, DX
|
||||
VPBROADCASTD DX, Z3
|
||||
|
||||
loop:
|
||||
VMOVDQU32 0(BX), Z4
|
||||
VMOVDQU32 64(BX), Z5
|
||||
VPERMI2D Z1, Z0, Z4 // W[a]
|
||||
VPERMI2D Z1, Z0, Z5 // W[b]
|
||||
VPSRLVD 128(BX), Z4, Z4 // >> ka
|
||||
VPSRLVD 192(BX), Z5, Z5 // >> kb
|
||||
VPTERNLOGD $0x96, 256(BX), Z5, Z4 // ^ e
|
||||
VPTESTMD Z3, Z4, K1 // the terms whose bit test fails
|
||||
VPANDD 320(BX), Z2, K1, Z2 // clear their DVs
|
||||
ADDQ $384, BX
|
||||
DECQ CX
|
||||
JNZ loop
|
||||
|
||||
// AND the 16 lanes together, first the 128 bit lanes and then the
|
||||
// words within them.
|
||||
VSHUFI64X2 $0x4e, Z2, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VSHUFI64X2 $0xb1, Z2, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VPSHUFD $0x4e, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VPSHUFD $0xb1, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VMOVD X2, AX
|
||||
VZEROUPPER
|
||||
MOVL AX, ret+24(FP)
|
||||
RET
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package ubc
|
||||
|
||||
// avx512Terms holds the bit tests of CalculateDvMask in the layout the
|
||||
// AVX-512 kernel consumes, 16 terms per group. Each term says that a set of
|
||||
// DVs survives only if bit ka of W[a] XOR bit kb of W[b] equals e, and names
|
||||
// the mask that clears those DVs when it does not.
|
||||
//
|
||||
// The six vectors of a group are, in order: a-34, b-34, ka, kb, e, and the
|
||||
// clear mask. Word indices are biased by 34 because the kernel keeps W[34..65]
|
||||
// in two registers. A group is 384 bytes, and the kernel runs one per pass.
|
||||
//
|
||||
// The terms are those of calculateDvMaskGeneric, which is a port of
|
||||
// ubc_check.c and has not changed since the collision detection was published
|
||||
// in 2017. TestAVX512MatchesGeneric holds the two implementations to the same
|
||||
// results over inputs that reach every DV, so a mistake here is a test
|
||||
// failure rather than a missed collision. The comment above each group names
|
||||
// its terms as a.ka^b.kb=e.
|
||||
var avx512Terms = [960]uint32{
|
||||
// terms 0-15
|
||||
// 44.29^45.29=0 49.29^50.29=0 48.29^49.29=0 47.4^50.29=0 47.29^48.29=0 46.4^49.29=0 46.29^47.29=0 45.4^48.29=0
|
||||
// 45.29^46.29=0 44.4^47.29=0 43.4^46.29=0 43.29^44.29=0 42.4^45.29=0 41.4^44.29=0 40.29^41.29=0 54.29^55.29=0
|
||||
0x0000000a, 0x0000000f, 0x0000000e, 0x0000000d, 0x0000000d, 0x0000000c, 0x0000000c, 0x0000000b, 0x0000000b, 0x0000000a, 0x00000009, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000014,
|
||||
0x0000000b, 0x00000010, 0x0000000f, 0x00000010, 0x0000000e, 0x0000000f, 0x0000000d, 0x0000000e, 0x0000000c, 0x0000000d, 0x0000000c, 0x0000000a, 0x0000000b, 0x0000000a, 0x00000007, 0x00000015,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d, 0x0000001d,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0xfd7c5f7f, 0x3d7efff7, 0x9f5f7ffb, 0x7dfedddf, 0xcfcfdffd, 0xbf7f7777, 0xe7e7f7fe, 0xdfdfdddb, 0xf5f57dff, 0xefeff775, 0xf7f7fdda, 0xff5ed7df, 0xfdfd7f75, 0xff7edfda, 0x7ff5ff5d, 0x3f77dfff,
|
||||
// terms 16-31
|
||||
// 53.29^54.29=0 52.29^53.29=0 50.4^53.29=0 50.29^51.29=0 49.4^52.29=0 48.4^51.29=0 42.29^43.29=0 41.29^42.29=0
|
||||
// 40.4^43.29=0 39.4^42.29=0 38.4^41.29=0 37.4^40.29=0 55.29^56.29=0 52.4^55.29=0 51.4^54.29=0 51.29^52.29=0
|
||||
0x00000013, 0x00000012, 0x00000010, 0x00000010, 0x0000000f, 0x0000000e, 0x00000008, 0x00000007, 0x00000006, 0x00000005, 0x00000004, 0x00000003, 0x00000015, 0x00000012, 0x00000011, 0x00000011,
|
||||
0x00000014, 0x00000013, 0x00000013, 0x00000011, 0x00000012, 0x00000011, 0x00000009, 0x00000008, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000016, 0x00000015, 0x00000014, 0x00000012,
|
||||
0x0000001d, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d, 0x0000001d, 0x00000004, 0x00000004, 0x00000004, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x9fddf7ff, 0xcfeefdff, 0xdfed77ff, 0x75fdffdf, 0xeff6ddff, 0xf7fd777f, 0xffcff5f7, 0xffe7fd7b, 0x7fdff7f5, 0xbfeffdfa, 0x5ff7ff7d, 0xaffdffde, 0x7def7fff, 0x7f6f7fff, 0xbfd7dfff, 0xe7f7ff7f,
|
||||
// terms 32-47
|
||||
// 36.4^40.29=0 53.29^56.29=1 51.29^54.29=1 50.29^52.29=1 49.29^51.29=1 48.29^50.29=1 47.29^49.29=1 46.29^48.29=1
|
||||
// 45.6^47.6=0 45.29^47.29=1 44.6^46.6=0 44.29^46.29=1 41.1^42.6=1 40.1^41.6=1 40.4^42.4=1 39.1^40.6=1
|
||||
0x00000002, 0x00000013, 0x00000011, 0x00000010, 0x0000000f, 0x0000000e, 0x0000000d, 0x0000000c, 0x0000000b, 0x0000000b, 0x0000000a, 0x0000000a, 0x00000007, 0x00000006, 0x00000006, 0x00000005,
|
||||
0x00000006, 0x00000016, 0x00000014, 0x00000012, 0x00000011, 0x00000010, 0x0000000f, 0x0000000e, 0x0000000d, 0x0000000d, 0x0000000c, 0x0000000c, 0x00000008, 0x00000007, 0x00000008, 0x00000006,
|
||||
0x00000004, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x00000006, 0x0000001d, 0x00000006, 0x0000001d, 0x00000001, 0x00000001, 0x00000004, 0x00000001,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x00000006, 0x0000001d, 0x00000006, 0x0000001d, 0x00000006, 0x00000006, 0x00000004, 0x00000006,
|
||||
0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
|
||||
0xffeefdf7, 0xffcf7fff, 0xfff5f7ff, 0xfffeddff, 0xffff777f, 0xffffdddf, 0xfffff777, 0xfffffddb, 0xffffbbbf, 0xffffff75, 0xffffeeef, 0xffffffda, 0xfbfbfeff, 0xfeffbfbf, 0x7ffffff5, 0xffbfefef,
|
||||
// terms 48-63
|
||||
// 39.4^41.4=1 38.4^40.4=1 37.4^39.4=1 36.1^37.6=1 35.4^39.29=0 63.0^64.5=1 63.1^64.6=1 62.0^63.5=1
|
||||
// 61.0^62.5=1 61.2^62.7=1 60.0^61.5=1 58.29^59.29=0 57.29^58.29=0 56.4^59.29=0 56.29^59.29=1 56.29^57.29=0
|
||||
0x00000005, 0x00000004, 0x00000003, 0x00000002, 0x00000001, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001b, 0x0000001b, 0x0000001a, 0x00000018, 0x00000017, 0x00000016, 0x00000016, 0x00000016,
|
||||
0x00000007, 0x00000006, 0x00000005, 0x00000003, 0x00000005, 0x0000001e, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001c, 0x0000001b, 0x00000019, 0x00000018, 0x00000019, 0x00000019, 0x00000017,
|
||||
0x00000004, 0x00000004, 0x00000004, 0x00000001, 0x00000004, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000002, 0x00000000, 0x0000001d, 0x0000001d, 0x00000004, 0x0000001d, 0x0000001d,
|
||||
0x00000004, 0x00000004, 0x00000004, 0x00000006, 0x0000001d, 0x00000005, 0x00000006, 0x00000005, 0x00000005, 0x00000007, 0x00000005, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d,
|
||||
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000,
|
||||
0xbffffffa, 0x5ffffffd, 0xaffffffe, 0xfffbefbf, 0xfff7ff7b, 0xffefff7f, 0xfffefffb, 0xfff7ffdf, 0xfffdfff7, 0xfffbffef, 0xfffefffb, 0xddffffff, 0xef7fffff, 0xd7ffffff, 0xf5ffffff, 0xf7dfffff,
|
||||
// terms 64-79
|
||||
// 55.4^58.29=0 54.4^57.29=0 53.4^56.29=0 50.6^51.1=0 48.6^50.6=0 48.29^55.29=1 47.6^49.6=0 47.6^48.1=0
|
||||
// 46.6^48.6=0 46.6^47.1=0 44.1^45.6=1 43.6^45.6=0 42.6^44.6=0 42.6^43.1=0 41.6^42.1=0 40.6^41.1=0
|
||||
0x00000015, 0x00000014, 0x00000013, 0x00000010, 0x0000000e, 0x0000000e, 0x0000000d, 0x0000000d, 0x0000000c, 0x0000000c, 0x0000000a, 0x00000009, 0x00000008, 0x00000008, 0x00000007, 0x00000006,
|
||||
0x00000018, 0x00000017, 0x00000016, 0x00000011, 0x00000010, 0x00000015, 0x0000000f, 0x0000000e, 0x0000000e, 0x0000000d, 0x0000000b, 0x0000000b, 0x0000000a, 0x00000009, 0x00000008, 0x00000007,
|
||||
0x00000004, 0x00000004, 0x00000004, 0x00000006, 0x00000006, 0x0000001d, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000001, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000006,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x00000001, 0x00000006, 0x0000001d, 0x00000006, 0x00000001, 0x00000006, 0x00000001, 0x00000006, 0x00000006, 0x00000006, 0x00000001, 0x00000001, 0x00000001,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0xedffffff, 0xf77fffff, 0xfddfffff, 0xfffbefff, 0xfffbefff, 0xffff5fff, 0xffffbbff, 0xfbffffbf, 0xffffeeff, 0xfeffffef, 0xffbfbfff, 0xfffffbbf, 0xfffffeef, 0xfbfbffff, 0xfeffbfff, 0xffbfefff,
|
||||
// terms 80-95
|
||||
// 39.4^43.29=0 38.4^42.29=0 37.1^38.6=1 37.4^41.29=0 36.4^38.4=1 35.1^36.6=1 35.3^39.28=0 61.1^62.6=1
|
||||
// 59.5^63.30=0 58.0^63.30=1 62.1^63.6=1 60.5^64.30=0 59.0^64.30=1 40.6^42.6=0 62.2^63.7=1 41.6^43.6=0
|
||||
0x00000005, 0x00000004, 0x00000003, 0x00000003, 0x00000002, 0x00000001, 0x00000001, 0x0000001b, 0x00000019, 0x00000018, 0x0000001c, 0x0000001a, 0x00000019, 0x00000006, 0x0000001c, 0x00000007,
|
||||
0x00000009, 0x00000008, 0x00000004, 0x00000007, 0x00000004, 0x00000002, 0x00000005, 0x0000001c, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001e, 0x0000001e, 0x00000008, 0x0000001d, 0x00000009,
|
||||
0x00000004, 0x00000004, 0x00000001, 0x00000004, 0x00000004, 0x00000001, 0x00000003, 0x00000001, 0x00000005, 0x00000000, 0x00000001, 0x00000005, 0x00000000, 0x00000006, 0x00000002, 0x00000006,
|
||||
0x0000001d, 0x0000001d, 0x00000006, 0x0000001d, 0x00000004, 0x00000006, 0x0000001c, 0x00000006, 0x0000001e, 0x0000001e, 0x00000006, 0x0000001e, 0x0000001e, 0x00000006, 0x00000007, 0x00000006,
|
||||
0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000,
|
||||
0xfdff7fff, 0xff7fdfff, 0xffffbeff, 0xffdff7ff, 0xd7ffffff, 0xfffffbef, 0xfff7dfff, 0xfffffffe, 0xfffffffe, 0xfffffffe, 0xfffffffd, 0xfffffffd, 0xfffffffd, 0xffffffef, 0xffffffbf, 0xffffffbf,
|
||||
// terms 96-111
|
||||
// 63.2^64.7=1 48.6^49.1=0 49.6^50.1=0 42.1^50.1=1 39.6^40.1=0 38.1^40.1=1 36.4^37.4=1 43.1^51.1=1
|
||||
// 37.4^38.4=1 51.6^52.1=0 49.6^51.6=0 37.1^37.6=0 35.5^39.30=0 38.4^39.4=1 47.1^51.1=1 36.3^40.28=0
|
||||
0x0000001d, 0x0000000e, 0x0000000f, 0x00000008, 0x00000005, 0x00000004, 0x00000002, 0x00000009, 0x00000003, 0x00000011, 0x0000000f, 0x00000003, 0x00000001, 0x00000004, 0x0000000d, 0x00000002,
|
||||
0x0000001e, 0x0000000f, 0x00000010, 0x00000010, 0x00000006, 0x00000006, 0x00000003, 0x00000011, 0x00000004, 0x00000012, 0x00000011, 0x00000003, 0x00000005, 0x00000005, 0x00000011, 0x00000006,
|
||||
0x00000002, 0x00000006, 0x00000006, 0x00000001, 0x00000006, 0x00000001, 0x00000004, 0x00000001, 0x00000004, 0x00000006, 0x00000006, 0x00000001, 0x00000005, 0x00000004, 0x00000001, 0x00000003,
|
||||
0x00000007, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000004, 0x00000001, 0x00000004, 0x00000001, 0x00000006, 0x00000006, 0x0000001e, 0x00000004, 0x00000001, 0x0000001c,
|
||||
0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000,
|
||||
0xfffffeff, 0xfffffeff, 0xfffffbff, 0xfffffbff, 0xfffffbff, 0xfffffbff, 0xfffff7ff, 0xffffefff, 0xffffdfff, 0xffffbfff, 0xffffbfff, 0xffffbfff, 0xffffbfff, 0xffff7fff, 0xfffbffff, 0xffefffff,
|
||||
// terms 112-127
|
||||
// 35.30^40.28=1 37.3^41.28=0 36.30^41.28=1 53.6^54.1=0 51.6^53.6=0 50.1^54.1=1 45.6^46.1=0 37.5^41.30=0
|
||||
// 36.0^41.30=1 55.29^58.29=1 38.3^42.28=0 37.30^42.28=1 54.6^55.1=0 52.6^54.6=0 51.1^55.1=1 45.1^47.1=1
|
||||
0x00000001, 0x00000003, 0x00000002, 0x00000013, 0x00000011, 0x00000010, 0x0000000b, 0x00000003, 0x00000002, 0x00000015, 0x00000004, 0x00000003, 0x00000014, 0x00000012, 0x00000011, 0x0000000b,
|
||||
0x00000006, 0x00000007, 0x00000007, 0x00000014, 0x00000013, 0x00000014, 0x0000000c, 0x00000007, 0x00000007, 0x00000018, 0x00000008, 0x00000008, 0x00000015, 0x00000014, 0x00000015, 0x0000000d,
|
||||
0x0000001e, 0x00000003, 0x0000001e, 0x00000006, 0x00000006, 0x00000001, 0x00000006, 0x00000005, 0x00000000, 0x0000001d, 0x00000003, 0x0000001e, 0x00000006, 0x00000006, 0x00000001, 0x00000001,
|
||||
0x0000001c, 0x0000001c, 0x0000001c, 0x00000001, 0x00000006, 0x00000001, 0x00000001, 0x0000001e, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001c, 0x00000001, 0x00000006, 0x00000001, 0x00000001,
|
||||
0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001,
|
||||
0xffefffff, 0xffdfffff, 0xffdfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xff7fffff, 0xff7fffff, 0xff7fffff, 0xfeffffff, 0xfeffffff, 0xfeffffff, 0xfeffffff,
|
||||
// terms 128-143
|
||||
// 38.5^42.30=0 37.0^42.30=1 39.3^43.28=0 38.30^43.28=1 55.6^56.1=0 53.6^55.6=0 52.1^56.1=1 46.1^48.1=1
|
||||
// 39.5^43.30=0 38.0^43.30=1 59.29^60.29=0 40.3^44.28=0 40.4^44.29=0 39.30^44.28=1 58.29^61.29=1 57.4^61.29=0
|
||||
0x00000004, 0x00000003, 0x00000005, 0x00000004, 0x00000015, 0x00000013, 0x00000012, 0x0000000c, 0x00000005, 0x00000004, 0x00000019, 0x00000006, 0x00000006, 0x00000005, 0x00000018, 0x00000017,
|
||||
0x00000008, 0x00000008, 0x00000009, 0x00000009, 0x00000016, 0x00000015, 0x00000016, 0x0000000e, 0x00000009, 0x00000009, 0x0000001a, 0x0000000a, 0x0000000a, 0x0000000a, 0x0000001b, 0x0000001b,
|
||||
0x00000005, 0x00000000, 0x00000003, 0x0000001e, 0x00000006, 0x00000006, 0x00000001, 0x00000001, 0x00000005, 0x00000000, 0x0000001d, 0x00000003, 0x00000004, 0x0000001e, 0x0000001d, 0x00000004,
|
||||
0x0000001e, 0x0000001e, 0x0000001c, 0x0000001c, 0x00000001, 0x00000006, 0x00000001, 0x00000001, 0x0000001e, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001d,
|
||||
0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000,
|
||||
0xfeffffff, 0xfeffffff, 0xfdffffff, 0xfdffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xf7ffffff, 0xf7ffffff, 0xf7ffffff, 0xf7ffffff, 0xefffffff, 0xefffffff,
|
||||
// terms 144-159
|
||||
// 41.3^45.28=0 41.4^45.29=0 58.4^62.29=0 42.3^46.28=0 42.4^46.29=0 59.4^63.29=0 57.4^59.29=0 43.3^47.28=0
|
||||
// 43.4^47.29=0 60.4^64.29=0 44.3^48.28=0 44.4^48.29=0 35.0^35.0=0 35.0^35.0=0 35.0^35.0=0 35.0^35.0=0
|
||||
0x00000007, 0x00000007, 0x00000018, 0x00000008, 0x00000008, 0x00000019, 0x00000017, 0x00000009, 0x00000009, 0x0000001a, 0x0000000a, 0x0000000a, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
|
||||
0x0000000b, 0x0000000b, 0x0000001c, 0x0000000c, 0x0000000c, 0x0000001d, 0x00000019, 0x0000000d, 0x0000000d, 0x0000001e, 0x0000000e, 0x0000000e, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
|
||||
0x00000003, 0x00000004, 0x00000004, 0x00000003, 0x00000004, 0x00000004, 0x00000004, 0x00000003, 0x00000004, 0x00000004, 0x00000003, 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x0000001c, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001d, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0xefffffff, 0xefffffff, 0xdfffffff, 0xdfffffff, 0xdfffffff, 0xbfffffff, 0xbfffffff, 0xbfffffff, 0xbfffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//go:build noasm || !gc || !amd64
|
||||
|
||||
package ubc
|
||||
|
||||
// CalculateDvMask takes as input an expanded message block and
|
||||
// verifies the unavoidable bitconditions for all listed DVs. It returns
|
||||
// a dvmask where each bit belonging to a DV is set if all unavoidable
|
||||
// bitconditions for that DV have been met.
|
||||
func CalculateDvMask(W *[80]uint32) uint32 {
|
||||
if W == nil {
|
||||
return 0
|
||||
}
|
||||
return calculateDvMaskGeneric(W)
|
||||
}
|
||||
+4
-8
@@ -23,16 +23,12 @@ type DvInfo struct {
|
||||
Dm [80]uint32
|
||||
}
|
||||
|
||||
// CalculateDvMask takes as input an expanded message block and
|
||||
// verifies the unavoidable bitconditions for all listed DVs. It returns
|
||||
// a dvmask where each bit belonging to a DV is set if all unavoidable
|
||||
// bitconditions for that DV have been met.
|
||||
// calculateDvMaskGeneric is the portable implementation of CalculateDvMask.
|
||||
// The AVX-512 kernel runs the same bit tests from avx512Terms, and
|
||||
// TestAVX512MatchesGeneric holds the two to the same results.
|
||||
//
|
||||
//go:nosplit
|
||||
func CalculateDvMask(W *[80]uint32) uint32 {
|
||||
if W == nil {
|
||||
return 0
|
||||
}
|
||||
func calculateDvMaskGeneric(W *[80]uint32) uint32 {
|
||||
mask := uint32(0xFFFFFFFF)
|
||||
mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | ^(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit))
|
||||
mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | ^(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | DV_II_55_0_bit | DV_II_56_0_bit))
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
*
|
||||
!.git/
|
||||
!build/root.tgz
|
||||
!cmd/
|
||||
!config/
|
||||
!internal/
|
||||
!mod/
|
||||
!pkg/
|
||||
!regclient/
|
||||
!scheme/
|
||||
!types/
|
||||
!vendor/
|
||||
!go.*
|
||||
!*.go
|
||||
!Makefile
|
||||
@@ -1,5 +0,0 @@
|
||||
artifacts/
|
||||
bin/
|
||||
output/
|
||||
vendor/
|
||||
.regctl_conf_ci.json
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
# all lists use a `-`
|
||||
MD004:
|
||||
style: dash
|
||||
|
||||
# allow tabs in code blocks (for Go)
|
||||
MD010:
|
||||
code_blocks: false
|
||||
|
||||
# disable line length, prefer one sentence per line for PRs
|
||||
MD013: false
|
||||
|
||||
# emphasis with underscore (`_emphasis_`)
|
||||
MD049:
|
||||
style: "underscore"
|
||||
|
||||
# bold with asterisk (`**bold**`)
|
||||
MD050:
|
||||
style: "asterisk"
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
GoVersionOverride = "1.26.3"
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
{"name":"docker-arg-alpine-digest","key":"docker.io/library/alpine:3.23.4","version":"sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11"}
|
||||
{"name":"docker-arg-alpine-tag","key":"docker.io/library/alpine","version":"3.23.4"}
|
||||
{"name":"docker-arg-ecr","key":"https://github.com/awslabs/amazon-ecr-credential-helper.git","version":"v0.12.0"}
|
||||
{"name":"docker-arg-gcr","key":"https://github.com/GoogleCloudPlatform/docker-credential-gcr.git","version":"v2.1.32"}
|
||||
{"name":"docker-arg-go-digest","key":"docker.io/library/golang:1.26.3-alpine","version":"sha256:91eda9776261207ea25fd06b5b7fed8d397dd2c0a283e77f2ab6e91bfa71079d"}
|
||||
{"name":"docker-arg-go-tag","key":"docker.io/library/golang","version":"1.26.3"}
|
||||
{"name":"docker-arg-lunajson","key":"https://github.com/grafi-tt/lunajson.git:master","version":"e3a9666eb1275741e887e29926b144f8daee3bef"}
|
||||
{"name":"docker-arg-semver","key":"https://github.com/kikito/semver.lua.git:master","version":"a4b708ba243208d46e575da870af969dca46a94d"}
|
||||
{"name":"gha-alpine-digest","key":"docker.io/library/alpine:3.23.4","version":"sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11"}
|
||||
{"name":"gha-alpine-tag-base","key":"docker.io/library/alpine","version":"3"}
|
||||
{"name":"gha-alpine-tag-comment","key":"docker.io/library/alpine","version":"3.23.4"}
|
||||
{"name":"gha-cosign-version","key":"https://github.com/sigstore/cosign.git","version":"v3.0.6"}
|
||||
{"name":"gha-golang-matrix","key":"golang-matrix","version":"[\"1.25\", \"1.26\"]"}
|
||||
{"name":"gha-golang-release","key":"golang-latest","version":"1.26"}
|
||||
{"name":"gha-syft-version","key":"docker.io/anchore/syft","version":"v1.44.0"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/checkout.git:v6.0.2","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/setup-go.git:v6.4.0","version":"4a3601121dd01d1626a1e23e37211e3254c1c06c"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/stale.git:v10.3.0","version":"eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/upload-artifact.git:v7.0.1","version":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/anchore/sbom-action.git:v0.24.0","version":"e22c389904149dbc22b58101806040fa8d37a610"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/docker/build-push-action.git:v7.2.0","version":"f9f3042f7e2789586610d6e8b85c8f03e5195baf"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/docker/login-action.git:v4.2.0","version":"650006c6eb7dba73a995cc03b0b2d7f5ca915bee"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/docker/setup-buildx-action.git:v4.1.0","version":"d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/regclient/actions.git:main","version":"c70ad64367908075211b10dcd2ab9fad4bfa1816"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/sigstore/cosign-installer.git:v4.1.2","version":"6f9f17788090df1f26f669e9d70d6ae9567deba6"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/softprops/action-gh-release.git:v3.0.0","version":"b4309332981a82ec1c5618f44dd2e27cc8bfbfda"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/checkout.git","version":"v6.0.2"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/setup-go.git","version":"v6.4.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/stale.git","version":"v10.3.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/upload-artifact.git","version":"v7.0.1"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/anchore/sbom-action.git","version":"v0.24.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/docker/build-push-action.git","version":"v7.2.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/docker/login-action.git","version":"v4.2.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/docker/setup-buildx-action.git","version":"v4.1.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/sigstore/cosign-installer.git","version":"v4.1.2"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/softprops/action-gh-release.git","version":"v3.0.0"}
|
||||
{"name":"go-mod-golang-release","key":"golang-oldest","version":"1.25.0"}
|
||||
{"name":"makefile-ci-distribution","key":"docker.io/library/registry","version":"3.1.1"}
|
||||
{"name":"makefile-ci-zot","key":"ghcr.io/project-zot/zot-linux-amd64","version":"v2.1.17"}
|
||||
{"name":"makefile-go-vulncheck","key":"https://go.googlesource.com/vuln.git","version":"v1.3.0"}
|
||||
{"name":"makefile-gofumpt","key":"https://github.com/mvdan/gofumpt.git","version":"v0.10.0"}
|
||||
{"name":"makefile-gomajor","key":"https://github.com/icholy/gomajor.git","version":"v0.15.0"}
|
||||
{"name":"makefile-gosec","key":"https://github.com/securego/gosec.git","version":"v2.26.1"}
|
||||
{"name":"makefile-markdown-lint","key":"docker.io/davidanson/markdownlint-cli2","version":"v0.22.1"}
|
||||
{"name":"makefile-osv-scanner","key":"https://github.com/google/osv-scanner.git","version":"v2.3.8"}
|
||||
{"name":"makefile-staticcheck","key":"https://github.com/dominikh/go-tools.git","version":"v0.7.0"}
|
||||
{"name":"makefile-syft-container-digest","key":"anchore/syft:v1.44.0","version":"sha256:86fde6445b483d902fe011dd9f68c4987dd94e07da1e9edc004e3c2422650de6"}
|
||||
{"name":"makefile-syft-container-tag","key":"anchore/syft","version":"v1.44.0"}
|
||||
{"name":"makefile-syft-version","key":"docker.io/anchore/syft","version":"v1.44.0"}
|
||||
{"name":"osv-golang-release","key":"docker.io/library/golang","version":"1.26.3"}
|
||||
{"name":"shell-alpine-digest","key":"docker.io/library/alpine:3.23.4","version":"sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11"}
|
||||
{"name":"shell-alpine-tag-base","key":"docker.io/library/alpine","version":"3"}
|
||||
{"name":"shell-alpine-tag-comment","key":"docker.io/library/alpine","version":"3.23.4"}
|
||||
-346
@@ -1,346 +0,0 @@
|
||||
files:
|
||||
"build/Dockerfile*":
|
||||
processors:
|
||||
- docker-arg-alpine-tag
|
||||
- docker-arg-alpine-digest
|
||||
- docker-arg-go-tag
|
||||
- docker-arg-go-digest
|
||||
- docker-arg-ecr
|
||||
- docker-arg-gcr
|
||||
- docker-arg-lunajson
|
||||
- docker-arg-semver
|
||||
"build/oci-image.sh":
|
||||
processors:
|
||||
- shell-alpine-tag-base
|
||||
- shell-alpine-tag-comment
|
||||
- shell-alpine-digest
|
||||
".github/workflows/*.yml":
|
||||
processors:
|
||||
- gha-golang-matrix
|
||||
- gha-golang-release
|
||||
- gha-uses-vx
|
||||
- gha-uses-semver
|
||||
- gha-uses-commit
|
||||
- gha-syft-version
|
||||
- gha-cosign-version
|
||||
- gha-alpine-tag-base
|
||||
- gha-alpine-tag-comment
|
||||
- gha-alpine-digest
|
||||
"Makefile":
|
||||
processors:
|
||||
- makefile-gofumpt
|
||||
- makefile-gomajor
|
||||
- makefile-go-vulncheck
|
||||
- makefile-markdown-lint
|
||||
- makefile-gosec
|
||||
- makefile-osv-scanner
|
||||
- makefile-staticcheck
|
||||
- makefile-syft-version
|
||||
- makefile-syft-container-tag
|
||||
- makefile-syft-container-digest
|
||||
- makefile-ci-distribution
|
||||
- makefile-ci-zot
|
||||
"go.mod":
|
||||
processors:
|
||||
- go-mod-golang-release
|
||||
".osv-scanner.toml":
|
||||
processors:
|
||||
- osv-golang-release
|
||||
|
||||
x-processor-tmpl:
|
||||
git-commit: &git-commit
|
||||
key: "{{ .SourceArgs.url }}:{{ .SourceArgs.ref }}"
|
||||
scan: "regexp"
|
||||
source: "git-commit"
|
||||
filter:
|
||||
expr: "^{{ .SourceArgs.ref }}$"
|
||||
git-tag-semver: &git-tag-semver
|
||||
key: "{{ .SourceArgs.url }}"
|
||||
scan: "regexp"
|
||||
source: "git-tag"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+\.\d+$'
|
||||
sort:
|
||||
method: "semver"
|
||||
registry-digest: ®istry-digest
|
||||
key: "{{ .SourceArgs.image }}"
|
||||
scan: "regexp"
|
||||
source: "registry-digest"
|
||||
registry-tag-semver: ®istry-tag-semver
|
||||
key: "{{ .SourceArgs.repo }}"
|
||||
scan: "regexp"
|
||||
source: "registry-tag"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+\.\d+$'
|
||||
sort:
|
||||
method: "semver"
|
||||
|
||||
processors:
|
||||
docker-arg-alpine-tag:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG ALPINE_VER=(?P<Version>v?\d+\.\d+\.\d+)@(?P<SHA>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
docker-arg-alpine-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^ARG ALPINE_VER=(?P<Tag>v?\d+\.\d+\.\d+)@(?P<Version>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/alpine:{{.ScanMatch.Tag}}"
|
||||
docker-arg-go-tag:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG GO_VER=(?P<Version>[a-z0-9\-\.]+)-alpine@(?P<SHA>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
docker-arg-go-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^ARG GO_VER=(?P<Tag>[a-z0-9\-\.]+)@(?P<Version>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/golang:{{.ScanMatch.Tag}}"
|
||||
docker-arg-ecr:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG ECR_HELPER_VER=(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/awslabs/amazon-ecr-credential-helper.git"
|
||||
# get the version for the ecr-login nested package in the repo
|
||||
filter:
|
||||
expr: '^ecr-login/v?\d+\.\d+\.\d+$'
|
||||
# sort and output only the version number without the package name prefix
|
||||
sort:
|
||||
method: "semver"
|
||||
template: '{{ index (split . "/") 1 }}'
|
||||
template: '{{ index (split .Version "/") 1 }}'
|
||||
docker-arg-gcr:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG GCR_HELPER_VER=(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/GoogleCloudPlatform/docker-credential-gcr.git"
|
||||
docker-arg-lunajson:
|
||||
<<: *git-commit
|
||||
scanArgs:
|
||||
regexp: '^ARG LUNAJSON_COMMIT=(?P<Version>[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/grafi-tt/lunajson.git"
|
||||
ref: master
|
||||
docker-arg-semver:
|
||||
<<: *git-commit
|
||||
scanArgs:
|
||||
regexp: '^ARG SEMVER_COMMIT=(?P<Version>[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/kikito/semver.lua.git"
|
||||
ref: master
|
||||
|
||||
gha-alpine-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST: "(?P<Version>sha256:[0-9a-f]+)"\s*#\s*(?P<Tag>\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/alpine:{{ .ScanMatch.Tag }}"
|
||||
gha-alpine-tag-base:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_NAME: "alpine:(?P<Version>v?\d+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
# only return the major version number in the tag to support detecting a change in the base image
|
||||
template: '{{ index ( split .Version "." ) 0 }}'
|
||||
gha-alpine-tag-comment:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST: "(?P<Digest>sha256:[0-9a-f]+)"\s*#\s*(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
gha-cosign-version:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*cosign-release: "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/sigstore/cosign.git"
|
||||
filter:
|
||||
expr: '^v?3\.\d+\.\d+$' # pin to v3, v4 will remove support for older clients
|
||||
gha-golang-matrix:
|
||||
<<: *registry-tag-semver
|
||||
key: "golang-matrix"
|
||||
scanArgs:
|
||||
regexp: '^\s*gover: (?P<Version>\[["0-9, \.]+\])\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+$'
|
||||
template: '["{{ index .VerMap ( index .VerList 1 ) }}", "{{ index .VerMap ( index .VerList 0 ) }}"]'
|
||||
gha-golang-release:
|
||||
<<: *registry-tag-semver
|
||||
key: "golang-latest"
|
||||
scanArgs:
|
||||
regexp: '^\s*RELEASE_GO_VER: "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+$'
|
||||
gha-syft-version:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*syft-version: "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/anchore/syft"
|
||||
gha-uses-vx:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s+-?\s+uses: (?P<Repo>[^@/]+/[^@/]+)[^@]*@(?P<Commit>[0-9a-f]+)\s+#\s+(?P<Version>v?\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/{{ .ScanMatch.Repo }}.git"
|
||||
filter:
|
||||
expr: '^v?\d+$'
|
||||
gha-uses-semver:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s+-?\s+uses: (?P<Repo>[^@/]+/[^@/]+)[^@]*@(?P<Commit>[0-9a-f]+)\s+#\s+(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/{{ .ScanMatch.Repo }}.git"
|
||||
gha-uses-commit:
|
||||
<<: *git-commit
|
||||
scanArgs:
|
||||
regexp: '^\s+-?\s+uses: (?P<Repo>[^@/]+/[^@/]+)[^@]*@(?P<Version>[0-9a-f]+)\s+#\s+(?P<Ref>[\w\d\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/{{ .ScanMatch.Repo }}.git"
|
||||
ref: "{{ .ScanMatch.Ref }}"
|
||||
|
||||
go-mod-golang-release:
|
||||
<<: *registry-tag-semver
|
||||
key: "golang-oldest"
|
||||
scanArgs:
|
||||
regexp: '^go (?P<Version>[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
filter:
|
||||
expr: '^\d+\.\d+$'
|
||||
template: '{{ index .VerMap ( index .VerList 1 ) }}.0'
|
||||
|
||||
makefile-ci-distribution:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^CI_DISTRIBUTION_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/registry"
|
||||
makefile-ci-zot:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^CI_ZOT_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "ghcr.io/project-zot/zot-linux-amd64"
|
||||
makefile-gofumpt:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GOFUMPT_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/mvdan/gofumpt.git"
|
||||
makefile-gomajor:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GOMAJOR_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/icholy/gomajor.git"
|
||||
makefile-gosec:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GOSEC_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/securego/gosec.git"
|
||||
makefile-go-vulncheck:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GO_VULNCHECK_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://go.googlesource.com/vuln.git"
|
||||
makefile-markdown-lint:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^MARKDOWN_LINT_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/davidanson/markdownlint-cli2"
|
||||
makefile-osv-scanner:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^OSV_SCANNER_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/google/osv-scanner.git"
|
||||
makefile-staticcheck:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^STATICCHECK_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/dominikh/go-tools.git"
|
||||
filter:
|
||||
# repo also has dated tags, ignore versions without a preceding "v"
|
||||
expr: '^v\d+\.\d+\.\d+$'
|
||||
makefile-syft-container-tag:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^SYFT_CONTAINER\?=(?P<Repo>[^:]*):(?P<Version>v?[0-9\.]+)@(?P<Digest>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "{{ .ScanMatch.Repo }}"
|
||||
makefile-syft-container-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^SYFT_CONTAINER\?=(?P<Image>[^:]*):(?P<Tag>v?[0-9\.]+)@(?P<Version>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
image: "{{ .ScanMatch.Image }}:{{.ScanMatch.Tag}}"
|
||||
makefile-syft-version:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^SYFT_VERSION\?=(?P<Version>v[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/anchore/syft"
|
||||
|
||||
osv-golang-release:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GoVersionOverride = "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
|
||||
shell-alpine-tag-base:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_NAME="alpine:(?P<Version>v?\d+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
# only return the major version number in the tag to support detecting a change in the base image
|
||||
template: '{{ index ( split .Version "." ) 0 }}'
|
||||
shell-alpine-tag-comment:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST="(?P<Digest>sha256:[0-9a-f]+)"\s*#\s*(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
shell-alpine-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST="(?P<Version>sha256:[0-9a-f]+)"\s*#\s*(?P<Tag>\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/alpine:{{ .ScanMatch.Tag }}"
|
||||
|
||||
scans:
|
||||
regexp:
|
||||
type: "regexp"
|
||||
|
||||
sources:
|
||||
git-commit:
|
||||
type: "git"
|
||||
args:
|
||||
type: "commit"
|
||||
git-tag:
|
||||
type: "git"
|
||||
args:
|
||||
type: "tag"
|
||||
registry-digest:
|
||||
type: "registry"
|
||||
registry-tag:
|
||||
type: "registry"
|
||||
args:
|
||||
type: "tag"
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
<git@bmitch.net> or slack (I'm found on the CNCF, Docker, OCI, and OpenSSF
|
||||
slacks).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
Please see [SECURITY.md](security.md) for the process to report security issues.
|
||||
|
||||
## Reporting other issues
|
||||
|
||||
Please search for similar issues and if none are seen, report an issue at [github.com/regclient/regclient/issues](https://github.com/regclient/regclient/issues).
|
||||
|
||||
## Code style
|
||||
|
||||
This project attempts to follow these principles:
|
||||
|
||||
- Code is canonical Go, following styles and patterns commonly used by the Go community.
|
||||
- Dependencies outside of the Go standard library should be minimized.
|
||||
- Dependencies should be pinned to a specific digest and tracked by Go or version-check.
|
||||
- Unit tests are strongly encouraged with a focus on test coverage of the successful path and common errors.
|
||||
- Linters and other style formatting tools are used, please run `make all` before committing any changes.
|
||||
|
||||
## LLM Policy
|
||||
|
||||
This project expects all contributions to be developed by a human or created with a reproducible tool.
|
||||
Developers using an AI/LLM tool to generate their contribution are expected to fully understand the entire contribution and the logic behind its design.
|
||||
Contributions that appear to have been generated by an AI/LLM without a human review may result in a ban from future contributions to the project.
|
||||
|
||||
## Pull requests
|
||||
|
||||
PRs are welcome following the below guides:
|
||||
|
||||
- For anything beyond a minor fix, opening an issue is suggested to discuss possible solutions.
|
||||
- Changes should be rebased on the main branch.
|
||||
- Changes should be squashed to a single commit per logical change.
|
||||
|
||||
All changes must be signed (`git commit -s`) to indicate you agree to the [Developer Certificate or Origin](https://developercertificate.org/):
|
||||
|
||||
```text
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
The sign-off will include the following message in your commit:
|
||||
|
||||
```text
|
||||
Signed-off-by: Your Name <your-email@example.org>
|
||||
```
|
||||
|
||||
This needs to be your real name, no aliases please.
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2020 The regclient Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-285
@@ -1,285 +0,0 @@
|
||||
COMMANDS?=regctl regsync regbot
|
||||
BINARIES?=$(addprefix bin/,$(COMMANDS))
|
||||
IMAGES?=$(addprefix docker-,$(COMMANDS))
|
||||
ARTIFACT_PLATFORMS?=linux-amd64 linux-arm64 linux-ppc64le linux-s390x linux-riscv64 darwin-amd64 darwin-arm64 windows-amd64.exe freebsd-amd64
|
||||
ARTIFACTS?=$(foreach cmd,$(addprefix artifacts/,$(COMMANDS)),$(addprefix $(cmd)-,$(ARTIFACT_PLATFORMS)))
|
||||
IMAGE_PLATFORMS?=linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x,linux/riscv64
|
||||
VCS_REPO?="https://github.com/regclient/regclient.git"
|
||||
VCS_REF?=$(shell git rev-list -1 HEAD)
|
||||
ifneq ($(shell git status --porcelain 2>/dev/null),)
|
||||
VCS_REF := $(VCS_REF)-dirty
|
||||
endif
|
||||
VCS_VERSION?=$(shell vcs_describe="$$(git describe --all)"; \
|
||||
vcs_version="(devel)"; \
|
||||
if [ "$${vcs_describe}" != "$${vcs_describe#tags/}" ]; then \
|
||||
vcs_version="$${vcs_describe#tags/}"; \
|
||||
elif [ "$${vcs_describe}" != "$${vcs_describe#heads/}" ]; then \
|
||||
vcs_version="$${vcs_describe#heads/}"; \
|
||||
if [ "main" = "$${vcs_version}" ]; then vcs_version=edge; fi; \
|
||||
fi; \
|
||||
echo "$${vcs_version}" | sed -r 's#/+#-#g')
|
||||
VCS_TAG?=$(shell git describe --tags --abbrev=0 2>/dev/null || true)
|
||||
VCS_SEC?=$(shell git log -1 --format=%ct)
|
||||
VCS_DATE?=$(shell date -d "@$(VCS_SEC)" +%Y-%m-%dT%H:%M:%SZ --utc)
|
||||
LD_FLAGS?=-s -w -extldflags -static -buildid= -X \"github.com/regclient/regclient/internal/version.vcsTag=$(VCS_TAG)\"
|
||||
GO_BUILD_FLAGS?=-trimpath -ldflags "$(LD_FLAGS)"
|
||||
DOCKERFILE_EXT?=$(shell if docker build --help 2>/dev/null | grep -q -- '--progress'; then echo ".buildkit"; fi)
|
||||
DOCKER_ARGS?=--build-arg "VCS_REF=$(VCS_REF)" --build-arg "VCS_VERSION=$(VCS_VERSION)" --build-arg "SOURCE_DATE_EPOCH=$(VCS_SEC)" --build-arg "BUILD_DATE=$(VCS_DATE)"
|
||||
GOPATH?=$(shell go env GOPATH)
|
||||
PWD:=$(shell pwd)
|
||||
VER_BUMP?=$(shell command -v version-bump 2>/dev/null)
|
||||
VER_BUMP_CONTAINER?=sudobmitch/version-bump:edge
|
||||
ifeq "$(strip $(VER_BUMP))" ''
|
||||
VER_BUMP=docker run --rm \
|
||||
-v "$(shell pwd)/:$(shell pwd)/" -w "$(shell pwd)" \
|
||||
-u "$(shell id -u):$(shell id -g)" \
|
||||
$(VER_BUMP_CONTAINER)
|
||||
endif
|
||||
MARKDOWN_LINT_VER?=v0.22.1
|
||||
GOFUMPT_VER?=v0.10.0
|
||||
GOMAJOR_VER?=v0.15.0
|
||||
GOSEC_VER?=v2.26.1
|
||||
GO_VULNCHECK_VER?=v1.3.0
|
||||
OSV_SCANNER_VER?=v2.3.8
|
||||
SYFT?=$(shell command -v syft 2>/dev/null)
|
||||
SYFT_CMD_VER:=$(shell [ -x "$(SYFT)" ] && echo "v$$($(SYFT) version | awk '/^Version: / {print $$2}')" || echo "0")
|
||||
SYFT_VERSION?=v1.44.0
|
||||
SYFT_CONTAINER?=anchore/syft:v1.44.0@sha256:86fde6445b483d902fe011dd9f68c4987dd94e07da1e9edc004e3c2422650de6
|
||||
ifneq "$(SYFT_CMD_VER)" "$(SYFT_VERSION)"
|
||||
SYFT=docker run --rm \
|
||||
-v "$(shell pwd)/:$(shell pwd)/" -w "$(shell pwd)" \
|
||||
-u "$(shell id -u):$(shell id -g)" \
|
||||
$(SYFT_CONTAINER)
|
||||
endif
|
||||
STATICCHECK_VER?=v0.7.0
|
||||
CI_DISTRIBUTION_VER?=3.1.1
|
||||
CI_ZOT_VER?=v2.1.17
|
||||
|
||||
.PHONY: .FORCE
|
||||
.FORCE:
|
||||
|
||||
.PHONY: all
|
||||
all: fmt gofumpt gofix goimports vet test lint binaries ## Full build of Go binaries (including fmt, vet, test, and lint)
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: ## go fmt
|
||||
go fmt ./...
|
||||
|
||||
.PHONY: gofumpt
|
||||
gofumpt: $(GOPATH)/bin/gofumpt ## gofumpt is a stricter alternative to go fmt
|
||||
gofumpt -l -w .
|
||||
|
||||
.PHONY: gofix
|
||||
gofix: ## go fix
|
||||
go fix ./...
|
||||
|
||||
goimports: $(GOPATH)/bin/goimports
|
||||
$(GOPATH)/bin/goimports -w -format-only -local github.com/regclient .
|
||||
|
||||
.PHONY: vet
|
||||
vet: ## go vet
|
||||
go vet ./...
|
||||
|
||||
.PHONY: test
|
||||
test: ## go test
|
||||
go test -cover -race ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint: lint-go lint-goimports lint-md lint-gosec ## Run all linting
|
||||
|
||||
.PHONY: lint-go
|
||||
lint-go: $(GOPATH)/bin/gofumpt $(GOPATH)/bin/staticcheck .FORCE ## Run linting for Go
|
||||
$(GOPATH)/bin/staticcheck -checks all ./...
|
||||
$(GOPATH)/bin/gofumpt -l -d .
|
||||
errors=$$(go fix -diff ./...); if [ "$${errors}" != "" ]; then echo "$${errors}"; exit 1; fi
|
||||
|
||||
lint-goimports: $(GOPATH)/bin/goimports
|
||||
@if [ -n "$$($(GOPATH)/bin/goimports -l -format-only -local github.com/regclient .)" ]; then \
|
||||
echo $(GOPATH)/bin/goimports -d -format-only -local github.com/regclient .; \
|
||||
$(GOPATH)/bin/goimports -d -format-only -local github.com/regclient .; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# excluding types/platform pending resultion to https://github.com/securego/gosec/issues/1116
|
||||
.PHONY: lint-gosec
|
||||
lint-gosec: $(GOPATH)/bin/gosec .FORCE ## Run gosec
|
||||
$(GOPATH)/bin/gosec -terse -exclude-dir types/platform ./...
|
||||
|
||||
.PHONY: lint-md
|
||||
lint-md: .FORCE ## Run linting for markdown
|
||||
docker run --rm -v "$(PWD):/workdir:ro" davidanson/markdownlint-cli2:$(MARKDOWN_LINT_VER) \
|
||||
"**/*.md" "#vendor"
|
||||
|
||||
.PHONY: vulnerability-scan
|
||||
vulnerability-scan: osv-scanner vulncheck-go ## Run all vulnerability scanners
|
||||
|
||||
.PHONY: osv-scanner
|
||||
osv-scanner: $(GOPATH)/bin/osv-scanner .FORCE ## Run OSV Scanner
|
||||
$(GOPATH)/bin/osv-scanner scan --config .osv-scanner.toml -r --licenses="Apache-2.0,BSD-3-Clause,MIT,CC-BY-SA-4.0,UNKNOWN" .
|
||||
|
||||
.PHONY: vulncheck-go
|
||||
vulncheck-go: $(GOPATH)/bin/govulncheck .FORCE ## Run govulncheck
|
||||
$(GOPATH)/bin/govulncheck ./...
|
||||
|
||||
.PHONY: vendor
|
||||
vendor: ## Vendor Go modules
|
||||
go mod vendor
|
||||
|
||||
.PHONY: binaries
|
||||
binaries: $(BINARIES) ## Build Go binaries
|
||||
|
||||
bin/%: .FORCE
|
||||
CGO_ENABLED=0 go build ${GO_BUILD_FLAGS} -o bin/$* ./cmd/$*
|
||||
|
||||
.PHONY: docker
|
||||
docker: $(IMAGES) ## Build Docker images
|
||||
|
||||
docker-%: .FORCE
|
||||
docker build -t regclient/$* -f build/Dockerfile.$*$(DOCKERFILE_EXT) $(DOCKER_ARGS) .
|
||||
docker build -t regclient/$*:alpine -f build/Dockerfile.$*$(DOCKERFILE_EXT) --target release-alpine $(DOCKER_ARGS) .
|
||||
|
||||
.PHONY: oci-image
|
||||
oci-image: $(addprefix oci-image-,$(COMMANDS)) ## Build reproducible images to an OCI Layout
|
||||
|
||||
oci-image-%: bin/regctl .FORCE
|
||||
PATH="$(PWD)/bin:$(PATH)" build/oci-image.sh -r scratch -i "$*" -p "$(IMAGE_PLATFORMS)"
|
||||
PATH="$(PWD)/bin:$(PATH)" build/oci-image.sh -r alpine -i "$*" -p "$(IMAGE_PLATFORMS)" -b "alpine:3"
|
||||
|
||||
.PHONY: test-docker
|
||||
test-docker: $(addprefix test-docker-,$(COMMANDS)) ## Build multi-platform docker images (but do not tag)
|
||||
|
||||
test-docker-%:
|
||||
docker buildx build --platform="$(IMAGE_PLATFORMS)" -f build/Dockerfile.$*.buildkit .
|
||||
docker buildx build --platform="$(IMAGE_PLATFORMS)" -f build/Dockerfile.$*.buildkit --target release-alpine .
|
||||
|
||||
.PHONY: ci
|
||||
ci: ci-distribution ci-zot ## Run CI tests against self hosted registries
|
||||
|
||||
.PHONY: ci-distribution
|
||||
ci-distribution:
|
||||
docker run --rm -d -p 5000 \
|
||||
--label regclient-ci=true --name regclient-ci-distribution \
|
||||
-e "REGISTRY_STORAGE_DELETE_ENABLED=true" \
|
||||
docker.io/library/registry:$(CI_DISTRIBUTION_VER)
|
||||
./build/ci-test.sh -t localhost:$$(docker port regclient-ci-distribution 5000 | head -1 | cut -f2 -d:)/test-ci
|
||||
docker stop regclient-ci-distribution
|
||||
|
||||
.PHONY: ci-zot
|
||||
ci-zot:
|
||||
docker run --rm -d -p 5000 \
|
||||
--label regclient-ci=true --name regclient-ci-zot \
|
||||
-v "$$(pwd)/build/zot-config.json:/etc/zot/config.json:ro" \
|
||||
ghcr.io/project-zot/zot-linux-amd64:$(CI_ZOT_VER)
|
||||
./build/ci-test.sh -t localhost:$$(docker port regclient-ci-zot 5000 | head -1 | cut -f2 -d:)/test-ci
|
||||
docker stop regclient-ci-zot
|
||||
|
||||
.PHONY: artifacts
|
||||
artifacts: $(ARTIFACTS) ## Generate artifacts
|
||||
|
||||
.PHONY: artifact-pre
|
||||
artifact-pre:
|
||||
mkdir -p artifacts
|
||||
|
||||
artifacts/%: artifact-pre .FORCE
|
||||
@set -e; \
|
||||
target="$*"; \
|
||||
command="$${target%%-*}"; \
|
||||
platform_ext="$${target#*-}"; \
|
||||
platform="$${platform_ext%.*}"; \
|
||||
export GOOS="$${platform%%-*}"; \
|
||||
export GOARCH="$${platform#*-}"; \
|
||||
echo export GOOS=$${GOOS}; \
|
||||
echo export GOARCH=$${GOARCH}; \
|
||||
echo go build ${GO_BUILD_FLAGS} -o "$@" ./cmd/$${command}/; \
|
||||
CGO_ENABLED=0 go build ${GO_BUILD_FLAGS} -o "$@" ./cmd/$${command}/; \
|
||||
$(SYFT) scan -q "file:$@" --source-name "$${command}" -o cyclonedx-json >"artifacts/$${command}-$${platform}.cyclonedx.json"; \
|
||||
$(SYFT) scan -q "file:$@" --source-name "$${command}" -o spdx-json >"artifacts/$${command}-$${platform}.spdx.json"
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## delete generated content
|
||||
[ ! -d artifacts ] || rm -r artifacts
|
||||
[ ! -d bin ] || rm -r bin
|
||||
[ ! -d output ] || rm -r output
|
||||
[ ! -d vendor ] || rm -r vendor
|
||||
|
||||
.PHONY: plugin-user
|
||||
plugin-user:
|
||||
mkdir -p ${HOME}/.docker/cli-plugins/
|
||||
cp docker-plugin/docker-regclient ${HOME}/.docker/cli-plugins/docker-regctl
|
||||
|
||||
.PHONY: plugin-host
|
||||
plugin-host:
|
||||
sudo cp docker-plugin/docker-regclient /usr/libexec/docker/cli-plugins/docker-regctl
|
||||
|
||||
.PHONY: util-golang-major
|
||||
util-golang-major: $(GOPATH)/bin/gomajor ## check for major dependency updates
|
||||
$(GOPATH)/bin/gomajor list
|
||||
|
||||
.PHONY: util-golang-update
|
||||
util-golang-update: ## update go module versions
|
||||
go get -u -t ./...
|
||||
go mod tidy
|
||||
[ ! -d vendor ] || go mod vendor
|
||||
|
||||
.PHONY: util-release-preview
|
||||
util-release-preview: $(GOPATH)/bin/gorelease ## preview changes for next release
|
||||
git checkout main
|
||||
./.github/release.sh -d
|
||||
gorelease
|
||||
|
||||
.PHONY: util-release-run
|
||||
util-release-run: ## generate a new release
|
||||
git checkout main
|
||||
./.github/release.sh
|
||||
|
||||
.PHONY: util-version-check
|
||||
util-version-check: ## check all dependencies for updates
|
||||
$(VER_BUMP) check
|
||||
|
||||
.PHONY: util-version-update
|
||||
util-version-update: ## update versions on all dependencies
|
||||
$(VER_BUMP) update
|
||||
|
||||
$(GOPATH)/bin/gofumpt: .FORCE
|
||||
@[ -f "$(GOPATH)/bin/gofumpt" ] \
|
||||
&& [ "$$($(GOPATH)/bin/gofumpt -version | cut -f 1 -d ' ')" = "$(GOFUMPT_VER)" ] \
|
||||
|| go install mvdan.cc/gofumpt@$(GOFUMPT_VER)
|
||||
|
||||
$(GOPATH)/bin/gomajor: .FORCE
|
||||
@[ -f "$(GOPATH)/bin/gomajor" ] \
|
||||
&& [ "$$($(GOPATH)/bin/gomajor version | grep '^version' | cut -f 2 -d ' ')" = "$(GOMAJOR_VER)" ] \
|
||||
|| go install github.com/icholy/gomajor@$(GOMAJOR_VER)
|
||||
|
||||
$(GOPATH)/bin/goimports: .FORCE
|
||||
@[ -f "$(GOPATH)/bin/goimports" ] && [ "$$(go version | cut -f3 -d' ')" = "$$(go version $(GOPATH)/bin/goimports | cut -f2 -d' ')" ] \
|
||||
|| go install golang.org/x/tools/cmd/goimports@latest
|
||||
|
||||
$(GOPATH)/bin/gorelease: .FORCE
|
||||
@[ -f "$(GOPATH)/bin/gorelease" ] && [ "$$(go version | cut -f3 -d' ')" = "$$(go version $(GOPATH)/bin/gorelease | cut -f2 -d' ')" ] \
|
||||
|| go install golang.org/x/exp/cmd/gorelease@latest
|
||||
|
||||
$(GOPATH)/bin/gosec: .FORCE
|
||||
@[ -f $(GOPATH)/bin/gosec ] \
|
||||
&& [ "$$($(GOPATH)/bin/gosec -version | grep '^Version' | cut -f 2 -d ' ')" = "$(GOSEC_VER)" ] \
|
||||
|| go install -ldflags '-X main.Version=$(GOSEC_VER) -X main.GitTag=$(GOSEC_VER)' \
|
||||
github.com/securego/gosec/v2/cmd/gosec@$(GOSEC_VER)
|
||||
|
||||
$(GOPATH)/bin/staticcheck: .FORCE
|
||||
@[ -f $(GOPATH)/bin/staticcheck ] \
|
||||
&& [ "$$($(GOPATH)/bin/staticcheck -version | cut -f 3 -d ' ' | tr -d '()')" = "$(STATICCHECK_VER)" ] \
|
||||
|| go install "honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VER)"
|
||||
|
||||
$(GOPATH)/bin/govulncheck: .FORCE
|
||||
@[ -f $(GOPATH)/bin/govulncheck ] \
|
||||
&& [ $$(go version -m $(GOPATH)/bin/govulncheck | \
|
||||
awk -F ' ' '{ if ($$1 == "mod" && $$2 == "golang.org/x/vuln") { printf "%s\n", $$3 } }') = "$(GO_VULNCHECK_VER)" ] \
|
||||
|| CGO_ENABLED=0 go install "golang.org/x/vuln/cmd/govulncheck@$(GO_VULNCHECK_VER)"
|
||||
|
||||
$(GOPATH)/bin/osv-scanner: .FORCE
|
||||
@[ -f $(GOPATH)/bin/osv-scanner ] \
|
||||
&& [ "$$(osv-scanner --version | awk -F ': ' '{ if ($$1 == "osv-scanner version") { printf "%s\n", $$2 } }')" = "$(OSV_SCANNER_VER)" ] \
|
||||
|| CGO_ENABLED=0 go install "github.com/google/osv-scanner/v2/cmd/osv-scanner@$(OSV_SCANNER_VER)"
|
||||
|
||||
.PHONY: help
|
||||
help: # Display help
|
||||
@awk -F ':|##' '/^[^\t].+?:.*?##/ { printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF }' $(MAKEFILE_LIST)
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
# regclient
|
||||
|
||||
[](https://github.com/regclient/regclient/actions/workflows/go.yml)
|
||||
[](https://github.com/regclient/regclient/actions/workflows/docker.yml)
|
||||
[](https://github.com/regclient/regclient/actions/workflows/version-check.yml)
|
||||
[](https://github.com/regclient/regclient/actions/workflows/vulnscans.yml)
|
||||
|
||||
[](https://pkg.go.dev/github.com/regclient/regclient)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/regclient/regclient)
|
||||
[](https://github.com/regclient/regclient/releases)
|
||||
|
||||
regclient is a client interface to OCI conformant registries and content shipped with the OCI Image Layout.
|
||||
It includes a Go library and several CLI commands.
|
||||
|
||||
## regclient Go Library Features
|
||||
|
||||
- Runs without a container runtime and without privileged access to the local host.
|
||||
- Querying for a tag listing, repository listing, and remotely inspecting the contents of images.
|
||||
- Efficiently copying and retagging images, only pulling layers when required, and without changing the image digest.
|
||||
- Support for multi-platform images.
|
||||
- Support for querying, creating, and copying OCI Artifacts, allowing arbitrary data to be stored in an OCI registry.
|
||||
- Support for packaging OCI Artifacts with an Index of multiple artifacts, which can be used for platform specific artifacts.
|
||||
- Support for querying OCI referrers, copying referrers, and pushing content with an OCI subject field, associating artifacts with other content on the registry.
|
||||
- Support for the “digest tags” used by projects like sigstore/cosign, allowing the content to be included when copying images.
|
||||
- Efficiently query for an image digest.
|
||||
- Efficiently query for pull rate limits used by Docker Hub.
|
||||
- Import and export content into OCI Layouts and Docker formatted tar files.
|
||||
- Support OCI Layouts in all commands as a local disk equivalent of a repository.
|
||||
- Support for deleting tags, manifests, and blobs.
|
||||
- Ability to mutate existing images, including:
|
||||
- Settings annotations or labels
|
||||
- Deleting content from layers
|
||||
- Changing timestamps for reproducibility
|
||||
- Converting between Docker and OCI media types
|
||||
- Replacing the base image layers
|
||||
- Add or remove volumes and exposed ports
|
||||
- Change digest algorithms
|
||||
- Support for registry warning headers, which may be used to notify users of issues with the server or content they are using.
|
||||
- Automatically import logins from the docker CLI, and registry certificates from the docker engine.
|
||||
- Automatic retry, and fallback to a chunked blob push, when network issues are encountered.
|
||||
|
||||
The full Go references is available on [pkg.go.dev](https://pkg.go.dev/github.com/regclient/regclient).
|
||||
|
||||
## regctl Features
|
||||
|
||||
`regctl` is a CLI interface to the `regclient` library.
|
||||
In addition to the features listed for `regclient`, `regctl` adds the following abilities:
|
||||
|
||||
- Generating multi-platform manifests from multiple images that may have been separately built.
|
||||
- Repackage a multi-platform image with only the requested platforms.
|
||||
- Push and pull arbitrary OCI artifacts.
|
||||
- Recursively list all content associated with an image.
|
||||
- Extract files from a layer or image.
|
||||
- Compare images, showing the differences between manifests, the config, and layers.
|
||||
- Formatted output using Go templates.
|
||||
|
||||
The project website includes [usage instructions](https://regclient.org/usage/regctl/) and a [CLI reference](https://regclient.org/cli/regctl/).
|
||||
|
||||
## regsync features
|
||||
|
||||
`regsync` is an image mirroring tool.
|
||||
It will copy images between two locations with the following additional features:
|
||||
|
||||
- Ability to run on a cron schedule, one time synchronization, or only report stale images.
|
||||
- Uses a yaml configuration.
|
||||
- Each source may be an entire registry (not recommended), a repository, or a single image, with the ability to filter repositories and tags.
|
||||
- Support for multi-platform images, OCI referrers, “digest tags”, and copying to or from an OCI Layout (for maintaining a mirror over an air-gap).
|
||||
- Ability to mirror multiple images concurrently.
|
||||
- Support for copying a single platform from multi-platform images.
|
||||
- Ability to backup an existing image before overwriting the tag.
|
||||
- Ability to postpone mirror step when rate limit (used by Docker Hub) is below a threshold.
|
||||
- Can use user’s docker configuration for user credentials and registry certificates.
|
||||
|
||||
The project website includes [usage instructions](https://regclient.org/usage/regsync/) and a [CLI reference](https://regclient.org/cli/regsync/).
|
||||
|
||||
## regbot features
|
||||
|
||||
`regbot` is a scripting tool on top of the `regclient` API with the following features:
|
||||
|
||||
- Ability to run on a cron schedule, one time execution, or test with a dry-run mode.
|
||||
- Uses a yaml configuration.
|
||||
- Scripts are written in Lua and executed directly in Go.
|
||||
- Built-in functions include:
|
||||
- Repository list
|
||||
- Tag list
|
||||
- Image manifest (either head or get, and optional resolving multi-platform reference)
|
||||
- Image config (this includes the creation time, labels, and other details shown in a docker image inspect)
|
||||
- Image rate limit and a wait function to delay the script when rate limit remaining is below a threshold
|
||||
- Image copy
|
||||
- Manifest delete
|
||||
- Tag delete
|
||||
|
||||
The project website includes [usage instructions](https://regclient.org/usage/regbot/) and a [CLI reference](https://regclient.org/cli/regbot/).
|
||||
|
||||
## Development Status
|
||||
|
||||
This project is using v0 version numbers due to Go's backwards compatibility requirements of a v1 release.
|
||||
The library and commands are stable for external use.
|
||||
Minor version updates may contain breaking changes, however effort is made to first deprecate and provide warnings to give users time to move off of older APIs and commands.
|
||||
|
||||
## Installing
|
||||
|
||||
See the [installation instructions](https://regclient.org/install/) on the project website for the various ways to download or build CLI binaries.
|
||||
|
||||
## Usage
|
||||
|
||||
See the [project documentation](https://regclient.org/usage/).
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/regclient/regclient/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=regclient/regclient" alt="contributor list"/>
|
||||
</a>
|
||||
|
||||
<!-- markdownlint-disable-file MD033 -->
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
# Reporting security issues
|
||||
|
||||
Please report security issues directly in GitHub at <https://github.com/regclient/regclient/security/advisories/new> or alternatively email <git@bmitch.net>.
|
||||
|
||||
We will typically respond within 7 working days of your report. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it. This project follows a 90 day disclosure timeline.
|
||||
-283
@@ -1,283 +0,0 @@
|
||||
package regclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/regclient/regclient/internal/pqueue"
|
||||
"github.com/regclient/regclient/internal/reqmeta"
|
||||
"github.com/regclient/regclient/scheme"
|
||||
"github.com/regclient/regclient/types"
|
||||
"github.com/regclient/regclient/types/blob"
|
||||
"github.com/regclient/regclient/types/descriptor"
|
||||
"github.com/regclient/regclient/types/errs"
|
||||
"github.com/regclient/regclient/types/ref"
|
||||
"github.com/regclient/regclient/types/warning"
|
||||
)
|
||||
|
||||
const blobCBFreq = time.Millisecond * 100
|
||||
|
||||
type blobOpt struct {
|
||||
callback func(kind types.CallbackKind, instance string, state types.CallbackState, cur, total int64)
|
||||
readerHook func(*blob.BReader) (*blob.BReader, error)
|
||||
}
|
||||
|
||||
// BlobOpts define options for the Image* commands.
|
||||
type BlobOpts func(*blobOpt)
|
||||
|
||||
// BlobWithCallback provides progress data to a callback function.
|
||||
func BlobWithCallback(callback func(kind types.CallbackKind, instance string, state types.CallbackState, cur, total int64)) BlobOpts {
|
||||
return func(opts *blobOpt) {
|
||||
opts.callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
// BlobWithReaderHook is called in [RegClient.BlobCopy] with the blob source.
|
||||
// The returned [blob.BReader] is pushed to the target.
|
||||
// If the hook returns an error, the copy will fail.
|
||||
func BlobWithReaderHook(hook func(*blob.BReader) (*blob.BReader, error)) BlobOpts {
|
||||
return func(opts *blobOpt) {
|
||||
opts.readerHook = hook
|
||||
}
|
||||
}
|
||||
|
||||
// BlobCopy copies a blob between two locations.
|
||||
// If the blob already exists in the target, the copy is skipped.
|
||||
// A server side cross repository blob mount is attempted.
|
||||
func (rc *RegClient) BlobCopy(ctx context.Context, refSrc ref.Ref, refTgt ref.Ref, d descriptor.Descriptor, opts ...BlobOpts) error {
|
||||
if !refSrc.IsSetRepo() {
|
||||
return fmt.Errorf("refSrc is not set: %s%.0w", refSrc.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
if !refTgt.IsSetRepo() {
|
||||
return fmt.Errorf("refTgt is not set: %s%.0w", refTgt.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
var opt blobOpt
|
||||
for _, optFn := range opts {
|
||||
optFn(&opt)
|
||||
}
|
||||
// dedup warnings
|
||||
if w := warning.FromContext(ctx); w == nil {
|
||||
ctx = warning.NewContext(ctx, &warning.Warning{Hook: warning.DefaultHook()})
|
||||
}
|
||||
tDesc := d
|
||||
tDesc.URLs = []string{} // ignore URLs when pushing to target
|
||||
if opt.callback != nil {
|
||||
opt.callback(types.CallbackBlob, d.Digest.String(), types.CallbackStarted, 0, d.Size)
|
||||
}
|
||||
// for the same repository, there's nothing to copy
|
||||
if ref.EqualRepository(refSrc, refTgt) {
|
||||
if opt.callback != nil {
|
||||
opt.callback(types.CallbackBlob, d.Digest.String(), types.CallbackSkipped, 0, d.Size)
|
||||
}
|
||||
rc.slog.Debug("Blob copy skipped, same repo",
|
||||
slog.String("src", refSrc.Reference),
|
||||
slog.String("tgt", refTgt.Reference),
|
||||
slog.String("digest", string(d.Digest)))
|
||||
return nil
|
||||
}
|
||||
// check if layer already exists
|
||||
if _, err := rc.BlobHead(ctx, refTgt, tDesc); err == nil {
|
||||
if opt.callback != nil {
|
||||
opt.callback(types.CallbackBlob, d.Digest.String(), types.CallbackSkipped, 0, d.Size)
|
||||
}
|
||||
rc.slog.Debug("Blob copy skipped, already exists",
|
||||
slog.String("src", refSrc.Reference),
|
||||
slog.String("tgt", refTgt.Reference),
|
||||
slog.String("digest", string(d.Digest)))
|
||||
return nil
|
||||
}
|
||||
// acquire throttle for both src and tgt to avoid deadlocks
|
||||
tList := []*pqueue.Queue[reqmeta.Data]{}
|
||||
schemeSrcAPI, err := rc.schemeGet(refSrc.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schemeTgtAPI, err := rc.schemeGet(refTgt.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tSrc, ok := schemeSrcAPI.(scheme.Throttler); ok {
|
||||
tList = append(tList, tSrc.Throttle(refSrc, false)...)
|
||||
}
|
||||
if tTgt, ok := schemeTgtAPI.(scheme.Throttler); ok {
|
||||
tList = append(tList, tTgt.Throttle(refTgt, true)...)
|
||||
}
|
||||
if len(tList) > 0 {
|
||||
ctxMulti, done, err := pqueue.AcquireMulti[reqmeta.Data](ctx, reqmeta.Data{Kind: reqmeta.Blob, Size: d.Size}, tList...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if done != nil {
|
||||
defer done()
|
||||
}
|
||||
ctx = ctxMulti
|
||||
}
|
||||
|
||||
// try mounting blob from the source repo is the registry is the same
|
||||
if ref.EqualRegistry(refSrc, refTgt) {
|
||||
err := rc.BlobMount(ctx, refSrc, refTgt, d)
|
||||
if err == nil {
|
||||
if opt.callback != nil {
|
||||
opt.callback(types.CallbackBlob, d.Digest.String(), types.CallbackSkipped, 0, d.Size)
|
||||
}
|
||||
rc.slog.Debug("Blob copy performed server side with registry mount",
|
||||
slog.String("src", refSrc.Reference),
|
||||
slog.String("tgt", refTgt.Reference),
|
||||
slog.String("digest", string(d.Digest)))
|
||||
return nil
|
||||
}
|
||||
rc.slog.Warn("Failed to mount blob",
|
||||
slog.String("src", refSrc.Reference),
|
||||
slog.String("tgt", refTgt.Reference),
|
||||
slog.String("err", err.Error()))
|
||||
}
|
||||
// fast options failed, download layer from source and push to target
|
||||
blobIO, err := rc.BlobGet(ctx, refSrc, d)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
rc.slog.Warn("Failed to retrieve blob",
|
||||
slog.String("src", refSrc.Reference),
|
||||
slog.String("digest", string(d.Digest)),
|
||||
slog.String("err", err.Error()))
|
||||
}
|
||||
return err
|
||||
}
|
||||
if opt.callback != nil {
|
||||
opt.callback(types.CallbackBlob, d.Digest.String(), types.CallbackStarted, 0, d.Size)
|
||||
ticker := time.NewTicker(blobCBFreq)
|
||||
done := make(chan bool)
|
||||
defer func() {
|
||||
close(done)
|
||||
ticker.Stop()
|
||||
if ctx.Err() == nil {
|
||||
opt.callback(types.CallbackBlob, d.Digest.String(), types.CallbackFinished, d.Size, d.Size)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
offset, err := blobIO.Seek(0, io.SeekCurrent)
|
||||
if err == nil && offset > 0 {
|
||||
opt.callback(types.CallbackBlob, d.Digest.String(), types.CallbackActive, offset, d.Size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
if opt.readerHook != nil {
|
||||
blobIO, err = opt.readerHook(blobIO)
|
||||
if err != nil {
|
||||
rc.slog.Warn("Failed to apply reader hook to blob",
|
||||
slog.String("src", refSrc.Reference),
|
||||
slog.String("err", err.Error()))
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer blobIO.Close()
|
||||
if _, err := rc.BlobPut(ctx, refTgt, blobIO.GetDescriptor(), blobIO); err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
rc.slog.Warn("Failed to push blob",
|
||||
slog.String("src", refSrc.Reference),
|
||||
slog.String("tgt", refTgt.Reference),
|
||||
slog.String("err", err.Error()))
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlobDelete removes a blob from the registry.
|
||||
// This method should only be used to repair a damaged registry.
|
||||
// Typically a server side garbage collection should be used to purge unused blobs.
|
||||
func (rc *RegClient) BlobDelete(ctx context.Context, r ref.Ref, d descriptor.Descriptor) error {
|
||||
if !r.IsSetRepo() {
|
||||
return fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
schemeAPI, err := rc.schemeGet(r.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return schemeAPI.BlobDelete(ctx, r, d)
|
||||
}
|
||||
|
||||
// BlobGet retrieves a blob, returning a reader.
|
||||
// This reader must be closed to free up resources that limit concurrent pulls.
|
||||
func (rc *RegClient) BlobGet(ctx context.Context, r ref.Ref, d descriptor.Descriptor) (blob.Reader, error) {
|
||||
data, err := d.GetData()
|
||||
if err == nil {
|
||||
return blob.NewReader(blob.WithDesc(d), blob.WithRef(r), blob.WithReader(bytes.NewReader(data))), nil
|
||||
}
|
||||
if !r.IsSetRepo() {
|
||||
return nil, fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
schemeAPI, err := rc.schemeGet(r.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return schemeAPI.BlobGet(ctx, r, d)
|
||||
}
|
||||
|
||||
// BlobGetOCIConfig retrieves an OCI config from a blob, automatically extracting the JSON.
|
||||
func (rc *RegClient) BlobGetOCIConfig(ctx context.Context, r ref.Ref, d descriptor.Descriptor) (blob.OCIConfig, error) {
|
||||
if !r.IsSetRepo() {
|
||||
return nil, fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
b, err := rc.BlobGet(ctx, r, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.ToOCIConfig()
|
||||
}
|
||||
|
||||
// BlobHead is used to verify if a blob exists and is accessible.
|
||||
func (rc *RegClient) BlobHead(ctx context.Context, r ref.Ref, d descriptor.Descriptor) (blob.Reader, error) {
|
||||
if !r.IsSetRepo() {
|
||||
return nil, fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
schemeAPI, err := rc.schemeGet(r.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return schemeAPI.BlobHead(ctx, r, d)
|
||||
}
|
||||
|
||||
// BlobMount attempts to perform a server side copy/mount of the blob between repositories.
|
||||
func (rc *RegClient) BlobMount(ctx context.Context, refSrc ref.Ref, refTgt ref.Ref, d descriptor.Descriptor) error {
|
||||
if !refSrc.IsSetRepo() {
|
||||
return fmt.Errorf("ref is not set: %s%.0w", refSrc.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
if !refTgt.IsSetRepo() {
|
||||
return fmt.Errorf("ref is not set: %s%.0w", refTgt.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
schemeAPI, err := rc.schemeGet(refSrc.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return schemeAPI.BlobMount(ctx, refSrc, refTgt, d)
|
||||
}
|
||||
|
||||
// BlobPut uploads a blob to a repository.
|
||||
// Descriptor is optional, leave size and digest to zero value if unknown.
|
||||
// Reader must also be an [io.Seeker] to support chunked upload fallback.
|
||||
//
|
||||
// This will attempt an anonymous blob mount first which some registries may support.
|
||||
// It will then try doing a full put of the blob without chunking (most widely supported).
|
||||
// If the full put fails, it will fall back to a chunked upload (useful for flaky networks).
|
||||
func (rc *RegClient) BlobPut(ctx context.Context, r ref.Ref, d descriptor.Descriptor, rdr io.Reader) (descriptor.Descriptor, error) {
|
||||
if !r.IsSetRepo() {
|
||||
return descriptor.Descriptor{}, fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
|
||||
}
|
||||
schemeAPI, err := rc.schemeGet(r.Scheme)
|
||||
if err != nil {
|
||||
return descriptor.Descriptor{}, err
|
||||
}
|
||||
return schemeAPI.BlobPut(ctx, r, d, rdr)
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// credHelper wraps a command that manages user credentials.
|
||||
type credHelper struct {
|
||||
prog string
|
||||
env map[string]string
|
||||
}
|
||||
|
||||
func newCredHelper(prog string, env map[string]string) *credHelper {
|
||||
return &credHelper{prog: prog, env: env}
|
||||
}
|
||||
|
||||
func (ch *credHelper) run(arg string, input io.Reader) ([]byte, error) {
|
||||
//#nosec G204 only untrusted arg is a hostname which the executed command should not trust
|
||||
cmd := exec.Command(ch.prog, arg)
|
||||
cmd.Env = os.Environ()
|
||||
if ch.env != nil {
|
||||
for k, v := range ch.env {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = input
|
||||
return cmd.Output()
|
||||
}
|
||||
|
||||
type credStore struct {
|
||||
ServerURL string `json:"ServerURL"`
|
||||
Username string `json:"Username"`
|
||||
Secret string `json:"Secret"` //#nosec G117 exported struct intentionally holds secrets
|
||||
}
|
||||
|
||||
// get requests a credential from the helper for a given host.
|
||||
func (ch *credHelper) get(host *Host) error {
|
||||
hostname := host.Hostname
|
||||
if host.CredHost != "" {
|
||||
hostname = host.CredHost
|
||||
}
|
||||
hostIn := strings.NewReader(hostname)
|
||||
credOut := credStore{
|
||||
Username: host.User,
|
||||
Secret: host.Pass,
|
||||
}
|
||||
outB, err := ch.run("get", hostIn)
|
||||
if err != nil {
|
||||
outS := strings.TrimSpace(string(outB))
|
||||
return fmt.Errorf("error getting credentials, output: %s, error: %w", outS, err)
|
||||
}
|
||||
err = json.NewDecoder(bytes.NewReader(outB)).Decode(&credOut)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading credentials: %w", err)
|
||||
}
|
||||
if credOut.Username == tokenUser {
|
||||
host.User = ""
|
||||
host.Pass = ""
|
||||
host.Token = credOut.Secret
|
||||
} else {
|
||||
host.User = credOut.Username
|
||||
host.Pass = credOut.Secret
|
||||
host.Token = ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// list returns a list of hosts supported by the credential helper.
|
||||
func (ch *credHelper) list() ([]Host, error) {
|
||||
credList := map[string]string{}
|
||||
outB, err := ch.run("list", bytes.NewReader([]byte{}))
|
||||
if err != nil {
|
||||
outS := strings.TrimSpace(string(outB))
|
||||
return nil, fmt.Errorf("error getting credential list, output: %s, error: %w", outS, err)
|
||||
}
|
||||
err = json.NewDecoder(bytes.NewReader(outB)).Decode(&credList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading credential list: %w", err)
|
||||
}
|
||||
hostList := []Host{}
|
||||
for host, user := range credList {
|
||||
if !HostValidate(host) {
|
||||
continue
|
||||
}
|
||||
h := HostNewName(host)
|
||||
h.User = user
|
||||
h.CredHelper = ch.prog
|
||||
hostList = append(hostList, *h)
|
||||
}
|
||||
return hostList, nil
|
||||
}
|
||||
|
||||
// TODO: store method not implemented
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/regclient/regclient/internal/conffile"
|
||||
"github.com/regclient/regclient/types/errs"
|
||||
)
|
||||
|
||||
const (
|
||||
// dockerEnv is the environment variable used to look for Docker's config.json.
|
||||
dockerEnv = "DOCKER_CONFIG"
|
||||
// dockerEnvConfig is used to inject the config as an environment variable.
|
||||
dockerEnvConfig = "DOCKER_AUTH_CONFIG"
|
||||
// dockerDir is the directory name for Docker's config (inside the users home directory).
|
||||
dockerDir = ".docker"
|
||||
// dockerConfFile is the name of Docker's config file.
|
||||
dockerConfFile = "config.json"
|
||||
// dockerHelperPre is the prefix of docker credential helpers.
|
||||
dockerHelperPre = "docker-credential-"
|
||||
)
|
||||
|
||||
// dockerConfig is used to parse the ~/.docker/config.json
|
||||
type dockerConfig struct {
|
||||
AuthConfigs map[string]dockerAuthConfig `json:"auths"`
|
||||
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||
DetachKeys string `json:"detachKeys,omitempty"`
|
||||
CredentialsStore string `json:"credsStore,omitempty"`
|
||||
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||
Proxies map[string]dockerProxyConfig `json:"proxies,omitempty"`
|
||||
}
|
||||
|
||||
// dockerProxyConfig contains proxy configuration settings
|
||||
type dockerProxyConfig struct {
|
||||
HTTPProxy string `json:"httpProxy,omitempty"`
|
||||
HTTPSProxy string `json:"httpsProxy,omitempty"`
|
||||
NoProxy string `json:"noProxy,omitempty"`
|
||||
FTPProxy string `json:"ftpProxy,omitempty"`
|
||||
AllProxy string `json:"allProxy,omitempty"`
|
||||
}
|
||||
|
||||
// dockerAuthConfig contains the auths
|
||||
type dockerAuthConfig struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"` //#nosec G117 exported struct intentionally holds secrets
|
||||
Auth string `json:"auth,omitempty"`
|
||||
|
||||
ServerAddress string `json:"serveraddress,omitempty"`
|
||||
|
||||
// IdentityToken is used to authenticate the user and get
|
||||
// an access token for the registry.
|
||||
IdentityToken string `json:"identitytoken,omitempty"`
|
||||
|
||||
// RegistryToken is a bearer token to be sent to a registry
|
||||
RegistryToken string `json:"registrytoken,omitempty"`
|
||||
}
|
||||
|
||||
// DockerLoad returns a slice of hosts from the users docker config.
|
||||
// This will search for the config.json in either the DOCKER_CONFIG identified directory or the default .docker directory.
|
||||
// It also includes hosts extracted from the DOCKER_AUTH_CONFIG variable.
|
||||
// If the config file is missing and no value is injected using an environment variable, an empty list is returned.
|
||||
func DockerLoad() ([]Host, error) {
|
||||
hosts := []Host{}
|
||||
errList := []error{}
|
||||
// load from a file
|
||||
cf := conffile.New(
|
||||
conffile.WithHomeDir(dockerDir, dockerConfFile, true),
|
||||
conffile.WithEnvDir(dockerEnv, dockerConfFile),
|
||||
)
|
||||
rdr, err := cf.Open()
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
errList = append(errList, err)
|
||||
} else if err == nil {
|
||||
defer rdr.Close()
|
||||
hostsFile, err := dockerParse(rdr)
|
||||
if err != nil {
|
||||
errList = append(errList, err)
|
||||
} else {
|
||||
hosts = append(hosts, hostsFile...)
|
||||
}
|
||||
}
|
||||
// load from an env var
|
||||
hostsEnv, err := DockerLoadEnv(dockerEnvConfig)
|
||||
if err != nil && !errors.Is(err, errs.ErrNotFound) {
|
||||
errList = append(errList, err)
|
||||
} else if err == nil {
|
||||
hosts = append(hosts, hostsEnv...)
|
||||
}
|
||||
// return the concatenated result, only wrapping an error list if necessary
|
||||
if len(errList) == 1 {
|
||||
return hosts, errList[0]
|
||||
} else {
|
||||
return hosts, errors.Join(errList...)
|
||||
}
|
||||
}
|
||||
|
||||
// DockerLoadFile returns a slice of hosts from a named docker config file.
|
||||
func DockerLoadFile(fname string) ([]Host, error) {
|
||||
//#nosec G304 scoping file operations to a directory is not yet a feature of regclient.
|
||||
rdr, err := os.Open(fname)
|
||||
if err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||
return []Host{}, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
return dockerParse(rdr)
|
||||
}
|
||||
|
||||
// DockerLoadEnv returns a slice of hosts extracted from the config injected in an environment variable.
|
||||
func DockerLoadEnv(envName string) ([]Host, error) {
|
||||
envVal := os.Getenv(envName)
|
||||
if envVal == "" {
|
||||
return []Host{}, errs.ErrNotFound
|
||||
}
|
||||
return dockerParse(strings.NewReader(envVal))
|
||||
}
|
||||
|
||||
// dockerParse parses a docker config into a slice of Hosts.
|
||||
func dockerParse(rdr io.Reader) ([]Host, error) {
|
||||
dc := dockerConfig{}
|
||||
if err := json.NewDecoder(rdr).Decode(&dc); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
hosts := []Host{}
|
||||
for name, auth := range dc.AuthConfigs {
|
||||
if !HostValidate(name) {
|
||||
continue
|
||||
}
|
||||
h, err := dockerAuthToHost(name, dc, auth)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hosts = append(hosts, h)
|
||||
}
|
||||
// also include default entries for credential helpers
|
||||
for name, helper := range dc.CredentialHelpers {
|
||||
if !HostValidate(name) {
|
||||
continue
|
||||
}
|
||||
h := HostNewName(name)
|
||||
h.CredHelper = dockerHelperPre + helper
|
||||
if _, ok := dc.AuthConfigs[name]; ok {
|
||||
continue // skip fields with auth config
|
||||
}
|
||||
hosts = append(hosts, *h)
|
||||
}
|
||||
// add credStore entries
|
||||
if dc.CredentialsStore != "" {
|
||||
ch := newCredHelper(dockerHelperPre+dc.CredentialsStore, map[string]string{})
|
||||
csHosts, err := ch.list()
|
||||
if err == nil {
|
||||
hosts = append(hosts, csHosts...)
|
||||
}
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// dockerAuthToHost parses an auth entry from a docker config into a Host.
|
||||
func dockerAuthToHost(name string, conf dockerConfig, auth dockerAuthConfig) (Host, error) {
|
||||
helper := ""
|
||||
if conf.CredentialHelpers != nil && conf.CredentialHelpers[name] != "" {
|
||||
helper = dockerHelperPre + conf.CredentialHelpers[name]
|
||||
}
|
||||
// parse base64 auth into user/pass
|
||||
if auth.Auth != "" {
|
||||
var err error
|
||||
auth.Username, auth.Password, err = decodeAuth(auth.Auth)
|
||||
if err != nil {
|
||||
return Host{}, err
|
||||
}
|
||||
}
|
||||
if (auth.Username == "" || auth.Password == "") && auth.IdentityToken == "" && helper == "" {
|
||||
return Host{}, fmt.Errorf("no credentials found for %s", name)
|
||||
}
|
||||
|
||||
h := HostNewName(name)
|
||||
// ignore unknown names
|
||||
if h.Name != DockerRegistry && !strings.HasSuffix(strings.TrimSuffix(name, "/"), h.Name) {
|
||||
return Host{}, fmt.Errorf("rejecting entry with repository: %s", name)
|
||||
}
|
||||
h.User = auth.Username
|
||||
h.Pass = auth.Password
|
||||
h.Token = auth.IdentityToken
|
||||
h.CredHelper = helper
|
||||
return *h, nil
|
||||
}
|
||||
|
||||
// decodeAuth extracts a base64 encoded user:pass into the username and password.
|
||||
func decodeAuth(authStr string) (string, string, error) {
|
||||
if authStr == "" {
|
||||
return "", "", nil
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(authStr)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
userPass := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(userPass) != 2 {
|
||||
return "", "", fmt.Errorf("invalid auth configuration file")
|
||||
}
|
||||
return userPass[0], strings.Trim(userPass[1], "\x00"), nil
|
||||
}
|
||||
-521
@@ -1,521 +0,0 @@
|
||||
// Package config is used for all regclient configuration settings.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/regclient/regclient/internal/timejson"
|
||||
)
|
||||
|
||||
// TLSConf specifies whether TLS is enabled and verified for a host.
|
||||
type TLSConf int
|
||||
|
||||
const (
|
||||
// TLSUndefined indicates TLS is not passed, defaults to Enabled.
|
||||
TLSUndefined TLSConf = iota
|
||||
// TLSEnabled uses TLS (https) for the connection.
|
||||
TLSEnabled
|
||||
// TLSInsecure uses TLS but does not verify CA.
|
||||
TLSInsecure
|
||||
// TLSDisabled does not use TLS (http).
|
||||
TLSDisabled
|
||||
)
|
||||
|
||||
const (
|
||||
// DockerRegistry is the name resolved in docker images on Hub.
|
||||
DockerRegistry = "docker.io"
|
||||
// DockerRegistryAuth is the name provided in docker's config for Hub.
|
||||
DockerRegistryAuth = "https://index.docker.io/v1/"
|
||||
// DockerRegistryDNS is the host to connect to for Hub.
|
||||
DockerRegistryDNS = "registry-1.docker.io"
|
||||
// defaultExpire is the default time to expire a credential and force re-authentication.
|
||||
defaultExpire = time.Hour * 1
|
||||
// defaultCredHelperRetry is the time to refresh a credential from a failed credential helper command.
|
||||
defaultCredHelperRetry = time.Second * 5
|
||||
// defaultConcurrent is the default number of concurrent registry connections.
|
||||
defaultConcurrent = 3
|
||||
// defaultReqPerSec is the default maximum frequency to send requests to a registry.
|
||||
defaultReqPerSec = 0
|
||||
// tokenUser is the username returned by credential helpers that indicates the password is an identity token.
|
||||
tokenUser = "<token>"
|
||||
)
|
||||
|
||||
// MarshalJSON converts TLSConf to a json string using MarshalText.
|
||||
func (t TLSConf) MarshalJSON() ([]byte, error) {
|
||||
s, err := t.MarshalText()
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
return json.Marshal(string(s))
|
||||
}
|
||||
|
||||
// MarshalText converts TLSConf to a string.
|
||||
func (t TLSConf) MarshalText() ([]byte, error) {
|
||||
var s string
|
||||
switch t {
|
||||
default:
|
||||
s = ""
|
||||
case TLSEnabled:
|
||||
s = "enabled"
|
||||
case TLSInsecure:
|
||||
s = "insecure"
|
||||
case TLSDisabled:
|
||||
s = "disabled"
|
||||
}
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts TLSConf from a json string.
|
||||
func (t *TLSConf) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.UnmarshalText([]byte(s))
|
||||
}
|
||||
|
||||
// UnmarshalText converts TLSConf from a string.
|
||||
func (t *TLSConf) UnmarshalText(b []byte) error {
|
||||
switch strings.ToLower(string(b)) {
|
||||
default:
|
||||
return fmt.Errorf("unknown TLS value \"%s\"", b)
|
||||
case "":
|
||||
*t = TLSUndefined
|
||||
case "enabled":
|
||||
*t = TLSEnabled
|
||||
case "insecure":
|
||||
*t = TLSInsecure
|
||||
case "disabled":
|
||||
*t = TLSDisabled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Host defines settings for connecting to a registry.
|
||||
type Host struct {
|
||||
Name string `json:"-" yaml:"registry,omitempty"` // Name of the registry (required) (yaml configs pass this as a field, json provides this from the object key)
|
||||
TLS TLSConf `json:"tls,omitempty" yaml:"tls"` // TLS setting: enabled (default), disabled, insecure
|
||||
RegCert string `json:"regcert,omitempty" yaml:"regcert"` // public pem cert of registry
|
||||
ClientCert string `json:"clientCert,omitempty" yaml:"clientCert"` // public pem cert for client (mTLS)
|
||||
ClientKey string `json:"clientKey,omitempty" yaml:"clientKey"` //#nosec G117 private pem cert for client (mTLS)
|
||||
Hostname string `json:"hostname,omitempty" yaml:"hostname"` // hostname of registry, default is the registry name
|
||||
User string `json:"user,omitempty" yaml:"user"` // username, not used with credHelper
|
||||
Pass string `json:"pass,omitempty" yaml:"pass"` //#nosec G117 password, not used with credHelper
|
||||
Token string `json:"token,omitempty" yaml:"token"` // token, experimental for specific APIs
|
||||
CredHelper string `json:"credHelper,omitempty" yaml:"credHelper"` // credential helper command for requesting logins
|
||||
CredExpire timejson.Duration `json:"credExpire,omitempty" yaml:"credExpire"` // time until credential expires
|
||||
CredHost string `json:"credHost,omitempty" yaml:"credHost"` // used when a helper hostname doesn't match Hostname
|
||||
PathPrefix string `json:"pathPrefix,omitempty" yaml:"pathPrefix"` // used for mirrors defined within a repository namespace
|
||||
Mirrors []string `json:"mirrors,omitempty" yaml:"mirrors"` // list of other Host Names to use as mirrors
|
||||
Priority uint `json:"priority,omitempty" yaml:"priority"` // priority when sorting mirrors, higher priority attempted first
|
||||
RepoAuth bool `json:"repoAuth,omitempty" yaml:"repoAuth"` // tracks a separate auth per repo
|
||||
API string `json:"api,omitempty" yaml:"api"` // Deprecated: registry API to use
|
||||
APIOpts map[string]string `json:"apiOpts,omitempty" yaml:"apiOpts"` // options for APIs
|
||||
BlobChunk int64 `json:"blobChunk,omitempty" yaml:"blobChunk"` // size of each blob chunk
|
||||
BlobMax int64 `json:"blobMax,omitempty" yaml:"blobMax"` // threshold to switch to chunked upload, -1 to disable, 0 for regclient.blobMaxPut
|
||||
ReqPerSec float64 `json:"reqPerSec,omitempty" yaml:"reqPerSec"` // requests per second
|
||||
ReqConcurrent int64 `json:"reqConcurrent,omitempty" yaml:"reqConcurrent"` // concurrent requests, default is defaultConcurrent(3)
|
||||
Scheme string `json:"scheme,omitempty" yaml:"scheme"` // Deprecated: use TLS instead
|
||||
credRefresh time.Time `json:"-" yaml:"-"` // internal use, when to refresh credentials
|
||||
}
|
||||
|
||||
// Cred defines a user credential for accessing a registry.
|
||||
type Cred struct {
|
||||
User, Password, Token string //#nosec G117 exported struct intentionally holds secrets
|
||||
}
|
||||
|
||||
// HostNew creates a default Host entry.
|
||||
func HostNew() *Host {
|
||||
h := Host{
|
||||
TLS: TLSEnabled,
|
||||
APIOpts: map[string]string{},
|
||||
ReqConcurrent: int64(defaultConcurrent),
|
||||
ReqPerSec: float64(defaultReqPerSec),
|
||||
}
|
||||
return &h
|
||||
}
|
||||
|
||||
// HostNewName creates a default Host with a hostname.
|
||||
func HostNewName(name string) *Host {
|
||||
return HostNewDefName(nil, name)
|
||||
}
|
||||
|
||||
// HostNewDefName creates a host using provided defaults and hostname.
|
||||
func HostNewDefName(def *Host, name string) *Host {
|
||||
var h Host
|
||||
if def == nil {
|
||||
h = *HostNew()
|
||||
} else {
|
||||
h = *def
|
||||
// configure required defaults
|
||||
if h.TLS == TLSUndefined {
|
||||
h.TLS = TLSEnabled
|
||||
}
|
||||
if h.APIOpts == nil {
|
||||
h.APIOpts = map[string]string{}
|
||||
}
|
||||
if h.ReqConcurrent == 0 {
|
||||
h.ReqConcurrent = int64(defaultConcurrent)
|
||||
}
|
||||
if h.ReqPerSec == 0 {
|
||||
h.ReqPerSec = float64(defaultReqPerSec)
|
||||
}
|
||||
// copy any fields that are not passed by value
|
||||
if len(h.APIOpts) > 0 {
|
||||
orig := h.APIOpts
|
||||
h.APIOpts = map[string]string{}
|
||||
maps.Copy(h.APIOpts, orig)
|
||||
}
|
||||
if h.Mirrors != nil {
|
||||
orig := h.Mirrors
|
||||
h.Mirrors = make([]string, len(orig))
|
||||
copy(h.Mirrors, orig)
|
||||
}
|
||||
}
|
||||
// configure host
|
||||
scheme, registry, _ := parseName(name)
|
||||
if scheme == "http" {
|
||||
h.TLS = TLSDisabled
|
||||
}
|
||||
// Docker Hub is a special case
|
||||
if registry == DockerRegistry {
|
||||
h.Name = DockerRegistry
|
||||
h.Hostname = DockerRegistryDNS
|
||||
h.CredHost = DockerRegistryAuth
|
||||
return &h
|
||||
}
|
||||
h.Name = registry
|
||||
h.Hostname = registry
|
||||
if name != registry {
|
||||
h.CredHost = name
|
||||
}
|
||||
return &h
|
||||
}
|
||||
|
||||
// HostValidate returns true if the scheme is missing or a known value, and the path is not set.
|
||||
func HostValidate(name string) bool {
|
||||
scheme, _, path := parseName(name)
|
||||
return path == "" && (scheme == "https" || scheme == "http")
|
||||
}
|
||||
|
||||
// GetCred returns the credential, fetching from a credential helper if needed.
|
||||
func (host *Host) GetCred() Cred {
|
||||
// refresh from credHelper if needed
|
||||
if host.CredHelper != "" && (host.credRefresh.IsZero() || time.Now().After(host.credRefresh)) {
|
||||
host.refreshHelper()
|
||||
}
|
||||
return Cred{User: host.User, Password: host.Pass, Token: host.Token}
|
||||
}
|
||||
|
||||
func (host *Host) refreshHelper() {
|
||||
if host.CredHelper == "" {
|
||||
return
|
||||
}
|
||||
if host.CredExpire <= 0 {
|
||||
host.CredExpire = timejson.Duration(defaultExpire)
|
||||
}
|
||||
// run a cred helper, calling get method
|
||||
ch := newCredHelper(host.CredHelper, map[string]string{})
|
||||
err := ch.get(host)
|
||||
if err != nil {
|
||||
host.credRefresh = time.Now().Add(defaultCredHelperRetry)
|
||||
} else {
|
||||
host.credRefresh = time.Now().Add(time.Duration(host.CredExpire))
|
||||
}
|
||||
}
|
||||
|
||||
// IsZero returns true if the struct is set to the zero value or the result of [HostNew].
|
||||
func (host Host) IsZero() bool {
|
||||
if (host.TLS != TLSUndefined && host.TLS != TLSEnabled) ||
|
||||
host.RegCert != "" ||
|
||||
host.ClientCert != "" ||
|
||||
host.ClientKey != "" ||
|
||||
(host.Hostname != "" && host.Hostname != host.Name) ||
|
||||
host.User != "" ||
|
||||
host.Pass != "" ||
|
||||
host.Token != "" ||
|
||||
host.CredHelper != "" ||
|
||||
host.CredExpire != 0 ||
|
||||
host.CredHost != "" ||
|
||||
host.PathPrefix != "" ||
|
||||
len(host.Mirrors) != 0 ||
|
||||
host.Priority != 0 ||
|
||||
host.RepoAuth ||
|
||||
len(host.APIOpts) != 0 ||
|
||||
host.BlobChunk != 0 ||
|
||||
host.BlobMax != 0 ||
|
||||
(host.ReqPerSec != 0 && host.ReqPerSec != float64(defaultReqPerSec)) ||
|
||||
(host.ReqConcurrent != 0 && host.ReqConcurrent != int64(defaultConcurrent)) ||
|
||||
!host.credRefresh.IsZero() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Merge adds fields from a new config host entry.
|
||||
func (host *Host) Merge(newHost Host, log *slog.Logger) error {
|
||||
name := newHost.Name
|
||||
if name == "" {
|
||||
name = host.Name
|
||||
}
|
||||
if log == nil {
|
||||
log = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
|
||||
}
|
||||
|
||||
// merge the existing and new config host
|
||||
if host.Name == "" {
|
||||
// only set the name if it's not initialized, this shouldn't normally change
|
||||
host.Name = newHost.Name
|
||||
}
|
||||
|
||||
if newHost.CredHelper == "" && (newHost.Pass != "" || host.Token != "") {
|
||||
// unset existing cred helper for user/pass or token
|
||||
host.CredHelper = ""
|
||||
host.CredExpire = 0
|
||||
}
|
||||
if newHost.CredHelper != "" && newHost.User == "" && newHost.Pass == "" && newHost.Token == "" {
|
||||
// unset existing user/pass/token for cred helper
|
||||
host.User = ""
|
||||
host.Pass = ""
|
||||
host.Token = ""
|
||||
}
|
||||
|
||||
if newHost.User != "" {
|
||||
if host.User != "" && host.User != newHost.User {
|
||||
log.Warn("Changing login user for registry",
|
||||
slog.String("orig", host.User),
|
||||
slog.String("new", newHost.User),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.User = newHost.User
|
||||
}
|
||||
|
||||
if newHost.Pass != "" {
|
||||
if host.Pass != "" && host.Pass != newHost.Pass {
|
||||
log.Warn("Changing login password for registry",
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.Pass = newHost.Pass
|
||||
}
|
||||
|
||||
if newHost.Token != "" {
|
||||
if host.Token != "" && host.Token != newHost.Token {
|
||||
log.Warn("Changing login token for registry",
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.Token = newHost.Token
|
||||
}
|
||||
|
||||
if newHost.CredHelper != "" {
|
||||
if host.CredHelper != "" && host.CredHelper != newHost.CredHelper {
|
||||
log.Warn("Changing credential helper for registry",
|
||||
slog.String("host", name),
|
||||
slog.String("orig", host.CredHelper),
|
||||
slog.String("new", newHost.CredHelper))
|
||||
}
|
||||
host.CredHelper = newHost.CredHelper
|
||||
}
|
||||
|
||||
if newHost.CredExpire != 0 {
|
||||
if host.CredExpire != 0 && host.CredExpire != newHost.CredExpire {
|
||||
log.Warn("Changing credential expire for registry",
|
||||
slog.String("host", name),
|
||||
slog.Any("orig", host.CredExpire),
|
||||
slog.Any("new", newHost.CredExpire))
|
||||
}
|
||||
host.CredExpire = newHost.CredExpire
|
||||
}
|
||||
|
||||
if newHost.CredHost != "" {
|
||||
if host.CredHost != "" && host.CredHost != newHost.CredHost {
|
||||
log.Warn("Changing credential host for registry",
|
||||
slog.String("host", name),
|
||||
slog.String("orig", host.CredHost),
|
||||
slog.String("new", newHost.CredHost))
|
||||
}
|
||||
host.CredHost = newHost.CredHost
|
||||
}
|
||||
|
||||
if newHost.TLS != TLSUndefined {
|
||||
if host.TLS != TLSUndefined && host.TLS != newHost.TLS {
|
||||
tlsOrig, _ := host.TLS.MarshalText()
|
||||
tlsNew, _ := newHost.TLS.MarshalText()
|
||||
log.Warn("Changing TLS settings for registry",
|
||||
slog.String("orig", string(tlsOrig)),
|
||||
slog.String("new", string(tlsNew)),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.TLS = newHost.TLS
|
||||
}
|
||||
|
||||
if newHost.RegCert != "" {
|
||||
if host.RegCert != "" && host.RegCert != newHost.RegCert {
|
||||
log.Warn("Changing certificate settings for registry",
|
||||
slog.String("orig", host.RegCert),
|
||||
slog.String("new", newHost.RegCert),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.RegCert = newHost.RegCert
|
||||
}
|
||||
|
||||
if newHost.ClientCert != "" {
|
||||
if host.ClientCert != "" && host.ClientCert != newHost.ClientCert {
|
||||
log.Warn("Changing client certificate settings for registry",
|
||||
slog.String("orig", host.ClientCert),
|
||||
slog.String("new", newHost.ClientCert),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.ClientCert = newHost.ClientCert
|
||||
}
|
||||
|
||||
if newHost.ClientKey != "" {
|
||||
if host.ClientKey != "" && host.ClientKey != newHost.ClientKey {
|
||||
log.Warn("Changing client certificate key settings for registry",
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.ClientKey = newHost.ClientKey
|
||||
}
|
||||
|
||||
if newHost.Hostname != "" {
|
||||
if host.Hostname != "" && host.Hostname != newHost.Hostname {
|
||||
log.Warn("Changing hostname settings for registry",
|
||||
slog.String("orig", host.Hostname),
|
||||
slog.String("new", newHost.Hostname),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.Hostname = newHost.Hostname
|
||||
}
|
||||
|
||||
if newHost.PathPrefix != "" {
|
||||
newHost.PathPrefix = strings.Trim(newHost.PathPrefix, "/") // leading and trailing / are not needed
|
||||
if host.PathPrefix != "" && host.PathPrefix != newHost.PathPrefix {
|
||||
log.Warn("Changing path prefix settings for registry",
|
||||
slog.String("orig", host.PathPrefix),
|
||||
slog.String("new", newHost.PathPrefix),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.PathPrefix = newHost.PathPrefix
|
||||
}
|
||||
|
||||
if len(newHost.Mirrors) > 0 {
|
||||
if len(host.Mirrors) > 0 && !slices.Equal(host.Mirrors, newHost.Mirrors) {
|
||||
log.Warn("Changing mirror settings for registry",
|
||||
slog.Any("orig", host.Mirrors),
|
||||
slog.Any("new", newHost.Mirrors),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.Mirrors = newHost.Mirrors
|
||||
}
|
||||
|
||||
if newHost.Priority != 0 {
|
||||
if host.Priority != 0 && host.Priority != newHost.Priority {
|
||||
log.Warn("Changing priority settings for registry",
|
||||
slog.Uint64("orig", uint64(host.Priority)),
|
||||
slog.Uint64("new", uint64(newHost.Priority)),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.Priority = newHost.Priority
|
||||
}
|
||||
|
||||
if newHost.RepoAuth {
|
||||
host.RepoAuth = newHost.RepoAuth
|
||||
}
|
||||
|
||||
// TODO: eventually delete
|
||||
if newHost.API != "" {
|
||||
log.Warn("API field has been deprecated",
|
||||
slog.String("api", newHost.API),
|
||||
slog.String("host", name))
|
||||
}
|
||||
|
||||
if len(newHost.APIOpts) > 0 {
|
||||
if len(host.APIOpts) > 0 {
|
||||
merged := maps.Clone(host.APIOpts)
|
||||
for k, v := range newHost.APIOpts {
|
||||
if host.APIOpts[k] != "" && host.APIOpts[k] != v {
|
||||
log.Warn("Changing APIOpts setting for registry",
|
||||
slog.String("orig", host.APIOpts[k]),
|
||||
slog.String("new", newHost.APIOpts[k]),
|
||||
slog.String("opt", k),
|
||||
slog.String("host", name))
|
||||
}
|
||||
merged[k] = v
|
||||
}
|
||||
host.APIOpts = merged
|
||||
} else {
|
||||
host.APIOpts = newHost.APIOpts
|
||||
}
|
||||
}
|
||||
|
||||
if newHost.BlobChunk > 0 {
|
||||
if host.BlobChunk != 0 && host.BlobChunk != newHost.BlobChunk {
|
||||
log.Warn("Changing blobChunk settings for registry",
|
||||
slog.Int64("orig", host.BlobChunk),
|
||||
slog.Int64("new", newHost.BlobChunk),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.BlobChunk = newHost.BlobChunk
|
||||
}
|
||||
|
||||
if newHost.BlobMax != 0 {
|
||||
if host.BlobMax != 0 && host.BlobMax != newHost.BlobMax {
|
||||
log.Warn("Changing blobMax settings for registry",
|
||||
slog.Int64("orig", host.BlobMax),
|
||||
slog.Int64("new", newHost.BlobMax),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.BlobMax = newHost.BlobMax
|
||||
}
|
||||
|
||||
if newHost.ReqPerSec != 0 {
|
||||
if host.ReqPerSec != 0 && host.ReqPerSec != newHost.ReqPerSec {
|
||||
log.Warn("Changing reqPerSec settings for registry",
|
||||
slog.Float64("orig", host.ReqPerSec),
|
||||
slog.Float64("new", newHost.ReqPerSec),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.ReqPerSec = newHost.ReqPerSec
|
||||
}
|
||||
|
||||
if newHost.ReqConcurrent > 0 {
|
||||
if host.ReqConcurrent != 0 && host.ReqConcurrent != newHost.ReqConcurrent {
|
||||
log.Warn("Changing reqPerSec settings for registry",
|
||||
slog.Int64("orig", host.ReqConcurrent),
|
||||
slog.Int64("new", newHost.ReqConcurrent),
|
||||
slog.String("host", name))
|
||||
}
|
||||
host.ReqConcurrent = newHost.ReqConcurrent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseName splits a registry into the scheme, hostname, and repository/path.
|
||||
func parseName(name string) (string, string, string) {
|
||||
scheme := "https"
|
||||
path := ""
|
||||
// Docker Hub is a special case
|
||||
if name == DockerRegistryAuth || name == DockerRegistryDNS || name == DockerRegistry {
|
||||
return scheme, DockerRegistry, ""
|
||||
}
|
||||
// handle http/https prefix
|
||||
i := strings.Index(name, "://")
|
||||
if i > 0 {
|
||||
scheme = name[:i]
|
||||
name = name[i+3:]
|
||||
}
|
||||
// trim any repository path
|
||||
i = strings.Index(name, "/")
|
||||
if i > 0 {
|
||||
path = name[i+1:]
|
||||
name = name[:i]
|
||||
}
|
||||
return scheme, name, path
|
||||
}
|
||||
-1905
File diff suppressed because it is too large
Load Diff
-923
@@ -1,923 +0,0 @@
|
||||
// Package auth is used for HTTP authentication
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/regclient/regclient/internal/regnet"
|
||||
"github.com/regclient/regclient/types/errs"
|
||||
)
|
||||
|
||||
type charLU byte
|
||||
|
||||
var charLUs [256]charLU
|
||||
|
||||
var defaultClientID = "regclient"
|
||||
|
||||
// minTokenLife tokens are required to last at least 60 seconds to support older docker clients
|
||||
var minTokenLife = 60
|
||||
|
||||
// tokenBuffer is used to renew a token before it expires to account for time to process requests on the server
|
||||
var tokenBuffer = time.Second * 5
|
||||
|
||||
const (
|
||||
isSpace charLU = 1 << iota
|
||||
isToken
|
||||
)
|
||||
|
||||
func init() {
|
||||
for c := range 256 {
|
||||
charLUs[c] = 0
|
||||
if strings.ContainsRune(" \t\r\n", rune(c)) {
|
||||
charLUs[c] |= isSpace
|
||||
}
|
||||
if (rune('a') <= rune(c) && rune(c) <= rune('z')) || (rune('A') <= rune(c) && rune(c) <= rune('Z') || (rune('0') <= rune(c) && rune(c) <= rune('9')) || strings.ContainsRune("-._~+/", rune(c))) {
|
||||
charLUs[c] |= isToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CredsFn is passed to lookup credentials for a given hostname, response is a username and password or empty strings
|
||||
type CredsFn func(host string) Cred
|
||||
|
||||
// Cred is returned by the CredsFn.
|
||||
// If Token is provided and auth method is bearer, it will attempt to use it as a refresh token.
|
||||
// Else if user and password are provided, they are attempted with all auth methods.
|
||||
// Else if neither are provided and auth method is bearer, an anonymous login is attempted.
|
||||
type Cred struct {
|
||||
//#nosec G117 exported struct intentionally holds secrets
|
||||
User, Password string // clear text username and password
|
||||
Token string // refresh token only used for bearer auth
|
||||
}
|
||||
|
||||
// challenge is the extracted contents of the WWW-Authenticate header.
|
||||
type challenge struct {
|
||||
authType string
|
||||
params map[string]string
|
||||
}
|
||||
|
||||
// handler handles a challenge for a host to return an auth header
|
||||
type handler interface {
|
||||
AddScope(scope string) error
|
||||
ProcessChallenge(challenge) error
|
||||
UpdateRequest(*http.Request) error
|
||||
}
|
||||
|
||||
// handlerBuild is used to make a new handler for a specific authType and URL
|
||||
type handlerBuild func(client *http.Client, clientID, host string, credFn CredsFn, slog *slog.Logger) handler
|
||||
|
||||
// Opts configures options for NewAuth
|
||||
type Opts func(*Auth)
|
||||
|
||||
// Auth is used to handle authentication requests.
|
||||
type Auth struct {
|
||||
httpClient *http.Client
|
||||
clientID string
|
||||
credsFn CredsFn
|
||||
hbs map[string]handlerBuild // handler builders based on authType
|
||||
hs map[string]map[string]handler // handlers based on url and authType
|
||||
authTypes []string
|
||||
slog *slog.Logger
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewAuth creates a new Auth
|
||||
func NewAuth(opts ...Opts) *Auth {
|
||||
a := &Auth{
|
||||
httpClient: &http.Client{},
|
||||
clientID: defaultClientID,
|
||||
credsFn: DefaultCredsFn,
|
||||
hbs: map[string]handlerBuild{},
|
||||
hs: map[string]map[string]handler{},
|
||||
authTypes: []string{},
|
||||
slog: slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(a)
|
||||
}
|
||||
|
||||
if len(a.authTypes) == 0 {
|
||||
a.addDefaultHandlers()
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// WithCreds provides a user/pass lookup for a url
|
||||
func WithCreds(f CredsFn) Opts {
|
||||
return func(a *Auth) {
|
||||
if f != nil {
|
||||
a.credsFn = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithHTTPClient uses a specific http client with requests
|
||||
func WithHTTPClient(h *http.Client) Opts {
|
||||
return func(a *Auth) {
|
||||
if h != nil {
|
||||
a.httpClient = h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientID uses a client ID with request headers
|
||||
func WithClientID(clientID string) Opts {
|
||||
return func(a *Auth) {
|
||||
a.clientID = clientID
|
||||
}
|
||||
}
|
||||
|
||||
// WithHandler includes a handler for a specific auth type
|
||||
func WithHandler(authType string, hb handlerBuild) Opts {
|
||||
return func(a *Auth) {
|
||||
lcat := strings.ToLower(authType)
|
||||
a.hbs[lcat] = hb
|
||||
a.authTypes = append(a.authTypes, lcat)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultHandlers includes a Basic and Bearer handler, this is automatically added with "WithHandler" is not called
|
||||
func WithDefaultHandlers() Opts {
|
||||
return func(a *Auth) {
|
||||
a.addDefaultHandlers()
|
||||
}
|
||||
}
|
||||
|
||||
// WithLog injects a Logger
|
||||
func WithLog(slog *slog.Logger) Opts {
|
||||
return func(a *Auth) {
|
||||
a.slog = slog
|
||||
}
|
||||
}
|
||||
|
||||
// AddScope extends an existing auth with additional scopes.
|
||||
// This is used to pre-populate scopes with the Docker convention rather than
|
||||
// depend on the registry to respond with the correct http status and headers.
|
||||
func (a *Auth) AddScope(host, scope string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
success := false
|
||||
if a.hs[host] == nil {
|
||||
return errs.ErrNoNewChallenge
|
||||
}
|
||||
for _, at := range a.authTypes {
|
||||
if a.hs[host][at] != nil {
|
||||
err := a.hs[host][at].AddScope(scope)
|
||||
if err == nil {
|
||||
success = true
|
||||
} else if err != errs.ErrNoNewChallenge {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
return errs.ErrNoNewChallenge
|
||||
}
|
||||
a.slog.Debug("Auth scope added",
|
||||
slog.String("host", host),
|
||||
slog.String("scope", scope))
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleResponse parses the 401 response, extracting the WWW-Authenticate
|
||||
// header and verifying the requirement is different from what was included in
|
||||
// the last request
|
||||
func (a *Auth) HandleResponse(resp *http.Response) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// verify response is an access denied
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
return errs.ErrUnsupported
|
||||
}
|
||||
|
||||
// extract host and auth header
|
||||
host := resp.Request.URL.Host
|
||||
cl, err := ParseAuthHeaders(resp.Header.Values("WWW-Authenticate"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.slog.Debug("Auth request parsed",
|
||||
slog.Any("challenge", cl))
|
||||
if len(cl) < 1 {
|
||||
return errs.ErrEmptyChallenge
|
||||
}
|
||||
goodChallenge := false
|
||||
// loop over the received challenge(s)
|
||||
for _, c := range cl {
|
||||
if _, ok := a.hbs[c.authType]; !ok {
|
||||
a.slog.Warn("Unsupported auth type",
|
||||
slog.String("authtype", c.authType))
|
||||
continue
|
||||
}
|
||||
// setup a handler for the host and auth type
|
||||
if _, ok := a.hs[host]; !ok {
|
||||
a.hs[host] = map[string]handler{}
|
||||
}
|
||||
if _, ok := a.hs[host][c.authType]; !ok {
|
||||
h := a.hbs[c.authType](a.httpClient, a.clientID, host, a.credsFn, a.slog)
|
||||
if h == nil {
|
||||
continue
|
||||
}
|
||||
a.hs[host][c.authType] = h
|
||||
}
|
||||
// process the challenge with that handler
|
||||
err := a.hs[host][c.authType].ProcessChallenge(c)
|
||||
if err == nil {
|
||||
goodChallenge = true
|
||||
} else if err == errs.ErrNoNewChallenge {
|
||||
// handle race condition when another request updates the challenge
|
||||
// detect that by seeing the current auth header is different
|
||||
prevAH := resp.Request.Header.Get("Authorization")
|
||||
err := a.hs[host][c.authType].UpdateRequest(resp.Request)
|
||||
if err == nil && prevAH != resp.Request.Header.Get("Authorization") {
|
||||
goodChallenge = true
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !goodChallenge {
|
||||
return errs.ErrHTTPUnauthorized
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRequest adds Authorization headers to a request
|
||||
func (a *Auth) UpdateRequest(req *http.Request) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
host := req.URL.Host
|
||||
if a.hs[host] == nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
for _, at := range a.authTypes {
|
||||
if a.hs[host][at] != nil {
|
||||
err = a.hs[host][at].UpdateRequest(req)
|
||||
if err != nil {
|
||||
a.slog.Debug("Failed to generate auth",
|
||||
slog.String("err", err.Error()),
|
||||
slog.String("host", host),
|
||||
slog.String("authtype", at))
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Auth) addDefaultHandlers() {
|
||||
if _, ok := a.hbs["basic"]; !ok {
|
||||
a.hbs["basic"] = NewBasicHandler
|
||||
a.authTypes = append(a.authTypes, "basic")
|
||||
}
|
||||
if _, ok := a.hbs["bearer"]; !ok {
|
||||
a.hbs["bearer"] = NewBearerHandler
|
||||
a.authTypes = append(a.authTypes, "bearer")
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultCredsFn is used to return no credentials when auth is not configured with a CredsFn
|
||||
// This avoids the need to check for nil pointers
|
||||
func DefaultCredsFn(h string) Cred {
|
||||
return Cred{}
|
||||
}
|
||||
|
||||
// ParseAuthHeaders extracts the scheme and realm from WWW-Authenticate headers
|
||||
func ParseAuthHeaders(ahl []string) ([]challenge, error) {
|
||||
var cl []challenge
|
||||
for _, ah := range ahl {
|
||||
c, err := parseAuthHeader(ah)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse challenge header: %s, %w", ah, err)
|
||||
}
|
||||
cl = append(cl, c...)
|
||||
}
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// parseAuthHeader parses a single header line for WWW-Authenticate
|
||||
// Example values:
|
||||
// Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull,push"
|
||||
// Basic realm="GitHub Package Registry"
|
||||
func parseAuthHeader(ah string) ([]challenge, error) {
|
||||
var cl []challenge
|
||||
var c *challenge
|
||||
curElement := []byte{}
|
||||
curKey := ""
|
||||
stateElement := "string"
|
||||
stateSyntax := "start"
|
||||
|
||||
for _, b := range []byte(ah) {
|
||||
switch stateElement {
|
||||
case "string":
|
||||
// string: ignore leading space, enter quote only if first character, handle escapes, handle valid tokens, else end
|
||||
if charLUs[b]&isToken != 0 {
|
||||
// add valid tokens to element
|
||||
curElement = append(curElement, b)
|
||||
} else if charLUs[b]&isSpace != 0 && len(curElement) == 0 {
|
||||
// ignore leading spaces
|
||||
} else if b == '"' && len(curElement) == 0 {
|
||||
stateElement = "quote"
|
||||
} else if b == '\\' {
|
||||
stateElement = "escape_string"
|
||||
} else {
|
||||
stateElement = "end"
|
||||
}
|
||||
case "quote":
|
||||
// quote: handle escapes, handle closing quote (quote_end to read next character), all other tokens are valid
|
||||
if b == '\\' {
|
||||
stateElement = "escape_quote"
|
||||
} else if b == '"' {
|
||||
stateElement = "quote_end"
|
||||
} else {
|
||||
curElement = append(curElement, b)
|
||||
}
|
||||
case "quote_end":
|
||||
// any character after the close quote is the end of the element
|
||||
stateElement = "end"
|
||||
case "escape_string":
|
||||
// escape_string: handle any character and return to string state
|
||||
curElement = append(curElement, b)
|
||||
stateElement = "string"
|
||||
case "escape_quote":
|
||||
// escape_quote: handle any character and return to quote state
|
||||
curElement = append(curElement, b)
|
||||
stateElement = "quote"
|
||||
case "end":
|
||||
// finished parsing element, continue to processing element according to the current state
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled element case: %w", errs.ErrParsingFailed)
|
||||
}
|
||||
if stateElement != "end" {
|
||||
// continue parsing the element until it ends
|
||||
continue
|
||||
}
|
||||
|
||||
// syntax looks at each string within the overall challenge syntax
|
||||
switch stateSyntax {
|
||||
case "start":
|
||||
// start: (start of auth_type) read auth_type and space (end_auth_type) or auth_type and comma (start)
|
||||
if charLUs[b]&isSpace != 0 && len(curElement) > 0 {
|
||||
stateSyntax = "end_auth_type"
|
||||
} else if b == ',' && len(curElement) > 0 {
|
||||
// state remains at start
|
||||
} else {
|
||||
return nil, fmt.Errorf("start element did not end with a space or comma: %w", errs.ErrParsingFailed)
|
||||
}
|
||||
c = &challenge{authType: strings.ToLower(string(curElement)), params: map[string]string{}}
|
||||
cl = append(cl, *c)
|
||||
case "start_or_param":
|
||||
// start_or_param: (after param_value) read auth_type and space (end_auth_type) or param_key and equals (param_value)
|
||||
if charLUs[b]&isSpace != 0 && len(curElement) > 0 {
|
||||
c = &challenge{authType: strings.ToLower(string(curElement)), params: map[string]string{}}
|
||||
cl = append(cl, *c)
|
||||
stateSyntax = "end_auth_type"
|
||||
} else if b == '=' && len(curElement) > 0 {
|
||||
curKey = strings.ToLower(string(curElement))
|
||||
stateSyntax = "param_value"
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected auth type or param: %w", errs.ErrParsingFailed)
|
||||
}
|
||||
case "end_auth_type":
|
||||
// end_auth_type: (after reading auth_type) read param_key and equals (param_value) or just a comma (start)
|
||||
if b == '=' && len(curElement) > 0 {
|
||||
curKey = strings.ToLower(string(curElement))
|
||||
stateSyntax = "param_value"
|
||||
} else if b == ',' && len(curElement) == 0 {
|
||||
// ignore white space between end of auth_type and comma
|
||||
stateSyntax = "start"
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected param or comma: %w", errs.ErrParsingFailed)
|
||||
}
|
||||
case "param_value":
|
||||
// param_value: (after param_key) read param_value and comma (start_or_param)
|
||||
if b == ',' {
|
||||
c.params[curKey] = string(curElement)
|
||||
stateSyntax = "start_or_param"
|
||||
curKey = ""
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected param value: %w", errs.ErrParsingFailed)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled syntax case: %w", errs.ErrParsingFailed)
|
||||
}
|
||||
// reset element state
|
||||
stateElement = "string"
|
||||
curElement = []byte{}
|
||||
}
|
||||
// at end of parsing, if the element is not empty, process according to syntax state:
|
||||
if len(curElement) > 0 {
|
||||
// ensure this is not within an unclosed quote or partial escape
|
||||
if stateElement != "string" && stateElement != "quote_end" {
|
||||
return nil, fmt.Errorf("eol element in state %s: %w", stateElement, errs.ErrParsingFailed)
|
||||
}
|
||||
switch stateSyntax {
|
||||
case "start", "start_or_param":
|
||||
// add a new auth type if a string is seen at the start, before any equals
|
||||
c = &challenge{authType: strings.ToLower(string(curElement)), params: map[string]string{}}
|
||||
cl = append(cl, *c)
|
||||
case "param_value":
|
||||
// add the last param key=val
|
||||
c.params[curKey] = string(curElement)
|
||||
case "end_auth_type":
|
||||
// missing equals for param
|
||||
return nil, fmt.Errorf("eol at param without value: %w", errs.ErrParsingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// basicHandler supports Basic auth type requests
|
||||
type basicHandler struct {
|
||||
realm string
|
||||
host string
|
||||
credsFn CredsFn
|
||||
}
|
||||
|
||||
// NewBasicHandler creates a new BasicHandler
|
||||
func NewBasicHandler(client *http.Client, clientID, host string, credsFn CredsFn, slog *slog.Logger) handler {
|
||||
return &basicHandler{
|
||||
realm: "",
|
||||
host: host,
|
||||
credsFn: credsFn,
|
||||
}
|
||||
}
|
||||
|
||||
// AddScope is not valid for BasicHandler
|
||||
func (b *basicHandler) AddScope(scope string) error {
|
||||
return errs.ErrNoNewChallenge
|
||||
}
|
||||
|
||||
// ProcessChallenge for BasicHandler is a noop
|
||||
func (b *basicHandler) ProcessChallenge(c challenge) error {
|
||||
if _, ok := c.params["realm"]; !ok {
|
||||
return errs.ErrInvalidChallenge
|
||||
}
|
||||
if b.realm != c.params["realm"] {
|
||||
b.realm = c.params["realm"]
|
||||
return nil
|
||||
}
|
||||
return errs.ErrNoNewChallenge
|
||||
}
|
||||
|
||||
// UpdateRequest for BasicHandler generates base64 encoded user/pass for a host
|
||||
func (b *basicHandler) UpdateRequest(req *http.Request) error {
|
||||
cred := b.credsFn(b.host)
|
||||
if cred.User == "" || cred.Password == "" {
|
||||
return fmt.Errorf("no credentials available: %w", errs.ErrHTTPUnauthorized)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Basic %s",
|
||||
base64.StdEncoding.EncodeToString([]byte(cred.User+":"+cred.Password))))
|
||||
return nil
|
||||
}
|
||||
|
||||
// bearerHandler supports Bearer auth type requests
|
||||
type bearerHandler struct {
|
||||
client *http.Client
|
||||
clientID string
|
||||
realm, service string
|
||||
host string
|
||||
credsFn CredsFn
|
||||
scopes []string
|
||||
tokenURL *url.URL
|
||||
token bearerToken
|
||||
slog *slog.Logger
|
||||
}
|
||||
|
||||
// bearerToken is the json response to the Bearer request
|
||||
type bearerToken struct {
|
||||
Token string `json:"token"`
|
||||
AccessToken string `json:"access_token"` //#nosec G117 exported struct intentionally holds secrets
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
RefreshToken string `json:"refresh_token"` //#nosec G117 exported struct intentionally holds secrets
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
// NewBearerHandler creates a new BearerHandler
|
||||
func NewBearerHandler(client *http.Client, clientID, host string, credsFn CredsFn, slog *slog.Logger) handler {
|
||||
return &bearerHandler{
|
||||
client: client,
|
||||
clientID: clientID,
|
||||
host: host,
|
||||
credsFn: credsFn,
|
||||
realm: "",
|
||||
service: "",
|
||||
scopes: []string{},
|
||||
slog: slog,
|
||||
}
|
||||
}
|
||||
|
||||
// AddScope appends a new scope if it doesn't already exist
|
||||
func (b *bearerHandler) AddScope(scope string) error {
|
||||
if b.scopeExists(scope) {
|
||||
if b.token.Token == "" || !b.isExpired() {
|
||||
return errs.ErrNoNewChallenge
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b.addScope(scope)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bearerHandler) addScope(scope string) {
|
||||
if !b.tryExtendExistingScope(scope) {
|
||||
b.scopes = append(b.scopes, scope)
|
||||
}
|
||||
// delete old token
|
||||
b.token.Token = ""
|
||||
}
|
||||
|
||||
var knownActions = []string{"pull", "push", "delete"}
|
||||
|
||||
// tryExtendExistingScope extends an existing scope if both the new scope and the current scope contain only knownActions.
|
||||
// It returns true if actions are added or are already present. Otherwise, it returns false,
|
||||
// indicating that the new scope should be appended to b.scopes instead.
|
||||
func (b *bearerHandler) tryExtendExistingScope(scope string) bool {
|
||||
repo, actions, ok := parseScope(scope)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
scopePrefix := "repository:" + repo + ":"
|
||||
for i, cur := range b.scopes {
|
||||
if !strings.HasPrefix(cur, scopePrefix) {
|
||||
continue
|
||||
}
|
||||
_, curActions, curOk := parseScope(cur)
|
||||
if !curOk {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, a := range actions {
|
||||
if !slices.Contains(curActions, a) {
|
||||
curActions = append(curActions, a)
|
||||
}
|
||||
}
|
||||
b.scopes[i] = scopePrefix + strings.Join(curActions, ",")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseScope splits a scope into the repo and slice of actions.
|
||||
// Unknown actions in the scope will set bool to false.
|
||||
func parseScope(scope string) (string, []string, bool) {
|
||||
scopeSplit := strings.SplitN(scope, ":", 3)
|
||||
if scopeSplit[0] != "repository" || len(scopeSplit) < 3 {
|
||||
return "", nil, false
|
||||
}
|
||||
actionSplit := strings.Split(scopeSplit[2], ",")
|
||||
for _, a := range actionSplit {
|
||||
if !slices.Contains(knownActions, a) {
|
||||
return "", nil, false
|
||||
}
|
||||
}
|
||||
return scopeSplit[1], actionSplit, true
|
||||
}
|
||||
|
||||
// ProcessChallenge handles WWW-Authenticate header for bearer tokens
|
||||
// Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull,push"
|
||||
func (b *bearerHandler) ProcessChallenge(c challenge) error {
|
||||
if _, ok := c.params["realm"]; !ok {
|
||||
return errs.ErrInvalidChallenge
|
||||
}
|
||||
if _, ok := c.params["service"]; !ok {
|
||||
c.params["service"] = ""
|
||||
}
|
||||
if _, ok := c.params["scope"]; !ok {
|
||||
c.params["scope"] = ""
|
||||
}
|
||||
|
||||
existingScope := b.scopeExists(c.params["scope"])
|
||||
|
||||
if b.realm == c.params["realm"] && b.service == c.params["service"] && existingScope && (b.token.Token == "" || !b.isExpired()) {
|
||||
return errs.ErrNoNewChallenge
|
||||
}
|
||||
|
||||
if b.realm == "" {
|
||||
b.realm = c.params["realm"]
|
||||
} else if b.realm != c.params["realm"] {
|
||||
return errs.ErrInvalidChallenge
|
||||
}
|
||||
if b.service == "" {
|
||||
b.service = c.params["service"]
|
||||
} else if b.service != c.params["service"] {
|
||||
return errs.ErrInvalidChallenge
|
||||
}
|
||||
if !existingScope {
|
||||
b.addScope(c.params["scope"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRequest for BearerHandler adds a bearer token to the request.
|
||||
func (b *bearerHandler) UpdateRequest(req *http.Request) error {
|
||||
// handle relative realm values
|
||||
if b.tokenURL == nil {
|
||||
u, err := req.URL.Parse(b.realm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tokenURL = u
|
||||
}
|
||||
// verify tokenURL is allowed for request URL
|
||||
if err := regnet.AllowRedirect(*req.URL, *b.tokenURL); err != nil {
|
||||
return err
|
||||
}
|
||||
// if unexpired token already exists, return it
|
||||
if b.token.Token != "" && !b.isExpired() {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", b.token.Token))
|
||||
return nil
|
||||
}
|
||||
// attempt to post if a refresh token is available or token auth is being used
|
||||
cred := b.credsFn(b.host)
|
||||
if b.token.RefreshToken != "" || cred.Token != "" {
|
||||
if err := b.tryPost(cred); err == nil {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", b.token.Token))
|
||||
return nil
|
||||
} else if err != errs.ErrHTTPUnauthorized {
|
||||
return fmt.Errorf("failed to request auth token (post): %w%.0w", err, errs.ErrHTTPUnauthorized)
|
||||
}
|
||||
}
|
||||
// attempt a get (with basic auth if user/pass available)
|
||||
if err := b.tryGet(cred); err == nil {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", b.token.Token))
|
||||
return nil
|
||||
} else if err != errs.ErrHTTPUnauthorized {
|
||||
return fmt.Errorf("failed to request auth token (get): %w%.0w", err, errs.ErrHTTPUnauthorized)
|
||||
}
|
||||
return errs.ErrHTTPUnauthorized
|
||||
}
|
||||
|
||||
// isExpired returns true when token issue date is either 0, token has expired,
|
||||
// or will expire within buffer time
|
||||
func (b *bearerHandler) isExpired() bool {
|
||||
if b.token.IssuedAt.IsZero() {
|
||||
return true
|
||||
}
|
||||
expireSec := b.token.IssuedAt.Add(time.Duration(b.token.ExpiresIn) * time.Second)
|
||||
expireSec = expireSec.Add(tokenBuffer * -1)
|
||||
return time.Now().After(expireSec)
|
||||
}
|
||||
|
||||
// tryGet requests a new token with a GET request
|
||||
func (b *bearerHandler) tryGet(cred Cred) error {
|
||||
//#nosec G704 inputs follow specification
|
||||
req, err := http.NewRequest("GET", b.tokenURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqParams := req.URL.Query()
|
||||
reqParams.Add("client_id", b.clientID)
|
||||
// Note, an offline_token should not be requested by default due to broken OAuth2 implementations returning an invalid token
|
||||
if b.service != "" {
|
||||
reqParams.Add("service", b.service)
|
||||
}
|
||||
|
||||
for _, s := range b.scopes {
|
||||
reqParams.Add("scope", s)
|
||||
}
|
||||
|
||||
if cred.User != "" && cred.Password != "" {
|
||||
reqParams.Add("account", cred.User)
|
||||
req.SetBasicAuth(cred.User, cred.Password)
|
||||
}
|
||||
|
||||
req.Header.Add("User-Agent", b.clientID)
|
||||
req.URL.RawQuery = reqParams.Encode()
|
||||
|
||||
//#nosec G704 inputs follow specification
|
||||
resp, err := b.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return b.validateResponse(resp)
|
||||
}
|
||||
|
||||
// tryPost requests a new token via a POST request
|
||||
func (b *bearerHandler) tryPost(cred Cred) error {
|
||||
form := url.Values{}
|
||||
if len(b.scopes) > 0 {
|
||||
form.Set("scope", strings.Join(b.scopes, " "))
|
||||
}
|
||||
if b.service != "" {
|
||||
form.Set("service", b.service)
|
||||
}
|
||||
form.Set("client_id", b.clientID)
|
||||
if b.token.RefreshToken != "" {
|
||||
form.Set("grant_type", "refresh_token")
|
||||
form.Set("refresh_token", b.token.RefreshToken)
|
||||
} else if cred.Token != "" {
|
||||
form.Set("grant_type", "refresh_token")
|
||||
form.Set("refresh_token", cred.Token)
|
||||
} else if cred.User != "" && cred.Password != "" {
|
||||
form.Set("grant_type", "password")
|
||||
form.Set("username", cred.User)
|
||||
form.Set("password", cred.Password)
|
||||
}
|
||||
|
||||
//#nosec G704 inputs are user controlled or follow specification
|
||||
req, err := http.NewRequest("POST", b.tokenURL.String(), strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
req.Header.Add("User-Agent", b.clientID)
|
||||
|
||||
//#nosec G704 inputs are user controlled or follow specification
|
||||
resp, err := b.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return b.validateResponse(resp)
|
||||
}
|
||||
|
||||
// scopeExists check if the scope already exists within the list of scopes
|
||||
func (b *bearerHandler) scopeExists(search string) bool {
|
||||
if search == "" {
|
||||
return true
|
||||
}
|
||||
searchRepo, searchActions, searchOk := parseScope(search)
|
||||
if !searchOk {
|
||||
return slices.Contains(b.scopes, search)
|
||||
}
|
||||
scopePrefix := "repository:" + searchRepo + ":"
|
||||
for _, scope := range b.scopes {
|
||||
if scope == search {
|
||||
return true
|
||||
}
|
||||
if !strings.HasPrefix(scope, scopePrefix) {
|
||||
continue
|
||||
}
|
||||
_, actions, ok := parseScope(scope)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, sa := range searchActions {
|
||||
if !slices.Contains(actions, sa) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateResponse extracts the returned token
|
||||
func (b *bearerHandler) validateResponse(resp *http.Response) error {
|
||||
if resp.StatusCode != 200 {
|
||||
return errs.ErrHTTPUnauthorized
|
||||
}
|
||||
|
||||
// decode response and if successful, update token
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
decoded := bearerToken{}
|
||||
if err := decoder.Decode(&decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
b.token = decoded
|
||||
|
||||
if b.token.ExpiresIn < minTokenLife {
|
||||
b.token.ExpiresIn = minTokenLife
|
||||
}
|
||||
|
||||
// If token is already expired, it was sent with a zero value or
|
||||
// there may be a clock skew between the client and auth server.
|
||||
// Also handle cases of remote time in the future.
|
||||
// But if remote time is slightly in the past, leave as is so token
|
||||
// expires here before the server.
|
||||
if b.isExpired() || b.token.IssuedAt.After(time.Now()) {
|
||||
b.token.IssuedAt = time.Now().UTC()
|
||||
}
|
||||
|
||||
// AccessToken and Token should be the same and we use Token elsewhere
|
||||
if b.token.AccessToken != "" {
|
||||
b.token.Token = b.token.AccessToken
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// jwtHubHandler supports JWT auth type requests.
|
||||
type jwtHubHandler struct {
|
||||
client *http.Client
|
||||
clientID string
|
||||
realm string
|
||||
host string
|
||||
credsFn CredsFn
|
||||
jwt string
|
||||
}
|
||||
|
||||
type jwtHubPost struct {
|
||||
User string `json:"username"`
|
||||
Pass string `json:"password"` //#nosec G117 exported struct intentionally holds secrets
|
||||
}
|
||||
type jwtHubResp struct {
|
||||
Detail string `json:"detail"`
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"` //#nosec G117 exported struct intentionally holds secrets
|
||||
}
|
||||
|
||||
// NewJWTHubHandler creates a new JWTHandler for Docker Hub.
|
||||
func NewJWTHubHandler(client *http.Client, clientID, host string, credsFn CredsFn, slog *slog.Logger) handler {
|
||||
// JWT handler is only tested against Hub, and the API is Hub specific
|
||||
if host == "hub.docker.com" {
|
||||
return &jwtHubHandler{
|
||||
client: client,
|
||||
clientID: clientID,
|
||||
host: host,
|
||||
credsFn: credsFn,
|
||||
realm: "https://hub.docker.com/v2/users/login",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddScope is not valid for JWTHubHandler
|
||||
func (j *jwtHubHandler) AddScope(scope string) error {
|
||||
return errs.ErrNoNewChallenge
|
||||
}
|
||||
|
||||
// ProcessChallenge handles WWW-Authenticate header for JWT auth on Docker Hub
|
||||
func (j *jwtHubHandler) ProcessChallenge(c challenge) error {
|
||||
cred := j.credsFn(j.host)
|
||||
// use token if provided
|
||||
if cred.Token != "" {
|
||||
j.jwt = cred.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
// send a login request to hub
|
||||
bodyBytes, err := json.Marshal(jwtHubPost{
|
||||
User: cred.User,
|
||||
Pass: cred.Password, //#nosec G117 field name follows spec
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", j.realm, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("User-Agent", j.clientID)
|
||||
|
||||
//#nosec G704 inputs are user controlled or follow specification requirements
|
||||
resp, err := j.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != 200 || resp.StatusCode >= 300 {
|
||||
return errs.ErrHTTPUnauthorized
|
||||
}
|
||||
|
||||
var bodyParsed jwtHubResp
|
||||
err = json.Unmarshal(body, &bodyParsed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j.jwt = bodyParsed.Token
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRequest for JWTHubHandler adds JWT header
|
||||
func (j *jwtHubHandler) UpdateRequest(req *http.Request) error {
|
||||
if len(j.jwt) > 0 {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("JWT %s", j.jwt))
|
||||
return nil
|
||||
}
|
||||
return errs.ErrHTTPUnauthorized
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/regclient/regclient/types/errs"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyChallenge indicates an issue with the received challenge in the WWW-Authenticate header
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrEmptyChallenge].
|
||||
//go:fix inline
|
||||
ErrEmptyChallenge = errs.ErrEmptyChallenge
|
||||
// ErrInvalidChallenge indicates an issue with the received challenge in the WWW-Authenticate header
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrInvalidChallenge].
|
||||
//go:fix inline
|
||||
ErrInvalidChallenge = errs.ErrInvalidChallenge
|
||||
// ErrNoNewChallenge indicates a challenge update did not result in any change
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrNoNewChallenge].
|
||||
//go:fix inline
|
||||
ErrNoNewChallenge = errs.ErrNoNewChallenge
|
||||
// ErrNotFound indicates no credentials found for basic auth
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrNotFound].
|
||||
//go:fix inline
|
||||
ErrNotFound = errs.ErrNotFound
|
||||
// ErrNotImplemented returned when method has not been implemented yet
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrNotImplemented].
|
||||
//go:fix inline
|
||||
ErrNotImplemented = errs.ErrNotImplemented
|
||||
// ErrParseFailure indicates the WWW-Authenticate header could not be parsed
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrParseFailure].
|
||||
//go:fix inline
|
||||
ErrParseFailure = errs.ErrParsingFailed
|
||||
// ErrUnauthorized request was not authorized
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrUnauthorized].
|
||||
//go:fix inline
|
||||
ErrUnauthorized = errs.ErrHTTPUnauthorized
|
||||
// ErrUnsupported indicates the request was unsupported
|
||||
//
|
||||
// Deprecated: replace with [errs.ErrUnsupported].
|
||||
//go:fix inline
|
||||
ErrUnsupported = errs.ErrUnsupported
|
||||
)
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
//go:build go1.18
|
||||
|
||||
// Package cache is used to store values with limits.
|
||||
// Items are automatically pruned when too many entries are stored, or values become stale.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/regclient/regclient/types/errs"
|
||||
)
|
||||
|
||||
type Cache[k comparable, v any] struct {
|
||||
mu sync.Mutex
|
||||
minAge time.Duration
|
||||
maxAge time.Duration
|
||||
minCount int
|
||||
maxCount int
|
||||
timer *time.Timer
|
||||
entries map[k]*Entry[v]
|
||||
}
|
||||
|
||||
type Entry[v any] struct {
|
||||
used time.Time
|
||||
value v
|
||||
}
|
||||
|
||||
type sortKeys[k comparable] struct {
|
||||
keys []k
|
||||
lessFn func(a, b k) bool
|
||||
}
|
||||
|
||||
type conf struct {
|
||||
minAge time.Duration
|
||||
maxCount int
|
||||
}
|
||||
|
||||
type cacheOpts func(*conf)
|
||||
|
||||
func WithAge(age time.Duration) cacheOpts {
|
||||
return func(c *conf) {
|
||||
c.minAge = age
|
||||
}
|
||||
}
|
||||
|
||||
func WithCount(count int) cacheOpts {
|
||||
return func(c *conf) {
|
||||
c.maxCount = count
|
||||
}
|
||||
}
|
||||
|
||||
func New[k comparable, v any](opts ...cacheOpts) Cache[k, v] {
|
||||
c := conf{}
|
||||
for _, opt := range opts {
|
||||
opt(&c)
|
||||
}
|
||||
maxAge := c.minAge + (c.minAge / 10)
|
||||
minCount := 0
|
||||
if c.maxCount > 0 {
|
||||
minCount = int(float64(c.maxCount) * 0.9)
|
||||
}
|
||||
return Cache[k, v]{
|
||||
minAge: c.minAge,
|
||||
maxAge: maxAge,
|
||||
minCount: minCount,
|
||||
maxCount: c.maxCount,
|
||||
entries: map[k]*Entry[v]{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache[k, v]) Delete(key k) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
delete(c.entries, key)
|
||||
if len(c.entries) == 0 && c.timer != nil {
|
||||
c.timer.Stop()
|
||||
c.timer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache[k, v]) Set(key k, val v) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.entries[key] = &Entry[v]{
|
||||
used: time.Now(),
|
||||
value: val,
|
||||
}
|
||||
if len(c.entries) > c.maxCount {
|
||||
c.pruneLocked()
|
||||
} else if c.timer == nil {
|
||||
// prune resets the timer, so this is only needed if the prune wasn't triggered
|
||||
c.timer = time.AfterFunc(c.maxAge, c.prune)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache[k, v]) Get(key k) (v, error) {
|
||||
if c == nil {
|
||||
var val v
|
||||
return val, errs.ErrNotFound
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if e, ok := c.entries[key]; ok {
|
||||
if e.used.Add(c.minAge).Before(time.Now()) {
|
||||
// entry expired
|
||||
go c.prune()
|
||||
} else {
|
||||
c.entries[key].used = time.Now()
|
||||
return e.value, nil
|
||||
}
|
||||
}
|
||||
var val v
|
||||
return val, errs.ErrNotFound
|
||||
}
|
||||
|
||||
func (c *Cache[k, v]) prune() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.pruneLocked()
|
||||
}
|
||||
|
||||
func (c *Cache[k, v]) pruneLocked() {
|
||||
// sort key list by last used date
|
||||
keyList := make([]k, 0, len(c.entries))
|
||||
for key := range c.entries {
|
||||
keyList = append(keyList, key)
|
||||
}
|
||||
sk := sortKeys[k]{
|
||||
keys: keyList,
|
||||
lessFn: func(a, b k) bool {
|
||||
return c.entries[a].used.Before(c.entries[b].used)
|
||||
},
|
||||
}
|
||||
sort.Sort(&sk)
|
||||
// prune entries
|
||||
now := time.Now()
|
||||
cutoff := now.Add(c.minAge * -1)
|
||||
nextTime := now
|
||||
delCount := len(keyList) - c.minCount
|
||||
for i, key := range keyList {
|
||||
if i < delCount || c.entries[key].used.Before(cutoff) {
|
||||
delete(c.entries, key)
|
||||
} else {
|
||||
nextTime = c.entries[key].used
|
||||
break
|
||||
}
|
||||
}
|
||||
// set next timer
|
||||
if len(c.entries) > 0 {
|
||||
dur := nextTime.Sub(now) + c.maxAge
|
||||
if c.timer == nil {
|
||||
// this shouldn't be possible
|
||||
c.timer = time.AfterFunc(dur, c.prune)
|
||||
} else {
|
||||
c.timer.Reset(dur)
|
||||
}
|
||||
} else if c.timer != nil {
|
||||
c.timer.Stop()
|
||||
c.timer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sk *sortKeys[k]) Len() int {
|
||||
return len(sk.keys)
|
||||
}
|
||||
|
||||
func (sk *sortKeys[k]) Less(i, j int) bool {
|
||||
return sk.lessFn(sk.keys[i], sk.keys[j])
|
||||
}
|
||||
|
||||
func (sk *sortKeys[k]) Swap(i, j int) {
|
||||
sk.keys[i], sk.keys[j] = sk.keys[j], sk.keys[i]
|
||||
}
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
// Package conffile wraps the read and write of configuration files
|
||||
package conffile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
perms int
|
||||
fullname string
|
||||
}
|
||||
|
||||
type Opt func(*File)
|
||||
|
||||
// New returns a new File.
|
||||
// The last successful option determines the filename.
|
||||
func New(opts ...Opt) *File {
|
||||
f := File{perms: 0o600}
|
||||
for _, fn := range opts {
|
||||
fn(&f)
|
||||
}
|
||||
if f.fullname == "" {
|
||||
return nil
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
// WithAppDir determines the filename from the XDG or Windows specification.
|
||||
// By default, this is based in $HOME/.config on Linux and %APPDATA% on Windows.
|
||||
// If the file does not exist, this will set the filename only if "force" is true.
|
||||
func WithAppDir(unixDir, winDir, name string, force bool) Opt {
|
||||
var dir string
|
||||
if winDir == "" {
|
||||
dir = unixDir
|
||||
} else {
|
||||
dir = osString(unixDir, winDir)
|
||||
}
|
||||
return func(f *File) {
|
||||
fullname := filepath.Join(appDir(), dir, name)
|
||||
if force || exists(fullname) {
|
||||
f.fullname = fullname
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDirName determines the filename from a subdirectory in the user's HOME.
|
||||
//
|
||||
// Deprecated: Replace with [WithHomeDir]
|
||||
//
|
||||
//go:fix inline
|
||||
func WithDirName(dir, name string) Opt {
|
||||
return WithHomeDir(dir, name, true)
|
||||
}
|
||||
|
||||
// WithEnvFile sets the fullname to the environment value if defined.
|
||||
func WithEnvFile(envVar string) Opt {
|
||||
return func(f *File) {
|
||||
val := os.Getenv(envVar)
|
||||
if val != "" {
|
||||
f.fullname = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnvDir sets the fullname to the environment value + filename if the environment variable is defined.
|
||||
func WithEnvDir(envVar, name string) Opt {
|
||||
return func(f *File) {
|
||||
val := os.Getenv(envVar)
|
||||
if val != "" {
|
||||
f.fullname = filepath.Join(val, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithFullname specifies the filename.
|
||||
// This will always set the filename even if the file does not exist.
|
||||
func WithFullname(fullname string) Opt {
|
||||
return func(f *File) {
|
||||
f.fullname = fullname
|
||||
}
|
||||
}
|
||||
|
||||
// WithHomeDir determines the filename from a subdirectory in the user's HOME
|
||||
// e.g. dir=".app", name="config.json", sets the fullname to "$HOME/.app/config.json".
|
||||
// If the file does not exist, this will set the filename only if "force" is true.
|
||||
func WithHomeDir(dir, name string, force bool) Opt {
|
||||
return func(f *File) {
|
||||
filename := filepath.Join(homeDir(), dir, name)
|
||||
if force || exists(filename) {
|
||||
f.fullname = filename
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithPerms specifies the permissions to create a file with (default 0600).
|
||||
func WithPerms(perms int) Opt {
|
||||
return func(f *File) {
|
||||
f.perms = perms
|
||||
}
|
||||
}
|
||||
|
||||
func (f *File) Name() string {
|
||||
return f.fullname
|
||||
}
|
||||
|
||||
func (f *File) Open() (io.ReadCloser, error) {
|
||||
return os.Open(f.fullname)
|
||||
}
|
||||
|
||||
func (f *File) Write(rdr io.Reader) error {
|
||||
// create temp file/open
|
||||
dir := filepath.Dir(f.fullname)
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, err := os.CreateTemp(dir, filepath.Base(f.fullname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpStat, err := tmp.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpName := tmpStat.Name()
|
||||
tmpFullname := filepath.Join(dir, tmpName)
|
||||
defer os.Remove(tmpFullname)
|
||||
|
||||
// copy from rdr to temp file
|
||||
_, err = io.Copy(tmp, rdr)
|
||||
errC := tmp.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
if errC != nil {
|
||||
return fmt.Errorf("failed to close config: %w", errC)
|
||||
}
|
||||
|
||||
// adjust file ownership/permissions
|
||||
mode := os.FileMode(0o600)
|
||||
uid := os.Getuid()
|
||||
gid := os.Getgid()
|
||||
// adjust defaults based on existing file if available
|
||||
stat, err := os.Stat(f.fullname)
|
||||
if err == nil {
|
||||
// adjust mode to existing file
|
||||
if stat.Mode().IsRegular() {
|
||||
mode = stat.Mode()
|
||||
}
|
||||
uid, gid, _ = getFileOwner(stat)
|
||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
// update mode and owner of temp file
|
||||
//#nosec G703 tempfile location is user controlled
|
||||
if err := os.Chmod(tmpFullname, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
if uid > 0 && gid > 0 {
|
||||
//#nosec G703 tempfile location is user controlled
|
||||
_ = os.Chown(tmpFullname, uid, gid)
|
||||
}
|
||||
// move temp file to target filename
|
||||
//#nosec G703 tempfile location is user controlled
|
||||
return os.Rename(tmpFullname, f.fullname)
|
||||
}
|
||||
|
||||
func exists(name string) bool {
|
||||
_, err := os.Stat(name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
home := os.Getenv(homeEnv)
|
||||
if home == "" {
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
home = u.HomeDir
|
||||
}
|
||||
}
|
||||
return home
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package conffile
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
appDirEnv = "XDG_CONFIG_HOME"
|
||||
homeEnv = "HOME"
|
||||
)
|
||||
|
||||
func appDir() string {
|
||||
appDir := os.Getenv(appDirEnv)
|
||||
if appDir == "" {
|
||||
home := homeDir()
|
||||
appDir = filepath.Join(home, ".config")
|
||||
}
|
||||
return appDir
|
||||
}
|
||||
|
||||
func getFileOwner(stat fs.FileInfo) (int, int, error) {
|
||||
var uid, gid int
|
||||
if sysstat, ok := stat.Sys().(*syscall.Stat_t); ok {
|
||||
uid = int(sysstat.Uid)
|
||||
gid = int(sysstat.Gid)
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
func osString(unix, _ string) string {
|
||||
return unix
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package conffile
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
appDirEnv = "APPDATA"
|
||||
homeEnv = "USERPROFILE"
|
||||
)
|
||||
|
||||
func appDir() string {
|
||||
appDir := os.Getenv(appDirEnv)
|
||||
if appDir == "" {
|
||||
home := homeDir()
|
||||
appDir = filepath.Join(home, "AppData")
|
||||
}
|
||||
return appDir
|
||||
}
|
||||
|
||||
func getFileOwner(_ fs.FileInfo) (int, int, error) {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
func osString(_, win string) string {
|
||||
return win
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user