mirror of https://github.com/docker/cli.git
Compare commits
15 Commits
8dccdd1d13
...
6dc8a31385
Author | SHA1 | Date |
---|---|---|
Sebastiaan van Stijn | 6dc8a31385 | |
Sebastiaan van Stijn | 6c76914532 | |
Sebastiaan van Stijn | 7b67057c32 | |
Sebastiaan van Stijn | 9ccc462005 | |
Sebastiaan van Stijn | 35bf069da2 | |
Sebastiaan van Stijn | 9b72a58d35 | |
Sebastiaan van Stijn | 6aeba15e55 | |
Sebastiaan van Stijn | 11fbc99939 | |
dependabot[bot] | b0c0cd5e32 | |
Sebastiaan van Stijn | f6599300ff | |
Sebastiaan van Stijn | 446d4138ed | |
Sebastiaan van Stijn | 07e5ddd054 | |
Sebastiaan van Stijn | 93a931920b | |
Sebastiaan van Stijn | be3646b87c | |
Sebastiaan van Stijn | 7187c78554 |
|
@ -75,7 +75,7 @@ jobs:
|
||||||
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
|
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
|
||||||
-
|
-
|
||||||
name: Send to Codecov
|
name: Send to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
file: ./build/coverage/coverage.txt
|
files: ./build/coverage/coverage.txt
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
|
@ -40,9 +40,9 @@ jobs:
|
||||||
targets: test-coverage
|
targets: test-coverage
|
||||||
-
|
-
|
||||||
name: Send to Codecov
|
name: Send to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
file: ./build/coverage/coverage.txt
|
files: ./build/coverage/coverage.txt
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
host:
|
host:
|
||||||
|
@ -78,8 +78,8 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
-
|
-
|
||||||
name: Send to Codecov
|
name: Send to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
file: /tmp/coverage.txt
|
files: /tmp/coverage.txt
|
||||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
|
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
|
@ -6,7 +6,7 @@ ARG BASE_DEBIAN_DISTRO=bookworm
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.3
|
ARG GO_VERSION=1.23.3
|
||||||
ARG XX_VERSION=1.5.0
|
ARG XX_VERSION=1.5.0
|
||||||
ARG GOVERSIONINFO_VERSION=v1.3.0
|
ARG GOVERSIONINFO_VERSION=v1.4.1
|
||||||
ARG GOTESTSUM_VERSION=v1.10.0
|
ARG GOTESTSUM_VERSION=v1.10.0
|
||||||
ARG BUILDX_VERSION=0.18.0
|
ARG BUILDX_VERSION=0.18.0
|
||||||
ARG COMPOSE_VERSION=v2.30.3
|
ARG COMPOSE_VERSION=v2.30.3
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
imagetypes "github.com/docker/docker/api/types/image"
|
imagetypes "github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/fvbommel/sortorder"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,16 +82,46 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
||||||
|
|
||||||
details.ContentSize = units.HumanSizeWithPrecision(float64(totalContent), 3)
|
details.ContentSize = units.HumanSizeWithPrecision(float64(totalContent), 3)
|
||||||
|
|
||||||
view.images = append(view.images, topImage{
|
if len(img.RepoTags) == 0 {
|
||||||
Names: img.RepoTags,
|
// Untagged image
|
||||||
Details: details,
|
view.images = append(view.images, topImage{
|
||||||
Children: children,
|
Names: img.RepoTags,
|
||||||
created: img.Created,
|
Details: details,
|
||||||
})
|
Children: children,
|
||||||
|
created: img.Created,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Present images tagged under multiple names as separate images.
|
||||||
|
for _, n := range img.RepoTags {
|
||||||
|
view.images = append(view.images, topImage{
|
||||||
|
Names: []string{n}, // Consider changing Names to be a single name for purpose of this presentation.
|
||||||
|
Details: details,
|
||||||
|
Children: children,
|
||||||
|
created: img.Created,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort images alphabetically using natural-sort, with untagged images last.
|
||||||
sort.Slice(view.images, func(i, j int) bool {
|
sort.Slice(view.images, func(i, j int) bool {
|
||||||
return view.images[i].created > view.images[j].created
|
iUntagged, jUntagged := len(view.images[i].Names) == 0, len(view.images[j].Names) == 0
|
||||||
|
if iUntagged || jUntagged {
|
||||||
|
switch {
|
||||||
|
case iUntagged && jUntagged:
|
||||||
|
// Both untagged images; sort by created date (desc)
|
||||||
|
return view.images[i].created > view.images[j].created
|
||||||
|
case iUntagged:
|
||||||
|
// Sort untagged images last
|
||||||
|
return false
|
||||||
|
case jUntagged:
|
||||||
|
// Sort untagged images last
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort alphabetically, ascending
|
||||||
|
return sortorder.NaturalLess(view.images[i].Names[0], view.images[j].Names[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
return printImageTree(dockerCLI, view)
|
return printImageTree(dockerCLI, view)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.3
|
ARG GO_VERSION=1.23.3
|
||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.20
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.61.0
|
ARG GOLANGCI_LINT_VERSION=v1.62.0
|
||||||
|
|
||||||
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
|
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,14 @@ require (
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/fvbommel/sortorder v1.1.0
|
github.com/fvbommel/sortorder v1.1.0
|
||||||
github.com/go-jose/go-jose/v4 v4.0.4
|
github.com/go-jose/go-jose/v4 v4.0.4
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0
|
github.com/go-viper/mapstructure/v2 v2.2.1
|
||||||
github.com/gogo/protobuf v1.3.2
|
github.com/gogo/protobuf v1.3.2
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/mattn/go-runewidth v0.0.15
|
github.com/mattn/go-runewidth v0.0.15
|
||||||
github.com/moby/patternmatcher v0.6.0
|
github.com/moby/patternmatcher v0.6.0
|
||||||
github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e
|
github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e
|
||||||
github.com/moby/sys/capability v0.3.0
|
github.com/moby/sys/capability v0.4.0
|
||||||
github.com/moby/sys/sequential v0.6.0
|
github.com/moby/sys/sequential v0.6.0
|
||||||
github.com/moby/sys/signal v0.7.1
|
github.com/moby/sys/signal v0.7.1
|
||||||
github.com/moby/term v0.5.0
|
github.com/moby/term v0.5.0
|
||||||
|
@ -39,7 +39,7 @@ require (
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a
|
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a
|
||||||
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d
|
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0
|
github.com/xeipuuv/gojsonschema v1.2.0
|
||||||
go.opentelemetry.io/otel v1.28.0
|
go.opentelemetry.io/otel v1.28.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0
|
||||||
|
|
12
vendor.sum
12
vendor.sum
|
@ -89,8 +89,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
@ -168,8 +168,8 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV
|
||||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e h1:1yC8fRqStY6NirU/swI74fsrHvZVMbtxsHcvl8YpzDg=
|
github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e h1:1yC8fRqStY6NirU/swI74fsrHvZVMbtxsHcvl8YpzDg=
|
||||||
github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e/go.mod h1:mTTGIAz/59OGZR5Qe+QByIe3Nxc+sSuJkrsStFhr6Lg=
|
github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e/go.mod h1:mTTGIAz/59OGZR5Qe+QByIe3Nxc+sSuJkrsStFhr6Lg=
|
||||||
github.com/moby/sys/capability v0.3.0 h1:kEP+y6te0gEXIaeQhIi0s7vKs/w0RPoH1qPa6jROcVg=
|
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||||
github.com/moby/sys/capability v0.3.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
||||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||||
|
@ -266,8 +266,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a h1:tlJ7tGUHvcvL1v3yR6NcCc9nOqh2L+CG6HWrYQtwzQ0=
|
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a h1:tlJ7tGUHvcvL1v3yR6NcCc9nOqh2L+CG6HWrYQtwzQ0=
|
||||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a/go.mod h1:Y94A6rPp2OwNfP/7vmf8O2xx2IykP8pPXQ1DLouGnEw=
|
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a/go.mod h1:Y94A6rPp2OwNfP/7vmf8O2xx2IykP8pPXQ1DLouGnEw=
|
||||||
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d h1:wvQZpqy8p0D/FUia6ipKDhXrzPzBVJE4PZyPc5+5Ay0=
|
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346 h1:TvtdmeYsYEij78hS4oxnwikoiLdIrgav3BA+CbhaDAI=
|
||||||
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ=
|
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ=
|
||||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss=
|
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss=
|
||||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
|
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -36,6 +37,30 @@ func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cachedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
||||||
|
// it into a closure to be used directly
|
||||||
|
// if the type fails to convert we return a closure always erroring to keep the previous behaviour
|
||||||
|
func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||||
|
switch f := typedDecodeHook(raw).(type) {
|
||||||
|
case DecodeHookFuncType:
|
||||||
|
return func(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||||
|
return f(from.Type(), to.Type(), from.Interface())
|
||||||
|
}
|
||||||
|
case DecodeHookFuncKind:
|
||||||
|
return func(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||||
|
return f(from.Kind(), to.Kind(), from.Interface())
|
||||||
|
}
|
||||||
|
case DecodeHookFuncValue:
|
||||||
|
return func(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||||
|
return f(from, to)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return func(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||||
|
return nil, errors.New("invalid decode hook signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeHookExec executes the given decode hook. This should be used
|
// DecodeHookExec executes the given decode hook. This should be used
|
||||||
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
||||||
// that took reflect.Kind instead of reflect.Type.
|
// that took reflect.Kind instead of reflect.Type.
|
||||||
|
@ -61,13 +86,17 @@ func DecodeHookExec(
|
||||||
// The composed funcs are called in order, with the result of the
|
// The composed funcs are called in order, with the result of the
|
||||||
// previous transformation.
|
// previous transformation.
|
||||||
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
||||||
|
cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(fs))
|
||||||
|
for _, f := range fs {
|
||||||
|
cached = append(cached, cachedDecodeHook(f))
|
||||||
|
}
|
||||||
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
|
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
data := f.Interface()
|
data := f.Interface()
|
||||||
|
|
||||||
newFrom := f
|
newFrom := f
|
||||||
for _, f1 := range fs {
|
for _, c := range cached {
|
||||||
data, err = DecodeHookExec(f1, newFrom, t)
|
data, err = c(newFrom, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -81,13 +110,17 @@ func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
||||||
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
|
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
|
||||||
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
|
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
|
||||||
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
|
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
|
||||||
|
cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(ff))
|
||||||
|
for _, f := range ff {
|
||||||
|
cached = append(cached, cachedDecodeHook(f))
|
||||||
|
}
|
||||||
return func(a, b reflect.Value) (interface{}, error) {
|
return func(a, b reflect.Value) (interface{}, error) {
|
||||||
var allErrs string
|
var allErrs string
|
||||||
var out interface{}
|
var out interface{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
for _, f := range ff {
|
for _, c := range cached {
|
||||||
out, err = DecodeHookExec(f, a, b)
|
out, err = c(a, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
allErrs += err.Error() + "\n"
|
allErrs += err.Error() + "\n"
|
||||||
continue
|
continue
|
||||||
|
@ -144,6 +177,26 @@ func StringToTimeDurationHookFunc() DecodeHookFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringToURLHookFunc returns a DecodeHookFunc that converts
|
||||||
|
// strings to *url.URL.
|
||||||
|
func StringToURLHookFunc() DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{},
|
||||||
|
) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if t != reflect.TypeOf(&url.URL{}) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it by parsing
|
||||||
|
return url.Parse(data.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StringToIPHookFunc returns a DecodeHookFunc that converts
|
// StringToIPHookFunc returns a DecodeHookFunc that converts
|
||||||
// strings to net.IP
|
// strings to net.IP
|
||||||
func StringToIPHookFunc() DecodeHookFunc {
|
func StringToIPHookFunc() DecodeHookFunc {
|
||||||
|
|
|
@ -266,6 +266,10 @@ type DecoderConfig struct {
|
||||||
// defaults to "mapstructure"
|
// defaults to "mapstructure"
|
||||||
TagName string
|
TagName string
|
||||||
|
|
||||||
|
// The option of the value in the tag that indicates a field should
|
||||||
|
// be squashed. This defaults to "squash".
|
||||||
|
SquashTagOption string
|
||||||
|
|
||||||
// IgnoreUntaggedFields ignores all struct fields without explicit
|
// IgnoreUntaggedFields ignores all struct fields without explicit
|
||||||
// TagName, comparable to `mapstructure:"-"` as default behaviour.
|
// TagName, comparable to `mapstructure:"-"` as default behaviour.
|
||||||
IgnoreUntaggedFields bool
|
IgnoreUntaggedFields bool
|
||||||
|
@ -274,6 +278,10 @@ type DecoderConfig struct {
|
||||||
// field name or tag. Defaults to `strings.EqualFold`. This can be used
|
// field name or tag. Defaults to `strings.EqualFold`. This can be used
|
||||||
// to implement case-sensitive tag values, support snake casing, etc.
|
// to implement case-sensitive tag values, support snake casing, etc.
|
||||||
MatchName func(mapKey, fieldName string) bool
|
MatchName func(mapKey, fieldName string) bool
|
||||||
|
|
||||||
|
// DecodeNil, if set to true, will cause the DecodeHook (if present) to run
|
||||||
|
// even if the input is nil. This can be used to provide default values.
|
||||||
|
DecodeNil bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Decoder takes a raw interface value and turns it into structured
|
// A Decoder takes a raw interface value and turns it into structured
|
||||||
|
@ -283,7 +291,8 @@ type DecoderConfig struct {
|
||||||
// structure. The top-level Decode method is just a convenience that sets
|
// structure. The top-level Decode method is just a convenience that sets
|
||||||
// up the most basic Decoder.
|
// up the most basic Decoder.
|
||||||
type Decoder struct {
|
type Decoder struct {
|
||||||
config *DecoderConfig
|
config *DecoderConfig
|
||||||
|
cachedDecodeHook func(from reflect.Value, to reflect.Value) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata contains information about decoding a structure that
|
// Metadata contains information about decoding a structure that
|
||||||
|
@ -401,6 +410,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
|
||||||
config.TagName = "mapstructure"
|
config.TagName = "mapstructure"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.SquashTagOption == "" {
|
||||||
|
config.SquashTagOption = "squash"
|
||||||
|
}
|
||||||
|
|
||||||
if config.MatchName == nil {
|
if config.MatchName == nil {
|
||||||
config.MatchName = strings.EqualFold
|
config.MatchName = strings.EqualFold
|
||||||
}
|
}
|
||||||
|
@ -408,6 +421,9 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
|
||||||
result := &Decoder{
|
result := &Decoder{
|
||||||
config: config,
|
config: config,
|
||||||
}
|
}
|
||||||
|
if config.DecodeHook != nil {
|
||||||
|
result.cachedDecodeHook = cachedDecodeHook(config.DecodeHook)
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
@ -426,19 +442,26 @@ func (d *Decoder) Decode(input interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isNil returns true if the input is nil or a typed nil pointer.
|
||||||
|
func isNil(input interface{}) bool {
|
||||||
|
if input == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val := reflect.ValueOf(input)
|
||||||
|
return val.Kind() == reflect.Ptr && val.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
// Decodes an unknown data type into a specific reflection value.
|
// Decodes an unknown data type into a specific reflection value.
|
||||||
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
|
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
|
||||||
var inputVal reflect.Value
|
var (
|
||||||
if input != nil {
|
inputVal = reflect.ValueOf(input)
|
||||||
inputVal = reflect.ValueOf(input)
|
outputKind = getKind(outVal)
|
||||||
|
decodeNil = d.config.DecodeNil && d.cachedDecodeHook != nil
|
||||||
// We need to check here if input is a typed nil. Typed nils won't
|
)
|
||||||
// match the "input == nil" below so we check that here.
|
if isNil(input) {
|
||||||
if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() {
|
// Typed nils won't match the "input == nil" below, so reset input.
|
||||||
input = nil
|
input = nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if input == nil {
|
if input == nil {
|
||||||
// If the data is nil, then we don't set anything, unless ZeroFields is set
|
// If the data is nil, then we don't set anything, unless ZeroFields is set
|
||||||
// to true.
|
// to true.
|
||||||
|
@ -449,30 +472,46 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
||||||
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
if !decodeNil {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
if !inputVal.IsValid() {
|
}
|
||||||
// If the input value is invalid, then we just set the value
|
if !inputVal.IsValid() {
|
||||||
// to be the zero value.
|
if !decodeNil {
|
||||||
outVal.Set(reflect.Zero(outVal.Type()))
|
// If the input value is invalid, then we just set the value
|
||||||
if d.config.Metadata != nil && name != "" {
|
// to be the zero value.
|
||||||
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
outVal.Set(reflect.Zero(outVal.Type()))
|
||||||
|
if d.config.Metadata != nil && name != "" {
|
||||||
|
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Hooks need a valid inputVal, so reset it to zero value of outVal type.
|
||||||
|
switch outputKind {
|
||||||
|
case reflect.Struct, reflect.Map:
|
||||||
|
var mapVal map[string]interface{}
|
||||||
|
inputVal = reflect.ValueOf(mapVal) // create nil map pointer
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
var sliceVal []interface{}
|
||||||
|
inputVal = reflect.ValueOf(sliceVal) // create nil slice pointer
|
||||||
|
default:
|
||||||
|
inputVal = reflect.Zero(outVal.Type())
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.config.DecodeHook != nil {
|
if d.cachedDecodeHook != nil {
|
||||||
// We have a DecodeHook, so let's pre-process the input.
|
// We have a DecodeHook, so let's pre-process the input.
|
||||||
var err error
|
var err error
|
||||||
input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal)
|
input, err = d.cachedDecodeHook(inputVal, outVal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error decoding '%s': %w", name, err)
|
return fmt.Errorf("error decoding '%s': %w", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isNil(input) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
outputKind := getKind(outVal)
|
|
||||||
addMetaKey := true
|
addMetaKey := true
|
||||||
switch outputKind {
|
switch outputKind {
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
|
@ -753,8 +792,8 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
|
"'%s' expected type '%s', got unconvertible type '%#v', value: '%#v'",
|
||||||
name, val.Type(), dataVal.Type(), data)
|
name, val, dataVal, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -973,7 +1012,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
||||||
}
|
}
|
||||||
|
|
||||||
// If "squash" is specified in the tag, we squash the field down.
|
// If "squash" is specified in the tag, we squash the field down.
|
||||||
squash = squash || strings.Index(tagValue[index+1:], "squash") != -1
|
squash = squash || strings.Contains(tagValue[index+1:], d.config.SquashTagOption)
|
||||||
if squash {
|
if squash {
|
||||||
// When squashing, the embedded type can be a pointer to a struct.
|
// When squashing, the embedded type can be a pointer to a struct.
|
||||||
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
|
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
|
||||||
|
@ -1351,7 +1390,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
||||||
// We always parse the tags cause we're looking for other tags too
|
// We always parse the tags cause we're looking for other tags too
|
||||||
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
|
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
|
||||||
for _, tag := range tagParts[1:] {
|
for _, tag := range tagParts[1:] {
|
||||||
if tag == "squash" {
|
if tag == d.config.SquashTagOption {
|
||||||
squash = true
|
squash = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -1363,10 +1402,15 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
||||||
}
|
}
|
||||||
|
|
||||||
if squash {
|
if squash {
|
||||||
if fieldVal.Kind() != reflect.Struct {
|
switch fieldVal.Kind() {
|
||||||
errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind()))
|
case reflect.Struct:
|
||||||
} else {
|
|
||||||
structs = append(structs, fieldVal)
|
structs = append(structs, fieldVal)
|
||||||
|
case reflect.Interface:
|
||||||
|
if !fieldVal.IsNil() {
|
||||||
|
structs = append(structs, fieldVal.Elem().Elem())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind()))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[codespell]
|
|
||||||
skip = ./.git
|
|
||||||
ignore-words-list = nd
|
|
|
@ -1,6 +0,0 @@
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- gofumpt
|
|
||||||
- errorlint
|
|
|
@ -5,6 +5,30 @@ from https://github.com/syndtr/gocapability/commit/42c35b4376354fd5.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.4.0] - 2024-11-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* New separate API for ambient ([GetAmbient], [SetAmbient], [ResetAmbient])
|
||||||
|
and bound ([GetBound], [DropBound]) capabilities, modelled after libcap. (#176)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* [Apply] now returns an error if called for non-zero `pid`. Before this change,
|
||||||
|
it could silently change some capabilities of the current process, instead of
|
||||||
|
the one identified by the `pid`. (#168, #174)
|
||||||
|
* Fixed tests that change capabilities to be run in a separate process. (#173)
|
||||||
|
* Other improvements in tests. (#169, #170)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Use raw syscalls (which are slightly faster). (#176)
|
||||||
|
* Most tests are now limited to testing the public API of the package. (#162)
|
||||||
|
* Simplify parsing /proc/*pid*/status, add a test case. (#162)
|
||||||
|
* Optimize the number of syscall to set ambient capabilities in Apply
|
||||||
|
by clearing them first; add a test case. (#163, #164)
|
||||||
|
* Better documentation for [Apply], [NewFile], [NewFile2], [NewPid], [NewPid2]. (#175)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* `.golangci.yml` and `.codespellrc` are no longer part of the package. (#158)
|
||||||
|
|
||||||
## [0.3.0] - 2024-09-25
|
## [0.3.0] - 2024-09-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -63,14 +87,24 @@ This is an initial release since the fork.
|
||||||
* Removed init function so programs that use this package start faster. [#6]
|
* Removed init function so programs that use this package start faster. [#6]
|
||||||
* Removed `CAP_LAST_CAP` (use [LastCap] instead). [#6]
|
* Removed `CAP_LAST_CAP` (use [LastCap] instead). [#6]
|
||||||
|
|
||||||
<!-- Doc links. -->
|
<!-- Doc links (please keep sorted). -->
|
||||||
[Apply]: https://pkg.go.dev/github.com/moby/sys/capability#Capabilities.Apply
|
[Apply]: https://pkg.go.dev/github.com/moby/sys/capability#Capabilities.Apply
|
||||||
|
[DropBound]: https://pkg.go.dev/github.com/moby/sys/capability#DropBound
|
||||||
|
[GetAmbient]: https://pkg.go.dev/github.com/moby/sys/capability#GetAmbient
|
||||||
|
[GetBound]: https://pkg.go.dev/github.com/moby/sys/capability#GetBound
|
||||||
[LastCap]: https://pkg.go.dev/github.com/moby/sys/capability#LastCap
|
[LastCap]: https://pkg.go.dev/github.com/moby/sys/capability#LastCap
|
||||||
[List]: https://pkg.go.dev/github.com/moby/sys/capability#List
|
|
||||||
[ListKnown]: https://pkg.go.dev/github.com/moby/sys/capability#ListKnown
|
[ListKnown]: https://pkg.go.dev/github.com/moby/sys/capability#ListKnown
|
||||||
[ListSupported]: https://pkg.go.dev/github.com/moby/sys/capability#ListSupported
|
[ListSupported]: https://pkg.go.dev/github.com/moby/sys/capability#ListSupported
|
||||||
|
[List]: https://pkg.go.dev/github.com/moby/sys/capability#List
|
||||||
|
[NewFile2]: https://pkg.go.dev/github.com/moby/sys/capability#NewFile2
|
||||||
|
[NewFile]: https://pkg.go.dev/github.com/moby/sys/capability#NewFile
|
||||||
|
[NewPid2]: https://pkg.go.dev/github.com/moby/sys/capability#NewPid2
|
||||||
|
[NewPid]: https://pkg.go.dev/github.com/moby/sys/capability#NewPid
|
||||||
|
[ResetAmbient]: https://pkg.go.dev/github.com/moby/sys/capability#ResetAmbient
|
||||||
|
[SetAmbient]: https://pkg.go.dev/github.com/moby/sys/capability#SetAmbient
|
||||||
|
|
||||||
<!-- Minor releases. -->
|
<!-- Minor releases. -->
|
||||||
|
[0.4.0]: https://github.com/moby/sys/releases/tag/capability%2Fv0.4.0
|
||||||
[0.3.0]: https://github.com/moby/sys/releases/tag/capability%2Fv0.3.0
|
[0.3.0]: https://github.com/moby/sys/releases/tag/capability%2Fv0.3.0
|
||||||
[0.2.0]: https://github.com/moby/sys/releases/tag/capability%2Fv0.2.0
|
[0.2.0]: https://github.com/moby/sys/releases/tag/capability%2Fv0.2.0
|
||||||
[0.1.1]: https://github.com/kolyshkin/capability/compare/v0.1.0...v0.1.1
|
[0.1.1]: https://github.com/kolyshkin/capability/compare/v0.1.0...v0.1.1
|
||||||
|
|
|
@ -56,16 +56,16 @@ type Capabilities interface {
|
||||||
// outstanding changes.
|
// outstanding changes.
|
||||||
Load() error
|
Load() error
|
||||||
|
|
||||||
// Apply apply the capabilities settings, so all changes will take
|
// Apply apply the capabilities settings, so all changes made by
|
||||||
// effect.
|
// [Set], [Unset], [Fill], or [Clear] will take effect.
|
||||||
Apply(kind CapType) error
|
Apply(kind CapType) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPid initializes a new [Capabilities] object for given pid when
|
// NewPid initializes a new [Capabilities] object for given pid when
|
||||||
// it is nonzero, or for the current process if pid is 0.
|
// it is nonzero, or for the current process if pid is 0.
|
||||||
//
|
//
|
||||||
// Deprecated: Replace with [NewPid2] followed by [Capabilities.Load].
|
// Deprecated: replace with [NewPid2] followed by optional [Capabilities.Load]
|
||||||
// For example, replace:
|
// (only if needed). For example, replace:
|
||||||
//
|
//
|
||||||
// c, err := NewPid(0)
|
// c, err := NewPid(0)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
@ -93,16 +93,16 @@ func NewPid(pid int) (Capabilities, error) {
|
||||||
|
|
||||||
// NewPid2 initializes a new [Capabilities] object for given pid when
|
// NewPid2 initializes a new [Capabilities] object for given pid when
|
||||||
// it is nonzero, or for the current process if pid is 0. This
|
// it is nonzero, or for the current process if pid is 0. This
|
||||||
// does not load the process's current capabilities; to do that you
|
// does not load the process's current capabilities; if needed,
|
||||||
// must call [Capabilities.Load] explicitly.
|
// call [Capabilities.Load].
|
||||||
func NewPid2(pid int) (Capabilities, error) {
|
func NewPid2(pid int) (Capabilities, error) {
|
||||||
return newPid(pid)
|
return newPid(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFile initializes a new Capabilities object for given file path.
|
// NewFile initializes a new Capabilities object for given file path.
|
||||||
//
|
//
|
||||||
// Deprecated: Replace with [NewFile2] followed by [Capabilities.Load].
|
// Deprecated: replace with [NewFile2] followed by optional [Capabilities.Load]
|
||||||
// For example, replace:
|
// (only if needed). For example, replace:
|
||||||
//
|
//
|
||||||
// c, err := NewFile(path)
|
// c, err := NewFile(path)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
@ -130,7 +130,7 @@ func NewFile(path string) (Capabilities, error) {
|
||||||
|
|
||||||
// NewFile2 creates a new initialized [Capabilities] object for given
|
// NewFile2 creates a new initialized [Capabilities] object for given
|
||||||
// file path. This does not load the process's current capabilities;
|
// file path. This does not load the process's current capabilities;
|
||||||
// to do that you must call [Capabilities.Load] explicitly.
|
// if needed, call [Capabilities.Load].
|
||||||
func NewFile2(path string) (Capabilities, error) {
|
func NewFile2(path string) (Capabilities, error) {
|
||||||
return newFile(path)
|
return newFile(path)
|
||||||
}
|
}
|
||||||
|
@ -142,3 +142,35 @@ func NewFile2(path string) (Capabilities, error) {
|
||||||
func LastCap() (Cap, error) {
|
func LastCap() (Cap, error) {
|
||||||
return lastCap()
|
return lastCap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAmbient determines if a specific ambient capability is raised in the
|
||||||
|
// calling thread.
|
||||||
|
func GetAmbient(c Cap) (bool, error) {
|
||||||
|
return getAmbient(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAmbient raises or lowers specified ambient capabilities for the calling
|
||||||
|
// thread. To complete successfully, the prevailing effective capability set
|
||||||
|
// must have a raised CAP_SETPCAP. Further, to raise a specific ambient
|
||||||
|
// capability the inheritable and permitted sets of the calling thread must
|
||||||
|
// already contain the specified capability.
|
||||||
|
func SetAmbient(raise bool, caps ...Cap) error {
|
||||||
|
return setAmbient(raise, caps...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAmbient resets all of the ambient capabilities for the calling thread
|
||||||
|
// to their lowered value.
|
||||||
|
func ResetAmbient() error {
|
||||||
|
return resetAmbient()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBound determines if a specific bounding capability is raised in the
|
||||||
|
// calling thread.
|
||||||
|
func GetBound(c Cap) (bool, error) {
|
||||||
|
return getBound(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropBound lowers the specified bounding set capability.
|
||||||
|
func DropBound(caps ...Cap) error {
|
||||||
|
return dropBound(caps...)
|
||||||
|
}
|
||||||
|
|
|
@ -117,6 +117,13 @@ func newPid(pid int) (c Capabilities, retErr error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ignoreEINVAL(err error) error {
|
||||||
|
if errors.Is(err, syscall.EINVAL) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type capsV3 struct {
|
type capsV3 struct {
|
||||||
hdr capHeader
|
hdr capHeader
|
||||||
data [2]capData
|
data [2]capData
|
||||||
|
@ -307,15 +314,15 @@ func (c *capsV3) Load() (err error) {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "CapB") {
|
if val, ok := strings.CutPrefix(line, "CapBnd:\t"); ok {
|
||||||
_, err = fmt.Sscanf(line[4:], "nd: %08x%08x", &c.bounds[1], &c.bounds[0])
|
_, err = fmt.Sscanf(val, "%08x%08x", &c.bounds[1], &c.bounds[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "CapA") {
|
if val, ok := strings.CutPrefix(line, "CapAmb:\t"); ok {
|
||||||
_, err = fmt.Sscanf(line[4:], "mb: %08x%08x", &c.ambient[1], &c.ambient[0])
|
_, err = fmt.Sscanf(val, "%08x%08x", &c.ambient[1], &c.ambient[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -327,7 +334,10 @@ func (c *capsV3) Load() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *capsV3) Apply(kind CapType) (err error) {
|
func (c *capsV3) Apply(kind CapType) error {
|
||||||
|
if c.hdr.pid != 0 {
|
||||||
|
return errors.New("unable to modify capabilities of another process")
|
||||||
|
}
|
||||||
last, err := LastCap()
|
last, err := LastCap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -336,21 +346,17 @@ func (c *capsV3) Apply(kind CapType) (err error) {
|
||||||
var data [2]capData
|
var data [2]capData
|
||||||
err = capget(&c.hdr, &data[0])
|
err = capget(&c.hdr, &data[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if (1<<uint(CAP_SETPCAP))&data[0].effective != 0 {
|
if (1<<uint(CAP_SETPCAP))&data[0].effective != 0 {
|
||||||
for i := Cap(0); i <= last; i++ {
|
for i := Cap(0); i <= last; i++ {
|
||||||
if c.Get(BOUNDING, i) {
|
if c.Get(BOUNDING, i) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = prctl(syscall.PR_CAPBSET_DROP, uintptr(i), 0, 0, 0)
|
// Ignore EINVAL since the capability may not be supported in this system.
|
||||||
|
err = ignoreEINVAL(dropBound(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Ignore EINVAL since the capability may not be supported in this system.
|
return err
|
||||||
if err == syscall.EINVAL { //nolint:errorlint // Errors from syscall are bare.
|
|
||||||
err = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,29 +365,73 @@ func (c *capsV3) Apply(kind CapType) (err error) {
|
||||||
if kind&CAPS == CAPS {
|
if kind&CAPS == CAPS {
|
||||||
err = capset(&c.hdr, &c.data[0])
|
err = capset(&c.hdr, &c.data[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind&AMBS == AMBS {
|
if kind&AMBS == AMBS {
|
||||||
|
// Ignore EINVAL as not supported on kernels before 4.3
|
||||||
|
err = ignoreEINVAL(resetAmbient())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for i := Cap(0); i <= last; i++ {
|
for i := Cap(0); i <= last; i++ {
|
||||||
action := pr_CAP_AMBIENT_LOWER
|
if !c.Get(AMBIENT, i) {
|
||||||
if c.Get(AMBIENT, i) {
|
continue
|
||||||
action = pr_CAP_AMBIENT_RAISE
|
|
||||||
}
|
}
|
||||||
err = prctl(pr_CAP_AMBIENT, action, uintptr(i), 0, 0)
|
// Ignore EINVAL as not supported on kernels before 4.3
|
||||||
|
err = ignoreEINVAL(setAmbient(true, i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Ignore EINVAL as not supported on kernels before 4.3
|
return err
|
||||||
if err == syscall.EINVAL { //nolint:errorlint // Errors from syscall are bare.
|
|
||||||
err = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAmbient(c Cap) (bool, error) {
|
||||||
|
res, err := prctlRetInt(pr_CAP_AMBIENT, pr_CAP_AMBIENT_IS_SET, uintptr(c))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return res > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAmbient(raise bool, caps ...Cap) error {
|
||||||
|
op := pr_CAP_AMBIENT_RAISE
|
||||||
|
if !raise {
|
||||||
|
op = pr_CAP_AMBIENT_LOWER
|
||||||
|
}
|
||||||
|
for _, val := range caps {
|
||||||
|
err := prctl(pr_CAP_AMBIENT, op, uintptr(val))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetAmbient() error {
|
||||||
|
return prctl(pr_CAP_AMBIENT, pr_CAP_AMBIENT_CLEAR_ALL, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBound(c Cap) (bool, error) {
|
||||||
|
res, err := prctlRetInt(syscall.PR_CAPBSET_READ, uintptr(c), 0)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return res > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropBound(caps ...Cap) error {
|
||||||
|
for _, val := range caps {
|
||||||
|
err := prctl(syscall.PR_CAPBSET_DROP, uintptr(val), 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFile(path string) (c Capabilities, err error) {
|
func newFile(path string) (c Capabilities, err error) {
|
||||||
|
|
|
@ -24,3 +24,23 @@ func newFile(_ string) (Capabilities, error) {
|
||||||
func lastCap() (Cap, error) {
|
func lastCap() (Cap, error) {
|
||||||
return -1, errNotSup
|
return -1, errNotSup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAmbient(_ Cap) (bool, error) {
|
||||||
|
return false, errNotSup
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAmbient(_ bool, _ ...Cap) error {
|
||||||
|
return errNotSup
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetAmbient() error {
|
||||||
|
return errNotSup
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBound(_ Cap) (bool, error) {
|
||||||
|
return false, errNotSup
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropBound(_ ...Cap) error {
|
||||||
|
return errNotSup
|
||||||
|
}
|
||||||
|
|
|
@ -316,7 +316,7 @@ func ListKnown() []Cap {
|
||||||
return list()
|
return list()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListSupported retuns the list of all capabilities known to the package,
|
// ListSupported returns the list of all capabilities known to the package,
|
||||||
// except those that are not supported by the currently running Linux kernel.
|
// except those that are not supported by the currently running Linux kernel.
|
||||||
func ListSupported() ([]Cap, error) {
|
func ListSupported() ([]Cap, error) {
|
||||||
last, err := LastCap()
|
last, err := LastCap()
|
||||||
|
|
|
@ -24,7 +24,7 @@ type capData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func capget(hdr *capHeader, data *capData) (err error) {
|
func capget(hdr *capHeader, data *capData) (err error) {
|
||||||
_, _, e1 := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0)
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
err = e1
|
err = e1
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func capget(hdr *capHeader, data *capData) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func capset(hdr *capHeader, data *capData) (err error) {
|
func capset(hdr *capHeader, data *capData) (err error) {
|
||||||
_, _, e1 := syscall.Syscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0)
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
err = e1
|
err = e1
|
||||||
}
|
}
|
||||||
|
@ -48,14 +48,22 @@ const (
|
||||||
pr_CAP_AMBIENT_CLEAR_ALL = uintptr(4)
|
pr_CAP_AMBIENT_CLEAR_ALL = uintptr(4)
|
||||||
)
|
)
|
||||||
|
|
||||||
func prctl(option int, arg2, arg3, arg4, arg5 uintptr) (err error) {
|
func prctl(option int, arg2, arg3 uintptr) (err error) {
|
||||||
_, _, e1 := syscall.Syscall6(syscall.SYS_PRCTL, uintptr(option), arg2, arg3, arg4, arg5, 0)
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_PRCTL, uintptr(option), arg2, arg3)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
err = e1
|
err = e1
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prctlRetInt(option int, arg2, arg3 uintptr) (int, error) {
|
||||||
|
ret, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, uintptr(option), arg2, arg3)
|
||||||
|
if err != 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
vfsXattrName = "security.capability"
|
vfsXattrName = "security.capability"
|
||||||
|
|
||||||
|
@ -92,7 +100,7 @@ func getVfsCap(path string, dest *vfscapData) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(dest)), vfscapDataSizeV2, 0, 0)
|
r0, _, e1 := syscall.RawSyscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(dest)), vfscapDataSizeV2, 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
if e1 == syscall.ENODATA {
|
if e1 == syscall.ENODATA {
|
||||||
dest.version = 2
|
dest.version = 2
|
||||||
|
@ -145,7 +153,7 @@ func setVfsCap(path string, data *vfscapData) (err error) {
|
||||||
} else {
|
} else {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(data)), size, 0, 0)
|
_, _, e1 := syscall.RawSyscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(data)), size, 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
err = e1
|
err = e1
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
go-rosetta
|
||||||
|
==========
|
||||||
|
|
||||||
|
[![PkgGoDev](https://pkg.go.dev/badge/github.com/tonistiigi/go-rosetta)](https://pkg.go.dev/github.com/tonistiigi/go-rosetta)
|
||||||
|
|
||||||
|
`go-rosetta` provides utilities to detect if an application is running as a
|
||||||
|
[Rosetta](https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment) translated binary, and
|
||||||
|
to determine the native architecture.
|
|
@ -1,17 +1,28 @@
|
||||||
|
//go:build darwin
|
||||||
// +build darwin
|
// +build darwin
|
||||||
|
|
||||||
package rosetta
|
package rosetta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Available returns true if Rosetta is installed/available
|
||||||
|
func Available() bool {
|
||||||
|
_, err := os.Stat("/Library/Apple/usr/share/rosetta")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled returns true if running in a Rosetta Translated Binary, false otherwise.
|
||||||
func Enabled() bool {
|
func Enabled() bool {
|
||||||
v, err := syscall.SysctlUint32("sysctl.proc_translated")
|
v, err := syscall.SysctlUint32("sysctl.proc_translated")
|
||||||
return err == nil && v == 1
|
return err == nil && v == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NativeArch returns the native architecture, even if binary architecture
|
||||||
|
// is emulated by Rosetta.
|
||||||
func NativeArch() string {
|
func NativeArch() string {
|
||||||
if Enabled() && runtime.GOARCH == "amd64" {
|
if Enabled() && runtime.GOARCH == "amd64" {
|
||||||
return "arm64"
|
return "arm64"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !darwin
|
||||||
// +build !darwin
|
// +build !darwin
|
||||||
|
|
||||||
package rosetta
|
package rosetta
|
||||||
|
@ -6,10 +7,18 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Available returns true if Rosetta is installed/available
|
||||||
|
func Available() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled returns true if running in a Rosetta Translated Binary, false otherwise.
|
||||||
func Enabled() bool {
|
func Enabled() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NativeArch returns the native architecture, even if binary architecture
|
||||||
|
// is emulated by Rosetta.
|
||||||
func NativeArch() string {
|
func NativeArch() string {
|
||||||
return runtime.GOARCH
|
return runtime.GOARCH
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ github.com/go-logr/logr/funcr
|
||||||
# github.com/go-logr/stdr v1.2.2
|
# github.com/go-logr/stdr v1.2.2
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/go-logr/stdr
|
github.com/go-logr/stdr
|
||||||
# github.com/go-viper/mapstructure/v2 v2.0.0
|
# github.com/go-viper/mapstructure/v2 v2.2.1
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/go-viper/mapstructure/v2
|
github.com/go-viper/mapstructure/v2
|
||||||
github.com/go-viper/mapstructure/v2/internal/errors
|
github.com/go-viper/mapstructure/v2/internal/errors
|
||||||
|
@ -205,7 +205,7 @@ github.com/moby/swarmkit/v2/api/defaults
|
||||||
github.com/moby/swarmkit/v2/api/genericresource
|
github.com/moby/swarmkit/v2/api/genericresource
|
||||||
github.com/moby/swarmkit/v2/manager/raftselector
|
github.com/moby/swarmkit/v2/manager/raftselector
|
||||||
github.com/moby/swarmkit/v2/protobuf/plugin
|
github.com/moby/swarmkit/v2/protobuf/plugin
|
||||||
# github.com/moby/sys/capability v0.3.0
|
# github.com/moby/sys/capability v0.4.0
|
||||||
## explicit; go 1.21
|
## explicit; go 1.21
|
||||||
github.com/moby/sys/capability
|
github.com/moby/sys/capability
|
||||||
# github.com/moby/sys/sequential v0.6.0
|
# github.com/moby/sys/sequential v0.6.0
|
||||||
|
@ -293,7 +293,7 @@ github.com/theupdateframework/notary/tuf/data
|
||||||
github.com/theupdateframework/notary/tuf/signed
|
github.com/theupdateframework/notary/tuf/signed
|
||||||
github.com/theupdateframework/notary/tuf/utils
|
github.com/theupdateframework/notary/tuf/utils
|
||||||
github.com/theupdateframework/notary/tuf/validation
|
github.com/theupdateframework/notary/tuf/validation
|
||||||
# github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d
|
# github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/tonistiigi/go-rosetta
|
github.com/tonistiigi/go-rosetta
|
||||||
# github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb
|
# github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb
|
||||||
|
|
Loading…
Reference in New Issue