Update RootlessKit to v0.14.5 (#3902)

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
pull/3916/head
Akihiro Suda 2021-08-25 23:28:59 +09:00 committed by GitHub
parent 176451f4ea
commit c23e63aeea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 159 additions and 42 deletions

4
go.mod
View File

@ -86,7 +86,7 @@ require (
github.com/go-sql-driver/mysql v1.6.0
github.com/golangplus/testing v1.0.0 // indirect
github.com/google/cadvisor v0.39.2
github.com/google/uuid v1.2.0
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/k3s-io/helm-controller v0.11.3
@ -110,7 +110,7 @@ require (
github.com/rancher/wharfie v0.3.4
github.com/rancher/wrangler v0.8.3
github.com/robfig/cron/v3 v3.0.1
github.com/rootless-containers/rootlesskit v0.14.0
github.com/rootless-containers/rootlesskit v0.14.5
github.com/sirupsen/logrus v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0

13
go.sum
View File

@ -374,8 +374,8 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI=
@ -445,8 +445,9 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@ -516,7 +517,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/insomniacslk/dhcp v0.0.0-20210120172423-cc9239ac6294/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
github.com/ishidawataru/sctp v0.0.0-20190723014705-7c296d48a2b5/go.mod h1:DM4VvS+hD/kDi1U1QsX2fnZowwBhqD0Dk3bRPKF/Oc8=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/jamescun/tuntap v0.0.0-20190712092105-cb1fb277045c/go.mod h1:zzwpsgcYhzzIP5WyF8g9ivCv38cY9uAV9Gu0m3lThhE=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@ -860,8 +860,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rootless-containers/rootlesskit v0.14.0 h1:4zfZqDv7JzsVuMkj4ZMB9cs9mQQnyl1gWBsrpOYcmtk=
github.com/rootless-containers/rootlesskit v0.14.0/go.mod h1:nV3TpRISvwhZQSwo0nmQQnxjCxXr3mvrMi0oASLvzcg=
github.com/rootless-containers/rootlesskit v0.14.5 h1:X4eNt2e1h/uSjlssKqpeTY5fatrjDz9F9FX05RJB7Tw=
github.com/rootless-containers/rootlesskit v0.14.5/go.mod h1:Ai3detLzryb/4EkzXmNfh8aByUcBXp/qqkQusJs1SO8=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021 h1:if3/24+h9Sq6eDx8UUz1SO9cT9tizyIsATfB7b4D3tc=
@ -899,6 +899,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=

View File

@ -34,6 +34,13 @@ const (
writeLock lockType = unix.F_WRLCK
)
type cmdType int
const (
tryLock cmdType = unix.F_SETLK
waitLock cmdType = unix.F_SETLKW
)
type inode = uint64
type inodeLock struct {
@ -90,7 +97,7 @@ func (f *Flock) lock(locked *bool, flag lockType) error {
defer f.ensureFhState()
}
if _, err := f.doLock(flag, true); err != nil {
if _, err := f.doLock(waitLock, flag, true); err != nil {
return err
}
@ -98,7 +105,7 @@ func (f *Flock) lock(locked *bool, flag lockType) error {
return nil
}
func (f *Flock) doLock(lt lockType, blocking bool) (bool, error) {
func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) {
// POSIX locks apply per inode and process, and the lock for an inode is
// released when *any* descriptor for that inode is closed. So we need to
// synchronize access to each inode internally, and must serialize lock and
@ -143,10 +150,13 @@ func (f *Flock) doLock(lt lockType, blocking bool) (bool, error) {
wait <- f
}
err = setlkw(f.fh.Fd(), lt)
err = setlkw(f.fh.Fd(), cmd, lt)
if err != nil {
f.doUnlock()
if cmd == tryLock && err == unix.EACCES {
return false, nil
}
return false, err
}
@ -186,7 +196,7 @@ func (f *Flock) doUnlock() (err error) {
mu.Unlock()
if owner == f {
err = setlkw(f.fh.Fd(), unix.F_UNLCK)
err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK)
}
mu.Lock()
@ -246,7 +256,7 @@ func (f *Flock) try(locked *bool, flag lockType) (bool, error) {
defer f.ensureFhState()
}
haslock, err := f.doLock(flag, false)
haslock, err := f.doLock(tryLock, flag, false)
if err != nil {
return false, err
}
@ -255,10 +265,10 @@ func (f *Flock) try(locked *bool, flag lockType) (bool, error) {
return haslock, nil
}
// setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd.
func setlkw(fd uintptr, lt lockType) error {
// setlkw calls FcntlFlock with cmd for the entire file indicated by fd.
func setlkw(fd uintptr, cmd cmdType, lt lockType) error {
for {
err := unix.FcntlFlock(fd, unix.F_SETLKW, &unix.Flock_t{
err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{
Type: int16(lt),
Whence: io.SeekStart,
Start: 0,

View File

@ -12,6 +12,7 @@ import (
"fmt"
"io"
"strings"
"sync"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
@ -33,7 +34,15 @@ const (
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
const randPoolSize = 16 * 16
var (
rander = rand.Reader // random function
poolEnabled = false
poolMu sync.Mutex
poolPos = randPoolSize // protected with poolMu
pool [randPoolSize]byte // protected with poolMu
)
type invalidLengthError struct{ len int }
@ -41,6 +50,12 @@ func (err invalidLengthError) Error() string {
return fmt.Sprintf("invalid UUID length: %d", err.len)
}
// IsInvalidLengthError is matcher function for custom error invalidLengthError
func IsInvalidLengthError(err error) bool {
_, ok := err.(invalidLengthError)
return ok
}
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
@ -249,3 +264,31 @@ func SetRand(r io.Reader) {
}
rander = r
}
// EnableRandPool enables internal randomness pool used for Random
// (Version 4) UUID generation. The pool contains random bytes read from
// the random number generator on demand in batches. Enabling the pool
// may improve the UUID generation throughput significantly.
//
// Since the pool is stored on the Go heap, this feature may be a bad fit
// for security sensitive applications.
//
// Both EnableRandPool and DisableRandPool are not thread-safe and should
// only be called when there is no possibility that New or any other
// UUID Version 4 generation function will be called concurrently.
func EnableRandPool() {
poolEnabled = true
}
// DisableRandPool disables the randomness pool if it was previously
// enabled with EnableRandPool.
//
// Both EnableRandPool and DisableRandPool are not thread-safe and should
// only be called when there is no possibility that New or any other
// UUID Version 4 generation function will be called concurrently.
func DisableRandPool() {
poolEnabled = false
defer poolMu.Unlock()
poolMu.Lock()
poolPos = randPoolSize
}

View File

@ -27,6 +27,8 @@ func NewString() string {
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// Uses the randomness pool if it was enabled with EnableRandPool.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
@ -35,7 +37,10 @@ func NewString() string {
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
return NewRandomFromReader(rander)
if !poolEnabled {
return NewRandomFromReader(rander)
}
return newRandomFromPool()
}
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
@ -49,3 +54,23 @@ func NewRandomFromReader(r io.Reader) (UUID, error) {
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}
func newRandomFromPool() (UUID, error) {
var uuid UUID
poolMu.Lock()
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return Nil, err
}
poolPos = 0
}
copy(uuid[:], pool[poolPos:(poolPos+16)])
poolPos += 16
poolMu.Unlock()
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

View File

@ -5,7 +5,7 @@ import "net"
const (
// Version of the REST API, not implementation version.
// See openapi.yaml for the definition.
Version = "1.1.0"
Version = "1.1.1"
)
// ErrorJSON is returned with "application/json" content type and non-2XX status code
@ -25,12 +25,15 @@ type Info struct {
// NetworkDriverInfo in Info
type NetworkDriverInfo struct {
Driver string `json:"driver"`
DNS []net.IP `json:"dns,omitempty"`
Driver string `json:"driver"`
DNS []net.IP `json:"dns,omitempty"`
ChildIP net.IP `json:"childIP,omitempty"` // since API v1.1.1 (RootlessKit v0.14.1)
DynamicChildIP bool `json:"dynamicChildIP,omitempty"` // since API v1.1.1
}
// PortDriverInfo in Info
type PortDriverInfo struct {
Driver string `json:"driver"`
Protos []string `json:"protos"`
Driver string `json:"driver"`
Protos []string `json:"protos"`
DisallowLoopbackChildIP bool `json:"disallowLoopbackChildIP,omitempty"` // since API v1.1.1
}

View File

@ -1,7 +1,7 @@
# When you made a change to this YAML, please validate with https://editor.swagger.io
openapi: 3.0.3
info:
version: 1.1.0
version: 1.1.1
title: RootlessKit API
servers:
- url: 'http://rootlesskit/v1'
@ -144,6 +144,13 @@ components:
items:
type: string
example: ["10.0.2.3"]
childIP:
type: string
description: "Child IP (v4)"
example: "10.0.2.100"
dynamicChildIP:
type: boolean
description: "Child IP may change"
PortDriverInfo:
required:
- driver
@ -159,3 +166,6 @@ components:
example: ["tcp","udp"]
items:
$ref: '#/components/schemas/Proto'
disallowLoopbackChildIP:
type: boolean
description: "If this field is set to true, loopback IP such as 127.0.0.1 cannot be specified as a child IP"

View File

@ -257,8 +257,10 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir string) (*common.
d.infoMu.Lock()
d.info = func() *api.NetworkDriverInfo {
return &api.NetworkDriverInfo{
Driver: DriverName,
DNS: []net.IP{net.ParseIP(netmsg.DNS)},
Driver: DriverName,
DNS: []net.IP{net.ParseIP(netmsg.DNS)},
ChildIP: net.ParseIP(netmsg.IP),
DynamicChildIP: false,
}
}
d.infoMu.Unlock()

View File

@ -12,6 +12,7 @@ import (
"strings"
"sync"
"syscall"
"time"
"github.com/pkg/errors"
@ -41,7 +42,7 @@ func NewDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error)
socketPath: socketPath,
childReadyPipePath: childReadyPipePath,
ports: make(map[int]*port.Status, 0),
stoppers: make(map[int]func() error, 0),
stoppers: make(map[int]func(context.Context) error, 0),
nextID: 1,
}
return &d, nil
@ -53,14 +54,15 @@ type driver struct {
childReadyPipePath string
mu sync.Mutex
ports map[int]*port.Status
stoppers map[int]func() error
stoppers map[int]func(context.Context) error
nextID int
}
func (d *driver) Info(ctx context.Context) (*api.PortDriverInfo, error) {
info := &api.PortDriverInfo{
Driver: "builtin",
Protos: []string{"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"},
Driver: "builtin",
Protos: []string{"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"},
DisallowLoopbackChildIP: false,
}
return info, nil
}
@ -137,16 +139,27 @@ func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, err
if err != nil {
return nil, err
}
// NOTE: routineStopCh is close-only channel. Do not send any data.
// See commit 4803f18fae1e39d200d98f09e445a97ccd6f5526 `Revert "port/builtin: RemovePort() block until conn is closed"`
routineStopCh := make(chan struct{})
routineStop := func() error {
routineStoppedCh := make(chan error)
routineStop := func(ctx context.Context) error {
close(routineStopCh)
return nil // FIXME
select {
case stoppedResult, stoppedResultOk := <-routineStoppedCh:
if stoppedResultOk {
return stoppedResult
}
return errors.New("routineStoppedCh was closed without sending data?")
case <-ctx.Done():
return errors.Wrap(err, "timed out while waiting for routineStoppedCh after closing routineStopCh")
}
}
switch spec.Proto {
case "tcp", "tcp4", "tcp6":
err = tcp.Run(d.socketPath, spec, routineStopCh, d.logWriter)
err = tcp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter)
case "udp", "udp4", "udp6":
err = udp.Run(d.socketPath, spec, routineStopCh, d.logWriter)
err = udp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter)
default:
// NOTREACHED
return nil, errors.New("spec was not validated?")
@ -187,7 +200,12 @@ func (d *driver) RemovePort(ctx context.Context, id int) error {
if !ok {
return errors.Errorf("unknown id: %d", id)
}
err := stop()
if _, ok := ctx.Deadline(); !ok {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
defer cancel()
}
err := stop(ctx)
delete(d.stoppers, id)
delete(d.ports, id)
return err

View File

@ -12,7 +12,7 @@ import (
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/msg"
)
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error {
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, stoppedCh chan error, logWriter io.Writer) error {
ln, err := net.Listen(spec.Proto, net.JoinHostPort(spec.ParentIP, strconv.Itoa(spec.ParentPort)))
if err != nil {
fmt.Fprintf(logWriter, "listen: %v\n", err)
@ -31,7 +31,10 @@ func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io
}
}()
go func() {
defer ln.Close()
defer func() {
stoppedCh <- ln.Close()
close(stoppedCh)
}()
for {
select {
case c, ok := <-newConns:

View File

@ -13,7 +13,7 @@ import (
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udpproxy"
)
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error {
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, stoppedCh chan error, logWriter io.Writer) error {
addr, err := net.ResolveUDPAddr(spec.Proto, net.JoinHostPort(spec.ParentIP, strconv.Itoa(spec.ParentPort)))
if err != nil {
return err
@ -51,6 +51,8 @@ func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io
case <-stopCh:
// udpp.Close closes ln as well
udpp.Close()
stoppedCh <- nil
close(stoppedCh)
return
}
}

View File

@ -1,3 +1,3 @@
package version
const Version = "0.14.0"
const Version = "0.14.5"

6
vendor/modules.txt vendored
View File

@ -540,7 +540,7 @@ github.com/go-openapi/swag
github.com/go-sql-driver/mysql
# github.com/godbus/dbus/v5 v5.0.4
github.com/godbus/dbus/v5
# github.com/gofrs/flock v0.8.0
# github.com/gofrs/flock v0.8.1
github.com/gofrs/flock
# github.com/gofrs/uuid v4.0.0+incompatible
github.com/gofrs/uuid
@ -631,7 +631,7 @@ github.com/google/go-containerregistry/pkg/name
github.com/google/gofuzz
# github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/shlex
# github.com/google/uuid v1.2.0
# github.com/google/uuid v1.3.0
## explicit
github.com/google/uuid
# github.com/googleapis/gax-go/v2 v2.0.5
@ -1003,7 +1003,7 @@ github.com/rancher/wrangler/pkg/yaml
# github.com/robfig/cron/v3 v3.0.1
## explicit
github.com/robfig/cron/v3
# github.com/rootless-containers/rootlesskit v0.14.0
# github.com/rootless-containers/rootlesskit v0.14.5
## explicit
github.com/rootless-containers/rootlesskit/pkg/api
github.com/rootless-containers/rootlesskit/pkg/api/client