diff --git a/.gitignore b/.gitignore index d3f7b05c6e2..6e4f467a5ba 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ lib/* /tetragon-bench /tetragon-operator /tetra +/tetragon-oci-hook +/tetragon-oci-hook-setup /parsertest /checkerpc observer.test diff --git a/Dockerfile b/Dockerfile index 87975f4696b..a00d9d48f39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.5@sha256:58e14a9334 WORKDIR /go/src/github.com/cilium/tetragon ARG TETRAGON_VERSION TARGETARCH COPY . . -RUN make VERSION=$TETRAGON_VERSION TARGET_ARCH=$TARGETARCH tetragon tetra +RUN make VERSION=$TETRAGON_VERSION TARGET_ARCH=$TARGETARCH tetragon tetra tetragon-oci-hook tetragon-oci-hook-setup # Third builder (cross-)compile a stripped gops FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.5-alpine@sha256:5c1cabd9a3c6851a3e18735a2c133fbd8f67fe37eb3203318b7af2ffd2547095 as gops @@ -76,6 +76,8 @@ RUN mkdir /var/lib/tetragon/ && \ apk add --no-cache --update bash COPY --from=tetragon-builder /go/src/github.com/cilium/tetragon/tetragon /usr/bin/ COPY --from=tetragon-builder /go/src/github.com/cilium/tetragon/tetra /usr/bin/ +COPY --from=tetragon-builder /go/src/github.com/cilium/tetragon/contrib/rthooks/tetragon-oci-hook/tetragon-oci-hook /usr/bin/ +COPY --from=tetragon-builder /go/src/github.com/cilium/tetragon/contrib/rthooks/tetragon-oci-hook/tetragon-oci-hook-setup /usr/bin/ COPY --from=gops /gops/gops /usr/bin/ COPY --from=bpf-builder /go/src/github.com/cilium/tetragon/bpf/objs/*.o /var/lib/tetragon/ ENTRYPOINT ["/usr/bin/tetragon"] diff --git a/Makefile b/Makefile index 195afa566d7..8c1b9344faf 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,7 @@ ifdef EXTRA_GO_BUILD_FLAGS endif GO_BUILD = CGO_ENABLED=0 GOARCH=$(GOARCH) $(GO) build $(GO_BUILD_FLAGS) +GO_BUILD_HOOK = CGO_ENABLED=0 GOARCH=$(GOARCH) $(GO) -C contrib/rthooks/tetragon-oci-hook build $(GO_BUILD_FLAGS) .PHONY: all all: tetragon-bpf tetragon tetra generate-flags test-compile tester-progs protoc-gen-go-tetragon tetragon-bench @@ -179,6 +180,12 @@ tetragon-bench: tetragon-operator: $(GO_BUILD) -o $@ ./operator +tetragon-oci-hook: + $(GO_BUILD_HOOK) -o $@ ./cmd/hook + +tetragon-oci-hook-setup: + $(GO_BUILD_HOOK) -o $@ ./cmd/setup + .PHONY: alignchecker alignchecker: $(GO) test -c ./pkg/alignchecker -o alignchecker diff --git a/contrib/rthooks/tetragon-oci-hook/.gitignore b/contrib/rthooks/tetragon-oci-hook/.gitignore index f7fe3263ff5..3b43f1de506 100644 --- a/contrib/rthooks/tetragon-oci-hook/.gitignore +++ b/contrib/rthooks/tetragon-oci-hook/.gitignore @@ -1 +1,2 @@ tetragon-oci-hook +tetragon-oci-hook-setup diff --git a/contrib/rthooks/tetragon-oci-hook/Makefile b/contrib/rthooks/tetragon-oci-hook/Makefile index 97999b5b7eb..453f31c1404 100644 --- a/contrib/rthooks/tetragon-oci-hook/Makefile +++ b/contrib/rthooks/tetragon-oci-hook/Makefile @@ -3,8 +3,28 @@ GO ?= go +all: tetragon-oci-hook tetragon-oci-hook-setup + +images: setup-image setup-image-debug + +.PHONY: setup-image +setup-image: + docker build . -f dockerfiles/setup -t quay.io/isovalent/tetragon-oci-hook-setup:latest + +.PHONY: setup-image-debug +setup-image-debug: + docker build . -f dockerfiles/setup --build-arg BASE_IMAGE=ubuntu -t quay.io/isovalent/tetragon-oci-hook-setup:debug-latest + +tetragon-oci-hook-setup: FORCE + $(GO) build -o $@ ./cmd/setup + +tetragon-oci-hook: FORCE + $(GO) build -o $@ ./cmd/hook + .PHONY: vendor vendor: $(GO) mod tidy $(GO) mod vendor $(GO) mod verify + +FORCE: diff --git a/contrib/rthooks/tetragon-oci-hook/cel.go b/contrib/rthooks/tetragon-oci-hook/cmd/hook/cel.go similarity index 100% rename from contrib/rthooks/tetragon-oci-hook/cel.go rename to contrib/rthooks/tetragon-oci-hook/cmd/hook/cel.go diff --git a/contrib/rthooks/tetragon-oci-hook/cel_test.go b/contrib/rthooks/tetragon-oci-hook/cmd/hook/cel_test.go similarity index 100% rename from contrib/rthooks/tetragon-oci-hook/cel_test.go rename to contrib/rthooks/tetragon-oci-hook/cmd/hook/cel_test.go diff --git a/contrib/rthooks/tetragon-oci-hook/main.go b/contrib/rthooks/tetragon-oci-hook/cmd/hook/main.go similarity index 100% rename from contrib/rthooks/tetragon-oci-hook/main.go rename to contrib/rthooks/tetragon-oci-hook/cmd/hook/main.go diff --git a/contrib/rthooks/tetragon-oci-hook/cmd/setup/main.go b/contrib/rthooks/tetragon-oci-hook/cmd/setup/main.go new file mode 100644 index 00000000000..2de24524606 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/cmd/setup/main.go @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Tetragon + +package main + +import ( + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "time" + + "github.com/alecthomas/kong" + ociHooks "github.com/containers/common/pkg/hooks/1.0.0" + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +const ( + logBaseName = "tetragon-oci-hook.log" +) + +type Install struct { + Interface string `default:"oci-hooks" enum:"oci-hooks" help:"Hooks interface (${enum})"` + LocalBinary string `default:"/usr/bin/tetragon-oci-hook" help:"Source binary path (in the container)"` + LocalInstallDir string `required help:"Installation dir (in the container)."` + HostInstallDir string `required help:"Installation dir (in the host). Used for the binary and the hook logfile."` + + OciHooks struct { + LocalDir string `default:"/hostHooks" help:"oci-hooks drop-in directory (inside the container)"` + } `embed:"" prefix:"oci-hooks."` +} + +func (i *Install) ociHooksInstall(log *logrus.Logger) { + + _, binBaseName := path.Split(i.LocalBinary) + binFname := filepath.Join(i.HostInstallDir, binBaseName) + + logFname := filepath.Join(i.HostInstallDir, logBaseName) + yes := true + hook := ociHooks.Hook{ + Version: "1.0.0", + Hook: rspec.Hook{ + Path: binFname, + Args: []string{ + binFname, + "createContainer", + "--log-fname", logFname, + }, + Env: []string{}, + Timeout: nil, + }, + When: ociHooks.When{ + Always: &yes, + Annotations: map[string]string{}, + Commands: []string{}, + HasBindMounts: nil, + }, + Stages: []string{"createRuntime"}, + } + + data, err := json.MarshalIndent(&hook, "", " ") + if err != nil { + log.WithError(err).Fatal("failed to unmarshall hook info") + } + + confDst := filepath.Join(i.OciHooks.LocalDir, fmt.Sprintf("%s.json", binBaseName)) + if err := os.WriteFile(confDst, data, 0755); err != nil { + log.WithField("conf-dst", confDst).WithError(err).Fatal("writing file failed") + } + + log.WithFields(logrus.Fields{ + "conf-dst-path": confDst, + }).Info("written conf") +} + +func (i *Install) Run(log *logrus.Logger) error { + i.copyBinary(log) + switch i.Interface { + case "oci-hooks": + i.ociHooksInstall(log) + default: + log.WithField("interface", i.Interface).Fatal("unknown interface") + } + return nil +} + +func (i *Install) copyBinary(log *logrus.Logger) { + + data, err := os.ReadFile(i.LocalBinary) + if err != nil { + log.WithField("bin-src-path", i.LocalBinary).WithError(err).Fatal("reading hook source failed") + } + _, base := path.Split(i.LocalBinary) + + // write to a temp file first, and then do a rename to avoid issues with an existing binary + tmpBase := fmt.Sprintf("%s-%s", base, time.Now().Format("20060102.150405000000")) + tmpFname := filepath.Join(i.LocalInstallDir, tmpBase) + if err = os.WriteFile(tmpFname, data, 0755); err != nil { + log.WithField("bin-tmp-path", tmpFname).WithError(err).Fatal("failed to write binary") + } + + binDst := filepath.Join(i.LocalInstallDir, base) + if err := os.Rename(tmpFname, binDst); err != nil { + log.WithFields(logrus.Fields{ + "bin-tmp-path": tmpFname, + "bin-dst-path": binDst, + }).WithError(err).Fatal("failed to rename tmp binary to dst") + } + + log.WithFields(logrus.Fields{ + "hook-dst-path": binDst, + }).Info("written binary") +} + +type Uninstall struct { + Interface string `default:"oci-hooks" enum:"oci-hooks" help:"Hooks interface (${enum})"` + BinaryName string `default:"tetragon-oci-hook" help:"Binary name"` + LocalInstallDir string `required help:"Installation dir (in the container)"` + + OciHooks struct { + LocalDir string `default:"/hostHooks" help:"oci-hooks drop-in directory (inside the container)"` + } `embed:"" prefix:"oci-hooks."` +} + +func (u *Uninstall) removeBinary(log *logrus.Logger) { + binDst := filepath.Join(u.LocalInstallDir, u.BinaryName) + if err := os.Remove(binDst); err != nil { + log.WithField("bin-dst-path", binDst).WithError(err).Warn("failed to remove binary") + } else { + log.WithField("bin-dst-path", binDst).WithError(err).Info("binary removed") + } +} + +func (u *Uninstall) ociHooksUninstall(log *logrus.Logger) { + confDst := filepath.Join(u.OciHooks.LocalDir, fmt.Sprintf("%s.json", u.BinaryName)) + if err := os.Remove(confDst); err != nil { + log.WithField("conf-dst-path", confDst).WithError(err).Warn("failed to remove conf") + } else { + log.WithField("conf-dst-path", confDst).WithError(err).Info("conf removed") + } +} + +func (u *Uninstall) Run(log *logrus.Logger) error { + switch u.Interface { + case "oci-hooks": + u.ociHooksUninstall(log) + default: + log.WithField("interface", u.Interface).Fatal("unknown interface") + } + u.removeBinary(log) + return nil +} + +type CLI struct { + Install Install `cmd:"" help:"Install hook"` + Uninstall Uninstall `cmd:"" help:"Uninstall hook"` +} + +func main() { + + log := logrus.New() + + var conf CLI + ctx := kong.Parse(&conf) + + err := ctx.Run(log) + ctx.FatalIfErrorf(err) +} diff --git a/contrib/rthooks/tetragon-oci-hook/docs/demo.md b/contrib/rthooks/tetragon-oci-hook/docs/demo.md new file mode 100644 index 00000000000..3f1d4806f2d --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/docs/demo.md @@ -0,0 +1,63 @@ +This is a dev demo of how to install the teragon OCI hook on a CRI-O runtime. + +Note: we should move this to the documentation once the PR is merged and `tetragon-oci-hook` and +`tetragon-oci-hook-setup` are part of the tetragon development image. + + +Start minikube: + +```shell-session +minikube start --driver=kvm2 --container-runtime=cri-o +``` + +Build images and load them to minikube: + +```shell-session +make image image-operator +minikube image load cilium/tetragon:latest +minikube image load cilium/tetragon-operator:latest +minikube image list | grep tetragon +localhost/cilium/tetragon:latest +localhost/cilium/tetragon-operator:latest +``` + +Install the image enabling the init container: + +``` +helm install --namespace kube-system \ + --set tetragonOperator.image.override=localhost/cilium/tetragon-operator:latest \ + --set tetragon.image.override=localhost/cilium/tetragon:latest \ + --set tetragon.grpc.address="unix:///var/run/cilium/tetragon/tetragon.sock" \ + --set tetragon.ociHookSetup.enabled=true \ + tetragon ./install/kubernetes +... +kubectl logs -n kube-system tetragon-289tf -c oci-hook-setup +time="2023-12-05T09:28:50Z" level=info msg="written binary" hook-dst-path=/hostInstall/tetragon-oci-hook +time="2023-12-05T09:28:50Z" level=info msg="written conf" conf-dst-path=/hostHooks/tetragon-oci-hook.json +``` + +Check the hook looks: +``` +minikube ssh -- tail -f /opt/tetragon/tetragon-oci-hook.log +... +time="2023-12-05T09:31:08Z" level=info msg="hook request to agent succeeded" hook=create-container ... +``` + +You can now uninstall tetragon: +``` +helm uninstall -n kube-system tetragon +``` + +In many situations, you would want the hook to keep running even if tetragon is +not. Doing so, will allow you to configure a class of pods that can only run if tetragon is availble. + + +To uninstall the hook, you can install the following daemonset: +``` +kubectl -n kube-system apply -f contrib/rthooks/tetragon-oci-hook/k8s/ds-uninstall.yaml +kubectl -n kube-system logs tetragon-oci-hook-uninstall-8t4bl -c setup +time="2023-12-05T09:37:37Z" level=info msg="conf removed" conf-dst-path=/hostHooks/tetragon-oci-hook.json error="" +time="2023-12-05T09:37:37Z" level=info msg="binary removed" bin-dst-path=/hostInstall/tetragon-oci-hook error="" +``` + +(NB: above is not ideal, we should find a better way to do this) diff --git a/contrib/rthooks/tetragon-oci-hook/go.mod b/contrib/rthooks/tetragon-oci-hook/go.mod index 1c93ff3e439..74de8725f8a 100644 --- a/contrib/rthooks/tetragon-oci-hook/go.mod +++ b/contrib/rthooks/tetragon-oci-hook/go.mod @@ -4,8 +4,10 @@ module github.com/cilium/tetragon/contrib/rthooks/tetragon-oci-hook go 1.21.0 require ( + github.com/alecthomas/kong v0.8.1 github.com/cilium/lumberjack/v2 v2.3.0 github.com/cilium/tetragon/api v0.0.0-00010101000000-000000000000 + github.com/containers/common v0.57.0 github.com/google/cel-go v0.18.2 github.com/opencontainers/runc v1.1.10 github.com/opencontainers/runtime-spec v1.1.0 @@ -18,15 +20,15 @@ require ( require ( github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/cilium/ebpf v0.7.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/godbus/dbus/v5 v5.0.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/moby/sys/mountinfo v0.5.0 // indirect + github.com/moby/sys/mountinfo v0.7.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/contrib/rthooks/tetragon-oci-hook/go.sum b/contrib/rthooks/tetragon-oci-hook/go.sum index 927b99db95b..1df1272ca2d 100644 --- a/contrib/rthooks/tetragon-oci-hook/go.sum +++ b/contrib/rthooks/tetragon-oci-hook/go.sum @@ -1,13 +1,22 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= +github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= +github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY= +github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= +github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= +github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/cilium/lumberjack/v2 v2.3.0 h1:IhVJMvPpqDYmQzC0KDhAoy7KlaRsyOsZnT97Nsa3u0o= github.com/cilium/lumberjack/v2 v2.3.0/go.mod h1:yfbtPGmg4i//5oEqzaMxDqSWqgfZFmMoV70Mc2k6v0A= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/containers/common v0.57.0 h1:5O/+6QUBafKK0/zeok9y1rLPukfWgdE0sT4nuzmyAqk= +github.com/containers/common v0.57.0/go.mod h1:t/Z+/sFrapvFMEJe3YnecN49/Tae2wYEQShbEN6SRaU= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -17,8 +26,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -26,15 +35,17 @@ github.com/google/cel-go v0.18.2 h1:L0B6sNBSVmt0OyECi8v6VOS74KOc9W/tLiWKfZABvf4= github.com/google/cel-go v0.18.2/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= +github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40= github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M= github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= @@ -42,8 +53,8 @@ github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY= +github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -55,12 +66,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/contrib/rthooks/tetragon-oci-hook/k8s/ds-uninstall.yaml b/contrib/rthooks/tetragon-oci-hook/k8s/ds-uninstall.yaml new file mode 100644 index 00000000000..491f4d00bd4 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/k8s/ds-uninstall.yaml @@ -0,0 +1,49 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: tetragon-oci-hook-uninstall + namespace: kube-system + labels: + k8s-app: tetragon-oci-hook-setup-test +spec: + selector: + matchLabels: + name: tetragon-oci-hook-setup + template: + metadata: + labels: + name: tetragon-oci-hook-setup + spec: + initContainers: + - name: setup + securityContext: + privileged: true + image: localhost/cilium/tetragon:latest + imagePullPolicy: IfNotPresent + command: + - "tetragon-oci-hook-setup" + - "uninstall" + - "--interface=oci-hooks" + - "--local-install-dir=/hostInstall" + - "--oci-hooks.local-dir=/hostHooks" + volumeMounts: + - name: hooks-path + mountPath: /hostHooks + - name: opt-path + mountPath: /hostInstall + containers: + - name: sleep + image: busybox:1.28 + command: ['sh', '-c', 'sleep 365d'] + volumeMounts: + - name: opt-path + mountPath: /hostInstall + volumes: + - name: hooks-path + hostPath: + path: /usr/share/containers/oci/hooks.d/ + type: DirectoryOrCreate + - name: opt-path + hostPath: + path: /opt/tetragon + type: DirectoryOrCreate diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/.gitignore b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/.gitignore new file mode 100644 index 00000000000..ba077a4031a --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/.gitignore @@ -0,0 +1 @@ +bin diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/.golangci.yml b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/.golangci.yml new file mode 100644 index 00000000000..1ee8fa6f542 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/.golangci.yml @@ -0,0 +1,68 @@ +run: + tests: true + +output: + print-issued-lines: false + +linters: + enable-all: true + disable: + - maligned + - lll + - gochecknoglobals + - wsl + - funlen + - gocognit + - gomnd + - goprintffuncname + - paralleltest + - nlreturn + - goerr113 + - ifshort + - testpackage + - wrapcheck + - exhaustivestruct + - forbidigo + - gci + - godot + - gofumpt + - cyclop + - errorlint + - nestif + - golint + - scopelint + - interfacer + - tagliatelle + - thelper + - godox + - goconst + - varnamelen + - ireturn + - exhaustruct + - nonamedreturns + - nilnil + +linters-settings: + govet: + check-shadowing: true + dupl: + threshold: 100 + gocyclo: + min-complexity: 20 + exhaustive: + default-signifies-exhaustive: true + +issues: + max-per-linter: 0 + max-same: 0 + exclude-use-default: false + exclude: + - '^(G104|G204):' + # Very commonly not checked. + - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' + - 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON) should have comment or be unexported' + - 'composite literal uses unkeyed fields' + - 'bad syntax for struct tag key' + - 'bad syntax for struct tag pair' + - 'result .* \(error\) is always nil' + - 'package io/ioutil is deprecated' diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/COPYING b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/COPYING new file mode 100644 index 00000000000..22707acf7be --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2018 Alec Thomas + +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. diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/README.md b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/README.md new file mode 100644 index 00000000000..4110465a622 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/README.md @@ -0,0 +1,746 @@ + +

+ +# Kong is a command-line parser for Go + +[![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3) + + + +- [Introduction](#introduction) +- [Help](#help) + - [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application) + - [Defining help in Kong](#defining-help-in-kong) +- [Command handling](#command-handling) + - [Switch on the command string](#switch-on-the-command-string) + - [Attach a Run... error method to each command](#attach-a-run-error-method-to-each-command) +- [Hooks: BeforeReset, BeforeResolve, BeforeApply, AfterApply and the Bind option](#hooks-beforereset-beforeresolve-beforeapply-afterapply-and-the-bind-option) +- [Flags](#flags) +- [Commands and sub-commands](#commands-and-sub-commands) +- [Branching positional arguments](#branching-positional-arguments) +- [Positional arguments](#positional-arguments) +- [Slices](#slices) +- [Maps](#maps) +- [Pointers](#pointers) +- [Nested data structure](#nested-data-structure) +- [Custom named decoders](#custom-named-decoders) +- [Supported field types](#supported-field-types) +- [Custom decoders mappers](#custom-decoders-mappers) +- [Supported tags](#supported-tags) +- [Plugins](#plugins) +- [Dynamic Commands](#dynamic-commands) +- [Variable interpolation](#variable-interpolation) +- [Validation](#validation) +- [Modifying Kong's behaviour](#modifying-kongs-behaviour) + - [Namehelp and Descriptionhelp - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description) + - [Configurationloader, paths... - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files) + - [Resolver... - support for default values from external sources](#resolver---support-for-default-values-from-external-sources) + - [\*Mapper... - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values) + - [ConfigureHelpHelpOptions and HelpHelpFunc - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help) + - [Bind... - bind values for callback hooks and Run methods](#bind---bind-values-for-callback-hooks-and-run-methods) + - [Other options](#other-options) + + + +## Introduction + +Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible. + +To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is mapped onto the struct. + +For example, the following command-line: + + shell rm [-f] [-r] ... + shell ls [ ...] + +Can be represented by the following command-line structure: + +```go +package main + +import "github.com/alecthomas/kong" + +var CLI struct { + Rm struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"` + } `cmd:"" help:"Remove files."` + + Ls struct { + Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"` + } `cmd:"" help:"List paths."` +} + +func main() { + ctx := kong.Parse(&CLI) + switch ctx.Command() { + case "rm ": + case "ls": + default: + panic(ctx.Command()) + } +} +``` + +## Help + +### Help as a user of a Kong application + +Every Kong application includes a `--help` flag that will display auto-generated help. + +eg. + + $ shell --help + usage: shell + + A shell-like example app. + + Flags: + --help Show context-sensitive help. + --debug Debug mode. + + Commands: + rm ... + Remove files. + + ls [ ...] + List paths. + +If a command is provided, the help will show full detail on the command including all available flags. + +eg. + + $ shell --help rm + usage: shell rm ... + + Remove files. + + Arguments: + ... Paths to remove. + + Flags: + --debug Debug mode. + + -f, --force Force removal. + -r, --recursive Recursively remove files. + +### Defining help in Kong + +Help is automatically generated from the command-line structure itself, +including `help:""` and other tags. [Variables](#variable-interpolation) will +also be interpolated into the help string. + +Finally, any command, or argument type implementing the interface +`Help() string` will have this function called to retrieve more detail to +augment the help tag. This allows for much more descriptive text than can +fit in Go tags. [See \_examples/shell/help](./_examples/shell/help) + +#### Showing the _command_'s detailed help + +A command's additional help text is _not_ shown from top-level help, but can be displayed within contextual help: + +**Top level help** + +```bash + $ go run ./_examples/shell/help --help +Usage: help + +An app demonstrating HelpProviders + +Flags: + -h, --help Show context-sensitive help. + --flag Regular flag help + +Commands: + echo Regular command help +``` + +**Contextual** + +```bash + $ go run ./_examples/shell/help echo --help +Usage: help echo + +Regular command help + +🚀 additional command help + +Arguments: + Regular argument help + +Flags: + -h, --help Show context-sensitive help. + --flag Regular flag help +``` + +#### Showing an _argument_'s detailed help + +Custom help will only be shown for _positional arguments with named fields_ ([see the README section on positional arguments for more details on what that means](../../../README.md#branching-positional-arguments)) + +**Contextual argument help** + +```bash + $ go run ./_examples/shell/help msg --help +Usage: help echo + +Regular argument help + +📣 additional argument help + +Flags: + -h, --help Show context-sensitive help. + --flag Regular flag help +``` + +## Command handling + +There are two ways to handle commands in Kong. + +### Switch on the command string + +When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example: + +There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/commandstring/main.go). + +eg. + +```go +package main + +import "github.com/alecthomas/kong" + +var CLI struct { + Rm struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"` + } `cmd:"" help:"Remove files."` + + Ls struct { + Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"` + } `cmd:"" help:"List paths."` +} + +func main() { + ctx := kong.Parse(&CLI) + switch ctx.Command() { + case "rm ": + case "ls": + default: + panic(ctx.Command()) + } +} +``` + +This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile. + +### Attach a `Run(...) error` method to each command + +A more robust approach is to break each command out into their own structs: + +1. Break leaf commands out into separate structs. +2. Attach a `Run(...) error` method to all leaf commands. +3. Call `kong.Kong.Parse()` to obtain a `kong.Context`. +4. Call `kong.Context.Run(bindings...)` to call the selected parsed command. + +Once a command node is selected by Kong it will search from that node back to the root. Each +encountered command node with a `Run(...) error` will be called in reverse order. This allows +sub-trees to be re-used fairly conveniently. + +In addition to values bound with the `kong.Bind(...)` option, any values +passed through to `kong.Context.Run(...)` are also bindable to the target's +`Run()` arguments. + +Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`. + +There's a full example emulating part of the Docker CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker). + +eg. + +```go +type Context struct { + Debug bool +} + +type RmCmd struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"` +} + +func (r *RmCmd) Run(ctx *Context) error { + fmt.Println("rm", r.Paths) + return nil +} + +type LsCmd struct { + Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"` +} + +func (l *LsCmd) Run(ctx *Context) error { + fmt.Println("ls", l.Paths) + return nil +} + +var cli struct { + Debug bool `help:"Enable debug mode."` + + Rm RmCmd `cmd:"" help:"Remove files."` + Ls LsCmd `cmd:"" help:"List paths."` +} + +func main() { + ctx := kong.Parse(&cli) + // Call the Run() method of the selected parsed command. + err := ctx.Run(&Context{Debug: cli.Debug}) + ctx.FatalIfErrorf(err) +} + +``` + +## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option + +If a node in the grammar has a `BeforeReset(...)`, `BeforeResolve +(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those +methods will be called before values are reset, before validation/assignment, +and after validation/assignment, respectively. + +The `--help` flag is implemented with a `BeforeReset` hook. + +Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`. + +eg. + +```go +// A flag with a hook that, if triggered, will set the debug loggers output to stdout. +type debugFlag bool + +func (d debugFlag) BeforeApply(logger *log.Logger) error { + logger.SetOutput(os.Stdout) + return nil +} + +var cli struct { + Debug debugFlag `help:"Enable debug logging."` +} + +func main() { + // Debug logger going to discard. + logger := log.New(ioutil.Discard, "", log.LstdFlags) + + ctx := kong.Parse(&cli, kong.Bind(logger)) + + // ... +} +``` + +Another example of using hooks is load the env-file: + +```go +package main + +import ( + "fmt" + "github.com/alecthomas/kong" + "github.com/joho/godotenv" +) + +type EnvFlag string + +// BeforeResolve loads env file. +func (c EnvFlag) BeforeReset(ctx *kong.Context, trace *kong.Path) error { + path := string(ctx.FlagValue(trace.Flag).(EnvFlag)) // nolint + path = kong.ExpandPath(path) + if err := godotenv.Load(path); err != nil { + return err + } + return nil +} + +var CLI struct { + EnvFile EnvFlag + Flag `env:"FLAG"` +} + +func main() { + _ = kong.Parse(&CLI) + fmt.Println(CLI.Flag) +} +``` + +## Flags + +Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure _not_ tagged with `cmd` or `arg` will be a flag. Flags are optional by default. + +eg. The command-line `app [--flag="foo"]` can be represented by the following. + +```go +type CLI struct { + Flag string +} +``` + +## Commands and sub-commands + +Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands. + +eg. The following struct represents the CLI structure `command [--flag="str"] sub-command`. + +```go +type CLI struct { + Command struct { + Flag string + + SubCommand struct { + } `cmd` + } `cmd` +} +``` + +If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments. If a sub-command is tagged with `default:"withargs"` it will be selected even if there are further arguments or flags and those arguments or flags are valid for the sub-command. This allows the user to omit the sub-command name on the CLI if its arguments/flags are not ambiguous with the sibling commands or flags. + +## Branching positional arguments + +In addition to sub-commands, structs can also be configured as branching positional arguments. + +This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure: + + app rename to + +Can be represented with the following: + +```go +var CLI struct { + Rename struct { + Name struct { + Name string `arg` // <-- NOTE: identical name to enclosing struct field. + To struct { + Name struct { + Name string `arg` + } `arg` + } `cmd` + } `arg` + } `cmd` +} +``` + +This looks a little verbose in this contrived example, but typically this will not be the case. + +## Positional arguments + +If a field is tagged with `arg:""` it will be treated as the final positional +value to be parsed on the command line. By default positional arguments are +required, but specifying `optional:""` will alter this. + +If a positional argument is a slice, all remaining arguments will be appended +to that slice. + +## Slices + +Slice values are treated specially. First the input is split on the `sep:""` tag (defaults to `,`), then each element is parsed by the slice element type and appended to the slice. If the same value is encountered multiple times, elements continue to be appended. + +To represent the following command-line: + + cmd ls ... + +You would use the following: + +```go +var CLI struct { + Ls struct { + Files []string `arg:"" type:"existingfile"` + } `cmd` +} +``` + +## Maps + +Maps are similar to slices except that only one key/value pair can be assigned per value, and the `sep` tag denotes the assignment character and defaults to `=`. + +To represent the following command-line: + + cmd config set = = ... + +You would use the following: + +```go +var CLI struct { + Config struct { + Set struct { + Config map[string]float64 `arg:"" type:"file:"` + } `cmd` + } `cmd` +} +``` + +For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`. + +## Pointers + +Pointers work like the underlying type, except that you can differentiate between the presence of the zero value and no value being supplied. + +For example: + +```go +var CLI struct { + Foo *int +} +``` + +Would produce a nil value for `Foo` if no `--foo` argument is supplied, but would have a pointer to the value 0 if the argument `--foo=0` was supplied. + +## Nested data structure + +Kong support a nested data structure as well with `embed:""`. You can combine `embed:""` with `prefix:""`: + +```go +var CLI struct { + Logging struct { + Level string `enum:"debug,info,warn,error" default:"info"` + Type string `enum:"json,console" default:"console"` + } `embed:"" prefix:"logging."` +} +``` + +This configures Kong to accept flags `--logging.level` and `--logging.type`. + +## Custom named decoders + +Kong includes a number of builtin custom type mappers. These can be used by +specifying the tag `type:""`. They are registered with the option +function `NamedMapper(name, mapper)`. + +| Name | Description | +| -------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `path` | A path. ~ expansion is applied. `-` is accepted for stdout, and will be passed unaltered. | +| `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. | +| `existingdir` | An existing directory. ~ expansion is applied. | +| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`. | +| `filecontent` | Read the file at path into the field. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. | + +Slices and maps treat type tags specially. For slices, the `type:""` tag +specifies the element type. For maps, the tag has the format +`tag:"[]:[]"` where either may be omitted. + +## Supported field types + +## Custom decoders (mappers) + +Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces +for decoding values. Kong also includes builtin support for many common Go types: + +| Type | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------- | +| `time.Duration` | Populated using `time.ParseDuration()`. | +| `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag. | +| `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user. | +| `*url.URL` | Populated with `url.Parse()`. | + +For more fine-grained control, if a field implements the +[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue) +interface it will be used to decode arguments into the field. + +## Supported tags + +Tags can be in two forms: + +1. Standard Go syntax, eg. `kong:"required,name='foo'"`. +2. Bare tags, eg. `required:"" name:"foo"` + +Both can coexist with standard Tag parsing. + +| Tag | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `cmd:""` | If present, struct is a command. | +| `arg:""` | If present, field is an argument. Required by default. | +| `env:"X,Y,..."` | Specify envars to use for default value. The envs are resolved in the declared order. The first value found is used. | +| `name:"X"` | Long name, for overriding field name. | +| `help:"X"` | Help text. | +| `type:"X"` | Specify [named types](#custom-named-decoders) to use. | +| `placeholder:"X"` | Placeholder text. | +| `default:"X"` | Default value. | +| `default:"1"` | On a command, make it the default. | +| `default:"withargs"` | On a command, make it the default and allow args/flags from that command | +| `short:"X"` | Short name, if flag. | +| `aliases:"X,Y"` | One or more aliases (for cmd). | +| `required:""` | If present, flag/arg is required. | +| `optional:""` | If present, flag/arg is optional. | +| `hidden:""` | If present, command or flag is hidden. | +| `negatable:""` | If present on a `bool` field, supports prefixing a flag with `--no-` to invert the default value | +| `format:"X"` | Format for parsing input, if supported. | +| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. | +| `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. | +| `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`. | +| `group:"X"` | Logical group for a flag or command. | +| `xor:"X,Y,..."` | Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with `required`, at least one of the `xor` group will be required. | +| `prefix:"X"` | Prefix for all sub-flags. | +| `envprefix:"X"` | Envar prefix for all sub-flags. | +| `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. | +| `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition. | +| `passthrough:""` | If present on a positional argument, it stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`. On a command it requires that the command contains only one argument of type `[]string` which is then filled with everything following the command, unparsed. | +| `-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` `` | + +## Plugins + +Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example: + +```go +var pluginOne struct { + PluginOneFlag string +} +var pluginTwo struct { + PluginTwoFlag string +} +var cli struct { + BaseFlag string + kong.Plugins +} +cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo} +``` + +Additionally if an interface type is embedded, it can also be populated with a Kong annotated struct. + +## Dynamic Commands + +While plugins give complete control over extending command-line interfaces, Kong +also supports dynamically adding commands via `kong.DynamicCommand()`. + +## Variable interpolation + +Kong supports limited variable interpolation into help strings, enum lists and +default values. + +Variables are in the form: + + ${} + ${=} + +Variables are set with the `Vars{"key": "value", ...}` option. Undefined +variable references in the grammar without a default will result in an error at +construction time. + +Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that +node and all children. This is useful for composition by allowing the same struct to be reused. + +When interpolating into flag or argument help strings, some extra variables +are defined from the value itself: + + ${default} + ${enum} + +For flags with associated environment variables, the variable `${env}` can be +interpolated into the help string. In the absence of this variable in the +help string, Kong will append `($$${env})` to the help string. + +eg. + +```go +type cli struct { + Config string `type:"path" default:"${config_file}"` +} + +func main() { + kong.Parse(&cli, + kong.Vars{ + "config_file": "~/.app.conf", + }) +} +``` + +## Validation + +Kong does validation on the structure of a command-line, but also supports +extensible validation. Any node in the tree may implement the following +interface: + +```go +type Validatable interface { + Validate() error + } +``` + +If one of these nodes is in the active command-line it will be called during +normal validation. + +## Modifying Kong's behaviour + +Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`. + +The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option). + +### `Name(help)` and `Description(help)` - set the application name description + +Set the application name and/or description. + +The name of the application will default to the binary name, but can be overridden with `Name(name)`. + +As with all help in Kong, text will be wrapped to the terminal. + +### `Configuration(loader, paths...)` - load defaults from configuration files + +This option provides Kong with support for loading defaults from a set of configuration files. Each file is opened, if possible, and the loader called to create a resolver for that file. + +eg. + +```go +kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json")) +``` + +[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L206) for an example of how the JSON file is structured. + +#### List of Configuration Loaders + +- [YAML](https://github.com/alecthomas/kong-yaml) +- [HCL](https://github.com/alecthomas/kong-hcl) +- [TOML](https://github.com/alecthomas/kong-toml) +- [JSON](https://github.com/alecthomas/kong) + +### `Resolver(...)` - support for default values from external sources + +Resolvers are Kong's extension point for providing default values from external sources. As an example, support for environment variables via the `env` tag is provided by a resolver. There's also a builtin resolver for JSON configuration files. + +Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go). + +### `*Mapper(...)` - customising how the command-line is mapped to Go values + +Command-line arguments are mapped to Go values via the Mapper interface: + +```go +// A Mapper represents how a field is mapped from command-line values to Go. +// +// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag. +// +// Additionally, if a type implements the MapperValue interface, it will be used. +type Mapper interface { + // Decode ctx.Value with ctx.Scanner into target. + Decode(ctx *DecodeContext, target reflect.Value) error +} +``` + +All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default. Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways: + +1. `NamedMapper(string, Mapper)` and using the tag key `type:""`. +2. `KindMapper(reflect.Kind, Mapper)`. +3. `TypeMapper(reflect.Type, Mapper)`. +4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar. + +### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help + +The default help output is usually sufficient, but if not there are two solutions. + +1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details). +2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example. +3. Use `ValueFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments. +4. Use `Groups([]Group)` if you want to customize group titles or add a header. + +### `Bind(...)` - bind values for callback hooks and Run() methods + +See the [section on hooks](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) for details. + +### Other options + +The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option). diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/build.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/build.go new file mode 100644 index 00000000000..e23c11518ec --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/build.go @@ -0,0 +1,352 @@ +package kong + +import ( + "fmt" + "reflect" + "strings" +) + +// Plugins are dynamically embedded command-line structures. +// +// Each element in the Plugins list *must* be a pointer to a structure. +type Plugins []interface{} + +func build(k *Kong, ast interface{}) (app *Application, err error) { + v := reflect.ValueOf(ast) + iv := reflect.Indirect(v) + if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct { + return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast) + } + + app = &Application{} + extraFlags := k.extraFlags() + seenFlags := map[string]bool{} + for _, flag := range extraFlags { + seenFlags[flag.Name] = true + } + + node, err := buildNode(k, iv, ApplicationNode, newEmptyTag(), seenFlags) + if err != nil { + return nil, err + } + if len(node.Positional) > 0 && len(node.Children) > 0 { + return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast) + } + app.Node = node + app.Node.Flags = append(extraFlags, app.Node.Flags...) + app.Tag = newEmptyTag() + app.Tag.Vars = k.vars + return app, nil +} + +func dashedString(s string) string { + return strings.Join(camelCase(s), "-") +} + +type flattenedField struct { + field reflect.StructField + value reflect.Value + tag *Tag +} + +func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) { + v = reflect.Indirect(v) + for i := 0; i < v.NumField(); i++ { + ft := v.Type().Field(i) + fv := v.Field(i) + tag, err := parseTag(v, ft) + if err != nil { + return nil, err + } + if tag.Ignored { + continue + } + // Assign group if it's not already set. + if tag.Group == "" { + tag.Group = ptag.Group + } + // Accumulate prefixes. + tag.Prefix = ptag.Prefix + tag.Prefix + tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix + // Combine parent vars. + tag.Vars = ptag.Vars.CloneWith(tag.Vars) + // Command and embedded structs can be pointers, so we hydrate them now. + if (tag.Cmd || tag.Embed) && ft.Type.Kind() == reflect.Ptr { + fv = reflect.New(ft.Type.Elem()).Elem() + v.FieldByIndex(ft.Index).Set(fv.Addr()) + } + if !ft.Anonymous && !tag.Embed { + if fv.CanSet() { + field := flattenedField{field: ft, value: fv, tag: tag} + out = append(out, field) + } + continue + } + + // Embedded type. + if fv.Kind() == reflect.Interface { + fv = fv.Elem() + } else if fv.Type() == reflect.TypeOf(Plugins{}) { + for i := 0; i < fv.Len(); i++ { + fields, ferr := flattenedFields(fv.Index(i).Elem(), tag) + if ferr != nil { + return nil, ferr + } + out = append(out, fields...) + } + continue + } + sub, err := flattenedFields(fv, tag) + if err != nil { + return nil, err + } + out = append(out, sub...) + } + return out, nil +} + +// Build a Node in the Kong data model. +// +// "v" is the value to create the node from, "typ" is the output Node type. +func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) { + node := &Node{ + Type: typ, + Target: v, + Tag: tag, + } + fields, err := flattenedFields(v, tag) + if err != nil { + return nil, err + } + +MAIN: + for _, field := range fields { + for _, r := range k.ignoreFields { + if r.MatchString(v.Type().Name() + "." + field.field.Name) { + continue MAIN + } + } + + ft := field.field + fv := field.value + + tag := field.tag + name := tag.Name + if name == "" { + name = tag.Prefix + k.flagNamer(ft.Name) + } else { + name = tag.Prefix + name + } + + if len(tag.Envs) != 0 { + for i := range tag.Envs { + tag.Envs[i] = tag.EnvPrefix + tag.Envs[i] + } + } + + // Nested structs are either commands or args, unless they implement the Mapper interface. + if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil { + typ := CommandNode + if tag.Arg { + typ = ArgumentNode + } + err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags) + } else { + err = buildField(k, node, v, ft, fv, tag, name, seenFlags) + } + if err != nil { + return nil, err + } + } + + // Validate if there are no duplicate names + if err := checkDuplicateNames(node, v); err != nil { + return nil, err + } + + // "Unsee" flags. + for _, flag := range node.Flags { + delete(seenFlags, "--"+flag.Name) + if flag.Short != 0 { + delete(seenFlags, "-"+string(flag.Short)) + } + } + + if err := validatePositionalArguments(node); err != nil { + return nil, err + } + + return node, nil +} + +func validatePositionalArguments(node *Node) error { + var last *Value + for i, curr := range node.Positional { + if last != nil { + // Scan through argument positionals to ensure optional is never before a required. + if !last.Required && curr.Required { + return fmt.Errorf("%s: required %q cannot come after optional %q", node.FullPath(), curr.Name, last.Name) + } + + // Cumulative argument needs to be last. + if last.IsCumulative() { + return fmt.Errorf("%s: argument %q cannot come after cumulative %q", node.FullPath(), curr.Name, last.Name) + } + } + + last = curr + curr.Position = i + } + + return nil +} + +func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error { + child, err := buildNode(k, fv, typ, newEmptyTag(), seenFlags) + if err != nil { + return err + } + child.Name = name + child.Tag = tag + child.Parent = node + child.Help = tag.Help + child.Hidden = tag.Hidden + child.Group = buildGroupForKey(k, tag.Group) + child.Aliases = tag.Aliases + + if provider, ok := fv.Addr().Interface().(HelpProvider); ok { + child.Detail = provider.Help() + } + + // A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that + // a positional argument is provided to the child, and move it to the branching argument field. + if tag.Arg { + if len(child.Positional) == 0 { + return failField(v, ft, "positional branch must have at least one child positional argument named %q", name) + } + if child.Positional[0].Name != name { + return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name) + } + + child.Argument = child.Positional[0] + child.Positional = child.Positional[1:] + if child.Help == "" { + child.Help = child.Argument.Help + } + } else { + if tag.HasDefault { + if node.DefaultCmd != nil { + return failField(v, ft, "can't have more than one default command under %s", node.Summary()) + } + if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) { + return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary()) + } + node.DefaultCmd = child + } + if tag.Passthrough { + if len(child.Children) > 0 || len(child.Flags) > 0 { + return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary()) + } + if len(child.Positional) != 1 { + return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary()) + } + if !checkPassthroughArg(child.Positional[0].Target) { + return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary()) + } + child.Passthrough = true + } + } + node.Children = append(node.Children, child) + + if len(child.Positional) > 0 && len(child.Children) > 0 { + return failField(v, ft, "can't mix positional arguments and branching arguments") + } + + return nil +} + +func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error { + mapper := k.registry.ForNamedValue(tag.Type, fv) + if mapper == nil { + return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type) + } + + value := &Value{ + Name: name, + Help: tag.Help, + OrigHelp: tag.Help, + HasDefault: tag.HasDefault, + Default: tag.Default, + DefaultValue: reflect.New(fv.Type()).Elem(), + Mapper: mapper, + Tag: tag, + Target: fv, + Enum: tag.Enum, + Passthrough: tag.Passthrough, + + // Flags are optional by default, and args are required by default. + Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional), + Format: tag.Format, + } + + if tag.Arg { + node.Positional = append(node.Positional, value) + } else { + if seenFlags["--"+value.Name] { + return failField(v, ft, "duplicate flag --%s", value.Name) + } + seenFlags["--"+value.Name] = true + if tag.Short != 0 { + if seenFlags["-"+string(tag.Short)] { + return failField(v, ft, "duplicate short flag -%c", tag.Short) + } + seenFlags["-"+string(tag.Short)] = true + } + flag := &Flag{ + Value: value, + Short: tag.Short, + PlaceHolder: tag.PlaceHolder, + Envs: tag.Envs, + Group: buildGroupForKey(k, tag.Group), + Xor: tag.Xor, + Hidden: tag.Hidden, + } + value.Flag = flag + node.Flags = append(node.Flags, flag) + } + return nil +} + +func buildGroupForKey(k *Kong, key string) *Group { + if key == "" { + return nil + } + for _, group := range k.groups { + if group.Key == key { + return &group + } + } + + // No group provided with kong.ExplicitGroups. We create one ad-hoc for this key. + return &Group{ + Key: key, + Title: key, + } +} + +func checkDuplicateNames(node *Node, v reflect.Value) error { + seenNames := make(map[string]struct{}) + for _, node := range node.Children { + if _, ok := seenNames[node.Name]; ok { + name := v.Type().Name() + if name == "" { + name = "" + } + return fmt.Errorf("duplicate command name %q in command %q", node.Name, name) + } + + seenNames[node.Name] = struct{}{} + } + + return nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/callbacks.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/callbacks.go new file mode 100644 index 00000000000..8771a3ecba9 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/callbacks.go @@ -0,0 +1,129 @@ +package kong + +import ( + "fmt" + "reflect" + "strings" +) + +type bindings map[reflect.Type]func() (reflect.Value, error) + +func (b bindings) String() string { + out := []string{} + for k := range b { + out = append(out, k.String()) + } + return "bindings{" + strings.Join(out, ", ") + "}" +} + +func (b bindings) add(values ...interface{}) bindings { + for _, v := range values { + v := v + b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil } + } + return b +} + +func (b bindings) addTo(impl, iface interface{}) { + valueOf := reflect.ValueOf(impl) + b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil } +} + +func (b bindings) addProvider(provider interface{}) error { + pv := reflect.ValueOf(provider) + t := pv.Type() + if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() { + return fmt.Errorf("%T must be a function with the signature func()(T, error)", provider) + } + rt := pv.Type().Out(0) + b[rt] = func() (reflect.Value, error) { + out := pv.Call(nil) + errv := out[1] + var err error + if !errv.IsNil() { + err = errv.Interface().(error) // nolint + } + return out[0], err + } + return nil +} + +// Clone and add values. +func (b bindings) clone() bindings { + out := make(bindings, len(b)) + for k, v := range b { + out[k] = v + } + return out +} + +func (b bindings) merge(other bindings) bindings { + for k, v := range other { + b[k] = v + } + return b +} + +func getMethod(value reflect.Value, name string) reflect.Value { + method := value.MethodByName(name) + if !method.IsValid() { + if value.CanAddr() { + method = value.Addr().MethodByName(name) + } + } + return method +} + +func callFunction(f reflect.Value, bindings bindings) error { + if f.Kind() != reflect.Func { + return fmt.Errorf("expected function, got %s", f.Type()) + } + in := []reflect.Value{} + t := f.Type() + if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) { + return fmt.Errorf("return value of %s must implement \"error\"", t) + } + for i := 0; i < t.NumIn(); i++ { + pt := t.In(i) + if argf, ok := bindings[pt]; ok { + argv, err := argf() + if err != nil { + return err + } + in = append(in, argv) + } else { + return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt) + } + } + out := f.Call(in) + if out[0].IsNil() { + return nil + } + return out[0].Interface().(error) // nolint +} + +func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) { + if f.Kind() != reflect.Func { + return nil, fmt.Errorf("expected function, got %s", f.Type()) + } + in := []reflect.Value{} + t := f.Type() + for i := 0; i < t.NumIn(); i++ { + pt := t.In(i) + if argf, ok := bindings[pt]; ok { + argv, err := argf() + if err != nil { + return nil, err + } + in = append(in, argv) + } else { + return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt) + } + } + outv := f.Call(in) + out = make([]any, len(outv)) + for i, v := range outv { + out[i] = v.Interface() + } + return out, nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/camelcase.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/camelcase.go new file mode 100644 index 00000000000..acf29f75770 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/camelcase.go @@ -0,0 +1,90 @@ +package kong + +// NOTE: This code is from https://github.com/fatih/camelcase. MIT license. + +import ( + "unicode" + "unicode/utf8" +) + +// Split splits the camelcase word and returns a list of words. It also +// supports digits. Both lower camel case and upper camel case are supported. +// For more info please check: http://en.wikipedia.org/wiki/CamelCase +// +// Examples +// +// "" => [""] +// "lowercase" => ["lowercase"] +// "Class" => ["Class"] +// "MyClass" => ["My", "Class"] +// "MyC" => ["My", "C"] +// "HTML" => ["HTML"] +// "PDFLoader" => ["PDF", "Loader"] +// "AString" => ["A", "String"] +// "SimpleXMLParser" => ["Simple", "XML", "Parser"] +// "vimRPCPlugin" => ["vim", "RPC", "Plugin"] +// "GL11Version" => ["GL", "11", "Version"] +// "99Bottles" => ["99", "Bottles"] +// "May5" => ["May", "5"] +// "BFG9000" => ["BFG", "9000"] +// "BöseÜberraschung" => ["Böse", "Überraschung"] +// "Two spaces" => ["Two", " ", "spaces"] +// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] +// +// Splitting rules +// +// 1) If string is not valid UTF-8, return it without splitting as +// single item array. +// 2) Assign all unicode characters into one of 4 sets: lower case +// letters, upper case letters, numbers, and all other characters. +// 3) Iterate through characters of string, introducing splits +// between adjacent characters that belong to different sets. +// 4) Iterate through array of split strings, and if a given string +// is upper case: +// if subsequent string is lower case: +// move last character of upper case string to beginning of +// lower case string +func camelCase(src string) (entries []string) { + // don't split invalid utf8 + if !utf8.ValidString(src) { + return []string{src} + } + entries = []string{} + var runes [][]rune + lastClass := 0 + // split into fields based on class of unicode character + for _, r := range src { + var class int + switch { + case unicode.IsLower(r): + class = 1 + case unicode.IsUpper(r): + class = 2 + case unicode.IsDigit(r): + class = 3 + default: + class = 4 + } + if class == lastClass { + runes[len(runes)-1] = append(runes[len(runes)-1], r) + } else { + runes = append(runes, []rune{r}) + } + lastClass = class + } + // handle upper case -> lower case sequences, e.g. + // "PDFL", "oader" -> "PDF", "Loader" + for i := 0; i < len(runes)-1; i++ { + if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { + runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) + runes[i] = runes[i][:len(runes[i])-1] + } + } + // construct []string from results + for _, s := range runes { + if len(s) > 0 { + entries = append(entries, string(s)) + } + } + return entries +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/context.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/context.go new file mode 100644 index 00000000000..0b41079fa3a --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/context.go @@ -0,0 +1,1010 @@ +package kong + +import ( + "errors" + "fmt" + "os" + "reflect" + "sort" + "strconv" + "strings" +) + +// Path records the nodes and parsed values from the current command-line. +type Path struct { + Parent *Node + + // One of these will be non-nil. + App *Application + Positional *Positional + Flag *Flag + Argument *Argument + Command *Command + + // Flags added by this node. + Flags []*Flag + + // True if this Path element was created as the result of a resolver. + Resolved bool +} + +// Node returns the Node associated with this Path, or nil if Path is a non-Node. +func (p *Path) Node() *Node { + switch { + case p.App != nil: + return p.App.Node + + case p.Argument != nil: + return p.Argument + + case p.Command != nil: + return p.Command + } + return nil +} + +// Visitable returns the Visitable for this path element. +func (p *Path) Visitable() Visitable { + switch { + case p.App != nil: + return p.App + + case p.Argument != nil: + return p.Argument + + case p.Command != nil: + return p.Command + + case p.Flag != nil: + return p.Flag + + case p.Positional != nil: + return p.Positional + } + return nil +} + +// Context contains the current parse context. +type Context struct { + *Kong + // A trace through parsed nodes. + Path []*Path + // Original command-line arguments. + Args []string + // Error that occurred during trace, if any. + Error error + + values map[*Value]reflect.Value // Temporary values during tracing. + bindings bindings + resolvers []Resolver // Extra context-specific resolvers. + scan *Scanner +} + +// Trace path of "args" through the grammar tree. +// +// The returned Context will include a Path of all commands, arguments, positionals and flags. +// +// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(), +// Validate() and Apply(). +func Trace(k *Kong, args []string) (*Context, error) { + c := &Context{ + Kong: k, + Args: args, + Path: []*Path{ + {App: k.Model, Flags: k.Model.Flags}, + }, + values: map[*Value]reflect.Value{}, + scan: Scan(args...), + bindings: bindings{}, + } + c.Error = c.trace(c.Model.Node) + return c, nil +} + +// Bind adds bindings to the Context. +func (c *Context) Bind(args ...interface{}) { + c.bindings.add(args...) +} + +// BindTo adds a binding to the Context. +// +// This will typically have to be called like so: +// +// BindTo(impl, (*MyInterface)(nil)) +func (c *Context) BindTo(impl, iface interface{}) { + c.bindings.addTo(impl, iface) +} + +// BindToProvider allows binding of provider functions. +// +// This is useful when the Run() function of different commands require different values that may +// not all be initialisable from the main() function. +func (c *Context) BindToProvider(provider interface{}) error { + return c.bindings.addProvider(provider) +} + +// Value returns the value for a particular path element. +func (c *Context) Value(path *Path) reflect.Value { + switch { + case path.Positional != nil: + return c.values[path.Positional] + case path.Flag != nil: + return c.values[path.Flag.Value] + case path.Argument != nil: + return c.values[path.Argument.Argument] + } + panic("can only retrieve value for flag, argument or positional") +} + +// Selected command or argument. +func (c *Context) Selected() *Node { + var selected *Node + for _, path := range c.Path { + switch { + case path.Command != nil: + selected = path.Command + case path.Argument != nil: + selected = path.Argument + } + } + return selected +} + +// Empty returns true if there were no arguments provided. +func (c *Context) Empty() bool { + for _, path := range c.Path { + if !path.Resolved && path.App == nil { + return false + } + } + return true +} + +// Validate the current context. +func (c *Context) Validate() error { // nolint: gocyclo + err := Visit(c.Model, func(node Visitable, next Next) error { + switch node := node.(type) { + case *Value: + ok := atLeastOneEnvSet(node.Tag.Envs) + if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) { + if err := checkEnum(node, node.Target); err != nil { + return err + } + } + + case *Flag: + ok := atLeastOneEnvSet(node.Tag.Envs) + if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) { + if err := checkEnum(node.Value, node.Target); err != nil { + return err + } + } + } + return next(nil) + }) + if err != nil { + return err + } + for _, el := range c.Path { + var ( + value reflect.Value + desc string + ) + switch node := el.Visitable().(type) { + case *Value: + value = node.Target + desc = node.ShortSummary() + + case *Flag: + value = node.Target + desc = node.ShortSummary() + + case *Application: + value = node.Target + desc = "" + + case *Node: + value = node.Target + desc = node.Path() + } + if validate := isValidatable(value); validate != nil { + if err := validate.Validate(); err != nil { + if desc != "" { + return fmt.Errorf("%s: %w", desc, err) + } + return err + } + } + } + for _, resolver := range c.combineResolvers() { + if err := resolver.Validate(c.Model); err != nil { + return err + } + } + for _, path := range c.Path { + var value *Value + switch { + case path.Flag != nil: + value = path.Flag.Value + + case path.Positional != nil: + value = path.Positional + } + if value != nil && value.Tag.Enum != "" { + if err := checkEnum(value, value.Target); err != nil { + return err + } + } + if err := checkMissingFlags(path.Flags); err != nil { + return err + } + } + // Check the terminal node. + node := c.Selected() + if node == nil { + node = c.Model.Node + } + + // Find deepest positional argument so we can check if all required positionals have been provided. + positionals := 0 + for _, path := range c.Path { + if path.Positional != nil { + positionals = path.Positional.Position + 1 + } + } + + if err := checkMissingChildren(node); err != nil { + return err + } + if err := checkMissingPositionals(positionals, node.Positional); err != nil { + return err + } + if err := checkXorDuplicates(c.Path); err != nil { + return err + } + + if node.Type == ArgumentNode { + value := node.Argument + if value.Required && !value.Set { + return fmt.Errorf("%s is required", node.Summary()) + } + } + return nil +} + +// Flags returns the accumulated available flags. +func (c *Context) Flags() (flags []*Flag) { + for _, trace := range c.Path { + flags = append(flags, trace.Flags...) + } + return +} + +// Command returns the full command path. +func (c *Context) Command() string { + command := []string{} + for _, trace := range c.Path { + switch { + case trace.Positional != nil: + command = append(command, "<"+trace.Positional.Name+">") + + case trace.Argument != nil: + command = append(command, "<"+trace.Argument.Name+">") + + case trace.Command != nil: + command = append(command, trace.Command.Name) + } + } + return strings.Join(command, " ") +} + +// AddResolver adds a context-specific resolver. +// +// This is most useful in the BeforeResolve() hook. +func (c *Context) AddResolver(resolver Resolver) { + c.resolvers = append(c.resolvers, resolver) +} + +// FlagValue returns the set value of a flag if it was encountered and exists, or its default value. +func (c *Context) FlagValue(flag *Flag) interface{} { + for _, trace := range c.Path { + if trace.Flag == flag { + v, ok := c.values[trace.Flag.Value] + if !ok { + break + } + return v.Interface() + } + } + if flag.Target.IsValid() { + return flag.Target.Interface() + } + return flag.DefaultValue.Interface() +} + +// Reset recursively resets values to defaults (as specified in the grammar) or the zero value. +func (c *Context) Reset() error { + return Visit(c.Model.Node, func(node Visitable, next Next) error { + if value, ok := node.(*Value); ok { + return next(value.Reset()) + } + return next(nil) + }) +} + +func (c *Context) endParsing() { + args := []string{} + for { + token := c.scan.Pop() + if token.Type == EOLToken { + break + } + args = append(args, token.String()) + } + // Note: tokens must be pushed in reverse order. + for i := range args { + c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken) + } +} + +func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo + positional := 0 + node.Active = true + + flags := []*Flag{} + flagNode := node + if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { + // Add flags of the default command if the current node has one + // and that default command allows args / flags without explicitly + // naming the command on the CLI. + flagNode = node.DefaultCmd + } + for _, group := range flagNode.AllFlags(false) { + flags = append(flags, group...) + } + + if node.Passthrough { + c.endParsing() + } + + for !c.scan.Peek().IsEOL() { + token := c.scan.Peek() + switch token.Type { + case UntypedToken: + switch v := token.Value.(type) { + case string: + + switch { + case v == "-": + fallthrough + default: // nolint + c.scan.Pop() + c.scan.PushTyped(token.Value, PositionalArgumentToken) + + // Indicates end of parsing. All remaining arguments are treated as positional arguments only. + case v == "--": + c.scan.Pop() + c.endParsing() + + // Long flag. + case strings.HasPrefix(v, "--"): + c.scan.Pop() + // Parse it and push the tokens. + parts := strings.SplitN(v[2:], "=", 2) + if len(parts) > 1 { + c.scan.PushTyped(parts[1], FlagValueToken) + } + c.scan.PushTyped(parts[0], FlagToken) + + // Short flag. + case strings.HasPrefix(v, "-"): + c.scan.Pop() + // Note: tokens must be pushed in reverse order. + if tail := v[2:]; tail != "" { + c.scan.PushTyped(tail, ShortFlagTailToken) + } + c.scan.PushTyped(v[1:2], ShortFlagToken) + } + default: + c.scan.Pop() + c.scan.PushTyped(token.Value, PositionalArgumentToken) + } + + case ShortFlagTailToken: + c.scan.Pop() + // Note: tokens must be pushed in reverse order. + if tail := token.String()[1:]; tail != "" { + c.scan.PushTyped(tail, ShortFlagTailToken) + } + c.scan.PushTyped(token.String()[0:1], ShortFlagToken) + + case FlagToken: + if err := c.parseFlag(flags, token.String()); err != nil { + return err + } + + case ShortFlagToken: + if err := c.parseFlag(flags, token.String()); err != nil { + return err + } + + case FlagValueToken: + return fmt.Errorf("unexpected flag argument %q", token.Value) + + case PositionalArgumentToken: + candidates := []string{} + + // Ensure we've consumed all positional arguments. + if positional < len(node.Positional) { + arg := node.Positional[positional] + + if arg.Passthrough { + c.endParsing() + } + + arg.Active = true + err := arg.Parse(c.scan, c.getValue(arg)) + if err != nil { + return err + } + c.Path = append(c.Path, &Path{ + Parent: node, + Positional: arg, + }) + positional++ + break + } + + // Assign token value to a branch name if tagged as an alias + // An alias will be ignored in the case of an existing command + cmds := make(map[string]bool) + for _, branch := range node.Children { + if branch.Type == CommandNode { + cmds[branch.Name] = true + } + } + for _, branch := range node.Children { + for _, a := range branch.Aliases { + _, ok := cmds[a] + if token.Value == a && !ok { + token.Value = branch.Name + break + } + } + } + + // After positional arguments have been consumed, check commands next... + for _, branch := range node.Children { + if branch.Type == CommandNode && !branch.Hidden { + candidates = append(candidates, branch.Name) + } + if branch.Type == CommandNode && branch.Name == token.Value { + c.scan.Pop() + c.Path = append(c.Path, &Path{ + Parent: node, + Command: branch, + Flags: branch.Flags, + }) + return c.trace(branch) + } + } + + // Finally, check arguments. + for _, branch := range node.Children { + if branch.Type == ArgumentNode { + arg := branch.Argument + if err := arg.Parse(c.scan, c.getValue(arg)); err == nil { + c.Path = append(c.Path, &Path{ + Parent: node, + Argument: branch, + Flags: branch.Flags, + }) + return c.trace(branch) + } + } + } + + // If there is a default command that allows args and nothing else + // matches, take the branch of the default command + if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { + c.Path = append(c.Path, &Path{ + Parent: node, + Command: node.DefaultCmd, + Flags: node.DefaultCmd.Flags, + }) + return c.trace(node.DefaultCmd) + } + + return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token) + default: + return fmt.Errorf("unexpected token %s", token) + } + } + return c.maybeSelectDefault(flags, node) +} + +// End of the line, check for a default command, but only if we're not displaying help, +// otherwise we'd only ever display the help for the default command. +func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error { + for _, flag := range flags { + if flag.Name == "help" && flag.Set { + return nil + } + } + if node.DefaultCmd != nil { + c.Path = append(c.Path, &Path{ + Parent: node.DefaultCmd, + Command: node.DefaultCmd, + Flags: node.DefaultCmd.Flags, + }) + } + return nil +} + +// Resolve walks through the traced path, applying resolvers to any unset flags. +func (c *Context) Resolve() error { + resolvers := c.combineResolvers() + if len(resolvers) == 0 { + return nil + } + + inserted := []*Path{} + for _, path := range c.Path { + for _, flag := range path.Flags { + // Flag has already been set on the command-line. + if _, ok := c.values[flag.Value]; ok { + continue + } + + // Pick the last resolved value. + var selected interface{} + for _, resolver := range resolvers { + s, err := resolver.Resolve(c, path, flag) + if err != nil { + return fmt.Errorf("%s: %w", flag.ShortSummary(), err) + } + if s == nil { + continue + } + selected = s + } + + if selected == nil { + continue + } + + scan := Scan().PushTyped(selected, FlagValueToken) + delete(c.values, flag.Value) + err := flag.Parse(scan, c.getValue(flag.Value)) + if err != nil { + return err + } + inserted = append(inserted, &Path{ + Flag: flag, + Resolved: true, + }) + } + } + c.Path = append(c.Path, inserted...) + return nil +} + +// Combine application-level resolvers and context resolvers. +func (c *Context) combineResolvers() []Resolver { + resolvers := []Resolver{} + resolvers = append(resolvers, c.Kong.resolvers...) + resolvers = append(resolvers, c.resolvers...) + return resolvers +} + +func (c *Context) getValue(value *Value) reflect.Value { + v, ok := c.values[value] + if !ok { + v = reflect.New(value.Target.Type()).Elem() + switch v.Kind() { + case reflect.Ptr: + v.Set(reflect.New(v.Type().Elem())) + case reflect.Slice: + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + case reflect.Map: + v.Set(reflect.MakeMap(v.Type())) + default: + } + c.values[value] = v + } + return v +} + +// ApplyDefaults if they are not already set. +func (c *Context) ApplyDefaults() error { + return Visit(c.Model.Node, func(node Visitable, next Next) error { + var value *Value + switch node := node.(type) { + case *Flag: + value = node.Value + case *Node: + value = node.Argument + case *Value: + value = node + default: + } + if value != nil { + if err := value.ApplyDefault(); err != nil { + return err + } + } + return next(nil) + }) +} + +// Apply traced context to the target grammar. +func (c *Context) Apply() (string, error) { + path := []string{} + + for _, trace := range c.Path { + var value *Value + switch { + case trace.App != nil: + case trace.Argument != nil: + path = append(path, "<"+trace.Argument.Name+">") + value = trace.Argument.Argument + case trace.Command != nil: + path = append(path, trace.Command.Name) + case trace.Flag != nil: + value = trace.Flag.Value + case trace.Positional != nil: + path = append(path, "<"+trace.Positional.Name+">") + value = trace.Positional + default: + panic("unsupported path ?!") + } + if value != nil { + value.Apply(c.getValue(value)) + } + } + + return strings.Join(path, " "), nil +} + +func flipBoolValue(value reflect.Value) error { + if value.Kind() == reflect.Bool { + value.SetBool(!value.Bool()) + return nil + } + + if value.Kind() == reflect.Ptr { + if !value.IsNil() { + return flipBoolValue(value.Elem()) + } + return nil + } + + return fmt.Errorf("cannot negate a value of %s", value.Type().String()) +} + +func (c *Context) parseFlag(flags []*Flag, match string) (err error) { + candidates := []string{} + for _, flag := range flags { + long := "--" + flag.Name + short := "-" + string(flag.Short) + neg := "--no-" + flag.Name + candidates = append(candidates, long) + if flag.Short != 0 { + candidates = append(candidates, short) + } + if short != match && long != match && !(match == neg && flag.Tag.Negatable) { + continue + } + // Found a matching flag. + c.scan.Pop() + if match == neg && flag.Tag.Negatable { + flag.Negated = true + } + err := flag.Parse(c.scan, c.getValue(flag.Value)) + if err != nil { + var expected *expectedError + if errors.As(err, &expected) && expected.token.InferredType().IsAny(FlagToken, ShortFlagToken) { + return fmt.Errorf("%s; perhaps try %s=%q?", err.Error(), flag.ShortSummary(), expected.token) + } + return err + } + if flag.Negated { + value := c.getValue(flag.Value) + err := flipBoolValue(value) + if err != nil { + return err + } + flag.Value.Apply(value) + } + c.Path = append(c.Path, &Path{Flag: flag}) + return nil + } + return findPotentialCandidates(match, candidates, "unknown flag %s", match) +} + +// Call an arbitrary function filling arguments with bound values. +func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) { + fv := reflect.ValueOf(fn) + bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) //nolint:govet + return callAnyFunction(fv, bindings) +} + +// RunNode calls the Run() method on an arbitrary node. +// +// This is useful in conjunction with Visit(), for dynamically running commands. +// +// Any passed values will be bindable to arguments of the target Run() method. Additionally, +// all parent nodes in the command structure will be bound. +func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) { + type targetMethod struct { + node *Node + method reflect.Value + binds bindings + } + methodBinds := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) + methods := []targetMethod{} + for i := 0; node != nil; i, node = i+1, node.Parent { + method := getMethod(node.Target, "Run") + methodBinds = methodBinds.clone() + for p := node; p != nil; p = p.Parent { + methodBinds = methodBinds.add(p.Target.Addr().Interface()) + } + if method.IsValid() { + methods = append(methods, targetMethod{node, method, methodBinds}) + } + } + if len(methods) == 0 { + return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary()) + } + _, err = c.Apply() + if err != nil { + return err + } + + for _, method := range methods { + if err = callFunction(method.method, method.binds); err != nil { + return err + } + } + return nil +} + +// Run executes the Run() method on the selected command, which must exist. +// +// Any passed values will be bindable to arguments of the target Run() method. Additionally, +// all parent nodes in the command structure will be bound. +func (c *Context) Run(binds ...interface{}) (err error) { + node := c.Selected() + if node == nil { + if len(c.Path) > 0 { + selected := c.Path[0].Node() + if selected.Type == ApplicationNode { + method := getMethod(selected.Target, "Run") + if method.IsValid() { + return c.RunNode(selected, binds...) + } + } + } + return fmt.Errorf("no command selected") + } + return c.RunNode(node, binds...) +} + +// PrintUsage to Kong's stdout. +// +// If summary is true, a summarised version of the help will be output. +func (c *Context) PrintUsage(summary bool) error { + options := c.helpOptions + options.Summary = summary + return c.help(options, c) +} + +func checkMissingFlags(flags []*Flag) error { + xorGroupSet := map[string]bool{} + xorGroup := map[string][]string{} + missing := []string{} + for _, flag := range flags { + if flag.Set { + for _, xor := range flag.Xor { + xorGroupSet[xor] = true + } + } + if !flag.Required || flag.Set { + continue + } + if len(flag.Xor) > 0 { + for _, xor := range flag.Xor { + if xorGroupSet[xor] { + continue + } + xorGroup[xor] = append(xorGroup[xor], flag.Summary()) + } + } else { + missing = append(missing, flag.Summary()) + } + } + for xor, flags := range xorGroup { + if !xorGroupSet[xor] && len(flags) > 1 { + missing = append(missing, strings.Join(flags, " or ")) + } + } + + if len(missing) == 0 { + return nil + } + + sort.Strings(missing) + + return fmt.Errorf("missing flags: %s", strings.Join(missing, ", ")) +} + +func checkMissingChildren(node *Node) error { + missing := []string{} + + missingArgs := []string{} + for _, arg := range node.Positional { + if arg.Required && !arg.Set { + missingArgs = append(missingArgs, arg.Summary()) + } + } + if len(missingArgs) > 0 { + missing = append(missing, strconv.Quote(strings.Join(missingArgs, " "))) + } + + for _, child := range node.Children { + if child.Hidden { + continue + } + if child.Argument != nil { + if !child.Argument.Required { + continue + } + missing = append(missing, strconv.Quote(child.Summary())) + } else { + missing = append(missing, strconv.Quote(child.Name)) + } + } + if len(missing) == 0 { + return nil + } + + if len(missing) > 5 { + missing = append(missing[:5], "...") + } + if len(missing) == 1 { + return fmt.Errorf("expected %s", missing[0]) + } + return fmt.Errorf("expected one of %s", strings.Join(missing, ", ")) +} + +// If we're missing any positionals and they're required, return an error. +func checkMissingPositionals(positional int, values []*Value) error { + // All the positionals are in. + if positional >= len(values) { + return nil + } + + // We're low on supplied positionals, but the missing one is optional. + if !values[positional].Required { + return nil + } + + missing := []string{} + for ; positional < len(values); positional++ { + arg := values[positional] + // TODO(aat): Fix hardcoding of these env checks all over the place :\ + if len(arg.Tag.Envs) != 0 { + if atLeastOneEnvSet(arg.Tag.Envs) { + continue + } + } + missing = append(missing, "<"+arg.Name+">") + } + if len(missing) == 0 { + return nil + } + return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " ")) +} + +func checkEnum(value *Value, target reflect.Value) error { + switch target.Kind() { + case reflect.Slice, reflect.Array: + for i := 0; i < target.Len(); i++ { + if err := checkEnum(value, target.Index(i)); err != nil { + return err + } + } + return nil + + case reflect.Map, reflect.Struct: + return errors.New("enum can only be applied to a slice or value") + + case reflect.Ptr: + if target.IsNil() { + return nil + } + return checkEnum(value, target.Elem()) + default: + enumSlice := value.EnumSlice() + v := fmt.Sprintf("%v", target) + enums := []string{} + for _, enum := range enumSlice { + if enum == v { + return nil + } + enums = append(enums, fmt.Sprintf("%q", enum)) + } + return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface()) + } +} + +func checkPassthroughArg(target reflect.Value) bool { + typ := target.Type() + switch typ.Kind() { + case reflect.Slice: + return typ.Elem().Kind() == reflect.String + default: + return false + } +} + +func checkXorDuplicates(paths []*Path) error { + for _, path := range paths { + seen := map[string]*Flag{} + for _, flag := range path.Flags { + if !flag.Set { + continue + } + for _, xor := range flag.Xor { + if seen[xor] != nil { + return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name) + } + seen[xor] = flag + } + } + } + return nil +} + +func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error { + if len(haystack) == 0 { + return fmt.Errorf(format, args...) + } + closestCandidates := []string{} + for _, candidate := range haystack { + if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 { + closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate)) + } + } + prefix := fmt.Sprintf(format, args...) + if len(closestCandidates) == 1 { + return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0]) + } else if len(closestCandidates) > 1 { + return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", ")) + } + return fmt.Errorf("%s", prefix) +} + +type validatable interface{ Validate() error } + +func isValidatable(v reflect.Value) validatable { + if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() { + return nil + } + if validate, ok := v.Interface().(validatable); ok { + return validate + } + if v.CanAddr() { + return isValidatable(v.Addr()) + } + return nil +} + +func atLeastOneEnvSet(envs []string) bool { + for _, env := range envs { + if _, ok := os.LookupEnv(env); ok { + return true + } + } + return false +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/defaults.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/defaults.go new file mode 100644 index 00000000000..f6728d79fa8 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/defaults.go @@ -0,0 +1,21 @@ +package kong + +// ApplyDefaults if they are not already set. +func ApplyDefaults(target interface{}, options ...Option) error { + app, err := New(target, options...) + if err != nil { + return err + } + ctx, err := Trace(app, nil) + if err != nil { + return err + } + err = ctx.Resolve() + if err != nil { + return err + } + if err = ctx.ApplyDefaults(); err != nil { + return err + } + return ctx.Validate() +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/doc.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/doc.go new file mode 100644 index 00000000000..78c4d11037b --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/doc.go @@ -0,0 +1,32 @@ +// Package kong aims to support arbitrarily complex command-line structures with as little developer effort as possible. +// +// Here's an example: +// +// shell rm [-f] [-r] ... +// shell ls [ ...] +// +// This can be represented by the following command-line structure: +// +// package main +// +// import "github.com/alecthomas/kong" +// +// var CLI struct { +// Rm struct { +// Force bool `short:"f" help:"Force removal."` +// Recursive bool `short:"r" help:"Recursively remove files."` +// +// Paths []string `arg help:"Paths to remove." type:"path"` +// } `cmd help:"Remove files."` +// +// Ls struct { +// Paths []string `arg optional help:"Paths to list." type:"path"` +// } `cmd help:"List paths."` +// } +// +// func main() { +// kong.Parse(&CLI) +// } +// +// See https://github.com/alecthomas/kong for details. +package kong diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/error.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/error.go new file mode 100644 index 00000000000..18225ef5b83 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/error.go @@ -0,0 +1,12 @@ +package kong + +// ParseError is the error type returned by Kong.Parse(). +// +// It contains the parse Context that triggered the error. +type ParseError struct { + error + Context *Context +} + +// Unwrap returns the original cause of the error. +func (p *ParseError) Unwrap() error { return p.error } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/global.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/global.go new file mode 100644 index 00000000000..d4b3cb56aa5 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/global.go @@ -0,0 +1,16 @@ +package kong + +import ( + "os" +) + +// Parse constructs a new parser and parses the default command-line. +func Parse(cli interface{}, options ...Option) *Context { + parser, err := New(cli, options...) + if err != nil { + panic(err) + } + ctx, err := parser.Parse(os.Args[1:]) + parser.FatalIfErrorf(err) + return ctx +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/guesswidth.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/guesswidth.go new file mode 100644 index 00000000000..46768e68301 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/guesswidth.go @@ -0,0 +1,9 @@ +// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd + +package kong + +import "io" + +func guessWidth(w io.Writer) int { + return 80 +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/guesswidth_unix.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/guesswidth_unix.go new file mode 100644 index 00000000000..db52595e248 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/guesswidth_unix.go @@ -0,0 +1,42 @@ +//go:build (!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd +// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd + +package kong + +import ( + "io" + "os" + "strconv" + "syscall" + "unsafe" +) + +func guessWidth(w io.Writer) int { + // check if COLUMNS env is set to comply with + // http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html + colsStr := os.Getenv("COLUMNS") + if colsStr != "" { + if cols, err := strconv.Atoi(colsStr); err == nil { + return cols + } + } + + if t, ok := w.(*os.File); ok { + fd := t.Fd() + var dimensions [4]uint16 + + if _, _, err := syscall.Syscall6( + syscall.SYS_IOCTL, + uintptr(fd), // nolint: unconvert + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(&dimensions)), // nolint: gas + 0, 0, 0, + ); err == 0 { + if dimensions[1] == 0 { + return 80 + } + return int(dimensions[1]) + } + } + return 80 +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/help.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/help.go new file mode 100644 index 00000000000..4fb77001d4c --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/help.go @@ -0,0 +1,578 @@ +package kong + +import ( + "bytes" + "fmt" + "go/doc" + "io" + "strings" +) + +const ( + defaultIndent = 2 + defaultColumnPadding = 4 +) + +// Help flag. +type helpValue bool + +func (h helpValue) BeforeReset(ctx *Context) error { + options := ctx.Kong.helpOptions + options.Summary = false + err := ctx.Kong.help(options, ctx) + if err != nil { + return err + } + ctx.Kong.Exit(0) + return nil +} + +// HelpOptions for HelpPrinters. +type HelpOptions struct { + // Don't print top-level usage summary. + NoAppSummary bool + + // Write a one-line summary of the context. + Summary bool + + // Write help in a more compact, but still fully-specified, form. + Compact bool + + // Tree writes command chains in a tree structure instead of listing them separately. + Tree bool + + // Place the flags after the commands listing. + FlagsLast bool + + // Indenter modulates the given prefix for the next layer in the tree view. + // The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter + // The kong.SpaceIndenter will be used by default. + Indenter HelpIndenter + + // Don't show the help associated with subcommands + NoExpandSubcommands bool + + // Clamp the help wrap width to a value smaller than the terminal width. + // If this is set to a non-positive number, the terminal width is used; otherwise, + // the min of this value or the terminal width is used. + WrapUpperBound int +} + +// Apply options to Kong as a configuration option. +func (h HelpOptions) Apply(k *Kong) error { + k.helpOptions = h + return nil +} + +// HelpProvider can be implemented by commands/args to provide detailed help. +type HelpProvider interface { + // This string is formatted by go/doc and thus has the same formatting rules. + Help() string +} + +// PlaceHolderProvider can be implemented by mappers to provide custom placeholder text. +type PlaceHolderProvider interface { + PlaceHolder(flag *Flag) string +} + +// HelpIndenter is used to indent new layers in the help tree. +type HelpIndenter func(prefix string) string + +// HelpPrinter is used to print context-sensitive help. +type HelpPrinter func(options HelpOptions, ctx *Context) error + +// HelpValueFormatter is used to format the help text of flags and positional arguments. +type HelpValueFormatter func(value *Value) string + +// DefaultHelpValueFormatter is the default HelpValueFormatter. +func DefaultHelpValueFormatter(value *Value) string { + if len(value.Tag.Envs) == 0 || HasInterpolatedVar(value.OrigHelp, "env") { + return value.Help + } + suffix := "(" + formatEnvs(value.Tag.Envs) + ")" + switch { + case strings.HasSuffix(value.Help, "."): + return value.Help[:len(value.Help)-1] + " " + suffix + "." + case value.Help == "": + return suffix + default: + return value.Help + " " + suffix + } +} + +// DefaultShortHelpPrinter is the default HelpPrinter for short help on error. +func DefaultShortHelpPrinter(options HelpOptions, ctx *Context) error { + w := newHelpWriter(ctx, options) + cmd := ctx.Selected() + app := ctx.Model + if cmd == nil { + w.Printf("Usage: %s%s", app.Name, app.Summary()) + w.Printf(`Run "%s --help" for more information.`, app.Name) + } else { + w.Printf("Usage: %s %s", app.Name, cmd.Summary()) + w.Printf(`Run "%s --help" for more information.`, cmd.FullPath()) + } + return w.Write(ctx.Stdout) +} + +// DefaultHelpPrinter is the default HelpPrinter. +func DefaultHelpPrinter(options HelpOptions, ctx *Context) error { + if ctx.Empty() { + options.Summary = false + } + w := newHelpWriter(ctx, options) + selected := ctx.Selected() + if selected == nil { + printApp(w, ctx.Model) + } else { + printCommand(w, ctx.Model, selected) + } + return w.Write(ctx.Stdout) +} + +func printApp(w *helpWriter, app *Application) { + if !w.NoAppSummary { + w.Printf("Usage: %s%s", app.Name, app.Summary()) + } + printNodeDetail(w, app.Node, true) + cmds := app.Leaves(true) + if len(cmds) > 0 && app.HelpFlag != nil { + w.Print("") + if w.Summary { + w.Printf(`Run "%s --help" for more information.`, app.Name) + } else { + w.Printf(`Run "%s --help" for more information on a command.`, app.Name) + } + } +} + +func printCommand(w *helpWriter, app *Application, cmd *Command) { + if !w.NoAppSummary { + w.Printf("Usage: %s %s", app.Name, cmd.Summary()) + } + printNodeDetail(w, cmd, true) + if w.Summary && app.HelpFlag != nil { + w.Print("") + w.Printf(`Run "%s --help" for more information.`, cmd.FullPath()) + } +} + +func printNodeDetail(w *helpWriter, node *Node, hide bool) { + if node.Help != "" { + w.Print("") + w.Wrap(node.Help) + } + if w.Summary { + return + } + if node.Detail != "" { + w.Print("") + w.Wrap(node.Detail) + } + if len(node.Positional) > 0 { + w.Print("") + w.Print("Arguments:") + writePositionals(w.Indent(), node.Positional) + } + printFlags := func() { + if flags := node.AllFlags(true); len(flags) > 0 { + groupedFlags := collectFlagGroups(flags) + for _, group := range groupedFlags { + w.Print("") + if group.Metadata.Title != "" { + w.Wrap(group.Metadata.Title) + } + if group.Metadata.Description != "" { + w.Indent().Wrap(group.Metadata.Description) + w.Print("") + } + writeFlags(w.Indent(), group.Flags) + } + } + } + if !w.FlagsLast { + printFlags() + } + var cmds []*Node + if w.NoExpandSubcommands { + cmds = node.Children + } else { + cmds = node.Leaves(hide) + } + if len(cmds) > 0 { + iw := w.Indent() + if w.Tree { + w.Print("") + w.Print("Commands:") + writeCommandTree(iw, node) + } else { + groupedCmds := collectCommandGroups(cmds) + for _, group := range groupedCmds { + w.Print("") + if group.Metadata.Title != "" { + w.Wrap(group.Metadata.Title) + } + if group.Metadata.Description != "" { + w.Indent().Wrap(group.Metadata.Description) + w.Print("") + } + + if w.Compact { + writeCompactCommandList(group.Commands, iw) + } else { + writeCommandList(group.Commands, iw) + } + } + } + } + if w.FlagsLast { + printFlags() + } +} + +func writeCommandList(cmds []*Node, iw *helpWriter) { + for i, cmd := range cmds { + if cmd.Hidden { + continue + } + printCommandSummary(iw, cmd) + if i != len(cmds)-1 { + iw.Print("") + } + } +} + +func writeCompactCommandList(cmds []*Node, iw *helpWriter) { + rows := [][2]string{} + for _, cmd := range cmds { + if cmd.Hidden { + continue + } + rows = append(rows, [2]string{cmd.Path(), cmd.Help}) + } + writeTwoColumns(iw, rows) +} + +func writeCommandTree(w *helpWriter, node *Node) { + rows := make([][2]string, 0, len(node.Children)*2) + for i, cmd := range node.Children { + if cmd.Hidden { + continue + } + rows = append(rows, w.CommandTree(cmd, "")...) + if i != len(node.Children)-1 { + rows = append(rows, [2]string{"", ""}) + } + } + writeTwoColumns(w, rows) +} + +type helpFlagGroup struct { + Metadata *Group + Flags [][]*Flag +} + +func collectFlagGroups(flags [][]*Flag) []helpFlagGroup { + // Group keys in order of appearance. + groups := []*Group{} + // Flags grouped by their group key. + flagsByGroup := map[string][][]*Flag{} + + for _, levelFlags := range flags { + levelFlagsByGroup := map[string][]*Flag{} + + for _, flag := range levelFlags { + key := "" + if flag.Group != nil { + key = flag.Group.Key + groupAlreadySeen := false + for _, group := range groups { + if key == group.Key { + groupAlreadySeen = true + break + } + } + if !groupAlreadySeen { + groups = append(groups, flag.Group) + } + } + + levelFlagsByGroup[key] = append(levelFlagsByGroup[key], flag) + } + + for key, flags := range levelFlagsByGroup { + flagsByGroup[key] = append(flagsByGroup[key], flags) + } + } + + out := []helpFlagGroup{} + // Ungrouped flags are always displayed first. + if ungroupedFlags, ok := flagsByGroup[""]; ok { + out = append(out, helpFlagGroup{ + Metadata: &Group{Title: "Flags:"}, + Flags: ungroupedFlags, + }) + } + for _, group := range groups { + out = append(out, helpFlagGroup{Metadata: group, Flags: flagsByGroup[group.Key]}) + } + return out +} + +type helpCommandGroup struct { + Metadata *Group + Commands []*Node +} + +func collectCommandGroups(nodes []*Node) []helpCommandGroup { + // Groups in order of appearance. + groups := []*Group{} + // Nodes grouped by their group key. + nodesByGroup := map[string][]*Node{} + + for _, node := range nodes { + key := "" + if group := node.ClosestGroup(); group != nil { + key = group.Key + if _, ok := nodesByGroup[key]; !ok { + groups = append(groups, group) + } + } + nodesByGroup[key] = append(nodesByGroup[key], node) + } + + out := []helpCommandGroup{} + // Ungrouped nodes are always displayed first. + if ungroupedNodes, ok := nodesByGroup[""]; ok { + out = append(out, helpCommandGroup{ + Metadata: &Group{Title: "Commands:"}, + Commands: ungroupedNodes, + }) + } + for _, group := range groups { + out = append(out, helpCommandGroup{Metadata: group, Commands: nodesByGroup[group.Key]}) + } + return out +} + +func printCommandSummary(w *helpWriter, cmd *Command) { + w.Print(cmd.Summary()) + if cmd.Help != "" { + w.Indent().Wrap(cmd.Help) + } +} + +type helpWriter struct { + indent string + width int + lines *[]string + helpFormatter HelpValueFormatter + HelpOptions +} + +func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter { + lines := []string{} + wrapWidth := guessWidth(ctx.Stdout) + if options.WrapUpperBound > 0 && wrapWidth > options.WrapUpperBound { + wrapWidth = options.WrapUpperBound + } + w := &helpWriter{ + indent: "", + width: wrapWidth, + lines: &lines, + helpFormatter: ctx.Kong.helpFormatter, + HelpOptions: options, + } + return w +} + +func (h *helpWriter) Printf(format string, args ...interface{}) { + h.Print(fmt.Sprintf(format, args...)) +} + +func (h *helpWriter) Print(text string) { + *h.lines = append(*h.lines, strings.TrimRight(h.indent+text, " ")) +} + +// Indent returns a new helpWriter indented by two characters. +func (h *helpWriter) Indent() *helpWriter { + return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions, helpFormatter: h.helpFormatter} +} + +func (h *helpWriter) String() string { + return strings.Join(*h.lines, "\n") +} + +func (h *helpWriter) Write(w io.Writer) error { + for _, line := range *h.lines { + _, err := io.WriteString(w, line+"\n") + if err != nil { + return err + } + } + return nil +} + +func (h *helpWriter) Wrap(text string) { + w := bytes.NewBuffer(nil) + doc.ToText(w, strings.TrimSpace(text), "", " ", h.width) + for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") { + h.Print(line) + } +} + +func writePositionals(w *helpWriter, args []*Positional) { + rows := [][2]string{} + for _, arg := range args { + rows = append(rows, [2]string{arg.Summary(), w.helpFormatter(arg)}) + } + writeTwoColumns(w, rows) +} + +func writeFlags(w *helpWriter, groups [][]*Flag) { + rows := [][2]string{} + haveShort := false + for _, group := range groups { + for _, flag := range group { + if flag.Short != 0 { + haveShort = true + break + } + } + } + for i, group := range groups { + if i > 0 { + rows = append(rows, [2]string{"", ""}) + } + for _, flag := range group { + if !flag.Hidden { + rows = append(rows, [2]string{formatFlag(haveShort, flag), w.helpFormatter(flag.Value)}) + } + } + } + writeTwoColumns(w, rows) +} + +func writeTwoColumns(w *helpWriter, rows [][2]string) { + maxLeft := 375 * w.width / 1000 + if maxLeft < 30 { + maxLeft = 30 + } + // Find size of first column. + leftSize := 0 + for _, row := range rows { + if c := len(row[0]); c > leftSize && c < maxLeft { + leftSize = c + } + } + + offsetStr := strings.Repeat(" ", leftSize+defaultColumnPadding) + + for _, row := range rows { + buf := bytes.NewBuffer(nil) + doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding) + lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") + + line := fmt.Sprintf("%-*s", leftSize, row[0]) + if len(row[0]) < maxLeft { + line += fmt.Sprintf("%*s%s", defaultColumnPadding, "", lines[0]) + lines = lines[1:] + } + w.Print(line) + for _, line := range lines { + w.Printf("%s%s", offsetStr, line) + } + } +} + +// haveShort will be true if there are short flags present at all in the help. Useful for column alignment. +func formatFlag(haveShort bool, flag *Flag) string { + flagString := "" + name := flag.Name + isBool := flag.IsBool() + if flag.Short != 0 { + if isBool && flag.Tag.Negatable { + flagString += fmt.Sprintf("-%c, --[no-]%s", flag.Short, name) + } else { + flagString += fmt.Sprintf("-%c, --%s", flag.Short, name) + } + } else { + if isBool && flag.Tag.Negatable { + if haveShort { + flagString = fmt.Sprintf(" --[no-]%s", name) + } else { + flagString = fmt.Sprintf("--[no-]%s", name) + } + } else { + if haveShort { + flagString += fmt.Sprintf(" --%s", name) + } else { + flagString += fmt.Sprintf("--%s", name) + } + } + } + if !isBool { + flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder()) + } + return flagString +} + +// CommandTree creates a tree with the given node name as root and its children's arguments and sub commands as leaves. +func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string) { + var nodeName string + switch node.Type { + default: + nodeName += prefix + node.Name + if len(node.Aliases) != 0 { + nodeName += fmt.Sprintf(" (%s)", strings.Join(node.Aliases, ",")) + } + case ArgumentNode: + nodeName += prefix + "<" + node.Name + ">" + } + rows = append(rows, [2]string{nodeName, node.Help}) + if h.Indenter == nil { + prefix = SpaceIndenter(prefix) + } else { + prefix = h.Indenter(prefix) + } + for _, arg := range node.Positional { + rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help}) + } + for _, subCmd := range node.Children { + if subCmd.Hidden { + continue + } + rows = append(rows, h.CommandTree(subCmd, prefix)...) + } + return +} + +// SpaceIndenter adds a space indent to the given prefix. +func SpaceIndenter(prefix string) string { + return prefix + strings.Repeat(" ", defaultIndent) +} + +// LineIndenter adds line points to every new indent. +func LineIndenter(prefix string) string { + if prefix == "" { + return "- " + } + return strings.Repeat(" ", defaultIndent) + prefix +} + +// TreeIndenter adds line points to every new indent and vertical lines to every layer. +func TreeIndenter(prefix string) string { + if prefix == "" { + return "|- " + } + return "|" + strings.Repeat(" ", defaultIndent) + prefix +} + +func formatEnvs(envs []string) string { + formatted := make([]string, len(envs)) + for i := range envs { + formatted[i] = "$" + envs[i] + } + + return strings.Join(formatted, ", ") +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/hooks.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/hooks.go new file mode 100644 index 00000000000..d166b08883a --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/hooks.go @@ -0,0 +1,19 @@ +package kong + +// BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied. +type BeforeResolve interface { + // This is not the correct signature - see README for details. + BeforeResolve(args ...interface{}) error +} + +// BeforeApply is a documentation-only interface describing hooks that run before values are set. +type BeforeApply interface { + // This is not the correct signature - see README for details. + BeforeApply(args ...interface{}) error +} + +// AfterApply is a documentation-only interface describing hooks that run after values are set. +type AfterApply interface { + // This is not the correct signature - see README for details. + AfterApply(args ...interface{}) error +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/interpolate.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/interpolate.go new file mode 100644 index 00000000000..e811632ddd1 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/interpolate.go @@ -0,0 +1,52 @@ +package kong + +import ( + "fmt" + "regexp" +) + +var interpolationRegex = regexp.MustCompile(`(\$\$)|((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`) + +// HasInterpolatedVar returns true if the variable "v" is interpolated in "s". +func HasInterpolatedVar(s string, v string) bool { + matches := interpolationRegex.FindAllStringSubmatch(s, -1) + for _, match := range matches { + if name := match[3]; name == v { + return true + } + } + return false +} + +// Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}. +func interpolate(s string, vars Vars, updatedVars map[string]string) (string, error) { + out := "" + matches := interpolationRegex.FindAllStringSubmatch(s, -1) + if len(matches) == 0 { + return s, nil + } + for key, val := range updatedVars { + if vars[key] != val { + vars = vars.CloneWith(updatedVars) + break + } + } + for _, match := range matches { + if dollar := match[1]; dollar != "" { + out += "$" + } else if name := match[3]; name != "" { + value, ok := vars[name] + if !ok { + // No default value. + if match[4] == "" { + return "", fmt.Errorf("undefined variable ${%s}", name) + } + value = match[4] + } + out += value + } else { + out += match[0] + } + } + return out, nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.go new file mode 100644 index 00000000000..c4eda712a6a --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.go @@ -0,0 +1,449 @@ +package kong + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" +) + +var ( + callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem() +) + +func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) error { + name := parent.Type().Name() + if name == "" { + name = "" + } + return fmt.Errorf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...)) +} + +// Must creates a new Parser or panics if there is an error. +func Must(ast interface{}, options ...Option) *Kong { + k, err := New(ast, options...) + if err != nil { + panic(err) + } + return k +} + +type usageOnError int + +const ( + shortUsage usageOnError = iota + 1 + fullUsage +) + +// Kong is the main parser type. +type Kong struct { + // Grammar model. + Model *Application + + // Termination function (defaults to os.Exit) + Exit func(int) + + Stdout io.Writer + Stderr io.Writer + + bindings bindings + loader ConfigurationLoader + resolvers []Resolver + registry *Registry + ignoreFields []*regexp.Regexp + + noDefaultHelp bool + usageOnError usageOnError + help HelpPrinter + shortHelp HelpPrinter + helpFormatter HelpValueFormatter + helpOptions HelpOptions + helpFlag *Flag + groups []Group + vars Vars + flagNamer func(string) string + + // Set temporarily by Options. These are applied after build(). + postBuildOptions []Option + embedded []embedded + dynamicCommands []*dynamicCommand +} + +// New creates a new Kong parser on grammar. +// +// See the README (https://github.com/alecthomas/kong) for usage instructions. +func New(grammar interface{}, options ...Option) (*Kong, error) { + k := &Kong{ + Exit: os.Exit, + Stdout: os.Stdout, + Stderr: os.Stderr, + registry: NewRegistry().RegisterDefaults(), + vars: Vars{}, + bindings: bindings{}, + helpFormatter: DefaultHelpValueFormatter, + ignoreFields: make([]*regexp.Regexp, 0), + flagNamer: func(s string) string { + return strings.ToLower(dashedString(s)) + }, + } + + options = append(options, Bind(k)) + + for _, option := range options { + if err := option.Apply(k); err != nil { + return nil, err + } + } + + if k.help == nil { + k.help = DefaultHelpPrinter + } + + if k.shortHelp == nil { + k.shortHelp = DefaultShortHelpPrinter + } + + model, err := build(k, grammar) + if err != nil { + return k, err + } + model.Name = filepath.Base(os.Args[0]) + k.Model = model + k.Model.HelpFlag = k.helpFlag + + // Embed any embedded structs. + for _, embed := range k.embedded { + tag, err := parseTagString(strings.Join(embed.tags, " ")) //nolint:govet + if err != nil { + return nil, err + } + tag.Embed = true + v := reflect.Indirect(reflect.ValueOf(embed.strct)) + node, err := buildNode(k, v, CommandNode, tag, map[string]bool{}) + if err != nil { + return nil, err + } + for _, child := range node.Children { + child.Parent = k.Model.Node + k.Model.Children = append(k.Model.Children, child) + } + k.Model.Flags = append(k.Model.Flags, node.Flags...) + } + + // Synthesise command nodes. + for _, dcmd := range k.dynamicCommands { + tag, terr := parseTagString(strings.Join(dcmd.tags, " ")) + if terr != nil { + return nil, terr + } + tag.Name = dcmd.name + tag.Help = dcmd.help + tag.Group = dcmd.group + tag.Cmd = true + v := reflect.Indirect(reflect.ValueOf(dcmd.cmd)) + err = buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{ + Name: dcmd.name, + Type: v.Type(), + }, v, tag, dcmd.name, map[string]bool{}) + if err != nil { + return nil, err + } + } + + for _, option := range k.postBuildOptions { + if err = option.Apply(k); err != nil { + return nil, err + } + } + k.postBuildOptions = nil + + if err = k.interpolate(k.Model.Node); err != nil { + return nil, err + } + + k.bindings.add(k.vars) + + return k, nil +} + +type varStack []Vars + +func (v *varStack) head() Vars { return (*v)[len(*v)-1] } +func (v *varStack) pop() { *v = (*v)[:len(*v)-1] } +func (v *varStack) push(vars Vars) Vars { + if len(*v) != 0 { + vars = (*v)[len(*v)-1].CloneWith(vars) + } + *v = append(*v, vars) + return vars +} + +// Interpolate variables into model. +func (k *Kong) interpolate(node *Node) (err error) { + stack := varStack{} + return Visit(node, func(node Visitable, next Next) error { + switch node := node.(type) { + case *Node: + vars := stack.push(node.Vars()) + node.Help, err = interpolate(node.Help, vars, nil) + if err != nil { + return fmt.Errorf("help for %s: %s", node.Path(), err) + } + err = next(nil) + stack.pop() + return err + + case *Value: + return next(k.interpolateValue(node, stack.head())) + } + return next(nil) + }) +} + +func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) { + if len(value.Tag.Vars) > 0 { + vars = vars.CloneWith(value.Tag.Vars) + } + if varsContributor, ok := value.Mapper.(VarsContributor); ok { + vars = vars.CloneWith(varsContributor.Vars(value)) + } + + if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil { + return fmt.Errorf("enum for %s: %s", value.Summary(), err) + } + + updatedVars := map[string]string{ + "default": value.Default, + "enum": value.Enum, + } + if value.Default, err = interpolate(value.Default, vars, nil); err != nil { + return fmt.Errorf("default value for %s: %s", value.Summary(), err) + } + if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil { + return fmt.Errorf("enum value for %s: %s", value.Summary(), err) + } + if value.Flag != nil { + for i, env := range value.Flag.Envs { + if value.Flag.Envs[i], err = interpolate(env, vars, nil); err != nil { + return fmt.Errorf("env value for %s: %s", value.Summary(), err) + } + } + value.Tag.Envs = value.Flag.Envs + updatedVars["env"] = "" + if len(value.Flag.Envs) != 0 { + updatedVars["env"] = value.Flag.Envs[0] + } + } + value.Help, err = interpolate(value.Help, vars, updatedVars) + if err != nil { + return fmt.Errorf("help for %s: %s", value.Summary(), err) + } + return nil +} + +// Provide additional builtin flags, if any. +func (k *Kong) extraFlags() []*Flag { + if k.noDefaultHelp { + return nil + } + var helpTarget helpValue + value := reflect.ValueOf(&helpTarget).Elem() + helpFlag := &Flag{ + Short: 'h', + Value: &Value{ + Name: "help", + Help: "Show context-sensitive help.", + OrigHelp: "Show context-sensitive help.", + Target: value, + Tag: &Tag{}, + Mapper: k.registry.ForValue(value), + DefaultValue: reflect.ValueOf(false), + }, + } + helpFlag.Flag = helpFlag + k.helpFlag = helpFlag + return []*Flag{helpFlag} +} + +// Parse arguments into target. +// +// The return Context can be used to further inspect the parsed command-line, to format help, to find the +// selected command, to run command Run() methods, and so on. See Context and README for more information. +// +// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically +// invalid one, which will report a normal error). +func (k *Kong) Parse(args []string) (ctx *Context, err error) { + ctx, err = Trace(k, args) + if err != nil { + return nil, err + } + if ctx.Error != nil { + return nil, &ParseError{error: ctx.Error, Context: ctx} + } + if err = k.applyHook(ctx, "BeforeReset"); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = ctx.Reset(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = k.applyHook(ctx, "BeforeResolve"); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = ctx.Resolve(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = k.applyHook(ctx, "BeforeApply"); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if _, err = ctx.Apply(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = ctx.Validate(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = k.applyHook(ctx, "AfterApply"); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + return ctx, nil +} + +func (k *Kong) applyHook(ctx *Context, name string) error { + for _, trace := range ctx.Path { + var value reflect.Value + switch { + case trace.App != nil: + value = trace.App.Target + case trace.Argument != nil: + value = trace.Argument.Target + case trace.Command != nil: + value = trace.Command.Target + case trace.Positional != nil: + value = trace.Positional.Target + case trace.Flag != nil: + value = trace.Flag.Value.Target + default: + panic("unsupported Path") + } + method := getMethod(value, name) + if !method.IsValid() { + continue + } + binds := k.bindings.clone() + binds.add(ctx, trace) + binds.add(trace.Node().Vars().CloneWith(k.vars)) + binds.merge(ctx.bindings) + if err := callFunction(method, binds); err != nil { + return err + } + } + // Path[0] will always be the app root. + return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name) +} + +// Call hook on any unset flags with default values. +func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error { + if node == nil { + return nil + } + return Visit(node, func(n Visitable, next Next) error { + node, ok := n.(*Node) + if !ok { + return next(nil) + } + binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars)) + for _, flag := range node.Flags { + if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() { + continue + } + method := getMethod(flag.Target, name) + if !method.IsValid() { + continue + } + path := &Path{Flag: flag} + if err := callFunction(method, binds.clone().add(path)); err != nil { + return next(err) + } + } + return next(nil) + }) +} + +func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) { + lines := strings.Split(fmt.Sprintf(format, args...), "\n") + leader := "" + for _, l := range leaders { + if l == "" { + continue + } + leader += l + ": " + } + fmt.Fprintf(w, "%s%s\n", leader, lines[0]) + for _, line := range lines[1:] { + fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line) + } +} + +// Printf writes a message to Kong.Stdout with the application name prefixed. +func (k *Kong) Printf(format string, args ...interface{}) *Kong { + formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...) + return k +} + +// Errorf writes a message to Kong.Stderr with the application name prefixed. +func (k *Kong) Errorf(format string, args ...interface{}) *Kong { + formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...) + return k +} + +// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status. +func (k *Kong) Fatalf(format string, args ...interface{}) { + k.Errorf(format, args...) + k.Exit(1) +} + +// FatalIfErrorf terminates with an error message if err != nil. +func (k *Kong) FatalIfErrorf(err error, args ...interface{}) { + if err == nil { + return + } + msg := err.Error() + if len(args) > 0 { + msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() // nolint + } + // Maybe display usage information. + var parseErr *ParseError + if errors.As(err, &parseErr) { + switch k.usageOnError { + case fullUsage: + _ = k.help(k.helpOptions, parseErr.Context) + fmt.Fprintln(k.Stdout) + case shortUsage: + _ = k.shortHelp(k.helpOptions, parseErr.Context) + fmt.Fprintln(k.Stdout) + } + } + k.Fatalf("%s", msg) +} + +// LoadConfig from path using the loader configured via Configuration(loader). +// +// "path" will have ~ and any variables expanded. +func (k *Kong) LoadConfig(path string) (Resolver, error) { + var err error + path = ExpandPath(path) + path, err = interpolate(path, k.vars, nil) + if err != nil { + return nil, err + } + r, err := os.Open(path) // nolint: gas + if err != nil { + return nil, err + } + defer r.Close() // nolint: gosec + + return k.loader(r) +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.png b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.png new file mode 100644 index 00000000000..151fb08dbd4 Binary files /dev/null and b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.png differ diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.sketch b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.sketch new file mode 100644 index 00000000000..38816d5f99c Binary files /dev/null and b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/kong.sketch differ diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/levenshtein.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/levenshtein.go new file mode 100644 index 00000000000..1816f301153 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/levenshtein.go @@ -0,0 +1,39 @@ +package kong + +import "unicode/utf8" + +// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go +// License: https://creativecommons.org/licenses/by-sa/3.0/ +func levenshtein(a, b string) int { + f := make([]int, utf8.RuneCountInString(b)+1) + + for j := range f { + f[j] = j + } + + for _, ca := range a { + j := 1 + fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration + f[0]++ + for _, cb := range b { + mn := min(f[j]+1, f[j-1]+1) // delete & insert + if cb != ca { + mn = min(mn, fj1+1) // change + } else { + mn = min(mn, fj1) // matched + } + + fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn + j++ + } + } + + return f[len(f)-1] +} + +func min(a, b int) int { + if a <= b { + return a + } + return b +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/mapper.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/mapper.go new file mode 100644 index 00000000000..c332cce4562 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/mapper.go @@ -0,0 +1,925 @@ +package kong + +import ( + "encoding" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "math/bits" + "net/url" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +var ( + mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem() + boolMapperValueType = reflect.TypeOf((*BoolMapperValue)(nil)).Elem() + jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem() +) + +// DecodeContext is passed to a Mapper's Decode(). +// +// It contains the Value being decoded into and the Scanner to parse from. +type DecodeContext struct { + // Value being decoded into. + Value *Value + // Scan contains the input to scan into Target. + Scan *Scanner +} + +// WithScanner creates a clone of this context with a new Scanner. +func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext { + return &DecodeContext{ + Value: r.Value, + Scan: scan, + } +} + +// MapperValue may be implemented by fields in order to provide custom mapping. +// Mappers may additionally implement PlaceHolderProvider to provide custom placeholder text. +type MapperValue interface { + Decode(ctx *DecodeContext) error +} + +// BoolMapperValue may be implemented by fields in order to provide custom mappings for boolean values. +type BoolMapperValue interface { + MapperValue + IsBool() bool +} + +type mapperValueAdapter struct { + isBool bool +} + +func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { + if target.Type().Implements(mapperValueType) { + return target.Interface().(MapperValue).Decode(ctx) // nolint + } + return target.Addr().Interface().(MapperValue).Decode(ctx) // nolint +} + +func (m *mapperValueAdapter) IsBool() bool { + return m.isBool +} + +type textUnmarshalerAdapter struct{} + +func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { + var value string + err := ctx.Scan.PopValueInto("value", &value) + if err != nil { + return err + } + if target.Type().Implements(textUnmarshalerType) { + return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) // nolint + } + return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) // nolint +} + +type binaryUnmarshalerAdapter struct{} + +func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { + var value string + err := ctx.Scan.PopValueInto("value", &value) + if err != nil { + return err + } + if target.Type().Implements(binaryUnmarshalerType) { + return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) // nolint + } + return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) // nolint +} + +type jsonUnmarshalerAdapter struct{} + +func (j *jsonUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error { + var value string + err := ctx.Scan.PopValueInto("value", &value) + if err != nil { + return err + } + if target.Type().Implements(jsonUnmarshalerType) { + return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) // nolint + } + return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) // nolint +} + +// A Mapper represents how a field is mapped from command-line values to Go. +// +// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag. +// +// Additionally, if a type implements the MapperValue interface, it will be used. +type Mapper interface { + // Decode ctx.Value with ctx.Scanner into target. + Decode(ctx *DecodeContext, target reflect.Value) error +} + +// VarsContributor can be implemented by a Mapper to contribute Vars during interpolation. +type VarsContributor interface { + Vars(ctx *Value) Vars +} + +// A BoolMapper is a Mapper to a value that is a boolean. +// +// This is used solely for formatting help. +type BoolMapper interface { + Mapper + IsBool() bool +} + +// BoolMapperExt allows a Mapper to dynamically determine if a value is a boolean. +type BoolMapperExt interface { + Mapper + IsBoolFromValue(v reflect.Value) bool +} + +// A MapperFunc is a single function that complies with the Mapper interface. +type MapperFunc func(ctx *DecodeContext, target reflect.Value) error + +func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { // nolint: revive + return m(ctx, target) +} + +// A Registry contains a set of mappers and supporting lookup methods. +type Registry struct { + names map[string]Mapper + types map[reflect.Type]Mapper + kinds map[reflect.Kind]Mapper + values map[reflect.Value]Mapper +} + +// NewRegistry creates a new (empty) Registry. +func NewRegistry() *Registry { + return &Registry{ + names: map[string]Mapper{}, + types: map[reflect.Type]Mapper{}, + kinds: map[reflect.Kind]Mapper{}, + values: map[reflect.Value]Mapper{}, + } +} + +// ForNamedValue finds a mapper for a value with a user-specified name. +// +// Will return nil if a mapper can not be determined. +func (r *Registry) ForNamedValue(name string, value reflect.Value) Mapper { + if mapper, ok := r.names[name]; ok { + return mapper + } + return r.ForValue(value) +} + +// ForValue looks up the Mapper for a reflect.Value. +func (r *Registry) ForValue(value reflect.Value) Mapper { + if mapper, ok := r.values[value]; ok { + return mapper + } + return r.ForType(value.Type()) +} + +// ForNamedType finds a mapper for a type with a user-specified name. +// +// Will return nil if a mapper can not be determined. +func (r *Registry) ForNamedType(name string, typ reflect.Type) Mapper { + if mapper, ok := r.names[name]; ok { + return mapper + } + return r.ForType(typ) +} + +// ForType finds a mapper from a type, by type, then kind. +// +// Will return nil if a mapper can not be determined. +func (r *Registry) ForType(typ reflect.Type) Mapper { + // Check if the type implements MapperValue. + for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} { + if impl.Implements(mapperValueType) { + // FIXME: This should pass in the bool mapper. + return &mapperValueAdapter{impl.Implements(boolMapperValueType)} + } + } + // Next, try explicitly registered types. + var mapper Mapper + var ok bool + if mapper, ok = r.types[typ]; ok { + return mapper + } + // Next try stdlib unmarshaler interfaces. + for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} { + switch { + case impl.Implements(textUnmarshalerType): + return &textUnmarshalerAdapter{} + case impl.Implements(binaryUnmarshalerType): + return &binaryUnmarshalerAdapter{} + case impl.Implements(jsonUnmarshalerType): + return &jsonUnmarshalerAdapter{} + } + } + // Finally try registered kinds. + if mapper, ok = r.kinds[typ.Kind()]; ok { + return mapper + } + return nil +} + +// RegisterKind registers a Mapper for a reflect.Kind. +func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry { + r.kinds[kind] = mapper + return r +} + +// RegisterName registers a mapper to be used if the value mapper has a "type" tag matching name. +// +// eg. +// +// Mapper string `kong:"type='colour'` +// registry.RegisterName("colour", ...) +func (r *Registry) RegisterName(name string, mapper Mapper) *Registry { + r.names[name] = mapper + return r +} + +// RegisterType registers a Mapper for a reflect.Type. +func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry { + r.types[typ] = mapper + return r +} + +// RegisterValue registers a Mapper by pointer to the field value. +func (r *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry { + key := reflect.ValueOf(ptr) + if key.Kind() != reflect.Ptr { + panic("expected a pointer") + } + key = key.Elem() + r.values[key] = mapper + return r +} + +// RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types. +func (r *Registry) RegisterDefaults() *Registry { + return r.RegisterKind(reflect.Int, intDecoder(bits.UintSize)). + RegisterKind(reflect.Int8, intDecoder(8)). + RegisterKind(reflect.Int16, intDecoder(16)). + RegisterKind(reflect.Int32, intDecoder(32)). + RegisterKind(reflect.Int64, intDecoder(64)). + RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)). + RegisterKind(reflect.Uint8, uintDecoder(8)). + RegisterKind(reflect.Uint16, uintDecoder(16)). + RegisterKind(reflect.Uint32, uintDecoder(32)). + RegisterKind(reflect.Uint64, uintDecoder(64)). + RegisterKind(reflect.Float32, floatDecoder(32)). + RegisterKind(reflect.Float64, floatDecoder(64)). + RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error { + return ctx.Scan.PopValueInto("string", target.Addr().Interface()) + })). + RegisterKind(reflect.Bool, boolMapper{}). + RegisterKind(reflect.Slice, sliceDecoder(r)). + RegisterKind(reflect.Map, mapDecoder(r)). + RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()). + RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()). + RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()). + RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)). + RegisterName("path", pathMapper(r)). + RegisterName("existingfile", existingFileMapper(r)). + RegisterName("existingdir", existingDirMapper(r)). + RegisterName("counter", counterMapper()). + RegisterName("filecontent", fileContentMapper(r)). + RegisterKind(reflect.Ptr, ptrMapper{r}) +} + +type boolMapper struct{} + +func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error { + if ctx.Scan.Peek().Type == FlagValueToken { + token := ctx.Scan.Pop() + switch v := token.Value.(type) { + case string: + v = strings.ToLower(v) + switch v { + case "true", "1", "yes": + target.SetBool(true) + + case "false", "0", "no": + target.SetBool(false) + + default: + return fmt.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v) + } + + case bool: + target.SetBool(v) + + default: + return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value) + } + } else { + target.SetBool(true) + } + return nil +} +func (boolMapper) IsBool() bool { return true } + +func durationDecoder() MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + t, err := ctx.Scan.PopValue("duration") + if err != nil { + return err + } + var d time.Duration + switch v := t.Value.(type) { + case string: + d, err = time.ParseDuration(v) + if err != nil { + return fmt.Errorf("expected duration but got %q: %v", v, err) + } + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: + d = reflect.ValueOf(v).Convert(reflect.TypeOf(time.Duration(0))).Interface().(time.Duration) // nolint: forcetypeassert + default: + return fmt.Errorf("expected duration but got %q", v) + } + target.Set(reflect.ValueOf(d)) + return nil + } +} + +func timeDecoder() MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + format := time.RFC3339 + if ctx.Value.Format != "" { + format = ctx.Value.Format + } + var value string + if err := ctx.Scan.PopValueInto("time", &value); err != nil { + return err + } + t, err := time.Parse(format, value) + if err != nil { + return err + } + target.Set(reflect.ValueOf(t)) + return nil + } +} + +func intDecoder(bits int) MapperFunc { // nolint: dupl + return func(ctx *DecodeContext, target reflect.Value) error { + t, err := ctx.Scan.PopValue("int") + if err != nil { + return err + } + var sv string + switch v := t.Value.(type) { + case string: + sv = v + + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + sv = fmt.Sprintf("%v", v) + + case float32, float64: + sv = fmt.Sprintf("%0.f", v) + + default: + return fmt.Errorf("expected an int but got %q (%T)", t, t.Value) + } + n, err := strconv.ParseInt(sv, 10, bits) + if err != nil { + return fmt.Errorf("expected a valid %d bit int but got %q", bits, sv) + } + target.SetInt(n) + return nil + } +} + +func uintDecoder(bits int) MapperFunc { // nolint: dupl + return func(ctx *DecodeContext, target reflect.Value) error { + t, err := ctx.Scan.PopValue("uint") + if err != nil { + return err + } + var sv string + switch v := t.Value.(type) { + case string: + sv = v + + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + sv = fmt.Sprintf("%v", v) + + case float32, float64: + sv = fmt.Sprintf("%0.f", v) + + default: + return fmt.Errorf("expected an int but got %q (%T)", t, t.Value) + } + n, err := strconv.ParseUint(sv, 10, bits) + if err != nil { + return fmt.Errorf("expected a valid %d bit uint but got %q", bits, sv) + } + target.SetUint(n) + return nil + } +} + +func floatDecoder(bits int) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + t, err := ctx.Scan.PopValue("float") + if err != nil { + return err + } + switch v := t.Value.(type) { + case string: + n, err := strconv.ParseFloat(v, bits) + if err != nil { + return fmt.Errorf("expected a float but got %q (%T)", t, t.Value) + } + target.SetFloat(n) + + case float32: + target.SetFloat(float64(v)) + + case float64: + target.SetFloat(v) + + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + target.Set(reflect.ValueOf(v)) + + default: + return fmt.Errorf("expected an int but got %q (%T)", t, t.Value) + } + return nil + } +} + +func mapDecoder(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if target.IsNil() { + target.Set(reflect.MakeMap(target.Type())) + } + el := target.Type() + mapsep := ctx.Value.Tag.MapSep + var childScanner *Scanner + if ctx.Value.Flag != nil { + t := ctx.Scan.Pop() + // If decoding a flag, we need an value. + if t.IsEOL() { + return fmt.Errorf("missing value, expecting \"=%c...\"", mapsep) + } + switch v := t.Value.(type) { + case string: + childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...) + + case []map[string]interface{}: + for _, m := range v { + err := jsonTranscode(m, target.Addr().Interface()) + if err != nil { + return err + } + } + return nil + + case map[string]interface{}: + return jsonTranscode(v, target.Addr().Interface()) + + default: + return fmt.Errorf("invalid map value %q (of type %T)", t, t.Value) + } + } else { + tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() }) + childScanner = ScanFromTokens(tokens...) + } + for !childScanner.Peek().IsEOL() { + var token string + err := childScanner.PopValueInto("map", &token) + if err != nil { + return err + } + parts := strings.SplitN(token, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("expected \"=\" but got %q", token) + } + key, value := parts[0], parts[1] + + keyTypeName, valueTypeName := "", "" + if typ := ctx.Value.Tag.Type; typ != "" { + parts := strings.Split(typ, ":") + if len(parts) != 2 { + return errors.New("type:\"\" on map field must be in the form \"[]:[]\"") + } + keyTypeName, valueTypeName = parts[0], parts[1] + } + + keyScanner := ScanAsType(FlagValueToken, key) + keyDecoder := r.ForNamedType(keyTypeName, el.Key()) + keyValue := reflect.New(el.Key()).Elem() + if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil { + return fmt.Errorf("invalid map key %q", key) + } + + valueScanner := ScanAsType(FlagValueToken, value) + valueDecoder := r.ForNamedType(valueTypeName, el.Elem()) + valueValue := reflect.New(el.Elem()).Elem() + if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil { + return fmt.Errorf("invalid map value %q", value) + } + + target.SetMapIndex(keyValue, valueValue) + } + return nil + } +} + +func sliceDecoder(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + el := target.Type().Elem() + sep := ctx.Value.Tag.Sep + var childScanner *Scanner + if ctx.Value.Flag != nil { + t := ctx.Scan.Pop() + // If decoding a flag, we need an value. + if t.IsEOL() { + return fmt.Errorf("missing value, expecting \"%c...\"", sep) + } + switch v := t.Value.(type) { + case string: + childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...) + + case []interface{}: + return jsonTranscode(v, target.Addr().Interface()) + + default: + v = []interface{}{v} + return jsonTranscode(v, target.Addr().Interface()) + } + } else { + tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() }) + childScanner = ScanFromTokens(tokens...) + } + childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el) + if childDecoder == nil { + return fmt.Errorf("no mapper for element type of %s", target.Type()) + } + for !childScanner.Peek().IsEOL() { + childValue := reflect.New(el).Elem() + err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue) + if err != nil { + return err + } + target.Set(reflect.Append(target, childValue)) + } + return nil + } +} + +func pathMapper(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if target.Kind() == reflect.Slice { + return sliceDecoder(r)(ctx, target) + } + if target.Kind() != reflect.String { + return fmt.Errorf("\"path\" type must be applied to a string not %s", target.Type()) + } + var path string + err := ctx.Scan.PopValueInto("file", &path) + if err != nil { + return err + } + if path != "-" { + path = ExpandPath(path) + } + target.SetString(path) + return nil + } +} + +func fileMapper(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if target.Kind() == reflect.Slice { + return sliceDecoder(r)(ctx, target) + } + var path string + err := ctx.Scan.PopValueInto("file", &path) + if err != nil { + return err + } + var file *os.File + if path == "-" { + file = os.Stdin + } else { + path = ExpandPath(path) + file, err = os.Open(path) // nolint: gosec + if err != nil { + return err + } + } + target.Set(reflect.ValueOf(file)) + return nil + } +} + +func existingFileMapper(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if target.Kind() == reflect.Slice { + return sliceDecoder(r)(ctx, target) + } + if target.Kind() != reflect.String { + return fmt.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type()) + } + var path string + err := ctx.Scan.PopValueInto("file", &path) + if err != nil { + return err + } + + if !ctx.Value.Active || ctx.Value.Set { + // early return to avoid checking extra files that may not exist; + // this hack only works because the value provided on the cli is + // checked before the default value is checked (if default is set). + return nil + } + + if path != "-" { + path = ExpandPath(path) + stat, err := os.Stat(path) + if err != nil { + return err + } + if stat.IsDir() { + return fmt.Errorf("%q exists but is a directory", path) + } + } + target.SetString(path) + return nil + } +} + +func existingDirMapper(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if target.Kind() == reflect.Slice { + return sliceDecoder(r)(ctx, target) + } + if target.Kind() != reflect.String { + return fmt.Errorf("\"existingdir\" must be applied to a string not %s", target.Type()) + } + var path string + err := ctx.Scan.PopValueInto("file", &path) + if err != nil { + return err + } + + if !ctx.Value.Active || ctx.Value.Set { + // early return to avoid checking extra dirs that may not exist; + // this hack only works because the value provided on the cli is + // checked before the default value is checked (if default is set). + return nil + } + + path = ExpandPath(path) + stat, err := os.Stat(path) + if err != nil { + return err + } + if !stat.IsDir() { + return fmt.Errorf("%q exists but is not a directory", path) + } + target.SetString(path) + return nil + } +} + +func fileContentMapper(r *Registry) MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if target.Kind() != reflect.Slice && target.Elem().Kind() != reflect.Uint8 { + return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type()) + } + var path string + err := ctx.Scan.PopValueInto("file", &path) + if err != nil { + return err + } + + if !ctx.Value.Active || ctx.Value.Set { + // early return to avoid checking extra dirs that may not exist; + // this hack only works because the value provided on the cli is + // checked before the default value is checked (if default is set). + return nil + } + + var data []byte + if path != "-" { + path = ExpandPath(path) + data, err = ioutil.ReadFile(path) //nolint:gosec + } else { + data, err = ioutil.ReadAll(os.Stdin) + } + if err != nil { + return err + } + target.SetBytes(data) + return nil + } +} + +type ptrMapper struct { + r *Registry +} + +var _ BoolMapperExt = (*ptrMapper)(nil) + +// IsBoolFromValue implements BoolMapperExt +func (p ptrMapper) IsBoolFromValue(target reflect.Value) bool { + elem := reflect.New(target.Type().Elem()).Elem() + nestedMapper := p.r.ForValue(elem) + if nestedMapper == nil { + return false + } + if bm, ok := nestedMapper.(BoolMapper); ok && bm.IsBool() { + return true + } + if bm, ok := nestedMapper.(BoolMapperExt); ok && bm.IsBoolFromValue(target) { + return true + } + return target.Kind() == reflect.Ptr && target.Type().Elem().Kind() == reflect.Bool +} + +func (p ptrMapper) Decode(ctx *DecodeContext, target reflect.Value) error { + elem := reflect.New(target.Type().Elem()).Elem() + nestedMapper := p.r.ForValue(elem) + if nestedMapper == nil { + return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String()) + } + err := nestedMapper.Decode(ctx, elem) + if err != nil { + return err + } + target.Set(elem.Addr()) + return nil +} + +func counterMapper() MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + if ctx.Scan.Peek().Type == FlagValueToken { + t, err := ctx.Scan.PopValue("counter") + if err != nil { + return err + } + switch v := t.Value.(type) { + case string: + n, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value) + } + target.SetInt(n) + + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + target.Set(reflect.ValueOf(v)) + + default: + return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value) + } + return nil + } + + switch target.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + target.SetInt(target.Int() + 1) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + target.SetUint(target.Uint() + 1) + + case reflect.Float32, reflect.Float64: + target.SetFloat(target.Float() + 1) + + default: + return fmt.Errorf("type:\"counter\" must be used with a numeric field") + } + return nil + } +} + +func urlMapper() MapperFunc { + return func(ctx *DecodeContext, target reflect.Value) error { + var urlStr string + err := ctx.Scan.PopValueInto("url", &urlStr) + if err != nil { + return err + } + url, err := url.Parse(urlStr) + if err != nil { + return err + } + target.Set(reflect.ValueOf(url)) + return nil + } +} + +// SplitEscaped splits a string on a separator. +// +// It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg. +// +// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"} +func SplitEscaped(s string, sep rune) (out []string) { + if sep == -1 { + return []string{s} + } + escaped := false + token := "" + for i, ch := range s { + switch { + case escaped: + if ch != sep { + token += `\` + } + token += string(ch) + escaped = false + case ch == '\\' && i < len(s)-1: + escaped = true + case ch == sep && !escaped: + out = append(out, token) + token = "" + escaped = false + default: + token += string(ch) + } + } + if token != "" { + out = append(out, token) + } + return +} + +// JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg. +// +// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob` +func JoinEscaped(s []string, sep rune) string { + escaped := []string{} + for _, e := range s { + escaped = append(escaped, strings.ReplaceAll(e, string(sep), `\`+string(sep))) + } + return strings.Join(escaped, string(sep)) +} + +// NamedFileContentFlag is a flag value that loads a file's contents and filename into its value. +type NamedFileContentFlag struct { + Filename string + Contents []byte +} + +func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { // nolint: revive + var filename string + err := ctx.Scan.PopValueInto("filename", &filename) + if err != nil { + return err + } + // This allows unsetting of file content flags. + if filename == "" { + *f = NamedFileContentFlag{} + return nil + } + filename = ExpandPath(filename) + data, err := ioutil.ReadFile(filename) // nolint: gosec + if err != nil { + return fmt.Errorf("failed to open %q: %v", filename, err) + } + f.Contents = data + f.Filename = filename + return nil +} + +// FileContentFlag is a flag value that loads a file's contents into its value. +type FileContentFlag []byte + +func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: revive + var filename string + err := ctx.Scan.PopValueInto("filename", &filename) + if err != nil { + return err + } + // This allows unsetting of file content flags. + if filename == "" { + *f = nil + return nil + } + filename = ExpandPath(filename) + data, err := ioutil.ReadFile(filename) // nolint: gosec + if err != nil { + return fmt.Errorf("failed to open %q: %v", filename, err) + } + *f = data + return nil +} + +func jsonTranscode(in, out interface{}) error { + data, err := json.Marshal(in) + if err != nil { + return err + } + if err = json.Unmarshal(data, out); err != nil { + return fmt.Errorf("%#v -> %T: %v", in, out, err) + } + return nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/model.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/model.go new file mode 100644 index 00000000000..793cf97bcce --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/model.go @@ -0,0 +1,495 @@ +package kong + +import ( + "fmt" + "math" + "os" + "reflect" + "strconv" + "strings" +) + +// A Visitable component in the model. +type Visitable interface { + node() +} + +// Application is the root of the Kong model. +type Application struct { + *Node + // Help flag, if the NoDefaultHelp() option is not specified. + HelpFlag *Flag +} + +// Argument represents a branching positional argument. +type Argument = Node + +// Command represents a command in the CLI. +type Command = Node + +// NodeType is an enum representing the type of a Node. +type NodeType int + +// Node type enumerations. +const ( + ApplicationNode NodeType = iota + CommandNode + ArgumentNode +) + +// Node is a branch in the CLI. ie. a command or positional argument. +type Node struct { + Type NodeType + Parent *Node + Name string + Help string // Short help displayed in summaries. + Detail string // Detailed help displayed when describing command/arg alone. + Group *Group + Hidden bool + Flags []*Flag + Positional []*Positional + Children []*Node + DefaultCmd *Node + Target reflect.Value // Pointer to the value in the grammar that this Node is associated with. + Tag *Tag + Aliases []string + Passthrough bool // Set to true to stop flag parsing when encountered. + Active bool // Denotes the node is part of an active branch in the CLI. + + Argument *Value // Populated when Type is ArgumentNode. +} + +func (*Node) node() {} + +// Leaf returns true if this Node is a leaf node. +func (n *Node) Leaf() bool { + return len(n.Children) == 0 +} + +// Find a command/argument/flag by pointer to its field. +// +// Returns nil if not found. Panics if ptr is not a pointer. +func (n *Node) Find(ptr interface{}) *Node { + key := reflect.ValueOf(ptr) + if key.Kind() != reflect.Ptr { + panic("expected a pointer") + } + return n.findNode(key) +} + +func (n *Node) findNode(key reflect.Value) *Node { + if n.Target == key { + return n + } + for _, child := range n.Children { + if found := child.findNode(key); found != nil { + return found + } + } + return nil +} + +// AllFlags returns flags from all ancestor branches encountered. +// +// If "hide" is true hidden flags will be omitted. +func (n *Node) AllFlags(hide bool) (out [][]*Flag) { + if n.Parent != nil { + out = append(out, n.Parent.AllFlags(hide)...) + } + group := []*Flag{} + for _, flag := range n.Flags { + if !hide || !flag.Hidden { + flag.Active = true + group = append(group, flag) + } + } + if len(group) > 0 { + out = append(out, group) + } + return +} + +// Leaves returns the leaf commands/arguments under Node. +// +// If "hidden" is true hidden leaves will be omitted. +func (n *Node) Leaves(hide bool) (out []*Node) { + _ = Visit(n, func(nd Visitable, next Next) error { + if nd == n { + return next(nil) + } + if node, ok := nd.(*Node); ok { + if hide && node.Hidden { + return nil + } + if len(node.Children) == 0 && node.Type != ApplicationNode { + out = append(out, node) + } + } + return next(nil) + }) + return +} + +// Depth of the command from the application root. +func (n *Node) Depth() int { + depth := 0 + p := n.Parent + for p != nil && p.Type != ApplicationNode { + depth++ + p = p.Parent + } + return depth +} + +// Summary help string for the node (not including application name). +func (n *Node) Summary() string { + summary := n.Path() + if flags := n.FlagSummary(true); flags != "" { + summary += " " + flags + } + args := []string{} + optional := 0 + for _, arg := range n.Positional { + argSummary := arg.Summary() + if arg.Tag.Optional { + optional++ + argSummary = strings.TrimRight(argSummary, "]") + } + args = append(args, argSummary) + } + if len(args) != 0 { + summary += " " + strings.Join(args, " ") + strings.Repeat("]", optional) + } else if len(n.Children) > 0 { + summary += " " + } + return summary +} + +// FlagSummary for the node. +func (n *Node) FlagSummary(hide bool) string { + required := []string{} + count := 0 + for _, group := range n.AllFlags(hide) { + for _, flag := range group { + count++ + if flag.Required { + required = append(required, flag.Summary()) + } + } + } + return strings.Join(required, " ") +} + +// FullPath is like Path() but includes the Application root node. +func (n *Node) FullPath() string { + root := n + for root.Parent != nil { + root = root.Parent + } + return strings.TrimSpace(root.Name + " " + n.Path()) +} + +// Vars returns the combined Vars defined by all ancestors of this Node. +func (n *Node) Vars() Vars { + if n == nil { + return Vars{} + } + return n.Parent.Vars().CloneWith(n.Tag.Vars) +} + +// Path through ancestors to this Node. +func (n *Node) Path() (out string) { + if n.Parent != nil { + out += " " + n.Parent.Path() + } + switch n.Type { + case CommandNode: + out += " " + n.Name + if len(n.Aliases) > 0 { + out += fmt.Sprintf(" (%s)", strings.Join(n.Aliases, ",")) + } + case ArgumentNode: + out += " " + "<" + n.Name + ">" + default: + } + return strings.TrimSpace(out) +} + +// ClosestGroup finds the first non-nil group in this node and its ancestors. +func (n *Node) ClosestGroup() *Group { + switch { + case n.Group != nil: + return n.Group + case n.Parent != nil: + return n.Parent.ClosestGroup() + default: + return nil + } +} + +// A Value is either a flag or a variable positional argument. +type Value struct { + Flag *Flag // Nil if positional argument. + Name string + Help string + OrigHelp string // Original help string, without interpolated variables. + HasDefault bool + Default string + DefaultValue reflect.Value + Enum string + Mapper Mapper + Tag *Tag + Target reflect.Value + Required bool + Set bool // Set to true when this value is set through some mechanism. + Format string // Formatting directive, if applicable. + Position int // Position (for positional arguments). + Passthrough bool // Set to true to stop flag parsing when encountered. + Active bool // Denotes the value is part of an active branch in the CLI. +} + +// EnumMap returns a map of the enums in this value. +func (v *Value) EnumMap() map[string]bool { + parts := strings.Split(v.Enum, ",") + out := make(map[string]bool, len(parts)) + for _, part := range parts { + out[strings.TrimSpace(part)] = true + } + return out +} + +// EnumSlice returns a slice of the enums in this value. +func (v *Value) EnumSlice() []string { + parts := strings.Split(v.Enum, ",") + out := make([]string, len(parts)) + for i, part := range parts { + out[i] = strings.TrimSpace(part) + } + return out +} + +// ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults. +func (v *Value) ShortSummary() string { + if v.Flag != nil { + return fmt.Sprintf("--%s", v.Name) + } + argText := "<" + v.Name + ">" + if v.IsCumulative() { + argText += " ..." + } + if !v.Required { + argText = "[" + argText + "]" + } + return argText +} + +// Summary returns a human-readable summary of the value. +func (v *Value) Summary() string { + if v.Flag != nil { + if v.IsBool() { + return fmt.Sprintf("--%s", v.Name) + } + return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder()) + } + argText := "<" + v.Name + ">" + if v.IsCumulative() { + argText += " ..." + } + if !v.Required { + argText = "[" + argText + "]" + } + return argText +} + +// IsCumulative returns true if the type can be accumulated into. +func (v *Value) IsCumulative() bool { + return v.IsSlice() || v.IsMap() +} + +// IsSlice returns true if the value is a slice. +func (v *Value) IsSlice() bool { + return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice +} + +// IsMap returns true if the value is a map. +func (v *Value) IsMap() bool { + return v.Target.Kind() == reflect.Map +} + +// IsBool returns true if the underlying value is a boolean. +func (v *Value) IsBool() bool { + if m, ok := v.Mapper.(BoolMapperExt); ok && m.IsBoolFromValue(v.Target) { + return true + } + if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() { + return true + } + return v.Target.Kind() == reflect.Bool +} + +// IsCounter returns true if the value is a counter. +func (v *Value) IsCounter() bool { + return v.Tag.Type == "counter" +} + +// Parse tokens into value, parse, and validate, but do not write to the field. +func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) { + if target.Kind() == reflect.Ptr && target.IsNil() { + target.Set(reflect.New(target.Type().Elem())) + } + err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target) + if err != nil { + return fmt.Errorf("%s: %w", v.ShortSummary(), err) + } + v.Set = true + return nil +} + +// Apply value to field. +func (v *Value) Apply(value reflect.Value) { + v.Target.Set(value) + v.Set = true +} + +// ApplyDefault value to field if it is not already set. +func (v *Value) ApplyDefault() error { + if reflectValueIsZero(v.Target) { + return v.Reset() + } + v.Set = true + return nil +} + +// Reset this value to its default, either the zero value or the parsed result of its envar, +// or its "default" tag. +// +// Does not include resolvers. +func (v *Value) Reset() error { + v.Target.Set(reflect.Zero(v.Target.Type())) + if len(v.Tag.Envs) != 0 { + for _, env := range v.Tag.Envs { + envar := os.Getenv(env) + // Parse the first non-empty ENV in the list + if envar != "" { + err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target) + if err != nil { + return fmt.Errorf("%s (from envar %s=%q)", err, env, envar) + } + return nil + } + } + } + if v.HasDefault { + return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target) + } + return nil +} + +func (*Value) node() {} + +// A Positional represents a non-branching command-line positional argument. +type Positional = Value + +// A Flag represents a command-line flag. +type Flag struct { + *Value + Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically. + Xor []string + PlaceHolder string + Envs []string + Short rune + Hidden bool + Negated bool +} + +func (f *Flag) String() string { + out := "--" + f.Name + if f.Short != 0 { + out = fmt.Sprintf("-%c, %s", f.Short, out) + } + if !f.IsBool() && !f.IsCounter() { + out += "=" + f.FormatPlaceHolder() + } + return out +} + +// FormatPlaceHolder formats the placeholder string for a Flag. +func (f *Flag) FormatPlaceHolder() string { + placeholderHelper, ok := f.Value.Mapper.(PlaceHolderProvider) + if ok { + return placeholderHelper.PlaceHolder(f) + } + tail := "" + if f.Value.IsSlice() && f.Value.Tag.Sep != -1 { + tail += string(f.Value.Tag.Sep) + "..." + } + if f.PlaceHolder != "" { + return f.PlaceHolder + tail + } + if f.HasDefault { + if f.Value.Target.Kind() == reflect.String { + return strconv.Quote(f.Default) + tail + } + return f.Default + tail + } + if f.Value.IsMap() { + if f.Value.Tag.MapSep != -1 { + tail = string(f.Value.Tag.MapSep) + "..." + } + return "KEY=VALUE" + tail + } + if f.Tag != nil && f.Tag.TypeName != "" { + return strings.ToUpper(dashedString(f.Tag.TypeName)) + tail + } + return strings.ToUpper(f.Name) + tail +} + +// Group holds metadata about a command or flag group used when printing help. +type Group struct { + // Key is the `group` field tag value used to identify this group. + Key string + // Title is displayed above the grouped items. + Title string + // Description is optional and displayed under the Title when non empty. + // It can be used to introduce the group's purpose to the user. + Description string +} + +// This is directly from the Go 1.13 source code. +func reflectValueIsZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return math.Float64bits(v.Float()) == 0 + case reflect.Complex64, reflect.Complex128: + c := v.Complex() + return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 + case reflect.Array: + for i := 0; i < v.Len(); i++ { + if !reflectValueIsZero(v.Index(i)) { + return false + } + } + return true + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: + return v.IsNil() + case reflect.String: + return v.Len() == 0 + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if !reflectValueIsZero(v.Field(i)) { + return false + } + } + return true + default: + // This should never happens, but will act as a safeguard for + // later, as a default value doesn't makes sense here. + panic(&reflect.ValueError{"reflect.Value.IsZero", v.Kind()}) + } +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/options.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/options.go new file mode 100644 index 00000000000..8d2893cf143 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/options.go @@ -0,0 +1,493 @@ +package kong + +import ( + "errors" + "fmt" + "io" + "os" + "os/user" + "path/filepath" + "reflect" + "regexp" + "strings" +) + +// An Option applies optional changes to the Kong application. +type Option interface { + Apply(k *Kong) error +} + +// OptionFunc is function that adheres to the Option interface. +type OptionFunc func(k *Kong) error + +func (o OptionFunc) Apply(k *Kong) error { return o(k) } // nolint: revive + +// Vars sets the variables to use for interpolation into help strings and default values. +// +// See README for details. +type Vars map[string]string + +// Apply lets Vars act as an Option. +func (v Vars) Apply(k *Kong) error { + for key, value := range v { + k.vars[key] = value + } + return nil +} + +// CloneWith clones the current Vars and merges "vars" onto the clone. +func (v Vars) CloneWith(vars Vars) Vars { + out := make(Vars, len(v)+len(vars)) + for key, value := range v { + out[key] = value + } + for key, value := range vars { + out[key] = value + } + return out +} + +// Exit overrides the function used to terminate. This is useful for testing or interactive use. +func Exit(exit func(int)) Option { + return OptionFunc(func(k *Kong) error { + k.Exit = exit + return nil + }) +} + +type embedded struct { + strct any + tags []string +} + +// Embed a struct into the root of the CLI. +// +// "strct" must be a pointer to a structure. +func Embed(strct any, tags ...string) Option { + t := reflect.TypeOf(strct) + if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { + panic("kong: Embed() must be called with a pointer to a struct") + } + return OptionFunc(func(k *Kong) error { + k.embedded = append(k.embedded, embedded{strct, tags}) + return nil + }) +} + +type dynamicCommand struct { + name string + help string + group string + tags []string + cmd interface{} +} + +// DynamicCommand registers a dynamically constructed command with the root of the CLI. +// +// This is useful for command-line structures that are extensible via user-provided plugins. +// +// "tags" is a list of extra tag strings to parse, in the form :"". +func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option { + return OptionFunc(func(k *Kong) error { + k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{ + name: name, + help: help, + group: group, + cmd: cmd, + tags: tags, + }) + return nil + }) +} + +// NoDefaultHelp disables the default help flags. +func NoDefaultHelp() Option { + return OptionFunc(func(k *Kong) error { + k.noDefaultHelp = true + return nil + }) +} + +// PostBuild provides read/write access to kong.Kong after initial construction of the model is complete but before +// parsing occurs. +// +// This is useful for, e.g., adding short options to flags, updating help, etc. +func PostBuild(fn func(*Kong) error) Option { + return OptionFunc(func(k *Kong) error { + k.postBuildOptions = append(k.postBuildOptions, OptionFunc(fn)) + return nil + }) +} + +// Name overrides the application name. +func Name(name string) Option { + return PostBuild(func(k *Kong) error { + k.Model.Name = name + return nil + }) +} + +// Description sets the application description. +func Description(description string) Option { + return PostBuild(func(k *Kong) error { + k.Model.Help = description + return nil + }) +} + +// TypeMapper registers a mapper to a type. +func TypeMapper(typ reflect.Type, mapper Mapper) Option { + return OptionFunc(func(k *Kong) error { + k.registry.RegisterType(typ, mapper) + return nil + }) +} + +// KindMapper registers a mapper to a kind. +func KindMapper(kind reflect.Kind, mapper Mapper) Option { + return OptionFunc(func(k *Kong) error { + k.registry.RegisterKind(kind, mapper) + return nil + }) +} + +// ValueMapper registers a mapper to a field value. +func ValueMapper(ptr interface{}, mapper Mapper) Option { + return OptionFunc(func(k *Kong) error { + k.registry.RegisterValue(ptr, mapper) + return nil + }) +} + +// NamedMapper registers a mapper to a name. +func NamedMapper(name string, mapper Mapper) Option { + return OptionFunc(func(k *Kong) error { + k.registry.RegisterName(name, mapper) + return nil + }) +} + +// Writers overrides the default writers. Useful for testing or interactive use. +func Writers(stdout, stderr io.Writer) Option { + return OptionFunc(func(k *Kong) error { + k.Stdout = stdout + k.Stderr = stderr + return nil + }) +} + +// Bind binds values for hooks and Run() function arguments. +// +// Any arguments passed will be available to the receiving hook functions, but may be omitted. Additionally, *Kong and +// the current *Context will also be made available. +// +// There are two hook points: +// +// BeforeApply(...) error +// AfterApply(...) error +// +// Called before validation/assignment, and immediately after validation/assignment, respectively. +func Bind(args ...interface{}) Option { + return OptionFunc(func(k *Kong) error { + k.bindings.add(args...) + return nil + }) +} + +// BindTo allows binding of implementations to interfaces. +// +// BindTo(impl, (*iface)(nil)) +func BindTo(impl, iface interface{}) Option { + return OptionFunc(func(k *Kong) error { + k.bindings.addTo(impl, iface) + return nil + }) +} + +// BindToProvider allows binding of provider functions. +// +// This is useful when the Run() function of different commands require different values that may +// not all be initialisable from the main() function. +func BindToProvider(provider interface{}) Option { + return OptionFunc(func(k *Kong) error { + return k.bindings.addProvider(provider) + }) +} + +// Help printer to use. +func Help(help HelpPrinter) Option { + return OptionFunc(func(k *Kong) error { + k.help = help + return nil + }) +} + +// ShortHelp configures the short usage message. +// +// It should be used together with kong.ShortUsageOnError() to display a +// custom short usage message on errors. +func ShortHelp(shortHelp HelpPrinter) Option { + return OptionFunc(func(k *Kong) error { + k.shortHelp = shortHelp + return nil + }) +} + +// HelpFormatter configures how the help text is formatted. +// +// Deprecated: Use ValueFormatter() instead. +func HelpFormatter(helpFormatter HelpValueFormatter) Option { + return OptionFunc(func(k *Kong) error { + k.helpFormatter = helpFormatter + return nil + }) +} + +// ValueFormatter configures how the help text is formatted. +func ValueFormatter(helpFormatter HelpValueFormatter) Option { + return OptionFunc(func(k *Kong) error { + k.helpFormatter = helpFormatter + return nil + }) +} + +// ConfigureHelp sets the HelpOptions to use for printing help. +func ConfigureHelp(options HelpOptions) Option { + return OptionFunc(func(k *Kong) error { + k.helpOptions = options + return nil + }) +} + +// AutoGroup automatically assigns groups to flags. +func AutoGroup(format func(parent Visitable, flag *Flag) *Group) Option { + return PostBuild(func(kong *Kong) error { + parents := []Visitable{kong.Model} + return Visit(kong.Model, func(node Visitable, next Next) error { + if flag, ok := node.(*Flag); ok && flag.Group == nil { + flag.Group = format(parents[len(parents)-1], flag) + } + parents = append(parents, node) + defer func() { parents = parents[:len(parents)-1] }() + return next(nil) + }) + }) +} + +// Groups associates `group` field tags with group metadata. +// +// This option is used to simplify Kong tags while providing +// rich group information such as title and optional description. +// +// Each key in the "groups" map corresponds to the value of a +// `group` Kong tag, while the first line of the value will be +// the title, and subsequent lines if any will be the description of +// the group. +// +// See also ExplicitGroups for a more structured alternative. +type Groups map[string]string + +func (g Groups) Apply(k *Kong) error { // nolint: revive + for key, info := range g { + lines := strings.Split(info, "\n") + title := strings.TrimSpace(lines[0]) + description := "" + if len(lines) > 1 { + description = strings.TrimSpace(strings.Join(lines[1:], "\n")) + } + k.groups = append(k.groups, Group{ + Key: key, + Title: title, + Description: description, + }) + } + return nil +} + +// ExplicitGroups associates `group` field tags with their metadata. +// +// It can be used to provide a title or header to a command or flag group. +func ExplicitGroups(groups []Group) Option { + return OptionFunc(func(k *Kong) error { + k.groups = groups + return nil + }) +} + +// UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error. +func UsageOnError() Option { + return OptionFunc(func(k *Kong) error { + k.usageOnError = fullUsage + return nil + }) +} + +// ShortUsageOnError configures Kong to display context-sensitive short +// usage if FatalIfErrorf is called with an error. The default short +// usage message can be overridden with kong.ShortHelp(...). +func ShortUsageOnError() Option { + return OptionFunc(func(k *Kong) error { + k.usageOnError = shortUsage + return nil + }) +} + +// ClearResolvers clears all existing resolvers. +func ClearResolvers() Option { + return OptionFunc(func(k *Kong) error { + k.resolvers = nil + return nil + }) +} + +// Resolvers registers flag resolvers. +func Resolvers(resolvers ...Resolver) Option { + return OptionFunc(func(k *Kong) error { + k.resolvers = append(k.resolvers, resolvers...) + return nil + }) +} + +// IgnoreFields will cause kong.New() to skip field names that match any +// of the provided regex patterns. This is useful if you are not able to add a +// kong="-" struct tag to a struct/element before the call to New. +// +// Example: When referencing protoc generated structs, you will likely want to +// ignore/skip XXX_* fields. +func IgnoreFields(regexes ...string) Option { + return OptionFunc(func(k *Kong) error { + for _, r := range regexes { + if r == "" { + return errors.New("regex input cannot be empty") + } + + re, err := regexp.Compile(r) + if err != nil { + return fmt.Errorf("unable to compile regex: %v", err) + } + + k.ignoreFields = append(k.ignoreFields, re) + } + + return nil + }) +} + +// ConfigurationLoader is a function that builds a resolver from a file. +type ConfigurationLoader func(r io.Reader) (Resolver, error) + +// Configuration provides Kong with support for loading defaults from a set of configuration files. +// +// Paths will be opened in order, and "loader" will be used to provide a Resolver which is registered with Kong. +// +// Note: The JSON function is a ConfigurationLoader. +// +// ~ and variable expansion will occur on the provided paths. +func Configuration(loader ConfigurationLoader, paths ...string) Option { + return OptionFunc(func(k *Kong) error { + k.loader = loader + for _, path := range paths { + f, err := os.Open(ExpandPath(path)) + if err != nil { + if os.IsNotExist(err) || os.IsPermission(err) { + continue + } + + return err + } + f.Close() + + resolver, err := k.LoadConfig(path) + if err != nil { + return fmt.Errorf("%s: %v", path, err) + } + if resolver != nil { + k.resolvers = append(k.resolvers, resolver) + } + } + return nil + }) +} + +// ExpandPath is a helper function to expand a relative or home-relative path to an absolute path. +// +// eg. ~/.someconf -> /home/alec/.someconf +func ExpandPath(path string) string { + if filepath.IsAbs(path) { + return path + } + if strings.HasPrefix(path, "~/") { + user, err := user.Current() + if err != nil { + return path + } + return filepath.Join(user.HomeDir, path[2:]) + } + abspath, err := filepath.Abs(path) + if err != nil { + return path + } + return abspath +} + +func siftStrings(ss []string, filter func(s string) bool) []string { + i := 0 + ss = append([]string(nil), ss...) + for _, s := range ss { + if filter(s) { + ss[i] = s + i++ + } + } + return ss[0:i] +} + +// DefaultEnvars option inits environment names for flags. +// The name will not generate if tag "env" is "-". +// Predefined environment variables are skipped. +// +// For example: +// +// --some.value -> PREFIX_SOME_VALUE +func DefaultEnvars(prefix string) Option { + processFlag := func(flag *Flag) { + switch env := flag.Envs; { + case flag.Name == "help": + return + case len(env) == 1 && env[0] == "-": + flag.Envs = nil + return + case len(env) > 0: + return + } + replacer := strings.NewReplacer("-", "_", ".", "_") + names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...) + names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") }) + name := strings.ToUpper(strings.Join(names, "_")) + flag.Envs = append(flag.Envs, name) + flag.Value.Tag.Envs = append(flag.Value.Tag.Envs, name) + } + + var processNode func(node *Node) + processNode = func(node *Node) { + for _, flag := range node.Flags { + processFlag(flag) + } + for _, node := range node.Children { + processNode(node) + } + } + + return PostBuild(func(k *Kong) error { + processNode(k.Model.Node) + return nil + }) +} + +// FlagNamer allows you to override the default kebab-case automated flag name generation. +func FlagNamer(namer func(fieldName string) string) Option { + return OptionFunc(func(k *Kong) error { + k.flagNamer = namer + return nil + }) +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/resolver.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/resolver.go new file mode 100644 index 00000000000..ac1de1fccd1 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/resolver.go @@ -0,0 +1,68 @@ +package kong + +import ( + "encoding/json" + "io" + "strings" +) + +// A Resolver resolves a Flag value from an external source. +type Resolver interface { + // Validate configuration against Application. + // + // This can be used to validate that all provided configuration is valid within this application. + Validate(app *Application) error + + // Resolve the value for a Flag. + Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) +} + +// ResolverFunc is a convenience type for non-validating Resolvers. +type ResolverFunc func(context *Context, parent *Path, flag *Flag) (interface{}, error) + +var _ Resolver = ResolverFunc(nil) + +func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) { // nolint: revive + return r(context, parent, flag) +} +func (r ResolverFunc) Validate(app *Application) error { return nil } // nolint: revive + +// JSON returns a Resolver that retrieves values from a JSON source. +// +// Flag names are used as JSON keys indirectly, by tring snake_case and camelCase variants. +func JSON(r io.Reader) (Resolver, error) { + values := map[string]interface{}{} + err := json.NewDecoder(r).Decode(&values) + if err != nil { + return nil, err + } + var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (interface{}, error) { + name := strings.ReplaceAll(flag.Name, "-", "_") + snakeCaseName := snakeCase(flag.Name) + raw, ok := values[name] + if ok { + return raw, nil + } else if raw, ok = values[snakeCaseName]; ok { + return raw, nil + } + raw = values + for _, part := range strings.Split(name, ".") { + if values, ok := raw.(map[string]interface{}); ok { + raw, ok = values[part] + if !ok { + return nil, nil + } + } else { + return nil, nil + } + } + return raw, nil + } + + return f, nil +} + +func snakeCase(name string) string { + name = strings.Join(strings.Split(strings.Title(name), "-"), "") //nolint: staticcheck + return strings.ToLower(name[:1]) + name[1:] +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/scanner.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/scanner.go new file mode 100644 index 00000000000..1766c4b20d2 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/scanner.go @@ -0,0 +1,222 @@ +package kong + +import ( + "fmt" + "strings" +) + +// TokenType is the type of a token. +type TokenType int + +// Token types. +const ( + UntypedToken TokenType = iota + EOLToken + FlagToken // -- + FlagValueToken // = + ShortFlagToken // -[ + PositionalArgumentToken // +) + +func (t TokenType) String() string { + switch t { + case UntypedToken: + return "untyped" + case EOLToken: + return "" + case FlagToken: // -- + return "long flag" + case FlagValueToken: // = + return "flag value" + case ShortFlagToken: // -[ + return "short flag remainder" + case PositionalArgumentToken: // + return "positional argument" + } + panic("unsupported type") +} + +// Token created by Scanner. +type Token struct { + Value interface{} + Type TokenType +} + +func (t Token) String() string { + switch t.Type { + case FlagToken: + return fmt.Sprintf("--%v", t.Value) + + case ShortFlagToken: + return fmt.Sprintf("-%v", t.Value) + + case EOLToken: + return "EOL" + + default: + return fmt.Sprintf("%v", t.Value) + } +} + +// IsEOL returns true if this Token is past the end of the line. +func (t Token) IsEOL() bool { + return t.Type == EOLToken +} + +// IsAny returns true if the token's type is any of those provided. +func (t TokenType) IsAny(types ...TokenType) bool { + for _, typ := range types { + if t == typ { + return true + } + } + return false +} + +// InferredType tries to infer the type of a token. +func (t Token) InferredType() TokenType { + if t.Type != UntypedToken { + return t.Type + } + if v, ok := t.Value.(string); ok { + if strings.HasPrefix(v, "--") { // nolint: gocritic + return FlagToken + } else if v == "-" { + return PositionalArgumentToken + } else if strings.HasPrefix(v, "-") { + return ShortFlagToken + } + } + return t.Type +} + +// IsValue returns true if token is usable as a parseable value. +// +// A parseable value is either a value typed token, or an untyped token NOT starting with a hyphen. +func (t Token) IsValue() bool { + tt := t.InferredType() + return tt.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken) || + (tt == UntypedToken && !strings.HasPrefix(t.String(), "-")) +} + +// Scanner is a stack-based scanner over command-line tokens. +// +// Initially all tokens are untyped. As the parser consumes tokens it assigns types, splits tokens, and pushes them back +// onto the stream. +// +// For example, the token "--foo=bar" will be split into the following by the parser: +// +// [{FlagToken, "foo"}, {FlagValueToken, "bar"}] +type Scanner struct { + args []Token +} + +// ScanAsType creates a new Scanner from args with the given type. +func ScanAsType(ttype TokenType, args ...string) *Scanner { + s := &Scanner{} + for _, arg := range args { + s.args = append(s.args, Token{Value: arg, Type: ttype}) + } + return s +} + +// Scan creates a new Scanner from args with untyped tokens. +func Scan(args ...string) *Scanner { + return ScanAsType(UntypedToken, args...) +} + +// ScanFromTokens creates a new Scanner from a slice of tokens. +func ScanFromTokens(tokens ...Token) *Scanner { + return &Scanner{args: tokens} +} + +// Len returns the number of input arguments. +func (s *Scanner) Len() int { + return len(s.args) +} + +// Pop the front token off the Scanner. +func (s *Scanner) Pop() Token { + if len(s.args) == 0 { + return Token{Type: EOLToken} + } + arg := s.args[0] + s.args = s.args[1:] + return arg +} + +type expectedError struct { + context string + token Token +} + +func (e *expectedError) Error() string { + return fmt.Sprintf("expected %s value but got %q (%s)", e.context, e.token, e.token.InferredType()) +} + +// PopValue pops a value token, or returns an error. +// +// "context" is used to assist the user if the value can not be popped, eg. "expected value but got " +func (s *Scanner) PopValue(context string) (Token, error) { + t := s.Pop() + if !t.IsValue() { + return t, &expectedError{context, t} + } + return t, nil +} + +// PopValueInto pops a value token into target or returns an error. +// +// "context" is used to assist the user if the value can not be popped, eg. "expected value but got " +func (s *Scanner) PopValueInto(context string, target interface{}) error { + t, err := s.PopValue(context) + if err != nil { + return err + } + return jsonTranscode(t.Value, target) +} + +// PopWhile predicate returns true. +func (s *Scanner) PopWhile(predicate func(Token) bool) (values []Token) { + for predicate(s.Peek()) { + values = append(values, s.Pop()) + } + return +} + +// PopUntil predicate returns true. +func (s *Scanner) PopUntil(predicate func(Token) bool) (values []Token) { + for !predicate(s.Peek()) { + values = append(values, s.Pop()) + } + return +} + +// Peek at the next Token or return an EOLToken. +func (s *Scanner) Peek() Token { + if len(s.args) == 0 { + return Token{Type: EOLToken} + } + return s.args[0] +} + +// Push an untyped Token onto the front of the Scanner. +func (s *Scanner) Push(arg interface{}) *Scanner { + s.PushToken(Token{Value: arg}) + return s +} + +// PushTyped pushes a typed token onto the front of the Scanner. +func (s *Scanner) PushTyped(arg interface{}, typ TokenType) *Scanner { + s.PushToken(Token{Value: arg, Type: typ}) + return s +} + +// PushToken pushes a preconstructed Token onto the front of the Scanner. +func (s *Scanner) PushToken(token Token) *Scanner { + s.args = append([]Token{token}, s.args...) + return s +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/tag.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/tag.go new file mode 100644 index 00000000000..f99059beab9 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/tag.go @@ -0,0 +1,351 @@ +package kong + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "unicode/utf8" +) + +// Tag represents the parsed state of Kong tags in a struct field tag. +type Tag struct { + Ignored bool // Field is ignored by Kong. ie. kong:"-" + Cmd bool + Arg bool + Required bool + Optional bool + Name string + Help string + Type string + TypeName string + HasDefault bool + Default string + Format string + PlaceHolder string + Envs []string + Short rune + Hidden bool + Sep rune + MapSep rune + Enum string + Group string + Xor []string + Vars Vars + Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix. + EnvPrefix string + Embed bool + Aliases []string + Negatable bool + Passthrough bool + + // Storage for all tag keys for arbitrary lookups. + items map[string][]string +} + +func (t *Tag) String() string { + out := []string{} + for key, list := range t.items { + for _, value := range list { + out = append(out, fmt.Sprintf("%s:%q", key, value)) + } + } + return strings.Join(out, " ") +} + +type tagChars struct { + sep, quote, assign rune + needsUnquote bool +} + +var kongChars = tagChars{sep: ',', quote: '\'', assign: '=', needsUnquote: false} +var bareChars = tagChars{sep: ' ', quote: '"', assign: ':', needsUnquote: true} + +// nolint:gocyclo +func parseTagItems(tagString string, chr tagChars) (map[string][]string, error) { + d := map[string][]string{} + key := []rune{} + value := []rune{} + quotes := false + inKey := true + + add := func() error { + // Bare tags are quoted, therefore we need to unquote them in the same fashion reflect.Lookup() (implicitly) + // unquotes "kong tags". + s := string(value) + + if chr.needsUnquote && s != "" { + if unquoted, err := strconv.Unquote(fmt.Sprintf(`"%s"`, s)); err == nil { + s = unquoted + } else { + return fmt.Errorf("unquoting tag value `%s`: %w", s, err) + } + } + + d[string(key)] = append(d[string(key)], s) + key = []rune{} + value = []rune{} + inKey = true + + return nil + } + + runes := []rune(tagString) + for idx := 0; idx < len(runes); idx++ { + r := runes[idx] + next := rune(0) + eof := false + if idx < len(runes)-1 { + next = runes[idx+1] + } else { + eof = true + } + if !quotes && r == chr.sep { + if err := add(); err != nil { + return nil, err + } + + continue + } + if r == chr.assign && inKey { + inKey = false + continue + } + if r == '\\' { + if next == chr.quote { + idx++ + + // We need to keep the backslashes, otherwise subsequent unquoting cannot work + if chr.needsUnquote { + value = append(value, r) + } + + r = chr.quote + } + } else if r == chr.quote { + if quotes { + quotes = false + if next == chr.sep || eof { + continue + } + return nil, fmt.Errorf("%v has an unexpected char at pos %v", tagString, idx) + } + quotes = true + continue + } + if inKey { + key = append(key, r) + } else { + value = append(value, r) + } + } + if quotes { + return nil, fmt.Errorf("%v is not quoted properly", tagString) + } + + if err := add(); err != nil { + return nil, err + } + + return d, nil +} + +func getTagInfo(ft reflect.StructField) (string, tagChars) { + s, ok := ft.Tag.Lookup("kong") + if ok { + return s, kongChars + } + + return string(ft.Tag), bareChars +} + +func newEmptyTag() *Tag { + return &Tag{items: map[string][]string{}} +} + +func tagSplitFn(r rune) bool { + return r == ',' || r == ' ' +} + +func parseTagString(s string) (*Tag, error) { + items, err := parseTagItems(s, bareChars) + if err != nil { + return nil, err + } + t := &Tag{ + items: items, + } + err = hydrateTag(t, nil) + if err != nil { + return nil, fmt.Errorf("%s: %s", s, err) + } + return t, nil +} + +func parseTag(parent reflect.Value, ft reflect.StructField) (*Tag, error) { + if ft.Tag.Get("kong") == "-" { + t := newEmptyTag() + t.Ignored = true + return t, nil + } + items, err := parseTagItems(getTagInfo(ft)) + if err != nil { + return nil, err + } + t := &Tag{ + items: items, + } + err = hydrateTag(t, ft.Type) + if err != nil { + return nil, failField(parent, ft, "%s", err) + } + return t, nil +} + +func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo + var typeName string + var isBool bool + var isBoolPtr bool + if typ != nil { + typeName = typ.Name() + isBool = typ.Kind() == reflect.Bool + isBoolPtr = typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Bool + } + var err error + t.Cmd = t.Has("cmd") + t.Arg = t.Has("arg") + required := t.Has("required") + optional := t.Has("optional") + if required && optional { + return fmt.Errorf("can't specify both required and optional") + } + t.Required = required + t.Optional = optional + t.HasDefault = t.Has("default") + t.Default = t.Get("default") + // Arguments with defaults are always optional. + if t.Arg && t.HasDefault { + t.Optional = true + } else if t.Arg && !optional { // Arguments are required unless explicitly made optional. + t.Required = true + } + t.Name = t.Get("name") + t.Help = t.Get("help") + t.Type = t.Get("type") + t.TypeName = typeName + for _, env := range t.GetAll("env") { + t.Envs = append(t.Envs, strings.FieldsFunc(env, tagSplitFn)...) + } + t.Short, err = t.GetRune("short") + if err != nil && t.Get("short") != "" { + return fmt.Errorf("invalid short flag name %q: %s", t.Get("short"), err) + } + t.Hidden = t.Has("hidden") + t.Format = t.Get("format") + t.Sep, _ = t.GetSep("sep", ',') + t.MapSep, _ = t.GetSep("mapsep", ';') + t.Group = t.Get("group") + for _, xor := range t.GetAll("xor") { + t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...) + } + t.Prefix = t.Get("prefix") + t.EnvPrefix = t.Get("envprefix") + t.Embed = t.Has("embed") + negatable := t.Has("negatable") + if negatable && !isBool && !isBoolPtr { + return fmt.Errorf("negatable can only be set on booleans") + } + t.Negatable = negatable + aliases := t.Get("aliases") + if len(aliases) > 0 { + t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...) + } + t.Vars = Vars{} + for _, set := range t.GetAll("set") { + parts := strings.SplitN(set, "=", 2) + if len(parts) == 0 { + return fmt.Errorf("set should be in the form key=value but got %q", set) + } + t.Vars[parts[0]] = parts[1] + } + t.PlaceHolder = t.Get("placeholder") + t.Enum = t.Get("enum") + scalarType := typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Ptr) + if t.Enum != "" && !(t.Required || t.HasDefault) && scalarType { + return fmt.Errorf("enum value is only valid if it is either required or has a valid default value") + } + passthrough := t.Has("passthrough") + if passthrough && !t.Arg && !t.Cmd { + return fmt.Errorf("passthrough only makes sense for positional arguments or commands") + } + t.Passthrough = passthrough + return nil +} + +// Has returns true if the tag contained the given key. +func (t *Tag) Has(k string) bool { + _, ok := t.items[k] + return ok +} + +// Get returns the value of the given tag. +// +// Note that this will return the empty string if the tag is missing. +func (t *Tag) Get(k string) string { + values := t.items[k] + if len(values) == 0 { + return "" + } + return values[0] +} + +// GetAll returns all encountered values for a tag, in the case of multiple occurrences. +func (t *Tag) GetAll(k string) []string { + return t.items[k] +} + +// GetBool returns true if the given tag looks like a boolean truth string. +func (t *Tag) GetBool(k string) (bool, error) { + return strconv.ParseBool(t.Get(k)) +} + +// GetFloat parses the given tag as a float64. +func (t *Tag) GetFloat(k string) (float64, error) { + return strconv.ParseFloat(t.Get(k), 64) +} + +// GetInt parses the given tag as an int64. +func (t *Tag) GetInt(k string) (int64, error) { + return strconv.ParseInt(t.Get(k), 10, 64) +} + +// GetRune parses the given tag as a rune. +func (t *Tag) GetRune(k string) (rune, error) { + value := t.Get(k) + r, size := utf8.DecodeRuneInString(value) + if r == utf8.RuneError || size < len(value) { + return 0, errors.New("invalid rune") + } + return r, nil +} + +// GetSep parses the given tag as a rune separator, allowing for a default or none. +// The separator is returned, or -1 if "none" is specified. If the tag value is an +// invalid utf8 sequence, the default rune is returned as well as an error. If the +// tag value is more than one rune, the first rune is returned as well as an error. +func (t *Tag) GetSep(k string, dflt rune) (rune, error) { + tv := t.Get(k) + if tv == "none" { + return -1, nil + } else if tv == "" { + return dflt, nil + } + r, size := utf8.DecodeRuneInString(tv) + if r == utf8.RuneError { + return dflt, fmt.Errorf(`%v:"%v" has a rune error`, k, tv) + } else if size != len(tv) { + return r, fmt.Errorf(`%v:"%v" is more than a single rune`, k, tv) + } + return r, nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/util.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/util.go new file mode 100644 index 00000000000..50b1dfef786 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/util.go @@ -0,0 +1,57 @@ +package kong + +import ( + "fmt" + "os" + "reflect" +) + +// ConfigFlag uses the configured (via kong.Configuration(loader)) configuration loader to load configuration +// from a file specified by a flag. +// +// Use this as a flag value to support loading of custom configuration via a flag. +type ConfigFlag string + +// BeforeResolve adds a resolver. +func (c ConfigFlag) BeforeResolve(kong *Kong, ctx *Context, trace *Path) error { + if kong.loader == nil { + return fmt.Errorf("kong must be configured with kong.Configuration(...)") + } + path := string(ctx.FlagValue(trace.Flag).(ConfigFlag)) // nolint + resolver, err := kong.LoadConfig(path) + if err != nil { + return err + } + ctx.AddResolver(resolver) + return nil +} + +// VersionFlag is a flag type that can be used to display a version number, stored in the "version" variable. +type VersionFlag bool + +// BeforeReset writes the version variable and terminates with a 0 exit status. +func (v VersionFlag) BeforeReset(app *Kong, vars Vars) error { + fmt.Fprintln(app.Stdout, vars["version"]) + app.Exit(0) + return nil +} + +// ChangeDirFlag changes the current working directory to a path specified by a flag +// early in the parsing process, changing how other flags resolve relative paths. +// +// Use this flag to provide a "git -C" like functionality. +// +// It is not compatible with custom named decoders, e.g., existingdir. +type ChangeDirFlag string + +// Decode is used to create a side effect of changing the current working directory. +func (c ChangeDirFlag) Decode(ctx *DecodeContext) error { + var path string + err := ctx.Scan.PopValueInto("string", &path) + if err != nil { + return err + } + path = ExpandPath(path) + ctx.Value.Target.Set(reflect.ValueOf(ChangeDirFlag(path))) + return os.Chdir(path) +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/visit.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/visit.go new file mode 100644 index 00000000000..f7dab532b6d --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/alecthomas/kong/visit.go @@ -0,0 +1,58 @@ +package kong + +import ( + "fmt" +) + +// Next should be called by Visitor to proceed with the walk. +// +// The walk will terminate if "err" is non-nil. +type Next func(err error) error + +// Visitor can be used to walk all nodes in the model. +type Visitor func(node Visitable, next Next) error + +// Visit all nodes. +func Visit(node Visitable, visitor Visitor) error { + return visitor(node, func(err error) error { + if err != nil { + return err + } + switch node := node.(type) { + case *Application: + return visitNodeChildren(node.Node, visitor) + case *Node: + return visitNodeChildren(node, visitor) + case *Value: + case *Flag: + return Visit(node.Value, visitor) + default: + panic(fmt.Sprintf("unsupported node type %T", node)) + } + return nil + }) +} + +func visitNodeChildren(node *Node, visitor Visitor) error { + if node.Argument != nil { + if err := Visit(node.Argument, visitor); err != nil { + return err + } + } + for _, flag := range node.Flags { + if err := Visit(flag, visitor); err != nil { + return err + } + } + for _, pos := range node.Positional { + if err := Visit(pos, visitor); err != nil { + return err + } + } + for _, child := range node.Children { + if err := Visit(child, visitor); err != nil { + return err + } + } + return nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/LICENSE b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/pkg/hooks/1.0.0/hook.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/pkg/hooks/1.0.0/hook.go new file mode 100644 index 00000000000..71f940a64cb --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/pkg/hooks/1.0.0/hook.go @@ -0,0 +1,89 @@ +// Package hook is the 1.0.0 hook configuration structure. +package hook + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "regexp" + + rspec "github.com/opencontainers/runtime-spec/specs-go" +) + +// Version is the hook configuration version defined in this package. +const Version = "1.0.0" + +// Hook is the hook configuration structure. +type Hook struct { + Version string `json:"version"` + Hook rspec.Hook `json:"hook"` + When When `json:"when"` + Stages []string `json:"stages"` +} + +// Read reads hook JSON bytes, verifies them, and returns the hook configuration. +func Read(content []byte) (hook *Hook, err error) { + if err = json.Unmarshal(content, &hook); err != nil { + return nil, err + } + return hook, nil +} + +// Validate performs load-time hook validation. +func (hook *Hook) Validate(extensionStages []string) (err error) { + if hook == nil { + return errors.New("nil hook") + } + + if hook.Version != Version { + return fmt.Errorf("unexpected hook version %q (expecting %v)", hook.Version, Version) + } + + if hook.Hook.Path == "" { + return errors.New("missing required property: hook.path") + } + + if _, err := os.Stat(hook.Hook.Path); err != nil { + return err + } + + for key, value := range hook.When.Annotations { + if _, err = regexp.Compile(key); err != nil { + return fmt.Errorf("invalid annotation key %q: %w", key, err) + } + if _, err = regexp.Compile(value); err != nil { + return fmt.Errorf("invalid annotation value %q: %w", value, err) + } + } + + for _, command := range hook.When.Commands { + if _, err = regexp.Compile(command); err != nil { + return fmt.Errorf("invalid command %q: %w", command, err) + } + } + + if hook.Stages == nil { + return errors.New("missing required property: stages") + } + + validStages := map[string]bool{ + "createContainer": true, + "createRuntime": true, + "prestart": true, + "poststart": true, + "poststop": true, + "startContainer": true, + } + for _, stage := range extensionStages { + validStages[stage] = true + } + + for _, stage := range hook.Stages { + if !validStages[stage] { + return fmt.Errorf("unknown stage %q", stage) + } + } + + return nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/pkg/hooks/1.0.0/when.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/pkg/hooks/1.0.0/when.go new file mode 100644 index 00000000000..a1351890f7e --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/containers/common/pkg/hooks/1.0.0/when.go @@ -0,0 +1,96 @@ +package hook + +import ( + "errors" + "fmt" + "regexp" + + rspec "github.com/opencontainers/runtime-spec/specs-go" +) + +// When holds hook-injection conditions. +type When struct { + Always *bool `json:"always,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` + Commands []string `json:"commands,omitempty"` + HasBindMounts *bool `json:"hasBindMounts,omitempty"` + + // Or enables any-of matching. + // + // Deprecated: this property is for is backwards-compatibility with + // 0.1.0 hooks. It will be removed when we drop support for them. + Or bool `json:"-"` +} + +// Match returns true if the given conditions match the configuration. +func (when *When) Match(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (match bool, err error) { + matches := 0 + + if when.Always != nil { + if *when.Always { + if when.Or { + return true, nil + } + matches++ + } else if !when.Or { + return false, nil + } + } + + if when.HasBindMounts != nil { + if *when.HasBindMounts && hasBindMounts { + if when.Or { + return true, nil + } + matches++ + } else if !when.Or { + return false, nil + } + } + + for keyPattern, valuePattern := range when.Annotations { + match := false + for key, value := range annotations { + match, err = regexp.MatchString(keyPattern, key) + if err != nil { + return false, fmt.Errorf("annotation key: %w", err) + } + if match { + match, err = regexp.MatchString(valuePattern, value) + if err != nil { + return false, fmt.Errorf("annotation value: %w", err) + } + if match { + break + } + } + } + if match { + if when.Or { + return true, nil + } + matches++ + } else if !when.Or { + return false, nil + } + } + + if config.Process != nil && len(when.Commands) > 0 { + if len(config.Process.Args) == 0 { + return false, errors.New("process.args must have at least one entry") + } + command := config.Process.Args[0] + for _, cmdPattern := range when.Commands { + match, err := regexp.MatchString(cmdPattern, command) + if err != nil { + return false, fmt.Errorf("command: %w", err) + } + if match { + return true, nil + } + } + return false, nil + } + + return matches > 0, nil +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go index cff5af1a64c..147f756fe24 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go @@ -176,6 +176,11 @@ func (c *Conn) Close() { c.sigconn.Close() } +// Connected returns whether conn is connected +func (c *Conn) Connected() bool { + return c.sysconn.Connected() && c.sigconn.Connected() +} + // NewConnection establishes a connection to a bus using a caller-supplied function. // This allows connecting to remote buses through a user-supplied mechanism. // The supplied function may be called multiple times, and should return independent connections. diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go index fa04afc708e..074148cb4d6 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go @@ -417,6 +417,29 @@ func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) { return status, nil } +// GetUnitByPID returns the unit object path of the unit a process ID +// belongs to. It takes a UNIX PID and returns the object path. The PID must +// refer to an existing system process +func (c *Conn) GetUnitByPID(ctx context.Context, pid uint32) (dbus.ObjectPath, error) { + var result dbus.ObjectPath + + err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.GetUnitByPID", 0, pid).Store(&result) + + return result, err +} + +// GetUnitNameByPID returns the name of the unit a process ID belongs to. It +// takes a UNIX PID and returns the object path. The PID must refer to an +// existing system process +func (c *Conn) GetUnitNameByPID(ctx context.Context, pid uint32) (string, error) { + path, err := c.GetUnitByPID(ctx, pid) + if err != nil { + return "", err + } + + return unitName(path), nil +} + // Deprecated: use ListUnitsContext instead. func (c *Conn) ListUnits() ([]UnitStatus, error) { return c.ListUnitsContext(context.Background()) @@ -828,3 +851,14 @@ func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) { return status, nil } + +// Freeze the cgroup associated with the unit. +// Note that FreezeUnit and ThawUnit are only supported on systems running with cgroup v2. +func (c *Conn) FreezeUnit(ctx context.Context, unit string) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.FreezeUnit", 0, unit).Store() +} + +// Unfreeze the cgroup associated with the unit. +func (c *Conn) ThawUnit(ctx context.Context, unit string) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store() +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/auth.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/auth.go index a59b4c0eb76..0f3b252c070 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/auth.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/auth.go @@ -176,9 +176,10 @@ func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, boo return err, false } state = waitingForReject + } else { + conn.uuid = string(s[1]) + return nil, true } - conn.uuid = string(s[1]) - return nil, true case state == waitingForData: err = authWriteLine(conn.transport, []byte("ERROR")) if err != nil { @@ -191,9 +192,10 @@ func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, boo return err, false } state = waitingForReject + } else { + conn.uuid = string(s[1]) + return nil, true } - conn.uuid = string(s[1]) - return nil, true case state == waitingForOk && string(s[0]) == "DATA": err = authWriteLine(conn.transport, []byte("DATA")) if err != nil { diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn.go index 76fc5cde3d2..69978ea26ab 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn.go @@ -169,7 +169,7 @@ func Connect(address string, opts ...ConnOption) (*Conn, error) { // SystemBusPrivate returns a new private connection to the system bus. // Note: this connection is not ready to use. One must perform Auth and Hello -// on the connection before it is useable. +// on the connection before it is usable. func SystemBusPrivate(opts ...ConnOption) (*Conn, error) { return Dial(getSystemBusPlatformAddress(), opts...) } @@ -284,10 +284,6 @@ func newConn(tr transport, opts ...ConnOption) (*Conn, error) { conn.ctx = context.Background() } conn.ctx, conn.cancelCtx = context.WithCancel(conn.ctx) - go func() { - <-conn.ctx.Done() - conn.Close() - }() conn.calls = newCallTracker() if conn.handler == nil { @@ -302,6 +298,11 @@ func newConn(tr transport, opts ...ConnOption) (*Conn, error) { conn.outHandler = &outputHandler{conn: conn} conn.names = newNameTracker() conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") + + go func() { + <-conn.ctx.Done() + conn.Close() + }() return conn, nil } @@ -550,6 +551,11 @@ func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call { call.ctx = ctx call.ctxCanceler = canceler conn.calls.track(msg.serial, call) + if ctx.Err() != nil { + // short path: don't even send the message if context already cancelled + conn.calls.handleSendError(msg, ctx.Err()) + return call + } go func() { <-ctx.Done() conn.calls.handleSendError(msg, ctx.Err()) @@ -649,7 +655,9 @@ func (conn *Conn) RemoveMatchSignalContext(ctx context.Context, options ...Match // Signal registers the given channel to be passed all received signal messages. // -// Multiple of these channels can be registered at the same time. +// Multiple of these channels can be registered at the same time. The channel is +// closed if the Conn is closed; it should not be closed by the caller before +// RemoveSignal was called on it. // // These channels are "overwritten" by Eavesdrop; i.e., if there currently is a // channel for eavesdropped messages, this channel receives all signals, and @@ -765,7 +773,12 @@ func getKey(s, key string) string { for _, keyEqualsValue := range strings.Split(s, ",") { keyValue := strings.SplitN(keyEqualsValue, "=", 2) if len(keyValue) == 2 && keyValue[0] == key { - return keyValue[1] + val, err := UnescapeBusAddressValue(keyValue[1]) + if err != nil { + // No way to return an error. + return "" + } + return val } } return "" diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn_other.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn_other.go index 616dcf66449..90289ca85a2 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn_other.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/conn_other.go @@ -54,7 +54,7 @@ func tryDiscoverDbusSessionBusAddress() string { if runUserBusFile := path.Join(runtimeDirectory, "bus"); fileExists(runUserBusFile) { // if /run/user//bus exists, that file itself // *is* the unix socket, so return its path - return fmt.Sprintf("unix:path=%s", runUserBusFile) + return fmt.Sprintf("unix:path=%s", EscapeBusAddressValue(runUserBusFile)) } if runUserSessionDbusFile := path.Join(runtimeDirectory, "dbus-session"); fileExists(runUserSessionDbusFile) { // if /run/user//dbus-session exists, it's a @@ -85,9 +85,6 @@ func getRuntimeDirectory() (string, error) { } func fileExists(filename string) bool { - if _, err := os.Stat(filename); !os.IsNotExist(err) { - return true - } else { - return false - } + _, err := os.Stat(filename) + return !os.IsNotExist(err) } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/dbus.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/dbus.go index ddf3b7afde9..c188d104854 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/dbus.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/dbus.go @@ -122,8 +122,11 @@ func isConvertibleTo(dest, src reflect.Type) bool { case dest.Kind() == reflect.Slice: return src.Kind() == reflect.Slice && isConvertibleTo(dest.Elem(), src.Elem()) + case dest.Kind() == reflect.Ptr: + dest = dest.Elem() + return isConvertibleTo(dest, src) case dest.Kind() == reflect.Struct: - return src == interfacesType + return src == interfacesType || dest.Kind() == src.Kind() default: return src.ConvertibleTo(dest) } @@ -274,13 +277,8 @@ func storeSliceIntoInterface(dest, src reflect.Value) error { func storeSliceIntoSlice(dest, src reflect.Value) error { if dest.IsNil() || dest.Len() < src.Len() { dest.Set(reflect.MakeSlice(dest.Type(), src.Len(), src.Cap())) - } - if dest.Len() != src.Len() { - return fmt.Errorf( - "dbus.Store: type mismatch: "+ - "slices are different lengths "+ - "need: %d have: %d", - src.Len(), dest.Len()) + } else if dest.Len() > src.Len() { + dest.Set(dest.Slice(0, src.Len())) } for i := 0; i < src.Len(); i++ { err := store(dest.Index(i), getVariantValue(src.Index(i))) diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/doc.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/doc.go index ade1df951cd..8f25a00d61e 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/doc.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/doc.go @@ -10,8 +10,10 @@ value. Conversion Rules For outgoing messages, Go types are automatically converted to the -corresponding D-Bus types. The following types are directly encoded as their -respective D-Bus equivalents: +corresponding D-Bus types. See the official specification at +https://dbus.freedesktop.org/doc/dbus-specification.html#type-system for more +information on the D-Bus type system. The following types are directly encoded +as their respective D-Bus equivalents: Go type | D-Bus type ------------+----------- @@ -39,8 +41,8 @@ Maps encode as DICTs, provided that their key type can be used as a key for a DICT. Structs other than Variant and Signature encode as a STRUCT containing their -exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will -be skipped. +exported fields in order. Fields whose tags contain `dbus:"-"` and unexported +fields will be skipped. Pointers encode as the value they're pointed to. diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/escape.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/escape.go new file mode 100644 index 00000000000..d1509d94581 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/escape.go @@ -0,0 +1,84 @@ +package dbus + +import "net/url" + +// EscapeBusAddressValue implements a requirement to escape the values +// in D-Bus server addresses, as defined by the D-Bus specification at +// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses. +func EscapeBusAddressValue(val string) string { + toEsc := strNeedsEscape(val) + if toEsc == 0 { + // Avoid unneeded allocation/copying. + return val + } + + // Avoid allocation for short paths. + var buf [64]byte + var out []byte + // Every to-be-escaped byte needs 2 extra bytes. + required := len(val) + 2*toEsc + if required <= len(buf) { + out = buf[:required] + } else { + out = make([]byte, required) + } + + j := 0 + for i := 0; i < len(val); i++ { + if ch := val[i]; needsEscape(ch) { + // Convert ch to %xx, where xx is hex value. + out[j] = '%' + out[j+1] = hexchar(ch >> 4) + out[j+2] = hexchar(ch & 0x0F) + j += 3 + } else { + out[j] = ch + j++ + } + } + + return string(out) +} + +// UnescapeBusAddressValue unescapes values in D-Bus server addresses, +// as defined by the D-Bus specification at +// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses. +func UnescapeBusAddressValue(val string) (string, error) { + // Looks like url.PathUnescape does exactly what is required. + return url.PathUnescape(val) +} + +// hexchar returns an octal representation of a n, where n < 16. +// For invalid values of n, the function panics. +func hexchar(n byte) byte { + const hex = "0123456789abcdef" + + // For n >= len(hex), runtime will panic. + return hex[n] +} + +// needsEscape tells if a byte is NOT one of optionally-escaped bytes. +func needsEscape(c byte) bool { + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { + return false + } + switch c { + case '-', '_', '/', '\\', '.', '*': + return false + } + + return true +} + +// strNeedsEscape tells how many bytes in the string need escaping. +func strNeedsEscape(val string) int { + count := 0 + + for i := 0; i < len(val); i++ { + if needsEscape(val[i]) { + count++ + } + } + + return count +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/export.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/export.go index 522334715b6..d3dd9f7cd62 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/export.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/export.go @@ -3,6 +3,7 @@ package dbus import ( "errors" "fmt" + "os" "reflect" "strings" ) @@ -209,28 +210,23 @@ func (conn *Conn) handleCall(msg *Message) { } reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...)) - conn.sendMessageAndIfClosed(reply, nil) + if err := reply.IsValid(); err != nil { + fmt.Fprintf(os.Stderr, "dbus: dropping invalid reply to %s.%s on obj %s: %s\n", ifaceName, name, path, err) + } else { + conn.sendMessageAndIfClosed(reply, nil) + } } } // Emit emits the given signal on the message bus. The name parameter must be // formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost". func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error { - if !path.IsValid() { - return errors.New("dbus: invalid object path") - } i := strings.LastIndex(name, ".") if i == -1 { return errors.New("dbus: invalid method name") } iface := name[:i] member := name[i+1:] - if !isValidMember(member) { - return errors.New("dbus: invalid method name") - } - if !isValidInterface(iface) { - return errors.New("dbus: invalid interface name") - } msg := new(Message) msg.Type = TypeSignal msg.Headers = make(map[HeaderField]Variant) @@ -241,6 +237,9 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro if len(values) > 0 { msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) } + if err := msg.IsValid(); err != nil { + return err + } var closed bool conn.sendMessageAndIfClosed(msg, func() { diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir.go index 0b745f9313a..c44d9b5fc24 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir.go @@ -2,27 +2,24 @@ package dbus import ( "os" - "sync" -) - -var ( - homeDir string - homeDirLock sync.Mutex + "os/user" ) +// Get returns the home directory of the current user, which is usually the +// value of HOME environment variable. In case it is not set or empty, os/user +// package is used. +// +// If linking statically with cgo enabled against glibc, make sure the +// osusergo build tag is used. +// +// If needing to do nss lookups, do not disable cgo or set osusergo. func getHomeDir() string { - homeDirLock.Lock() - defer homeDirLock.Unlock() - + homeDir := os.Getenv("HOME") if homeDir != "" { return homeDir } - - homeDir = os.Getenv("HOME") - if homeDir != "" { - return homeDir + if u, err := user.Current(); err == nil { + return u.HomeDir } - - homeDir = lookupHomeDir() - return homeDir + return "/" } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir_dynamic.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir_dynamic.go deleted file mode 100644 index 2732081e73b..00000000000 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir_dynamic.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !static_build - -package dbus - -import ( - "os/user" -) - -func lookupHomeDir() string { - u, err := user.Current() - if err != nil { - return "/" - } - return u.HomeDir -} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir_static.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir_static.go deleted file mode 100644 index b9d9cb5525a..00000000000 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/homedir_static.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build static_build - -package dbus - -import ( - "bufio" - "os" - "strconv" - "strings" -) - -func lookupHomeDir() string { - myUid := os.Getuid() - - f, err := os.Open("/etc/passwd") - if err != nil { - return "/" - } - defer f.Close() - - s := bufio.NewScanner(f) - - for s.Scan() { - if err := s.Err(); err != nil { - break - } - - line := strings.TrimSpace(s.Text()) - if line == "" { - continue - } - - parts := strings.Split(line, ":") - - if len(parts) >= 6 { - uid, err := strconv.Atoi(parts[2]) - if err == nil && uid == myUid { - return parts[5] - } - } - } - - // Default to / if we can't get a better value - return "/" -} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/message.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/message.go index 16693eb3017..bdf43fdd6e5 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/message.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/message.go @@ -208,7 +208,7 @@ func DecodeMessageWithFDs(rd io.Reader, fds []int) (msg *Message, err error) { // The possibly returned error can be an error of the underlying reader, an // InvalidMessageError or a FormatError. func DecodeMessage(rd io.Reader) (msg *Message, err error) { - return DecodeMessageWithFDs(rd, make([]int, 0)); + return DecodeMessageWithFDs(rd, make([]int, 0)) } type nullwriter struct{} @@ -227,8 +227,8 @@ func (msg *Message) CountFds() (int, error) { } func (msg *Message) EncodeToWithFDs(out io.Writer, order binary.ByteOrder) (fds []int, err error) { - if err := msg.IsValid(); err != nil { - return make([]int, 0), err + if err := msg.validateHeader(); err != nil { + return nil, err } var vs [7]interface{} switch order { @@ -237,7 +237,7 @@ func (msg *Message) EncodeToWithFDs(out io.Writer, order binary.ByteOrder) (fds case binary.BigEndian: vs[0] = byte('B') default: - return make([]int, 0), errors.New("dbus: invalid byte order") + return nil, errors.New("dbus: invalid byte order") } body := new(bytes.Buffer) fds = make([]int, 0) @@ -284,8 +284,13 @@ func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) (err error) } // IsValid checks whether msg is a valid message and returns an -// InvalidMessageError if it is not. +// InvalidMessageError or FormatError if it is not. func (msg *Message) IsValid() error { + var b bytes.Buffer + return msg.EncodeTo(&b, nativeEndian) +} + +func (msg *Message) validateHeader() error { if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected|FlagAllowInteractiveAuthorization) != 0 { return InvalidMessageError("invalid flags") } @@ -330,6 +335,7 @@ func (msg *Message) IsValid() error { return InvalidMessageError("missing signature") } } + return nil } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/server_interfaces.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/server_interfaces.go index 79d97edf3ec..e4e0389fdf0 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/server_interfaces.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/server_interfaces.go @@ -63,7 +63,7 @@ type Method interface { // any other decoding scheme. type ArgumentDecoder interface { // To decode the arguments of a method the sender and message are - // provided incase the semantics of the implementer provides access + // provided in case the semantics of the implementer provides access // to these as part of the method invocation. DecodeArguments(conn *Conn, sender string, msg *Message, args []interface{}) ([]interface{}, error) } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/sig.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/sig.go index 41a0398129d..6b9cadb5fbc 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/sig.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/sig.go @@ -102,7 +102,7 @@ func getSignature(t reflect.Type, depth *depthCounter) (sig string) { } } if len(s) == 0 { - panic("empty struct") + panic(InvalidTypeError{t}) } return "(" + s + ")" case reflect.Array, reflect.Slice: diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/transport_unix.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/transport_unix.go index 2212e7fa7f2..0a8c712ebdf 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/transport_unix.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/transport_unix.go @@ -154,17 +154,15 @@ func (t *unixTransport) ReadMessage() (*Message, error) { // substitute the values in the message body (which are indices for the // array receiver via OOB) with the actual values for i, v := range msg.Body { - switch v.(type) { + switch index := v.(type) { case UnixFDIndex: - j := v.(UnixFDIndex) - if uint32(j) >= unixfds { + if uint32(index) >= unixfds { return nil, InvalidMessageError("invalid index for unix fd") } - msg.Body[i] = UnixFD(fds[j]) + msg.Body[i] = UnixFD(fds[index]) case []UnixFDIndex: - idxArray := v.([]UnixFDIndex) - fdArray := make([]UnixFD, len(idxArray)) - for k, j := range idxArray { + fdArray := make([]UnixFD, len(index)) + for k, j := range index { if uint32(j) >= unixfds { return nil, InvalidMessageError("invalid index for unix fd") } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/transport_zos.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/transport_zos.go new file mode 100644 index 00000000000..1bba0d6bf78 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/transport_zos.go @@ -0,0 +1,6 @@ +package dbus + +func (t *unixTransport) SendNullByte() error { + _, err := t.Write([]byte{0}) + return err +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/variant.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/variant.go index f1e81f3ede6..ca3dbe16a44 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/variant.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/godbus/dbus/v5/variant.go @@ -49,7 +49,7 @@ func ParseVariant(s string, sig Signature) (Variant, error) { } // format returns a formatted version of v and whether this string can be parsed -// unambigously. +// unambiguously. func (v Variant) format() (string, bool) { switch v.sig.str[0] { case 'b', 'i': diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_linux.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_linux.go index 5c9e3e30e66..e78e726196e 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_linux.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_linux.go @@ -7,6 +7,34 @@ import ( "golang.org/x/sys/unix" ) +// MountedFast is a method of detecting a mount point without reading +// mountinfo from procfs. A caller can only trust the result if no error +// and sure == true are returned. Otherwise, other methods (e.g. parsing +// /proc/mounts) have to be used. If unsure, use Mounted instead (which +// uses MountedFast, but falls back to parsing mountinfo if needed). +// +// If a non-existent path is specified, an appropriate error is returned. +// In case the caller is not interested in this particular error, it should +// be handled separately using e.g. errors.Is(err, fs.ErrNotExist). +// +// This function is only available on Linux. When available (since kernel +// v5.6), openat2(2) syscall is used to reliably detect all mounts. Otherwise, +// the implementation falls back to using stat(2), which can reliably detect +// normal (but not bind) mounts. +func MountedFast(path string) (mounted, sure bool, err error) { + // Root is always mounted. + if path == string(os.PathSeparator) { + return true, true, nil + } + + path, err = normalizePath(path) + if err != nil { + return false, false, err + } + mounted, sure, err = mountedFast(path) + return +} + // mountedByOpenat2 is a method of detecting a mount that works for all kinds // of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel. func mountedByOpenat2(path string) (bool, error) { @@ -34,24 +62,40 @@ func mountedByOpenat2(path string) (bool, error) { return false, &os.PathError{Op: "openat2", Path: path, Err: err} } -func mounted(path string) (bool, error) { - path, err := normalizePath(path) - if err != nil { - return false, err +// mountedFast is similar to MountedFast, except it expects a normalized path. +func mountedFast(path string) (mounted, sure bool, err error) { + // Root is always mounted. + if path == string(os.PathSeparator) { + return true, true, nil } + // Try a fast path, using openat2() with RESOLVE_NO_XDEV. - mounted, err := mountedByOpenat2(path) + mounted, err = mountedByOpenat2(path) if err == nil { - return mounted, nil + return mounted, true, nil } + // Another fast path: compare st.st_dev fields. mounted, err = mountedByStat(path) // This does not work for bind mounts, so false negative // is possible, therefore only trust if return is true. if mounted && err == nil { + return true, true, nil + } + + return +} + +func mounted(path string) (bool, error) { + path, err := normalizePath(path) + if err != nil { + return false, err + } + mounted, sure, err := mountedFast(path) + if sure && err == nil { return mounted, nil } - // Fallback to parsing mountinfo + // Fallback to parsing mountinfo. return mountedByMountinfo(path) } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_unix.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_unix.go index 45ddad236f3..c7b7678f9a0 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_unix.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mounted_unix.go @@ -1,10 +1,9 @@ -//go:build linux || (freebsd && cgo) || (openbsd && cgo) || (darwin && cgo) -// +build linux freebsd,cgo openbsd,cgo darwin,cgo +//go:build linux || freebsd || openbsd || darwin +// +build linux freebsd openbsd darwin package mountinfo import ( - "fmt" "os" "path/filepath" @@ -33,13 +32,13 @@ func mountedByStat(path string) (bool, error) { func normalizePath(path string) (realPath string, err error) { if realPath, err = filepath.Abs(path); err != nil { - return "", fmt.Errorf("unable to get absolute path for %q: %w", path, err) + return "", err } if realPath, err = filepath.EvalSymlinks(realPath); err != nil { - return "", fmt.Errorf("failed to canonicalise path for %q: %w", path, err) + return "", err } if _, err := os.Stat(realPath); err != nil { - return "", fmt.Errorf("failed to stat target of %q: %w", path, err) + return "", err } return realPath, nil } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo.go index 9867a66dd85..574aeb8767a 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo.go @@ -13,9 +13,9 @@ func GetMounts(f FilterFunc) ([]*Info, error) { // Mounted determines if a specified path is a mount point. In case of any // error, false (and an error) is returned. // -// The non-existent path returns an error. If a caller is not interested -// in this particular error, it should handle it separately using e.g. -// errors.Is(err, os.ErrNotExist). +// If a non-existent path is specified, an appropriate error is returned. +// In case the caller is not interested in this particular error, it should +// be handled separately using e.g. errors.Is(err, fs.ErrNotExist). func Mounted(path string) (bool, error) { // root is always mounted if path == string(os.PathSeparator) { diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go index d5513a26d2f..8420f58c7a9 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go @@ -1,53 +1,37 @@ -//go:build (freebsd && cgo) || (openbsd && cgo) || (darwin && cgo) -// +build freebsd,cgo openbsd,cgo darwin,cgo +//go:build freebsd || openbsd || darwin +// +build freebsd openbsd darwin package mountinfo -/* -#include -#include -#include -*/ -import "C" - -import ( - "fmt" - "reflect" - "unsafe" -) +import "golang.org/x/sys/unix" // parseMountTable returns information about mounted filesystems func parseMountTable(filter FilterFunc) ([]*Info, error) { - var rawEntries *C.struct_statfs - - count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT)) - if count == 0 { - return nil, fmt.Errorf("failed to call getmntinfo") + count, err := unix.Getfsstat(nil, unix.MNT_WAIT) + if err != nil { + return nil, err } - var entries []C.struct_statfs - header := (*reflect.SliceHeader)(unsafe.Pointer(&entries)) - header.Cap = count - header.Len = count - header.Data = uintptr(unsafe.Pointer(rawEntries)) + entries := make([]unix.Statfs_t, count) + _, err = unix.Getfsstat(entries, unix.MNT_WAIT) + if err != nil { + return nil, err + } var out []*Info for _, entry := range entries { - var mountinfo Info var skip, stop bool - mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0]) - mountinfo.FSType = C.GoString(&entry.f_fstypename[0]) - mountinfo.Source = C.GoString(&entry.f_mntfromname[0]) + mountinfo := getMountinfo(&entry) if filter != nil { // filter out entries we're not interested in - skip, stop = filter(&mountinfo) + skip, stop = filter(mountinfo) if skip { continue } } - out = append(out, &mountinfo) + out = append(out, mountinfo) if stop { break } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsdlike.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsdlike.go new file mode 100644 index 00000000000..ecaaa7a9c11 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsdlike.go @@ -0,0 +1,14 @@ +//go:build freebsd || darwin +// +build freebsd darwin + +package mountinfo + +import "golang.org/x/sys/unix" + +func getMountinfo(entry *unix.Statfs_t) *Info { + return &Info{ + Mountpoint: unix.ByteSliceToString(entry.Mntonname[:]), + FSType: unix.ByteSliceToString(entry.Fstypename[:]), + Source: unix.ByteSliceToString(entry.Mntfromname[:]), + } +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go index 59332b07bf4..b32b5c9b150 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go @@ -5,15 +5,19 @@ import ( "fmt" "io" "os" + "runtime" "strconv" "strings" + "sync" + + "golang.org/x/sys/unix" ) // GetMountsFromReader retrieves a list of mounts from the // reader provided, with an optional filter applied (use nil // for no filter). This can be useful in tests or benchmarks // that provide fake mountinfo data, or when a source other -// than /proc/self/mountinfo needs to be read from. +// than /proc/thread-self/mountinfo needs to be read from. // // This function is Linux-specific. func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) { @@ -127,8 +131,40 @@ func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) { return out, nil } -func parseMountTable(filter FilterFunc) ([]*Info, error) { - f, err := os.Open("/proc/self/mountinfo") +var ( + haveProcThreadSelf bool + haveProcThreadSelfOnce sync.Once +) + +func parseMountTable(filter FilterFunc) (_ []*Info, err error) { + haveProcThreadSelfOnce.Do(func() { + _, err := os.Stat("/proc/thread-self/mountinfo") + haveProcThreadSelf = err == nil + }) + + // We need to lock ourselves to the current OS thread in order to make sure + // that the thread referenced by /proc/thread-self stays alive until we + // finish parsing the file. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var f *os.File + if haveProcThreadSelf { + f, err = os.Open("/proc/thread-self/mountinfo") + } else { + // On pre-3.17 kernels (such as CentOS 7), we don't have + // /proc/thread-self/ so we need to manually construct + // /proc/self/task// as a fallback. + f, err = os.Open("/proc/self/task/" + strconv.Itoa(unix.Gettid()) + "/mountinfo") + if os.IsNotExist(err) { + // If /proc/self/task/... failed, it means that our active pid + // namespace doesn't match the pid namespace of the /proc mount. In + // this case we just have to make do with /proc/self, since there + // is no other way of figuring out our tid in a parent pid + // namespace on pre-3.17 kernels. + f, err = os.Open("/proc/self/mountinfo") + } + } if err != nil { return nil, err } @@ -158,10 +194,10 @@ func PidMountInfo(pid int) ([]*Info, error) { // A few specific characters in mountinfo path entries (root and mountpoint) // are escaped using a backslash followed by a character's ascii code in octal. // -// space -- as \040 -// tab (aka \t) -- as \011 -// newline (aka \n) -- as \012 -// backslash (aka \\) -- as \134 +// space -- as \040 +// tab (aka \t) -- as \011 +// newline (aka \n) -- as \012 +// backslash (aka \\) -- as \134 // // This function converts path from mountinfo back, i.e. it unescapes the above sequences. func unescape(path string) (string, error) { diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_openbsd.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_openbsd.go new file mode 100644 index 00000000000..f682c2d3b59 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_openbsd.go @@ -0,0 +1,11 @@ +package mountinfo + +import "golang.org/x/sys/unix" + +func getMountinfo(entry *unix.Statfs_t) *Info { + return &Info{ + Mountpoint: unix.ByteSliceToString(entry.F_mntonname[:]), + FSType: unix.ByteSliceToString(entry.F_fstypename[:]), + Source: unix.ByteSliceToString(entry.F_mntfromname[:]), + } +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go index 95769a76dad..c2e64bc81c7 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go @@ -1,5 +1,5 @@ -//go:build (!windows && !linux && !freebsd && !openbsd && !darwin) || (freebsd && !cgo) || (openbsd && !cgo) || (darwin && !cgo) -// +build !windows,!linux,!freebsd,!openbsd,!darwin freebsd,!cgo openbsd,!cgo darwin,!cgo +//go:build !windows && !linux && !freebsd && !openbsd && !darwin +// +build !windows,!linux,!freebsd,!openbsd,!darwin package mountinfo diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/cmp.go b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/cmp.go new file mode 100644 index 00000000000..fbf1934a061 --- /dev/null +++ b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/cmp.go @@ -0,0 +1,44 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import "golang.org/x/exp/constraints" + +// min is a version of the predeclared function from the Go 1.21 release. +func min[T constraints.Ordered](a, b T) T { + if a < b || isNaN(a) { + return a + } + return b +} + +// max is a version of the predeclared function from the Go 1.21 release. +func max[T constraints.Ordered](a, b T) T { + if a > b || isNaN(a) { + return a + } + return b +} + +// cmpLess is a copy of cmp.Less from the Go 1.21 release. +func cmpLess[T constraints.Ordered](x, y T) bool { + return (isNaN(x) && !isNaN(y)) || x < y +} + +// cmpCompare is a copy of cmp.Compare from the Go 1.21 release. +func cmpCompare[T constraints.Ordered](x, y T) int { + xNaN := isNaN(x) + yNaN := isNaN(y) + if xNaN && yNaN { + return 0 + } + if xNaN || x < y { + return -1 + } + if yNaN || x > y { + return +1 + } + return 0 +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/slices.go b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/slices.go index 2540bd68255..5e8158bba86 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/slices.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/slices.go @@ -3,23 +3,20 @@ // license that can be found in the LICENSE file. // Package slices defines various functions useful with slices of any type. -// Unless otherwise specified, these functions all apply to the elements -// of a slice at index 0 <= i < len(s). -// -// Note that the less function in IsSortedFunc, SortFunc, SortStableFunc requires a -// strict weak ordering (https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings), -// or the sorting may fail to sort correctly. A common case is when sorting slices of -// floating-point numbers containing NaN values. package slices -import "golang.org/x/exp/constraints" +import ( + "unsafe" + + "golang.org/x/exp/constraints" +) // Equal reports whether two slices are equal: the same length and all // elements equal. If the lengths are different, Equal returns false. // Otherwise, the elements are compared in increasing index order, and the // comparison stops at the first unequal pair. // Floating point NaNs are not considered equal. -func Equal[E comparable](s1, s2 []E) bool { +func Equal[S ~[]E, E comparable](s1, s2 S) bool { if len(s1) != len(s2) { return false } @@ -31,12 +28,12 @@ func Equal[E comparable](s1, s2 []E) bool { return true } -// EqualFunc reports whether two slices are equal using a comparison +// EqualFunc reports whether two slices are equal using an equality // function on each pair of elements. If the lengths are different, // EqualFunc returns false. Otherwise, the elements are compared in // increasing index order, and the comparison stops at the first index // for which eq returns false. -func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool { +func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool { if len(s1) != len(s2) { return false } @@ -49,45 +46,37 @@ func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool { return true } -// Compare compares the elements of s1 and s2. -// The elements are compared sequentially, starting at index 0, +// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair +// of elements. The elements are compared sequentially, starting at index 0, // until one element is not equal to the other. // The result of comparing the first non-matching elements is returned. // If both slices are equal until one of them ends, the shorter slice is // considered less than the longer one. // The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. -// Comparisons involving floating point NaNs are ignored. -func Compare[E constraints.Ordered](s1, s2 []E) int { - s2len := len(s2) +func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int { for i, v1 := range s1 { - if i >= s2len { + if i >= len(s2) { return +1 } v2 := s2[i] - switch { - case v1 < v2: - return -1 - case v1 > v2: - return +1 + if c := cmpCompare(v1, v2); c != 0 { + return c } } - if len(s1) < s2len { + if len(s1) < len(s2) { return -1 } return 0 } -// CompareFunc is like Compare but uses a comparison function -// on each pair of elements. The elements are compared in increasing -// index order, and the comparisons stop after the first time cmp -// returns non-zero. +// CompareFunc is like [Compare] but uses a custom comparison function on each +// pair of elements. // The result is the first non-zero result of cmp; if cmp always // returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), // and +1 if len(s1) > len(s2). -func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { - s2len := len(s2) +func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { for i, v1 := range s1 { - if i >= s2len { + if i >= len(s2) { return +1 } v2 := s2[i] @@ -95,7 +84,7 @@ func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { return c } } - if len(s1) < s2len { + if len(s1) < len(s2) { return -1 } return 0 @@ -103,7 +92,7 @@ func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { // Index returns the index of the first occurrence of v in s, // or -1 if not present. -func Index[E comparable](s []E, v E) int { +func Index[S ~[]E, E comparable](s S, v E) int { for i := range s { if v == s[i] { return i @@ -114,7 +103,7 @@ func Index[E comparable](s []E, v E) int { // IndexFunc returns the first index i satisfying f(s[i]), // or -1 if none do. -func IndexFunc[E any](s []E, f func(E) bool) int { +func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { for i := range s { if f(s[i]) { return i @@ -124,39 +113,104 @@ func IndexFunc[E any](s []E, f func(E) bool) int { } // Contains reports whether v is present in s. -func Contains[E comparable](s []E, v E) bool { +func Contains[S ~[]E, E comparable](s S, v E) bool { return Index(s, v) >= 0 } // ContainsFunc reports whether at least one // element e of s satisfies f(e). -func ContainsFunc[E any](s []E, f func(E) bool) bool { +func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool { return IndexFunc(s, f) >= 0 } // Insert inserts the values v... into s at index i, // returning the modified slice. -// In the returned slice r, r[i] == v[0]. +// The elements at s[i:] are shifted up to make room. +// In the returned slice r, r[i] == v[0], +// and r[i+len(v)] == value originally at r[i]. // Insert panics if i is out of range. // This function is O(len(s) + len(v)). func Insert[S ~[]E, E any](s S, i int, v ...E) S { - tot := len(s) + len(v) - if tot <= cap(s) { - s2 := s[:tot] - copy(s2[i+len(v):], s[i:]) + m := len(v) + if m == 0 { + return s + } + n := len(s) + if i == n { + return append(s, v...) + } + if n+m > cap(s) { + // Use append rather than make so that we bump the size of + // the slice up to the next storage class. + // This is what Grow does but we don't call Grow because + // that might copy the values twice. + s2 := append(s[:i], make(S, n+m-i)...) copy(s2[i:], v) + copy(s2[i+m:], s[i:]) return s2 } - s2 := make(S, tot) - copy(s2, s[:i]) - copy(s2[i:], v) - copy(s2[i+len(v):], s[i:]) - return s2 + s = s[:n+m] + + // before: + // s: aaaaaaaabbbbccccccccdddd + // ^ ^ ^ ^ + // i i+m n n+m + // after: + // s: aaaaaaaavvvvbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // + // a are the values that don't move in s. + // v are the values copied in from v. + // b and c are the values from s that are shifted up in index. + // d are the values that get overwritten, never to be seen again. + + if !overlaps(v, s[i+m:]) { + // Easy case - v does not overlap either the c or d regions. + // (It might be in some of a or b, or elsewhere entirely.) + // The data we copy up doesn't write to v at all, so just do it. + + copy(s[i+m:], s[i:]) + + // Now we have + // s: aaaaaaaabbbbbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // Note the b values are duplicated. + + copy(s[i:], v) + + // Now we have + // s: aaaaaaaavvvvbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // That's the result we want. + return s + } + + // The hard case - v overlaps c or d. We can't just shift up + // the data because we'd move or clobber the values we're trying + // to insert. + // So instead, write v on top of d, then rotate. + copy(s[n:], v) + + // Now we have + // s: aaaaaaaabbbbccccccccvvvv + // ^ ^ ^ ^ + // i i+m n n+m + + rotateRight(s[i:], m) + + // Now we have + // s: aaaaaaaavvvvbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // That's the result we want. + return s } // Delete removes the elements s[i:j] from s, returning the modified slice. // Delete panics if s[i:j] is not a valid slice of s. -// Delete modifies the contents of the slice s; it does not create a new slice. // Delete is O(len(s)-j), so if many items must be deleted, it is better to // make a single call deleting them all together than to delete one at a time. // Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those @@ -168,22 +222,113 @@ func Delete[S ~[]E, E any](s S, i, j int) S { return append(s[:i], s[j:]...) } +// DeleteFunc removes any elements from s for which del returns true, +// returning the modified slice. +// When DeleteFunc removes m elements, it might not modify the elements +// s[len(s)-m:len(s)]. If those elements contain pointers you might consider +// zeroing those elements so that objects they reference can be garbage +// collected. +func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { + i := IndexFunc(s, del) + if i == -1 { + return s + } + // Don't start copying elements until we find one to delete. + for j := i + 1; j < len(s); j++ { + if v := s[j]; !del(v) { + s[i] = v + i++ + } + } + return s[:i] +} + // Replace replaces the elements s[i:j] by the given v, and returns the // modified slice. Replace panics if s[i:j] is not a valid slice of s. func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { _ = s[i:j] // verify that i:j is a valid subslice + + if i == j { + return Insert(s, i, v...) + } + if j == len(s) { + return append(s[:i], v...) + } + tot := len(s[:i]) + len(v) + len(s[j:]) - if tot <= cap(s) { - s2 := s[:tot] - copy(s2[i+len(v):], s[j:]) + if tot > cap(s) { + // Too big to fit, allocate and copy over. + s2 := append(s[:i], make(S, tot-i)...) // See Insert copy(s2[i:], v) + copy(s2[i+len(v):], s[j:]) return s2 } - s2 := make(S, tot) - copy(s2, s[:i]) - copy(s2[i:], v) - copy(s2[i+len(v):], s[j:]) - return s2 + + r := s[:tot] + + if i+len(v) <= j { + // Easy, as v fits in the deleted portion. + copy(r[i:], v) + if i+len(v) != j { + copy(r[i+len(v):], s[j:]) + } + return r + } + + // We are expanding (v is bigger than j-i). + // The situation is something like this: + // (example has i=4,j=8,len(s)=16,len(v)=6) + // s: aaaaxxxxbbbbbbbbyy + // ^ ^ ^ ^ + // i j len(s) tot + // a: prefix of s + // x: deleted range + // b: more of s + // y: area to expand into + + if !overlaps(r[i+len(v):], v) { + // Easy, as v is not clobbered by the first copy. + copy(r[i+len(v):], s[j:]) + copy(r[i:], v) + return r + } + + // This is a situation where we don't have a single place to which + // we can copy v. Parts of it need to go to two different places. + // We want to copy the prefix of v into y and the suffix into x, then + // rotate |y| spots to the right. + // + // v[2:] v[:2] + // | | + // s: aaaavvvvbbbbbbbbvv + // ^ ^ ^ ^ + // i j len(s) tot + // + // If either of those two destinations don't alias v, then we're good. + y := len(v) - (j - i) // length of y portion + + if !overlaps(r[i:j], v) { + copy(r[i:j], v[y:]) + copy(r[len(s):], v[:y]) + rotateRight(r[i:], y) + return r + } + if !overlaps(r[len(s):], v) { + copy(r[len(s):], v[:y]) + copy(r[i:j], v[y:]) + rotateRight(r[i:], y) + return r + } + + // Now we know that v overlaps both x and y. + // That means that the entirety of b is *inside* v. + // So we don't need to preserve b at all; instead we + // can copy v first, then copy the b part of v out of + // v to the right destination. + k := startIdx(v, s[j:]) + copy(r[i:], v) + copy(r[i+len(v):], r[i+k:]) + return r } // Clone returns a copy of the slice. @@ -198,7 +343,8 @@ func Clone[S ~[]E, E any](s S) S { // Compact replaces consecutive runs of equal elements with a single copy. // This is like the uniq command found on Unix. -// Compact modifies the contents of the slice s; it does not create a new slice. +// Compact modifies the contents of the slice s and returns the modified slice, +// which may have a smaller length. // When Compact discards m elements in total, it might not modify the elements // s[len(s)-m:len(s)]. If those elements contain pointers you might consider // zeroing those elements so that objects they reference can be garbage collected. @@ -218,7 +364,8 @@ func Compact[S ~[]E, E comparable](s S) S { return s[:i] } -// CompactFunc is like Compact but uses a comparison function. +// CompactFunc is like [Compact] but uses an equality function to compare elements. +// For runs of elements that compare equal, CompactFunc keeps the first one. func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { if len(s) < 2 { return s @@ -256,3 +403,97 @@ func Grow[S ~[]E, E any](s S, n int) S { func Clip[S ~[]E, E any](s S) S { return s[:len(s):len(s)] } + +// Rotation algorithm explanation: +// +// rotate left by 2 +// start with +// 0123456789 +// split up like this +// 01 234567 89 +// swap first 2 and last 2 +// 89 234567 01 +// join first parts +// 89234567 01 +// recursively rotate first left part by 2 +// 23456789 01 +// join at the end +// 2345678901 +// +// rotate left by 8 +// start with +// 0123456789 +// split up like this +// 01 234567 89 +// swap first 2 and last 2 +// 89 234567 01 +// join last parts +// 89 23456701 +// recursively rotate second part left by 6 +// 89 01234567 +// join at the end +// 8901234567 + +// TODO: There are other rotate algorithms. +// This algorithm has the desirable property that it moves each element exactly twice. +// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes. +// The follow-cycles algorithm can be 1-write but it is not very cache friendly. + +// rotateLeft rotates b left by n spaces. +// s_final[i] = s_orig[i+r], wrapping around. +func rotateLeft[E any](s []E, r int) { + for r != 0 && r != len(s) { + if r*2 <= len(s) { + swap(s[:r], s[len(s)-r:]) + s = s[:len(s)-r] + } else { + swap(s[:len(s)-r], s[r:]) + s, r = s[len(s)-r:], r*2-len(s) + } + } +} +func rotateRight[E any](s []E, r int) { + rotateLeft(s, len(s)-r) +} + +// swap swaps the contents of x and y. x and y must be equal length and disjoint. +func swap[E any](x, y []E) { + for i := 0; i < len(x); i++ { + x[i], y[i] = y[i], x[i] + } +} + +// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap. +func overlaps[E any](a, b []E) bool { + if len(a) == 0 || len(b) == 0 { + return false + } + elemSize := unsafe.Sizeof(a[0]) + if elemSize == 0 { + return false + } + // TODO: use a runtime/unsafe facility once one becomes available. See issue 12445. + // Also see crypto/internal/alias/alias.go:AnyOverlap + return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) && + uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1) +} + +// startIdx returns the index in haystack where the needle starts. +// prerequisite: the needle must be aliased entirely inside the haystack. +func startIdx[E any](haystack, needle []E) int { + p := &needle[0] + for i := range haystack { + if p == &haystack[i] { + return i + } + } + // TODO: what if the overlap is by a non-integral number of Es? + panic("needle not found") +} + +// Reverse reverses the elements of the slice in place. +func Reverse[S ~[]E, E any](s S) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/sort.go b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/sort.go index 231b6448acd..b67897f76b5 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/sort.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/sort.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp + package slices import ( @@ -11,57 +13,116 @@ import ( ) // Sort sorts a slice of any ordered type in ascending order. -// Sort may fail to sort correctly when sorting slices of floating-point -// numbers containing Not-a-number (NaN) values. -// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))}) -// instead if the input may contain NaNs. -func Sort[E constraints.Ordered](x []E) { +// When sorting floating-point numbers, NaNs are ordered before other values. +func Sort[S ~[]E, E constraints.Ordered](x S) { n := len(x) pdqsortOrdered(x, 0, n, bits.Len(uint(n))) } -// SortFunc sorts the slice x in ascending order as determined by the less function. -// This sort is not guaranteed to be stable. +// SortFunc sorts the slice x in ascending order as determined by the cmp +// function. This sort is not guaranteed to be stable. +// cmp(a, b) should return a negative number when a < b, a positive number when +// a > b and zero when a == b. // -// SortFunc requires that less is a strict weak ordering. +// SortFunc requires that cmp is a strict weak ordering. // See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. -func SortFunc[E any](x []E, less func(a, b E) bool) { +func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { n := len(x) - pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less) + pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp) } // SortStableFunc sorts the slice x while keeping the original order of equal -// elements, using less to compare elements. -func SortStableFunc[E any](x []E, less func(a, b E) bool) { - stableLessFunc(x, len(x), less) +// elements, using cmp to compare elements in the same way as [SortFunc]. +func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { + stableCmpFunc(x, len(x), cmp) } // IsSorted reports whether x is sorted in ascending order. -func IsSorted[E constraints.Ordered](x []E) bool { +func IsSorted[S ~[]E, E constraints.Ordered](x S) bool { for i := len(x) - 1; i > 0; i-- { - if x[i] < x[i-1] { + if cmpLess(x[i], x[i-1]) { return false } } return true } -// IsSortedFunc reports whether x is sorted in ascending order, with less as the -// comparison function. -func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool { +// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the +// comparison function as defined by [SortFunc]. +func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool { for i := len(x) - 1; i > 0; i-- { - if less(x[i], x[i-1]) { + if cmp(x[i], x[i-1]) < 0 { return false } } return true } +// Min returns the minimal value in x. It panics if x is empty. +// For floating-point numbers, Min propagates NaNs (any NaN value in x +// forces the output to be NaN). +func Min[S ~[]E, E constraints.Ordered](x S) E { + if len(x) < 1 { + panic("slices.Min: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + m = min(m, x[i]) + } + return m +} + +// MinFunc returns the minimal value in x, using cmp to compare elements. +// It panics if x is empty. If there is more than one minimal element +// according to the cmp function, MinFunc returns the first one. +func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { + if len(x) < 1 { + panic("slices.MinFunc: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + if cmp(x[i], m) < 0 { + m = x[i] + } + } + return m +} + +// Max returns the maximal value in x. It panics if x is empty. +// For floating-point E, Max propagates NaNs (any NaN value in x +// forces the output to be NaN). +func Max[S ~[]E, E constraints.Ordered](x S) E { + if len(x) < 1 { + panic("slices.Max: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + m = max(m, x[i]) + } + return m +} + +// MaxFunc returns the maximal value in x, using cmp to compare elements. +// It panics if x is empty. If there is more than one maximal element +// according to the cmp function, MaxFunc returns the first one. +func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { + if len(x) < 1 { + panic("slices.MaxFunc: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + if cmp(x[i], m) > 0 { + m = x[i] + } + } + return m +} + // BinarySearch searches for target in a sorted slice and returns the position // where target is found, or the position where target would appear in the // sort order; it also returns a bool saying whether the target is really found // in the slice. The slice must be sorted in increasing order. -func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) { +func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) { // Inlining is faster than calling BinarySearchFunc with a lambda. n := len(x) // Define x[-1] < target and x[n] >= target. @@ -70,24 +131,24 @@ func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) { for i < j { h := int(uint(i+j) >> 1) // avoid overflow when computing h // i ≤ h < j - if x[h] < target { + if cmpLess(x[h], target) { i = h + 1 // preserves x[i-1] < target } else { j = h // preserves x[j] >= target } } // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i. - return i, i < n && x[i] == target + return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target))) } -// BinarySearchFunc works like BinarySearch, but uses a custom comparison +// BinarySearchFunc works like [BinarySearch], but uses a custom comparison // function. The slice must be sorted in increasing order, where "increasing" // is defined by cmp. cmp should return 0 if the slice element matches // the target, a negative number if the slice element precedes the target, // or a positive number if the slice element follows the target. // cmp must implement the same ordering as the slice, such that if // cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice. -func BinarySearchFunc[E, T any](x []E, target T, cmp func(E, T) int) (int, bool) { +func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) { n := len(x) // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 . // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0. @@ -126,3 +187,9 @@ func (r *xorshift) Next() uint64 { func nextPowerOfTwo(length int) uint { return 1 << bits.Len(uint(length)) } + +// isNaN reports whether x is a NaN without requiring the math package. +// This will always return false if T is not floating-point. +func isNaN[T constraints.Ordered](x T) bool { + return x != x +} diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortfunc.go b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortanyfunc.go similarity index 64% rename from contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortfunc.go rename to contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortanyfunc.go index 2a632476c50..06f2c7a2481 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortfunc.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortanyfunc.go @@ -6,28 +6,28 @@ package slices -// insertionSortLessFunc sorts data[a:b] using insertion sort. -func insertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { +// insertionSortCmpFunc sorts data[a:b] using insertion sort. +func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { for i := a + 1; i < b; i++ { - for j := i; j > a && less(data[j], data[j-1]); j-- { + for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- { data[j], data[j-1] = data[j-1], data[j] } } } -// siftDownLessFunc implements the heap property on data[lo:hi]. +// siftDownCmpFunc implements the heap property on data[lo:hi]. // first is an offset into the array where the root of the heap lies. -func siftDownLessFunc[E any](data []E, lo, hi, first int, less func(a, b E) bool) { +func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) { root := lo for { child := 2*root + 1 if child >= hi { break } - if child+1 < hi && less(data[first+child], data[first+child+1]) { + if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) { child++ } - if !less(data[first+root], data[first+child]) { + if !(cmp(data[first+root], data[first+child]) < 0) { return } data[first+root], data[first+child] = data[first+child], data[first+root] @@ -35,30 +35,30 @@ func siftDownLessFunc[E any](data []E, lo, hi, first int, less func(a, b E) bool } } -func heapSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { +func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { first := a lo := 0 hi := b - a // Build heap with greatest element at top. for i := (hi - 1) / 2; i >= 0; i-- { - siftDownLessFunc(data, i, hi, first, less) + siftDownCmpFunc(data, i, hi, first, cmp) } // Pop elements, largest first, into end of data. for i := hi - 1; i >= 0; i-- { data[first], data[first+i] = data[first+i], data[first] - siftDownLessFunc(data, lo, i, first, less) + siftDownCmpFunc(data, lo, i, first, cmp) } } -// pdqsortLessFunc sorts data[a:b]. +// pdqsortCmpFunc sorts data[a:b]. // The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. // pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf // C++ implementation: https://github.com/orlp/pdqsort // Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ // limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. -func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) { +func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) { const maxInsertion = 12 var ( @@ -70,25 +70,25 @@ func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) { length := b - a if length <= maxInsertion { - insertionSortLessFunc(data, a, b, less) + insertionSortCmpFunc(data, a, b, cmp) return } // Fall back to heapsort if too many bad choices were made. if limit == 0 { - heapSortLessFunc(data, a, b, less) + heapSortCmpFunc(data, a, b, cmp) return } // If the last partitioning was imbalanced, we need to breaking patterns. if !wasBalanced { - breakPatternsLessFunc(data, a, b, less) + breakPatternsCmpFunc(data, a, b, cmp) limit-- } - pivot, hint := choosePivotLessFunc(data, a, b, less) + pivot, hint := choosePivotCmpFunc(data, a, b, cmp) if hint == decreasingHint { - reverseRangeLessFunc(data, a, b, less) + reverseRangeCmpFunc(data, a, b, cmp) // The chosen pivot was pivot-a elements after the start of the array. // After reversing it is pivot-a elements before the end of the array. // The idea came from Rust's implementation. @@ -98,48 +98,48 @@ func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) { // The slice is likely already sorted. if wasBalanced && wasPartitioned && hint == increasingHint { - if partialInsertionSortLessFunc(data, a, b, less) { + if partialInsertionSortCmpFunc(data, a, b, cmp) { return } } // Probably the slice contains many duplicate elements, partition the slice into // elements equal to and elements greater than the pivot. - if a > 0 && !less(data[a-1], data[pivot]) { - mid := partitionEqualLessFunc(data, a, b, pivot, less) + if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) { + mid := partitionEqualCmpFunc(data, a, b, pivot, cmp) a = mid continue } - mid, alreadyPartitioned := partitionLessFunc(data, a, b, pivot, less) + mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp) wasPartitioned = alreadyPartitioned leftLen, rightLen := mid-a, b-mid balanceThreshold := length / 8 if leftLen < rightLen { wasBalanced = leftLen >= balanceThreshold - pdqsortLessFunc(data, a, mid, limit, less) + pdqsortCmpFunc(data, a, mid, limit, cmp) a = mid + 1 } else { wasBalanced = rightLen >= balanceThreshold - pdqsortLessFunc(data, mid+1, b, limit, less) + pdqsortCmpFunc(data, mid+1, b, limit, cmp) b = mid } } } -// partitionLessFunc does one quicksort partition. +// partitionCmpFunc does one quicksort partition. // Let p = data[pivot] // Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. // On return, data[newpivot] = p -func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int, alreadyPartitioned bool) { +func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int, alreadyPartitioned bool) { data[a], data[pivot] = data[pivot], data[a] i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - for i <= j && less(data[i], data[a]) { + for i <= j && (cmp(data[i], data[a]) < 0) { i++ } - for i <= j && !less(data[j], data[a]) { + for i <= j && !(cmp(data[j], data[a]) < 0) { j-- } if i > j { @@ -151,10 +151,10 @@ func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) j-- for { - for i <= j && less(data[i], data[a]) { + for i <= j && (cmp(data[i], data[a]) < 0) { i++ } - for i <= j && !less(data[j], data[a]) { + for i <= j && !(cmp(data[j], data[a]) < 0) { j-- } if i > j { @@ -168,17 +168,17 @@ func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) return j, false } -// partitionEqualLessFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// partitionEqualCmpFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. // It assumed that data[a:b] does not contain elements smaller than the data[pivot]. -func partitionEqualLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int) { +func partitionEqualCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int) { data[a], data[pivot] = data[pivot], data[a] i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned for { - for i <= j && !less(data[a], data[i]) { + for i <= j && !(cmp(data[a], data[i]) < 0) { i++ } - for i <= j && less(data[a], data[j]) { + for i <= j && (cmp(data[a], data[j]) < 0) { j-- } if i > j { @@ -191,15 +191,15 @@ func partitionEqualLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) return i } -// partialInsertionSortLessFunc partially sorts a slice, returns true if the slice is sorted at the end. -func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) bool { +// partialInsertionSortCmpFunc partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) bool { const ( maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted shortestShifting = 50 // don't shift any elements on short arrays ) i := a + 1 for j := 0; j < maxSteps; j++ { - for i < b && !less(data[i], data[i-1]) { + for i < b && !(cmp(data[i], data[i-1]) < 0) { i++ } @@ -216,7 +216,7 @@ func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) b // Shift the smaller one to the left. if i-a >= 2 { for j := i - 1; j >= 1; j-- { - if !less(data[j], data[j-1]) { + if !(cmp(data[j], data[j-1]) < 0) { break } data[j], data[j-1] = data[j-1], data[j] @@ -225,7 +225,7 @@ func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) b // Shift the greater one to the right. if b-i >= 2 { for j := i + 1; j < b; j++ { - if !less(data[j], data[j-1]) { + if !(cmp(data[j], data[j-1]) < 0) { break } data[j], data[j-1] = data[j-1], data[j] @@ -235,9 +235,9 @@ func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) b return false } -// breakPatternsLessFunc scatters some elements around in an attempt to break some patterns +// breakPatternsCmpFunc scatters some elements around in an attempt to break some patterns // that might cause imbalanced partitions in quicksort. -func breakPatternsLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { +func breakPatternsCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { length := b - a if length >= 8 { random := xorshift(length) @@ -253,12 +253,12 @@ func breakPatternsLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { } } -// choosePivotLessFunc chooses a pivot in data[a:b]. +// choosePivotCmpFunc chooses a pivot in data[a:b]. // // [0,8): chooses a static pivot. // [8,shortestNinther): uses the simple median-of-three method. // [shortestNinther,∞): uses the Tukey ninther method. -func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (pivot int, hint sortedHint) { +func choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) (pivot int, hint sortedHint) { const ( shortestNinther = 50 maxSwaps = 4 * 3 @@ -276,12 +276,12 @@ func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (piv if l >= 8 { if l >= shortestNinther { // Tukey ninther method, the idea came from Rust's implementation. - i = medianAdjacentLessFunc(data, i, &swaps, less) - j = medianAdjacentLessFunc(data, j, &swaps, less) - k = medianAdjacentLessFunc(data, k, &swaps, less) + i = medianAdjacentCmpFunc(data, i, &swaps, cmp) + j = medianAdjacentCmpFunc(data, j, &swaps, cmp) + k = medianAdjacentCmpFunc(data, k, &swaps, cmp) } // Find the median among i, j, k and stores it into j. - j = medianLessFunc(data, i, j, k, &swaps, less) + j = medianCmpFunc(data, i, j, k, &swaps, cmp) } switch swaps { @@ -294,29 +294,29 @@ func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (piv } } -// order2LessFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. -func order2LessFunc[E any](data []E, a, b int, swaps *int, less func(a, b E) bool) (int, int) { - if less(data[b], data[a]) { +// order2CmpFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2CmpFunc[E any](data []E, a, b int, swaps *int, cmp func(a, b E) int) (int, int) { + if cmp(data[b], data[a]) < 0 { *swaps++ return b, a } return a, b } -// medianLessFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. -func medianLessFunc[E any](data []E, a, b, c int, swaps *int, less func(a, b E) bool) int { - a, b = order2LessFunc(data, a, b, swaps, less) - b, c = order2LessFunc(data, b, c, swaps, less) - a, b = order2LessFunc(data, a, b, swaps, less) +// medianCmpFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianCmpFunc[E any](data []E, a, b, c int, swaps *int, cmp func(a, b E) int) int { + a, b = order2CmpFunc(data, a, b, swaps, cmp) + b, c = order2CmpFunc(data, b, c, swaps, cmp) + a, b = order2CmpFunc(data, a, b, swaps, cmp) return b } -// medianAdjacentLessFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. -func medianAdjacentLessFunc[E any](data []E, a int, swaps *int, less func(a, b E) bool) int { - return medianLessFunc(data, a-1, a, a+1, swaps, less) +// medianAdjacentCmpFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentCmpFunc[E any](data []E, a int, swaps *int, cmp func(a, b E) int) int { + return medianCmpFunc(data, a-1, a, a+1, swaps, cmp) } -func reverseRangeLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { +func reverseRangeCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { i := a j := b - 1 for i < j { @@ -326,37 +326,37 @@ func reverseRangeLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { } } -func swapRangeLessFunc[E any](data []E, a, b, n int, less func(a, b E) bool) { +func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) { for i := 0; i < n; i++ { data[a+i], data[b+i] = data[b+i], data[a+i] } } -func stableLessFunc[E any](data []E, n int, less func(a, b E) bool) { +func stableCmpFunc[E any](data []E, n int, cmp func(a, b E) int) { blockSize := 20 // must be > 0 a, b := 0, blockSize for b <= n { - insertionSortLessFunc(data, a, b, less) + insertionSortCmpFunc(data, a, b, cmp) a = b b += blockSize } - insertionSortLessFunc(data, a, n, less) + insertionSortCmpFunc(data, a, n, cmp) for blockSize < n { a, b = 0, 2*blockSize for b <= n { - symMergeLessFunc(data, a, a+blockSize, b, less) + symMergeCmpFunc(data, a, a+blockSize, b, cmp) a = b b += 2 * blockSize } if m := a + blockSize; m < n { - symMergeLessFunc(data, a, m, n, less) + symMergeCmpFunc(data, a, m, n, cmp) } blockSize *= 2 } } -// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using +// symMergeCmpFunc merges the two sorted subsequences data[a:m] and data[m:b] using // the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum // Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz // Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in @@ -375,7 +375,7 @@ func stableLessFunc[E any](data []E, n int, less func(a, b E) bool) { // symMerge assumes non-degenerate arguments: a < m && m < b. // Having the caller check this condition eliminates many leaf recursion calls, // which improves performance. -func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { +func symMergeCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) { // Avoid unnecessary recursions of symMerge // by direct insertion of data[a] into data[m:b] // if data[a:m] only contains one element. @@ -387,7 +387,7 @@ func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { j := b for i < j { h := int(uint(i+j) >> 1) - if less(data[h], data[a]) { + if cmp(data[h], data[a]) < 0 { i = h + 1 } else { j = h @@ -411,7 +411,7 @@ func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { j := m for i < j { h := int(uint(i+j) >> 1) - if !less(data[m], data[h]) { + if !(cmp(data[m], data[h]) < 0) { i = h + 1 } else { j = h @@ -438,7 +438,7 @@ func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { for start < r { c := int(uint(start+r) >> 1) - if !less(data[p-c], data[c]) { + if !(cmp(data[p-c], data[c]) < 0) { start = c + 1 } else { r = c @@ -447,33 +447,33 @@ func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { end := n - start if start < m && m < end { - rotateLessFunc(data, start, m, end, less) + rotateCmpFunc(data, start, m, end, cmp) } if a < start && start < mid { - symMergeLessFunc(data, a, start, mid, less) + symMergeCmpFunc(data, a, start, mid, cmp) } if mid < end && end < b { - symMergeLessFunc(data, mid, end, b, less) + symMergeCmpFunc(data, mid, end, b, cmp) } } -// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// rotateCmpFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: // Data of the form 'x u v y' is changed to 'x v u y'. // rotate performs at most b-a many calls to data.Swap, // and it assumes non-degenerate arguments: a < m && m < b. -func rotateLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { +func rotateCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) { i := m - a j := b - m for i != j { if i > j { - swapRangeLessFunc(data, m-i, m, j, less) + swapRangeCmpFunc(data, m-i, m, j, cmp) i -= j } else { - swapRangeLessFunc(data, m-i, m+j-i, i, less) + swapRangeCmpFunc(data, m-i, m+j-i, i, cmp) j -= i } } // i == j - swapRangeLessFunc(data, m-i, m, i, less) + swapRangeCmpFunc(data, m-i, m, i, cmp) } diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortordered.go b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortordered.go index efaa1c8b714..99b47c3986a 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortordered.go +++ b/contrib/rthooks/tetragon-oci-hook/vendor/golang.org/x/exp/slices/zsortordered.go @@ -11,7 +11,7 @@ import "golang.org/x/exp/constraints" // insertionSortOrdered sorts data[a:b] using insertion sort. func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) { for i := a + 1; i < b; i++ { - for j := i; j > a && (data[j] < data[j-1]); j-- { + for j := i; j > a && cmpLess(data[j], data[j-1]); j-- { data[j], data[j-1] = data[j-1], data[j] } } @@ -26,10 +26,10 @@ func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) { if child >= hi { break } - if child+1 < hi && (data[first+child] < data[first+child+1]) { + if child+1 < hi && cmpLess(data[first+child], data[first+child+1]) { child++ } - if !(data[first+root] < data[first+child]) { + if !cmpLess(data[first+root], data[first+child]) { return } data[first+root], data[first+child] = data[first+child], data[first+root] @@ -107,7 +107,7 @@ func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) { // Probably the slice contains many duplicate elements, partition the slice into // elements equal to and elements greater than the pivot. - if a > 0 && !(data[a-1] < data[pivot]) { + if a > 0 && !cmpLess(data[a-1], data[pivot]) { mid := partitionEqualOrdered(data, a, b, pivot) a = mid continue @@ -138,10 +138,10 @@ func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivo data[a], data[pivot] = data[pivot], data[a] i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - for i <= j && (data[i] < data[a]) { + for i <= j && cmpLess(data[i], data[a]) { i++ } - for i <= j && !(data[j] < data[a]) { + for i <= j && !cmpLess(data[j], data[a]) { j-- } if i > j { @@ -153,10 +153,10 @@ func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivo j-- for { - for i <= j && (data[i] < data[a]) { + for i <= j && cmpLess(data[i], data[a]) { i++ } - for i <= j && !(data[j] < data[a]) { + for i <= j && !cmpLess(data[j], data[a]) { j-- } if i > j { @@ -177,10 +177,10 @@ func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (ne i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned for { - for i <= j && !(data[a] < data[i]) { + for i <= j && !cmpLess(data[a], data[i]) { i++ } - for i <= j && (data[a] < data[j]) { + for i <= j && cmpLess(data[a], data[j]) { j-- } if i > j { @@ -201,7 +201,7 @@ func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool ) i := a + 1 for j := 0; j < maxSteps; j++ { - for i < b && !(data[i] < data[i-1]) { + for i < b && !cmpLess(data[i], data[i-1]) { i++ } @@ -218,7 +218,7 @@ func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool // Shift the smaller one to the left. if i-a >= 2 { for j := i - 1; j >= 1; j-- { - if !(data[j] < data[j-1]) { + if !cmpLess(data[j], data[j-1]) { break } data[j], data[j-1] = data[j-1], data[j] @@ -227,7 +227,7 @@ func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool // Shift the greater one to the right. if b-i >= 2 { for j := i + 1; j < b; j++ { - if !(data[j] < data[j-1]) { + if !cmpLess(data[j], data[j-1]) { break } data[j], data[j-1] = data[j-1], data[j] @@ -298,7 +298,7 @@ func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, h // order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) { - if data[b] < data[a] { + if cmpLess(data[b], data[a]) { *swaps++ return b, a } @@ -389,7 +389,7 @@ func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { j := b for i < j { h := int(uint(i+j) >> 1) - if data[h] < data[a] { + if cmpLess(data[h], data[a]) { i = h + 1 } else { j = h @@ -413,7 +413,7 @@ func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { j := m for i < j { h := int(uint(i+j) >> 1) - if !(data[m] < data[h]) { + if !cmpLess(data[m], data[h]) { i = h + 1 } else { j = h @@ -440,7 +440,7 @@ func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { for start < r { c := int(uint(start+r) >> 1) - if !(data[p-c] < data[c]) { + if !cmpLess(data[p-c], data[c]) { start = c + 1 } else { r = c diff --git a/contrib/rthooks/tetragon-oci-hook/vendor/modules.txt b/contrib/rthooks/tetragon-oci-hook/vendor/modules.txt index 859bf1c6b5b..0fb3188c7aa 100644 --- a/contrib/rthooks/tetragon-oci-hook/vendor/modules.txt +++ b/contrib/rthooks/tetragon-oci-hook/vendor/modules.txt @@ -1,3 +1,6 @@ +# github.com/alecthomas/kong v0.8.1 +## explicit; go 1.18 +github.com/alecthomas/kong # github.com/antlr4-go/antlr/v4 v4.13.0 ## explicit; go 1.20 github.com/antlr4-go/antlr/v4 @@ -15,7 +18,10 @@ github.com/cilium/lumberjack/v2 # github.com/cilium/tetragon/api v0.0.0-00010101000000-000000000000 => ../../../api ## explicit; go 1.21.0 github.com/cilium/tetragon/api/v1/tetragon -# github.com/coreos/go-systemd/v22 v22.3.2 +# github.com/containers/common v0.57.0 +## explicit; go 1.18 +github.com/containers/common/pkg/hooks/1.0.0 +# github.com/coreos/go-systemd/v22 v22.5.0 ## explicit; go 1.12 github.com/coreos/go-systemd/v22/dbus # github.com/cyphar/filepath-securejoin v0.2.4 @@ -24,7 +30,7 @@ github.com/cyphar/filepath-securejoin # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/godbus/dbus/v5 v5.0.6 +# github.com/godbus/dbus/v5 v5.1.0 ## explicit; go 1.12 github.com/godbus/dbus/v5 # github.com/golang/protobuf v1.5.3 @@ -57,7 +63,7 @@ github.com/google/cel-go/common/types/traits github.com/google/cel-go/interpreter github.com/google/cel-go/parser github.com/google/cel-go/parser/gen -# github.com/moby/sys/mountinfo v0.5.0 +# github.com/moby/sys/mountinfo v0.7.1 ## explicit; go 1.16 github.com/moby/sys/mountinfo # github.com/opencontainers/runc v1.1.10 @@ -94,7 +100,7 @@ github.com/stoewer/go-strcase ## explicit; go 1.20 github.com/stretchr/testify/assert github.com/stretchr/testify/require -# golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc +# golang.org/x/exp v0.0.0-20231006140011-7918f672742d ## explicit; go 1.20 golang.org/x/exp/constraints golang.org/x/exp/slices diff --git a/docs/content/en/docs/reference/helm-chart.md b/docs/content/en/docs/reference/helm-chart.md index 5ca88dab2f4..8379d9c53dc 100644 --- a/docs/content/en/docs/reference/helm-chart.md +++ b/docs/content/en/docs/reference/helm-chart.md @@ -80,6 +80,12 @@ To use [the values available](#values), with `helm install` or `helm upgrade`, u | tetragon.image.override | string | `nil` | | | tetragon.image.repository | string | `"quay.io/cilium/tetragon"` | | | tetragon.image.tag | string | `"v1.0.0"` | | +| tetragon.ociHookSetup | object | `{"enabled":false,"extraVolumeMounts":[],"installDir":"/opt/tetragon","interface":"oci-hooks","resources":{},"securityContext":{"privileged":true}}` | Configure tetragon's init container for setting up tetragon-oci-hook on the host | +| tetragon.ociHookSetup.enabled | bool | `false` | enable init container to setup tetragon-oci-hook | +| tetragon.ociHookSetup.extraVolumeMounts | list | `[]` | Extra volume mounts to add to the oci-hook-setup init container | +| tetragon.ociHookSetup.interface | string | `"oci-hooks"` | interface specifices how the hook is configured. There is only one avaialble value for now: "oci-hooks" (https://github.com/containers/common/blob/main/pkg/hooks/docs/oci-hooks.5.md). | +| tetragon.ociHookSetup.resources | object | `{}` | resources for the the oci-hook-setup init container | +| tetragon.ociHookSetup.securityContext | object | `{"privileged":true}` | Security context for oci-hook-setup init container | | tetragon.processCacheSize | int | `65536` | | | tetragon.prometheus.address | string | `""` | The address at which to expose metrics. Set it to "" to expose on all available interfaces. | | tetragon.prometheus.enabled | bool | `true` | Whether to enable exposing Tetragon metrics. | diff --git a/install/kubernetes/tetragon/README.md b/install/kubernetes/tetragon/README.md index 138b20a82c0..b83e734a86b 100644 --- a/install/kubernetes/tetragon/README.md +++ b/install/kubernetes/tetragon/README.md @@ -63,6 +63,12 @@ Helm chart for Tetragon | tetragon.image.override | string | `nil` | | | tetragon.image.repository | string | `"quay.io/cilium/tetragon"` | | | tetragon.image.tag | string | `"v1.0.0"` | | +| tetragon.ociHookSetup | object | `{"enabled":false,"extraVolumeMounts":[],"installDir":"/opt/tetragon","interface":"oci-hooks","resources":{},"securityContext":{"privileged":true}}` | Configure tetragon's init container for setting up tetragon-oci-hook on the host | +| tetragon.ociHookSetup.enabled | bool | `false` | enable init container to setup tetragon-oci-hook | +| tetragon.ociHookSetup.extraVolumeMounts | list | `[]` | Extra volume mounts to add to the oci-hook-setup init container | +| tetragon.ociHookSetup.interface | string | `"oci-hooks"` | interface specifices how the hook is configured. There is only one avaialble value for now: "oci-hooks" (https://github.com/containers/common/blob/main/pkg/hooks/docs/oci-hooks.5.md). | +| tetragon.ociHookSetup.resources | object | `{}` | resources for the the oci-hook-setup init container | +| tetragon.ociHookSetup.securityContext | object | `{"privileged":true}` | Security context for oci-hook-setup init container | | tetragon.processCacheSize | int | `65536` | | | tetragon.prometheus.address | string | `""` | The address at which to expose metrics. Set it to "" to expose on all available interfaces. | | tetragon.prometheus.enabled | bool | `true` | Whether to enable exposing Tetragon metrics. | diff --git a/install/kubernetes/tetragon/templates/_container_oci_hooks_setup.tpl b/install/kubernetes/tetragon/templates/_container_oci_hooks_setup.tpl new file mode 100644 index 00000000000..895277d9bbe --- /dev/null +++ b/install/kubernetes/tetragon/templates/_container_oci_hooks_setup.tpl @@ -0,0 +1,26 @@ +{{- define "container.tetragon-oci-hook-setup" -}} +- name: oci-hook-setup + securityContext: + {{- toYaml .Values.tetragon.ociHookSetup.securityContext | nindent 4 }} + image: "{{ if .Values.tetragon.image.override }}{{ .Values.tetragon.image.override }}{{ else }}{{ .Values.tetragon.image.repository }}:{{ .Values.tetragon.image.tag | default .Chart.AppVersion }}{{ end }}" + terminationMessagePolicy: FallbackToLogsOnError + command: + - tetragon-oci-hook-setup + - install + - --interface={{ .Values.tetragon.ociHookSetup.interface }} + - --local-install-dir={{ include "container.tetragonOCIHookSetup.installPath" . }} + - --host-install-dir={{ .Values.tetragon.ociHookSetup.installDir }} + - --oci-hooks.local-dir={{ include "container.tetragonOCIHookSetup.hooksPath" . }} + volumeMounts: + {{- with .Values.tetragon.ociHookSetup.extraVolumeMounts }} + {{- toYaml . | nindent 4 }} + {{- end }} + - name: oci-hooks-path + mountPath: {{ include "container.tetragonOCIHookSetup.hooksPath" . }} + - name: oci-hooks-install-path + mountPath: {{ include "container.tetragonOCIHookSetup.installPath" . }} +{{- with .Values.tetragon.ociHookSetup.resources }} + resources: {} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end -}} diff --git a/install/kubernetes/tetragon/templates/_helpers.tpl b/install/kubernetes/tetragon/templates/_helpers.tpl index 79bedc4689c..b553c329c3e 100644 --- a/install/kubernetes/tetragon/templates/_helpers.tpl +++ b/install/kubernetes/tetragon/templates/_helpers.tpl @@ -65,4 +65,12 @@ ServiceAccounts {{- else -}} {{- printf "%s-operator-service-account" .Release.Name -}} {{- end -}} -{{- end }} \ No newline at end of file +{{- end }} + +{{- define "container.tetragonOCIHookSetup.installPath" -}} +{{- print "/hostInstall" -}} +{{- end }} + +{{- define "container.tetragonOCIHookSetup.hooksPath" -}} +{{- print "/hostHooks" -}} +{{- end }} diff --git a/install/kubernetes/tetragon/templates/daemonset.yaml b/install/kubernetes/tetragon/templates/daemonset.yaml index 28e474b4173..fd97a9265fd 100644 --- a/install/kubernetes/tetragon/templates/daemonset.yaml +++ b/install/kubernetes/tetragon/templates/daemonset.yaml @@ -50,6 +50,10 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} + initContainers: +{{- if .Values.tetragon.ociHookSetup.enabled }} + {{- include "container.tetragon-oci-hook-setup" . | nindent 6 -}} +{{- end }} containers: {{- if eq .Values.export.mode "stdout" }} {{- include "container.export.stdout" . | nindent 6 -}} @@ -93,6 +97,16 @@ spec: hostPath: path: {{ .Values.tetragon.hostProcPath }} type: Directory +{{- if .Values.tetragon.ociHookSetup.enabled }} + - name: oci-hooks-path + hostPath: + path: /usr/share/containers/oci/hooks.d/ + type: Directory + - name: oci-hooks-install-path + hostPath: + path: {{ quote .Values.tetragon.ociHookSetup.installDir }} + type: DirectoryOrCreate +{{- end }} {{- end }} {{- with .Values.extraVolumes }} {{- toYaml . | nindent 6 }} diff --git a/install/kubernetes/tetragon/values.yaml b/install/kubernetes/tetragon/values.yaml index de28f6b2b62..31fcec823d9 100644 --- a/install/kubernetes/tetragon/values.yaml +++ b/install/kubernetes/tetragon/values.yaml @@ -161,6 +161,21 @@ tetragon: # host, the path is /proc. Exceptions to this are environments like kind, where the runtime itself # does not run on the host. hostProcPath: "/proc" + # -- Configure tetragon's init container for setting up tetragon-oci-hook on the host + ociHookSetup: + # -- enable init container to setup tetragon-oci-hook + enabled: false + # -- interface specifices how the hook is configured. There is only one avaialble value for now: + # "oci-hooks" (https://github.com/containers/common/blob/main/pkg/hooks/docs/oci-hooks.5.md). + interface: "oci-hooks" + installDir: "/opt/tetragon" + # -- Security context for oci-hook-setup init container + securityContext: + privileged: true + # -- Extra volume mounts to add to the oci-hook-setup init container + extraVolumeMounts: [] + # -- resources for the the oci-hook-setup init container + resources: {} # -- Tetragon Operator settings tetragonOperator: