diff --git a/.github/workflows/.test.yml b/.github/workflows/.test.yml index a0764253c0..e98421bd9f 100644 --- a/.github/workflows/.test.yml +++ b/.github/workflows/.test.yml @@ -230,6 +230,9 @@ jobs: if [[ "$MODE" == *"nftables"* ]]; then echo "DOCKER_FIREWALL_BACKEND=nftables" >> $GITHUB_ENV fi + if [[ "$MODE" == *"embedded"* ]]; then + echo "TEST_INTEGRATION_CONTAINERD_EMBEDDED=1" >> $GITHUB_ENV + fi echo "CACHE_DEV_SCOPE=${CACHE_DEV_SCOPE}" >> $GITHUB_ENV - name: Set up AppArmor for RootlessKit diff --git a/.github/workflows/.windows.yml b/.github/workflows/.windows.yml index eabd60162d..7c088af183 100644 --- a/.github/workflows/.windows.yml +++ b/.github/workflows/.windows.yml @@ -20,6 +20,10 @@ on: required: false type: boolean default: false + containerd: + required: false + type: string + default: "socket" secrets: CODECOV_TOKEN: required: false @@ -373,6 +377,7 @@ jobs: id: start-daemon env: INPUT_STORAGE: ${{ inputs.storage }} + INPUT_CONTAINERD: ${{ inputs.containerd }} MATRIX_RUNTIME: ${{ matrix.runtime }} run: | Write-Host "Creating service" @@ -384,7 +389,9 @@ jobs: "--pidfile=$env:TEMP\docker.pid", ` "--register-service" ) - If ($env:MATRIX_RUNTIME -eq "containerd") { + If ($env:INPUT_CONTAINERD -eq "embedded") { + $args += "--feature=embedded-containerd" + } ElseIf ($env:MATRIX_RUNTIME -eq "containerd") { $args += "--default-runtime=io.containerd.runhcs.v1" echo "DOCKER_WINDOWS_CONTAINERD_RUNTIME=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } @@ -401,6 +408,10 @@ jobs: $dockerEnviron += @("TEST_INTEGRATION_USE_GRAPHDRIVER=1") echo "TEST_INTEGRATION_USE_GRAPHDRIVER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } + If ($env:INPUT_CONTAINERD -eq "embedded") { + $dockerEnviron += @("TEST_INTEGRATION_CONTAINERD_EMBEDDED=1") + echo "TEST_INTEGRATION_CONTAINERD_EMBEDDED=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append + } New-ItemProperty -Name "Environment" -Path "HKLM:\SYSTEM\CurrentControlSet\Services\docker" -PropertyType MultiString -Value $dockerEnviron Write-Host "Starting service" Start-Service -Name docker @@ -408,6 +419,7 @@ jobs: - name: Waiting for test daemon to start env: + INPUT_CONTAINERD: ${{ inputs.containerd }} MATRIX_RUNTIME: ${{ matrix.runtime }} DOCKER_HOST: npipe:////./pipe/docker_engine run: | @@ -428,7 +440,7 @@ jobs: Start-Sleep -Seconds 1 } Write-Host "Test daemon started and replied!" - If ($env:MATRIX_RUNTIME -eq "containerd") { + If ($env:INPUT_CONTAINERD -ne "embedded" -and $env:MATRIX_RUNTIME -eq "containerd") { $containerdProcesses = Get-Process -Name containerd -ErrorAction:SilentlyContinue If (-not $containerdProcesses) { Throw "containerd process is not running" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ab23e1b26..f0dee99d1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: - arch: amd64 storage: snapshotter runnerSuffix: "" - extraModes: '["rootless", "systemd", "rootless-systemd", "iptables+firewalld", "nftables", "nftables+firewalld"]' + extraModes: '["rootless", "systemd", "rootless-systemd", "iptables+firewalld", "nftables", "nftables+firewalld", "embedded"]' - arch: amd64 storage: graphdriver runnerSuffix: "" diff --git a/.github/workflows/windows-2022.yml b/.github/workflows/windows-2022.yml index cf7b91ab40..47bcf2fa16 100644 --- a/.github/workflows/windows-2022.yml +++ b/.github/workflows/windows-2022.yml @@ -24,12 +24,17 @@ jobs: strategy: fail-fast: false matrix: - storage: - - graphdriver - - snapshotter + include: + - storage: graphdriver + containerd: socket + - storage: snapshotter + containerd: socket + - storage: snapshotter + containerd: embedded with: os: windows-2022 storage: ${{ matrix.storage }} + containerd: ${{ matrix.containerd }} send_coverage: true secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/windows-2025.yml b/.github/workflows/windows-2025.yml index dc9ff07827..d17e67a99d 100644 --- a/.github/workflows/windows-2025.yml +++ b/.github/workflows/windows-2025.yml @@ -29,12 +29,17 @@ jobs: strategy: fail-fast: false matrix: - storage: - - graphdriver - - snapshotter + include: + - storage: graphdriver + containerd: socket + - storage: snapshotter + containerd: socket + - storage: snapshotter + containerd: embedded with: os: windows-2025 storage: ${{ matrix.storage }} + containerd: ${{ matrix.containerd }} send_coverage: false secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/daemon/command/daemon.go b/daemon/command/daemon.go index 366a98a90a..2029511081 100644 --- a/daemon/command/daemon.go +++ b/daemon/command/daemon.go @@ -30,7 +30,7 @@ import ( "github.com/moby/moby/v2/daemon/config" buildkit "github.com/moby/moby/v2/daemon/internal/builder-next" "github.com/moby/moby/v2/daemon/internal/builder-next/exporter" - "github.com/moby/moby/v2/daemon/internal/libcontainerd/supervisor" + "github.com/moby/moby/v2/daemon/internal/containerd/server/supervisor" "github.com/moby/moby/v2/daemon/internal/otelutil" "github.com/moby/moby/v2/daemon/internal/rootless" "github.com/moby/moby/v2/daemon/listeners" @@ -76,6 +76,10 @@ type daemonCLI struct { d *daemon.Daemon authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins + // containerdDialer is the in-memory dialer for the embedded containerd. It + // is set in embedded mode and nil otherwise. See [daemon.ContainerdDialer]. + containerdDialer daemon.ContainerdDialer + stopOnce sync.Once apiShutdown chan struct{} apiTLSConfig *tls.Config @@ -294,7 +298,7 @@ func (cli *daemonCLI) start(ctx context.Context) (retErr error) { } cli.authzMiddleware = authz - d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore, cli.authzMiddleware) + d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore, cli.authzMiddleware, cli.containerdDialer) if err != nil { return errors.Wrap(err, "failed to start daemon") } @@ -322,7 +326,7 @@ func (cli *daemonCLI) start(ctx context.Context) (retErr error) { // initialized the cluster. d.RestartSwarmContainers() - b, shutdownBuildKit, err := initBuildkit(ctx, d, cdiCache) + b, shutdownBuildKit, err := initBuildkit(ctx, d, cdiCache, cli.containerdDialer) if err != nil { return fmt.Errorf("error initializing buildkit: %w", err) } @@ -444,7 +448,7 @@ func setOTLPProtoDefault() { } } -func initBuildkit(ctx context.Context, d *daemon.Daemon, cdiCache *cdi.Cache) (_ builderOptions, closeFn func(), _ error) { +func initBuildkit(ctx context.Context, d *daemon.Daemon, cdiCache *cdi.Cache, containerdDialer daemon.ContainerdDialer) (_ builderOptions, closeFn func(), _ error) { log.G(ctx).Info("Initializing buildkit") closeFn = func() {} @@ -477,6 +481,7 @@ func initBuildkit(ctx context.Context, d *daemon.Daemon, cdiCache *cdi.Cache) (_ UseSnapshotter: d.UsesSnapshotter(), Snapshotter: d.ImageService().StorageDriver(), ContainerdAddress: cfg.ContainerdAddr, + ContainerdDialer: containerdDialer, ContainerdNamespace: cfg.ContainerdNamespace, HyperVIsolation: d.DefaultIsolation().IsHyperV(), Callbacks: exporter.BuildkitCallbacks{ @@ -1159,7 +1164,11 @@ func (cli *daemonCLI) initializeContainerd(ctx context.Context) (func(time.Durat return nil, errors.Wrap(err, "failed to generate containerd options") } - r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...) + rootDir, err := containerdRootDir(ctx, cli.Config) + if err != nil { + return nil, err + } + r, err := supervisor.Start(ctx, rootDir, filepath.Join(cli.Config.ExecRoot, "containerd"), opts...) if err != nil { return nil, errors.Wrap(err, "failed to start containerd") } @@ -1169,6 +1178,24 @@ func (cli *daemonCLI) initializeContainerd(ctx context.Context) (func(time.Durat return r.WaitTimeout, nil } +// containerdRootEnv is a temporary testing override for reusing a standalone +// containerd data root with embedded or supervised containerd. +const containerdRootEnv = "DOCKER_CONTAINERD_ROOT" + +func containerdRootDir(ctx context.Context, cfg *config.Config) (string, error) { + if root := os.Getenv(containerdRootEnv); root != "" { + if !filepath.IsAbs(root) { + return "", fmt.Errorf("%s must be an absolute path: %q", containerdRootEnv, root) + } + log.G(ctx).WithFields(log.Fields{ + "environment-variable": containerdRootEnv, + "root": root, + }).Warn("overriding containerd root from environment") + return root, nil + } + return filepath.Join(cfg.Root, "containerd", "daemon"), nil +} + // cdiEnabled returns true if CDI feature wasn't explicitly disabled via // features. func cdiEnabled(conf *config.Config) bool { diff --git a/daemon/command/daemon_embedded_containerd.go b/daemon/command/daemon_embedded_containerd.go new file mode 100644 index 0000000000..cea9a97bf7 --- /dev/null +++ b/daemon/command/daemon_embedded_containerd.go @@ -0,0 +1,36 @@ +//go:build !no_embedded_containerd + +package command + +import ( + "context" + "path/filepath" + "time" + + "github.com/containerd/log" + "github.com/moby/moby/v2/daemon/internal/containerd/server/embedded" + "github.com/pkg/errors" +) + +// initEmbeddedContainerd starts containerd inside the daemon process and points +// the containerd clients at it. It is selected by the "embedded-containerd" +// feature. +func (cli *daemonCLI) initEmbeddedContainerd(ctx context.Context) (func(time.Duration) error, error) { + rootDir, err := containerdRootDir(ctx, cli.Config) + if err != nil { + return nil, err + } + log.G(ctx).Warn("Running with experimental embedded-containerd mode") + d, err := embedded.Start( + ctx, + rootDir, + filepath.Join(cli.Config.ExecRoot, "containerd"), + ) + if err != nil { + return nil, errors.Wrap(err, "failed to start embedded containerd") + } + cli.Config.ContainerdAddr = d.Address() + cli.containerdDialer = d.Dial + + return d.WaitTimeout, nil +} diff --git a/daemon/command/daemon_no_embedded_containerd.go b/daemon/command/daemon_no_embedded_containerd.go new file mode 100644 index 0000000000..5146ffedcd --- /dev/null +++ b/daemon/command/daemon_no_embedded_containerd.go @@ -0,0 +1,13 @@ +//go:build no_embedded_containerd + +package command + +import ( + "context" + "errors" + "time" +) + +func (cli *daemonCLI) initEmbeddedContainerd(context.Context) (func(time.Duration) error, error) { + return nil, errors.New("embedded containerd is not supported in this build") +} diff --git a/daemon/command/daemon_test.go b/daemon/command/daemon_test.go index e494d2a2f6..dcabd2c71f 100644 --- a/daemon/command/daemon_test.go +++ b/daemon/command/daemon_test.go @@ -1,6 +1,7 @@ package command import ( + "path/filepath" "runtime" "testing" @@ -181,6 +182,24 @@ func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) { assert.Check(t, is.Equal("syslog", loadedConfig.LogConfig.Type)) } +func TestContainerdRootDir(t *testing.T) { + cfg := config.Config{CommonConfig: config.CommonConfig{Root: t.TempDir()}} + t.Setenv(containerdRootEnv, "") + root, err := containerdRootDir(t.Context(), &cfg) + assert.NilError(t, err) + assert.Check(t, is.Equal(root, filepath.Join(cfg.Root, "containerd", "daemon"))) + + override := t.TempDir() + t.Setenv(containerdRootEnv, override) + root, err = containerdRootDir(t.Context(), &cfg) + assert.NilError(t, err) + assert.Check(t, is.Equal(root, override)) + + t.Setenv(containerdRootEnv, filepath.Join("relative", "containerd")) + _, err = containerdRootDir(t.Context(), &cfg) + assert.Check(t, is.ErrorContains(err, "must be an absolute path")) +} + func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) { content := `{ "registry-mirrors": ["https://mirrors.example.com"], diff --git a/daemon/command/daemon_unix.go b/daemon/command/daemon_unix.go index 99f7bc5e84..c85cfc8627 100644 --- a/daemon/command/daemon_unix.go +++ b/daemon/command/daemon_unix.go @@ -117,6 +117,11 @@ func newCgroupParent(cfg *config.Config) string { } func (cli *daemonCLI) initContainerd(ctx context.Context) (func(time.Duration) error, error) { + // Check embedded-containerd first so daemon.json can opt in even when + // packaged service units pass a default --containerd socket. + if cli.Config.Features["embedded-containerd"] { + return cli.initEmbeddedContainerd(ctx) + } if cli.Config.ContainerdAddr != "" { // use system containerd at the given address. return nil, nil diff --git a/daemon/command/daemon_windows.go b/daemon/command/daemon_windows.go index a331ea828b..0c9f915dc0 100644 --- a/daemon/command/daemon_windows.go +++ b/daemon/command/daemon_windows.go @@ -110,6 +110,11 @@ func newCgroupParent(*config.Config) string { } func (cli *daemonCLI) initContainerd(ctx context.Context) (func(time.Duration) error, error) { + // Check embedded-containerd first so daemon.json can opt in even when + // packaged service units pass a default --containerd socket. + if cli.Config.Features["embedded-containerd"] { + return cli.initEmbeddedContainerd(ctx) + } if cli.Config.ContainerdAddr != "" { return nil, nil } diff --git a/daemon/config/config.go b/daemon/config/config.go index aa90c882b1..e347aec46b 100644 --- a/daemon/config/config.go +++ b/daemon/config/config.go @@ -119,6 +119,10 @@ var skipDuplicates = map[string]bool{ "runtimes": true, } +// errEmbeddedContainerdWithCRI is returned when both the "embedded-containerd" +// feature and CRI support are enabled. +var errEmbeddedContainerdWithCRI = errors.New(`conflicting options: cannot use the "embedded-containerd" feature and CRI support (--cri-containerd) at the same time`) + // migratedNamedConfig describes legacy configuration file keys that have been migrated // from simple entries equivalent to command line flags, to a named option. // @@ -710,6 +714,10 @@ func Validate(config *Config) error { return err } + if config.Features["embedded-containerd"] && config.CriContainerd { + return errEmbeddedContainerdWithCRI + } + // validate DNSSearch for _, dnsSearch := range config.DNSSearch { if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil { diff --git a/daemon/config/config_test.go b/daemon/config/config_test.go index 559131b690..44e7b4aef6 100644 --- a/daemon/config/config_test.go +++ b/daemon/config/config_test.go @@ -234,6 +234,16 @@ func TestValidateConfigurationErrors(t *testing.T) { }, expectedErr: "bad attribute format: one", }, + { + name: "embedded-containerd with cri-containerd", + config: &Config{ + CommonConfig: CommonConfig{ + Features: map[string]bool{"embedded-containerd": true}, + CriContainerd: true, + }, + }, + expectedErr: errEmbeddedContainerdWithCRI.Error(), + }, { name: "multiple label without value", config: &Config{ @@ -459,6 +469,17 @@ func TestValidateConfigurationErrors(t *testing.T) { } } +func TestValidateEmbeddedContainerdAllowsExplicitAddr(t *testing.T) { + cfg := &Config{ + CommonConfig: CommonConfig{ + Features: map[string]bool{"embedded-containerd": true}, + ContainerdAddr: "/run/containerd/containerd.sock", + }, + } + + assert.NilError(t, Validate(cfg)) +} + func withForceOverwrite(fieldName string) func(config *mergo.Config) { return mergo.WithTransformers(overwriteTransformer{fieldName: fieldName}) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 07cf316082..aef9045eb4 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -96,6 +96,13 @@ type configStore struct { Runtimes runtimes } +// ContainerdDialer dials the in-process containerd over an in-memory pipe. +// +// It is set in embedded mode, so the daemon's own client can skip socket +// syscalls, and is nil otherwise. The signature matches grpc.WithContextDialer, +// and the address argument is ignored. +type ContainerdDialer = func(ctx context.Context, addr string) (net.Conn, error) + // Daemon holds information about the Docker daemon. type Daemon struct { id string @@ -846,7 +853,7 @@ func CheckSystem() error { // NewDaemon sets up everything for the daemon to be able to service // requests from the webserver. -func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.Store, authzMiddleware *authorization.Middleware) (_ *Daemon, retErr error) { +func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.Store, authzMiddleware *authorization.Middleware, containerdDialer ContainerdDialer) (_ *Daemon, retErr error) { registryService, err := registry.NewService(config.ServiceOptions) if err != nil { return nil, err @@ -1009,6 +1016,12 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor), grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor), } + if containerdDialer != nil { + // Keep ContainerdAddr as the gRPC target (so containerd.New still + // installs its namespace interceptors) but override the connection to + // use the in-memory pipe. + gopts = append(gopts, grpc.WithContextDialer(containerdDialer)) + } if cfgStore.ContainerdAddr != "" { log.G(ctx).WithFields(log.Fields{ diff --git a/daemon/info.go b/daemon/info.go index ea689c5d74..bea897189b 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -251,6 +251,9 @@ func (daemon *Daemon) fillDebugInfo(ctx context.Context, v *system.Info) { // fillContainerdInfo provides information about the containerd configuration // for debugging purposes. func (daemon *Daemon) fillContainerdInfo(v *system.Info, cfg *config.Config) { + if cfg.Features["embedded-containerd"] { + v.Warnings = append(v.Warnings, "WARNING: Running with experimental embedded-containerd mode. This feature may change or be removed in a future release.") + } if cfg.ContainerdAddr == "" { return } diff --git a/daemon/internal/builder-next/builder.go b/daemon/internal/builder-next/builder.go index 30f2b80467..8f95729b30 100644 --- a/daemon/internal/builder-next/builder.go +++ b/daemon/internal/builder-next/builder.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "maps" + "net" "net/netip" "strconv" "strings" @@ -98,6 +99,10 @@ type Opt struct { UseSnapshotter bool Snapshotter string ContainerdAddress string + // ContainerdDialer, when set (embedded mode), connects to the in-process + // containerd over an in-memory pipe so BuildKit avoids socket syscalls. + // ContainerdAddress is still the gRPC target. + ContainerdDialer func(ctx context.Context, address string) (net.Conn, error) ContainerdNamespace string HyperVIsolation bool Callbacks exporter.BuildkitCallbacks diff --git a/daemon/internal/builder-next/controller.go b/daemon/internal/builder-next/controller.go index d4f913d2cf..04e080bd27 100644 --- a/daemon/internal/builder-next/controller.go +++ b/daemon/internal/builder-next/controller.go @@ -58,6 +58,7 @@ import ( "github.com/pkg/errors" bolt "go.etcd.io/bbolt" "go.opentelemetry.io/otel/sdk/trace" + "google.golang.org/grpc" ) func newController(ctx context.Context, rt http.RoundTripper, opt Opt) (*control.Controller, error) { @@ -134,7 +135,16 @@ func newSnapshotterController(ctx context.Context, rt http.RoundTripper, opt Opt CDIManager: cdiManager, } - wo, err := containerd.NewWorkerOpt(workerOpts, ctd.WithTimeout(60*time.Second)) + ctdOpts := []ctd.Opt{ctd.WithTimeout(60 * time.Second)} + if opt.ContainerdDialer != nil { + // Embedded mode: dial the in-process containerd over the in-memory + // pipe. The address stays the gRPC target, and the dialer overrides the + // connection. + ctdOpts = append(ctdOpts, ctd.WithExtraDialOpts([]grpc.DialOption{ + grpc.WithContextDialer(opt.ContainerdDialer), + })) + } + wo, err := containerd.NewWorkerOpt(workerOpts, ctdOpts...) if err != nil { return nil, err } @@ -163,6 +173,7 @@ func newSnapshotterController(ctx context.Context, rt http.RoundTripper, opt Opt rootless: opt.Rootless, identityMapping: opt.IdentityMapping, containerdAddr: opt.ContainerdAddress, + containerdDialer: opt.ContainerdDialer, containerdNamespace: opt.ContainerdNamespace, hypervIsolation: opt.HyperVIsolation, }) @@ -384,6 +395,7 @@ func newGraphDriverController(ctx context.Context, rt http.RoundTripper, opt Opt // Windows-only fields (currently not used, as newExecutorGD is not implemented on Windows) containerdAddr: opt.ContainerdAddress, + containerdDialer: opt.ContainerdDialer, containerdNamespace: opt.ContainerdNamespace, hypervIsolation: opt.HyperVIsolation, }) diff --git a/daemon/internal/builder-next/executor_opts.go b/daemon/internal/builder-next/executor_opts.go index 8bb0826185..2890cfdeb6 100644 --- a/daemon/internal/builder-next/executor_opts.go +++ b/daemon/internal/builder-next/executor_opts.go @@ -1,6 +1,9 @@ package buildkit import ( + "context" + "net" + "github.com/moby/buildkit/executor/oci" "github.com/moby/buildkit/solver/llbsolver/cdidevices" "github.com/moby/buildkit/util/network" @@ -26,6 +29,7 @@ type executorOpts struct { // windows-only fields containerdAddr string + containerdDialer func(ctx context.Context, address string) (net.Conn, error) containerdNamespace string hypervIsolation bool } diff --git a/daemon/internal/builder-next/executor_windows.go b/daemon/internal/builder-next/executor_windows.go index a7901df548..f29159ade6 100644 --- a/daemon/internal/builder-next/executor_windows.go +++ b/daemon/internal/builder-next/executor_windows.go @@ -12,6 +12,7 @@ import ( "github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/util/network" "github.com/opencontainers/runtime-spec/specs-go" + "google.golang.org/grpc" ) const networkName = "nat" @@ -23,8 +24,14 @@ func newExecutor(opts executorOpts) (executor.Executor, network.ProxyProvider, e pb.NetMode_NONE: network.NewNoneProvider(), } - opt := ctd.WithDefaultNamespace(opts.containerdNamespace) - client, err := ctd.New(opts.containerdAddr, opt) + ctdOpts := []ctd.Opt{ctd.WithDefaultNamespace(opts.containerdNamespace)} + if opts.containerdDialer != nil { + // Embedded containerd: connect over the in-memory pipe. + ctdOpts = append(ctdOpts, ctd.WithExtraDialOpts([]grpc.DialOption{ + grpc.WithContextDialer(opts.containerdDialer), + })) + } + client, err := ctd.New(opts.containerdAddr, ctdOpts...) if err != nil { return nil, nil, err } diff --git a/daemon/internal/containerd/server/embedded/containerd.go b/daemon/internal/containerd/server/embedded/containerd.go new file mode 100644 index 0000000000..80e3cf28cb --- /dev/null +++ b/daemon/internal/containerd/server/embedded/containerd.go @@ -0,0 +1,284 @@ +//go:build (linux || windows) && !no_embedded_containerd + +package embedded + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "sync" + "sync/atomic" + + "github.com/containerd/containerd/v2/defaults" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/sys" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/ttrpc" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" +) + +type grpcService interface { + Register(*grpc.Server) error +} + +type ttrpcService interface { + RegisterTTRPC(*ttrpc.Server) error +} + +type serverConfig struct { + root string + state string + grpcAddress string + ttrpcAddress string + maxRecvMessageSize int + maxSendMessageSize int +} + +type containerdServer struct { + grpcServer *grpc.Server + ttrpcServer *ttrpc.Server + serveCtx context.Context + cancelServe context.CancelFunc + plugins []*plugin.Plugin + ready sync.WaitGroup + stopOnce sync.Once +} + +// newServer initializes the containerd plugin graph and the RPC servers needed +// by dockerd. +// Command-owned behavior such as process mutation, proxy plugins, TCP/TLS, and +// Prometheus registration is intentionally not part of the embedded server. +func newServer(ctx context.Context, cfg *serverConfig) (*containerdServer, error) { + registrations := registry.Graph(nil) + return newServerWithRegistrations(ctx, cfg, registrations) +} + +// newServerWithRegistrations mirrors containerd's plugin lifecycle for the +// subset used by dockerd. +// It is based on containerd v2.3.3's cmd/containerd/server.New: +// https://github.com/containerd/containerd/blob/aad11006b869517fcd3009450b6f82da282e1a9b/cmd/containerd/server/server.go +func newServerWithRegistrations(ctx context.Context, cfg *serverConfig, registrations []plugin.Registration) (_ *containerdServer, retErr error) { + // BuildKit registers collectors with the same names in dockerd, and the + // embedded containerd metrics are not exposed, so omit gRPC metrics instead + // of mutating the process-global Prometheus registerer. + grpcOptions := []grpc.ServerOption{ + grpc.StatsHandler(otelgrpc.NewServerHandler()), + grpc.ChainStreamInterceptor(streamNamespaceInterceptor), + grpc.ChainUnaryInterceptor(unaryNamespaceInterceptor), + } + if cfg.maxRecvMessageSize > 0 { + grpcOptions = append(grpcOptions, grpc.MaxRecvMsgSize(cfg.maxRecvMessageSize)) + } + if cfg.maxSendMessageSize > 0 { + grpcOptions = append(grpcOptions, grpc.MaxSendMsgSize(cfg.maxSendMessageSize)) + } + + ttrpcServer, err := newTTRPCServer() + if err != nil { + return nil, fmt.Errorf("creating containerd ttrpc server: %w", err) + } + + serveCtx, cancelServe := context.WithCancel(ctx) + srv := &containerdServer{ + grpcServer: grpc.NewServer(grpcOptions...), + ttrpcServer: ttrpcServer, + serveCtx: serveCtx, + cancelServe: cancelServe, + } + defer func() { + if retErr != nil { + srv.Stop() + } + }() + + initialized := plugin.NewPluginSet() + + var grpcServices []grpcService + var ttrpcServices []ttrpcService + for _, registration := range registrations { + id := registration.URI() + log.G(ctx).WithFields(log.Fields{"id": id, "type": registration.Type}).Info("loading plugin") + + var mustSucceed atomic.Bool + initContext := plugin.NewContext( + ctx, + initialized, + map[string]string{ + plugins.PropertyRootDir: filepath.Join(cfg.root, id), + plugins.PropertyStateDir: filepath.Join(cfg.state, id), + plugins.PropertyGRPCAddress: cfg.grpcAddress, + plugins.PropertyTTRPCAddress: cfg.ttrpcAddress, + }, + ) + initContext.RegisterReadiness = func() func() { + mustSucceed.Store(true) + return srv.registerReadiness() + } + initContext.Config = registration.Config + + result := registration.Init(initContext) + if err := initialized.Add(result); err != nil { + return nil, fmt.Errorf("adding plugin %q to initialized set: %w", id, err) + } + + instance, err := result.Instance() + if err != nil { + fields := log.Fields{"error": err, "id": id, "type": registration.Type} + if plugin.IsSkipPlugin(err) { + log.G(ctx).WithFields(fields).Info("skip loading plugin") + } else { + log.G(ctx).WithFields(fields).Warn("failed to load plugin") + } + if mustSucceed.Load() { + return nil, fmt.Errorf("plugin %q failed after registering readiness: %w", id, err) + } + continue + } + + if service, ok := instance.(grpcService); ok { + grpcServices = append(grpcServices, service) + } + if service, ok := instance.(ttrpcService); ok { + ttrpcServices = append(ttrpcServices, service) + } + srv.plugins = append(srv.plugins, result) + } + + for _, service := range grpcServices { + if err := service.Register(srv.grpcServer); err != nil { + return nil, fmt.Errorf("registering containerd grpc service: %w", err) + } + } + for _, service := range ttrpcServices { + if err := service.RegisterTTRPC(srv.ttrpcServer); err != nil { + return nil, fmt.Errorf("registering containerd ttrpc service: %w", err) + } + } + return srv, nil +} + +func createTopLevelDirectories(cfg *serverConfig) error { + switch { + case cfg.root == "": + return errors.New("containerd root must be specified") + case cfg.state == "": + return errors.New("containerd state must be specified") + case cfg.root == cfg.state: + return errors.New("containerd root and state must be different paths") + } + + if err := sys.MkdirAllWithACL(cfg.root, 0o700); err != nil { + return fmt.Errorf("creating containerd root directory: %w", err) + } + if err := os.Chmod(cfg.root, 0o700); err != nil { + return fmt.Errorf("setting containerd root directory permissions: %w", err) + } + + // State must be searchable by remapped users so they can reach plugin-owned + // directories with more restrictive permissions below it. + if err := sys.MkdirAllWithACL(cfg.state, 0o711); err != nil { + return fmt.Errorf("creating containerd state directory: %w", err) + } + if cfg.state != defaults.DefaultStateDir { + // Shim sockets and FIFOs still use the default state directory even when + // containerd is configured with a different state directory. + if err := sys.MkdirAllWithACL(defaults.DefaultStateDir, 0o711); err != nil { + return fmt.Errorf("creating default containerd state directory: %w", err) + } + } + return nil +} + +func (s *containerdServer) ServeGRPC(listener net.Listener) error { + return trapClosedConnErr(s.grpcServer.Serve(listener)) +} + +func (s *containerdServer) ServeTTRPC(listener net.Listener) error { + return trapClosedConnErr(s.ttrpcServer.Serve(s.serveCtx, listener)) +} + +func (s *containerdServer) Stop() { + s.stopOnce.Do(func() { + s.cancelServe() + s.grpcServer.Stop() + _ = s.ttrpcServer.Close() + s.closePlugins() + }) +} + +func (s *containerdServer) registerReadiness() func() { + s.ready.Add(1) + return s.ready.Done +} + +func (s *containerdServer) Wait() { + s.ready.Wait() +} + +func (s *containerdServer) closePlugins() { + for i := len(s.plugins) - 1; i >= 0; i-- { + initialized := s.plugins[i] + instance, err := initialized.Instance() + if err != nil { + log.L.WithFields(log.Fields{ + "error": err, + "id": initialized.Registration.URI(), + }).Error("could not get plugin instance") + continue + } + closer, ok := instance.(io.Closer) + if !ok { + continue + } + if err := closer.Close(); err != nil { + log.L.WithFields(log.Fields{ + "error": err, + "id": initialized.Registration.URI(), + }).Error("failed to close plugin") + } + } +} + +func unaryNamespaceInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if namespace, ok := namespaces.Namespace(ctx); ok { + // Namespace reads incoming metadata; add it to outgoing metadata for + // service handlers that call other containerd components. + ctx = namespaces.WithNamespace(ctx, namespace) + } + return handler(ctx, req) +} + +func streamNamespaceInterceptor(server any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + ctx := stream.Context() + if namespace, ok := namespaces.Namespace(ctx); ok { + // Namespace reads incoming metadata; add it to outgoing metadata for + // service handlers that call other containerd components. + ctx = namespaces.WithNamespace(ctx, namespace) + stream = &serverStreamWithContext{ServerStream: stream, ctx: ctx} + } + return handler(server, stream) +} + +type serverStreamWithContext struct { + grpc.ServerStream + ctx context.Context +} + +func (s *serverStreamWithContext) Context() context.Context { + return s.ctx +} + +func trapClosedConnErr(err error) error { + if err == nil || errors.Is(err, net.ErrClosed) || errors.Is(err, ttrpc.ErrServerClosed) { + return nil + } + return err +} diff --git a/daemon/internal/containerd/server/embedded/containerd_test.go b/daemon/internal/containerd/server/embedded/containerd_test.go new file mode 100644 index 0000000000..5d6379382f --- /dev/null +++ b/daemon/internal/containerd/server/embedded/containerd_test.go @@ -0,0 +1,203 @@ +//go:build (linux || windows) && !no_embedded_containerd + +package embedded + +import ( + "context" + "errors" + "net" + "path/filepath" + "testing" + "time" + + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/plugin" + "github.com/containerd/ttrpc" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +type recordingService struct { + id string + grpcRegistrations *[]string + ttrpcRegistrations *[]string + closed *[]string + registerErr error +} + +func (s *recordingService) Register(*grpc.Server) error { + if s.grpcRegistrations != nil { + *s.grpcRegistrations = append(*s.grpcRegistrations, s.id) + } + return s.registerErr +} + +func (s *recordingService) RegisterTTRPC(*ttrpc.Server) error { + if s.ttrpcRegistrations != nil { + *s.ttrpcRegistrations = append(*s.ttrpcRegistrations, s.id) + } + return nil +} + +func (s *recordingService) Close() error { + if s.closed != nil { + *s.closed = append(*s.closed, s.id) + } + return nil +} + +func TestContainerdServerPluginLifecycle(t *testing.T) { + var initialized, grpcRegistrations, ttrpcRegistrations, closed []string + cfg := &serverConfig{ + root: filepath.Join(t.TempDir(), "root"), + state: filepath.Join(t.TempDir(), "state"), + grpcAddress: "grpc-address", + ttrpcAddress: "ttrpc-address", + } + + const pluginType = plugin.Type("io.moby.test.v1") + newRegistration := func(id string) plugin.Registration { + pluginConfig := &struct{ ID string }{ID: id} + return plugin.Registration{ + Type: pluginType, + ID: id, + Config: pluginConfig, + InitFn: func(initContext *plugin.InitContext) (any, error) { + uri := string(pluginType) + "." + id + assert.Check(t, is.Equal(initContext.Properties[plugins.PropertyRootDir], filepath.Join(cfg.root, uri))) + assert.Check(t, is.Equal(initContext.Properties[plugins.PropertyStateDir], filepath.Join(cfg.state, uri))) + assert.Check(t, is.Equal(initContext.Properties[plugins.PropertyGRPCAddress], cfg.grpcAddress)) + assert.Check(t, is.Equal(initContext.Properties[plugins.PropertyTTRPCAddress], cfg.ttrpcAddress)) + assert.Check(t, is.Equal(initContext.Config, pluginConfig)) + ready := initContext.RegisterReadiness() + ready() + initialized = append(initialized, id) + return &recordingService{ + id: id, + grpcRegistrations: &grpcRegistrations, + ttrpcRegistrations: &ttrpcRegistrations, + closed: &closed, + }, nil + }, + } + } + registrations := []plugin.Registration{ + newRegistration("first"), + newRegistration("second"), + } + server, err := newServerWithRegistrations(t.Context(), cfg, registrations) + assert.NilError(t, err) + server.Wait() + assert.Check(t, is.DeepEqual(initialized, []string{"first", "second"})) + assert.Check(t, is.DeepEqual(grpcRegistrations, []string{"first", "second"})) + assert.Check(t, is.DeepEqual(ttrpcRegistrations, []string{"first", "second"})) + + server.Stop() + server.Stop() + assert.Check(t, is.DeepEqual(closed, []string{"second", "first"})) +} + +func TestContainerdServerClosesPluginsAfterRegistrationFailure(t *testing.T) { + var closed []string + cfg := &serverConfig{ + root: t.TempDir(), + state: t.TempDir(), + } + registrations := []plugin.Registration{ + { + Type: "io.moby.test.v1", + ID: "loaded", + InitFn: func(*plugin.InitContext) (any, error) { + return &recordingService{id: "loaded", closed: &closed}, nil + }, + }, + { + Type: "io.moby.test.v1", + ID: "failing", + InitFn: func(*plugin.InitContext) (any, error) { + return &recordingService{ + id: "failing", + closed: &closed, + registerErr: errors.New("registration failed"), + }, nil + }, + }, + } + + server, err := newServerWithRegistrations(t.Context(), cfg, registrations) + assert.Check(t, is.Nil(server)) + assert.Check(t, is.ErrorContains(err, "registration failed")) + assert.Check(t, is.DeepEqual(closed, []string{"failing", "loaded"})) +} + +func TestContainerdServerStopCancelsTTRPCRequests(t *testing.T) { + ttrpcServer, err := ttrpc.NewServer() + assert.NilError(t, err) + + requestStarted := make(chan struct{}) + requestCanceled := make(chan struct{}) + ttrpcServer.RegisterService("test", &ttrpc.ServiceDesc{ + Methods: map[string]ttrpc.Method{ + "Wait": func(ctx context.Context, _ func(any) error) (any, error) { + close(requestStarted) + <-ctx.Done() + close(requestCanceled) + return nil, ctx.Err() + }, + }, + }) + + serveCtx, cancelServe := context.WithCancel(t.Context()) + server := &containerdServer{ + grpcServer: grpc.NewServer(), + ttrpcServer: ttrpcServer, + serveCtx: serveCtx, + cancelServe: cancelServe, + } + listener, err := net.Listen("tcp", "127.0.0.1:0") + assert.NilError(t, err) + t.Cleanup(func() { _ = listener.Close() }) + + serveErr := make(chan error, 1) + go func() { + serveErr <- server.ServeTTRPC(listener) + }() + + conn, err := net.Dial("tcp", listener.Addr().String()) + assert.NilError(t, err) + client := ttrpc.NewClient(conn) + t.Cleanup(func() { _ = client.Close() }) + callErr := make(chan error, 1) + go func() { + callErr <- client.Call(t.Context(), "test", "Wait", &emptypb.Empty{}, &emptypb.Empty{}) + }() + + deadline, cancel := context.WithTimeout(t.Context(), 5*time.Second) + defer cancel() + select { + case <-requestStarted: + case <-deadline.Done(): + t.Fatal("timed out waiting for ttrpc request to start") + } + + server.Stop() + + select { + case <-requestCanceled: + case <-deadline.Done(): + t.Fatal("timed out waiting for ttrpc request cancellation") + } + select { + case err := <-serveErr: + assert.NilError(t, err) + case <-deadline.Done(): + t.Fatal("timed out waiting for ttrpc server to stop") + } + select { + case <-callErr: + case <-deadline.Done(): + t.Fatal("timed out waiting for ttrpc client call to return") + } +} diff --git a/daemon/internal/containerd/server/embedded/embedded.go b/daemon/internal/containerd/server/embedded/embedded.go new file mode 100644 index 0000000000..85228814a9 --- /dev/null +++ b/daemon/internal/containerd/server/embedded/embedded.go @@ -0,0 +1,34 @@ +//go:build !no_embedded_containerd + +// Package embedded runs containerd's full gRPC server inside the dockerd +// process. +// +// The same API is served on two endpoints. One is a unix socket (a named pipe +// on Windows) in the daemon's exec-root, used by the plugin executor and by +// tooling such as ctr. The other is an in-memory pipe, used by dockerd's own +// client to avoid socket syscalls. +// +// containerd still runs each container's shim as a separate process, so +// containers keep running across a daemon restart, as they do today. +package embedded + +import ( + "context" + "net" + "time" +) + +// Daemon is an in-process containerd server. +type Daemon interface { + // Address returns the containerd gRPC address (unix socket path, or named + // pipe on Windows) external clients and tooling should dial. + Address() string + // Dial returns an in-memory connection to the server, which dockerd's own + // client uses to avoid socket syscalls. The signature matches + // grpc.WithContextDialer, and the addr argument is ignored. + Dial(ctx context.Context, addr string) (net.Conn, error) + // WaitTimeout waits up to d for the server to stop after Shutdown. + WaitTimeout(d time.Duration) error + // Shutdown gracefully stops the in-process server and waits for it to exit. + Shutdown(ctx context.Context) error +} diff --git a/daemon/internal/containerd/server/embedded/embedded_linux.go b/daemon/internal/containerd/server/embedded/embedded_linux.go new file mode 100644 index 0000000000..0f809bacf2 --- /dev/null +++ b/daemon/internal/containerd/server/embedded/embedded_linux.go @@ -0,0 +1,35 @@ +//go:build !no_embedded_containerd + +package embedded + +import ( + "net" + "path/filepath" + + "github.com/containerd/ttrpc" + "github.com/docker/go-connections/sockets" + + // Linux-specific containerd plugin registrations: the overlayfs + // snapshotter, the walking differ, the cgroups task monitor (for container + // metrics), and the runc runtime options type. Cross-platform plugins are + // registered in server.go. + _ "github.com/containerd/containerd/api/types/runc/options" + _ "github.com/containerd/containerd/v2/core/metrics/cgroups" + _ "github.com/containerd/containerd/v2/plugins/diff/walking/plugin" + _ "github.com/containerd/containerd/v2/plugins/snapshots/native/plugin" + _ "github.com/containerd/containerd/v2/plugins/snapshots/overlay/plugin" +) + +func defaultAddress(stateDir string) string { + return filepath.Join(stateDir, "containerd.sock") +} + +func listen(address string) (net.Listener, error) { + return sockets.NewUnixSocketWithOpts(address, sockets.WithChmod(0o660)) +} + +func newTTRPCServer() (*ttrpc.Server, error) { + return ttrpc.NewServer( + ttrpc.WithServerHandshaker(ttrpc.UnixSocketRequireSameUser()), + ) +} diff --git a/daemon/internal/containerd/server/embedded/embedded_test.go b/daemon/internal/containerd/server/embedded/embedded_test.go new file mode 100644 index 0000000000..5a6c1d7ccc --- /dev/null +++ b/daemon/internal/containerd/server/embedded/embedded_test.go @@ -0,0 +1,42 @@ +//go:build (linux || windows) && !no_embedded_containerd + +package embedded + +import ( + "path/filepath" + "strings" + "testing" + + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +// TestPluginGraphResolves guards the blank-import plugin set: it starts the +// embedded containerd server and fails if any required plugin is missing from +// the registry or its dependency graph cannot be satisfied. Runtime init +// failures unrelated to registration (e.g. requiring root) are tolerated, so +// the test is safe to run unprivileged. +func TestPluginGraphResolves(t *testing.T) { + ctx := t.Context() + d, err := Start(ctx, t.TempDir(), t.TempDir()) + if err != nil { + if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "no plugins registered") { + t.Fatalf("embedded containerd plugin graph is incomplete: %v", err) + } + t.Skipf("embedded containerd did not start in this environment: %v", err) + } + t.Cleanup(func() { _ = d.Shutdown(ctx) }) +} + +func TestBuildServerConfigUsesSupervisedLayout(t *testing.T) { + rootDir := filepath.Join(t.TempDir(), "root") + stateDir := filepath.Join(t.TempDir(), "state") + address := defaultAddress(stateDir) + + cfg := buildServerConfig(rootDir, stateDir, address) + + assert.Check(t, is.Equal(cfg.root, rootDir)) + assert.Check(t, is.Equal(cfg.state, filepath.Join(stateDir, "daemon"))) + assert.Check(t, is.Equal(cfg.grpcAddress, address)) + assert.Check(t, is.Equal(cfg.ttrpcAddress, address+".ttrpc")) +} diff --git a/daemon/internal/containerd/server/embedded/embedded_windows.go b/daemon/internal/containerd/server/embedded/embedded_windows.go new file mode 100644 index 0000000000..2e4716d269 --- /dev/null +++ b/daemon/internal/containerd/server/embedded/embedded_windows.go @@ -0,0 +1,43 @@ +//go:build !no_embedded_containerd + +package embedded + +import ( + "crypto/sha256" + "encoding/hex" + "net" + "path/filepath" + + "github.com/Microsoft/go-winio" + "github.com/containerd/ttrpc" + + // Windows-specific containerd plugin registrations. Cross-platform plugins + // are registered in server.go. + _ "github.com/containerd/containerd/v2/plugins/diff/lcow" + _ "github.com/containerd/containerd/v2/plugins/diff/windows" + _ "github.com/containerd/containerd/v2/plugins/snapshots/windows" +) + +func defaultAddress(stateDir string) string { + return `\\.\pipe\docker-containerd-embedded-` + stateDirID(stateDir) +} + +// stateDirID returns a stable identifier for the daemon state directory. +func stateDirID(stateDir string) string { + sum := sha256.Sum256([]byte(filepath.Clean(stateDir))) + return hex.EncodeToString(sum[:]) +} + +// namedPipePermissions grants full access only to built-in Administrators and +// LocalSystem. +const namedPipePermissions = "D:P(A;;GA;;;BA)(A;;GA;;;SY)" + +func listen(address string) (net.Listener, error) { + return winio.ListenPipe(address, &winio.PipeConfig{ + SecurityDescriptor: namedPipePermissions, + }) +} + +func newTTRPCServer() (*ttrpc.Server, error) { + return ttrpc.NewServer() +} diff --git a/daemon/internal/containerd/server/embedded/embedded_windows_test.go b/daemon/internal/containerd/server/embedded/embedded_windows_test.go new file mode 100644 index 0000000000..5f0b51106c --- /dev/null +++ b/daemon/internal/containerd/server/embedded/embedded_windows_test.go @@ -0,0 +1,20 @@ +//go:build windows && !no_embedded_containerd + +package embedded + +import ( + "strings" + "testing" + + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestDefaultAddressUsesStateDir(t *testing.T) { + stateDir := `C:\ProgramData\docker\execroot\daemon-1\containerd` + + address := defaultAddress(stateDir) + assert.Check(t, is.Equal(address, defaultAddress(stateDir))) + assert.Check(t, strings.HasPrefix(address, `\\.\pipe\docker-containerd-embedded-`)) + assert.Check(t, address != defaultAddress(`C:\ProgramData\docker\execroot\daemon-2\containerd`)) +} diff --git a/daemon/internal/containerd/server/embedded/server.go b/daemon/internal/containerd/server/embedded/server.go new file mode 100644 index 0000000000..dfbfb689c4 --- /dev/null +++ b/daemon/internal/containerd/server/embedded/server.go @@ -0,0 +1,243 @@ +//go:build (linux || windows) && !no_embedded_containerd + +package embedded + +import ( + "context" + "errors" + "net" + "path/filepath" + "runtime/debug" + "sync" + "sync/atomic" + "time" + + "github.com/containerd/containerd/v2/defaults" + "github.com/containerd/containerd/v2/version" + "github.com/containerd/log" + + // Cross-platform plugin registrations. Each blank import registers one or + // more containerd plugins that the local bootstrap then loads. Platform-specific + // snapshotters and differs are registered in embedded_linux.go / + // embedded_windows.go. + // + // This is a trimmed subset of containerd's cmd/containerd/builtins: dockerd + // does not need CRI, sandbox, streaming, transfer, NRI, or the restart + // monitor. + _ "github.com/containerd/containerd/v2/core/runtime/v2" + _ "github.com/containerd/containerd/v2/plugins/content/local/plugin" + _ "github.com/containerd/containerd/v2/plugins/events" + _ "github.com/containerd/containerd/v2/plugins/gc" + _ "github.com/containerd/containerd/v2/plugins/leases" + _ "github.com/containerd/containerd/v2/plugins/metadata" + _ "github.com/containerd/containerd/v2/plugins/mount" + _ "github.com/containerd/containerd/v2/plugins/services/containers" + _ "github.com/containerd/containerd/v2/plugins/services/content" + _ "github.com/containerd/containerd/v2/plugins/services/diff" + _ "github.com/containerd/containerd/v2/plugins/services/events" + _ "github.com/containerd/containerd/v2/plugins/services/healthcheck" + _ "github.com/containerd/containerd/v2/plugins/services/images" + _ "github.com/containerd/containerd/v2/plugins/services/introspection" + _ "github.com/containerd/containerd/v2/plugins/services/leases" + _ "github.com/containerd/containerd/v2/plugins/services/namespaces" + _ "github.com/containerd/containerd/v2/plugins/services/snapshots" + _ "github.com/containerd/containerd/v2/plugins/services/tasks" + _ "github.com/containerd/containerd/v2/plugins/services/version" + _ "github.com/containerd/containerd/v2/plugins/services/warning" +) + +// Start initializes and runs the embedded containerd server, using rootDir for +// persistent state (bolt DB, content store) and the daemon subdirectory under +// stateDir for runtime state. +// See the package doc for the transport layout. +// Plugins self-register via the blank imports above and in the platform-specific +// files. +func Start(ctx context.Context, rootDir, stateDir string) (Daemon, error) { + setContainerdVersion() + + address := defaultAddress(stateDir) + cfg := buildServerConfig(rootDir, stateDir, address) + + // Create the root and state directories with the permissions containerd + // expects (e.g. the state dir at 0o711 for userns-remapped containers), + // matching what containerd's own command does before initializing plugins. + if err := createTopLevelDirectories(cfg); err != nil { + return nil, err + } + + log.G(ctx).WithField("address", address).Info("starting embedded containerd server") + srv, err := newServer(ctx, cfg) + if err != nil { + return nil, err + } + + socket, err := listen(cfg.grpcAddress) + if err != nil { + srv.Stop() + return nil, err + } + // Shims (separate processes) publish task events back to containerd over + // ttrpc, so it must be a real socket. The runtime plugin hands its address + // (PropertyTTRPCAddress) to each shim. + ttrpcL, err := listen(cfg.ttrpcAddress) + if err != nil { + srv.Stop() + _ = socket.Close() + return nil, err + } + inMemory := newInMemoryListener() + + e := &embeddedDaemon{srv: srv, address: address, inMemory: inMemory, ttrpcL: ttrpcL, stopCh: make(chan struct{})} + + var wg sync.WaitGroup + serve := func(name string, l net.Listener, fn func(net.Listener) error) { + wg.Add(1) + go func() { + defer wg.Done() + // Serve returns an error once the server is stopped, which is + // expected during Shutdown, so only log unexpected failures. + if err := fn(l); err != nil && !e.stopping.Load() { + log.G(ctx).WithError(err).Errorf("embedded containerd %s server exited", name) + } + }() + } + serve("gRPC socket", socket, srv.ServeGRPC) + serve("gRPC in-memory", inMemory, srv.ServeGRPC) + serve("ttrpc", ttrpcL, srv.ServeTTRPC) + go func() { + wg.Wait() + close(e.stopCh) + }() + + // Tie the server to the daemon context: shut down when it is cancelled, + // leaving running shims untouched. WithoutCancel so Shutdown can still wait + // for the server to stop after ctx is already done. + go func() { + <-ctx.Done() + if err := e.Shutdown(context.WithoutCancel(ctx)); err != nil { + log.G(ctx).WithError(err).Error("failed to shut down embedded containerd") + } + }() + + // Do not report successful startup until asynchronous plugin initialization + // has completed. + srv.Wait() + return e, nil +} + +// setContainerdVersion makes the embedded server report the vendored containerd +// version in "docker info" instead of the source default ("2.2.4+unknown"). +// +// The version comes from the build's module info. The git commit is not +// recorded there, so only the version is set. +func setContainerdVersion() { + bi, ok := debug.ReadBuildInfo() + if !ok { + return + } + for _, dep := range bi.Deps { + if dep.Path == "github.com/containerd/containerd/v2" && dep.Version != "" { + version.Version = dep.Version + break + } + } +} + +func buildServerConfig(rootDir, stateDir, address string) *serverConfig { + return &serverConfig{ + root: rootDir, + state: filepath.Join(stateDir, "daemon"), + grpcAddress: address, + ttrpcAddress: address + ".ttrpc", + maxRecvMessageSize: defaults.DefaultMaxRecvMsgSize, + maxSendMessageSize: defaults.DefaultMaxSendMsgSize, + } +} + +type embeddedDaemon struct { + srv *containerdServer + address string + inMemory *inMemoryListener + ttrpcL net.Listener + stopCh chan struct{} + stopping atomic.Bool +} + +func (e *embeddedDaemon) Address() string { + return e.address +} + +// Dial returns one end of an in-memory pipe connected to the gRPC server. The +// addr argument is ignored, and only present to satisfy grpc.WithContextDialer. +func (e *embeddedDaemon) Dial(ctx context.Context, _ string) (net.Conn, error) { + serverConn, clientConn := net.Pipe() + select { + case e.inMemory.ch <- serverConn: + return clientConn, nil + case <-ctx.Done(): + _ = serverConn.Close() + _ = clientConn.Close() + return nil, ctx.Err() + case <-e.inMemory.done: + _ = serverConn.Close() + _ = clientConn.Close() + return nil, errors.New("embedded containerd server is stopped") + } +} + +func (e *embeddedDaemon) WaitTimeout(d time.Duration) error { + timer := time.NewTimer(d) + defer timer.Stop() + select { + case <-timer.C: + return errors.New("timeout waiting for embedded containerd to stop") + case <-e.stopCh: + return nil + } +} + +func (e *embeddedDaemon) Shutdown(ctx context.Context) error { + e.stopping.Store(true) + // Stop closes both RPC servers and their listeners. + e.srv.Stop() + e.inMemory.Close() + select { + case <-e.stopCh: + case <-ctx.Done(): + return ctx.Err() + } + return nil +} + +// inMemoryListener is a net.Listener whose connections are supplied by +// Dial rather than accepted from the kernel. +type inMemoryListener struct { + ch chan net.Conn + done chan struct{} + once sync.Once +} + +func newInMemoryListener() *inMemoryListener { + return &inMemoryListener{ + ch: make(chan net.Conn, 16), + done: make(chan struct{}), + } +} + +func (l *inMemoryListener) Accept() (net.Conn, error) { + select { + case conn := <-l.ch: + return conn, nil + case <-l.done: + return nil, net.ErrClosed + } +} + +func (l *inMemoryListener) Close() error { + l.once.Do(func() { close(l.done) }) + return nil +} + +func (l *inMemoryListener) Addr() net.Addr { + return &net.UnixAddr{Name: "embedded-containerd", Net: "inmem"} +} diff --git a/daemon/internal/libcontainerd/supervisor/remote_daemon.go b/daemon/internal/containerd/server/supervisor/remote_daemon.go similarity index 98% rename from daemon/internal/libcontainerd/supervisor/remote_daemon.go rename to daemon/internal/containerd/server/supervisor/remote_daemon.go index 25e1f32eb7..07f4749883 100644 --- a/daemon/internal/libcontainerd/supervisor/remote_daemon.go +++ b/daemon/internal/containerd/server/supervisor/remote_daemon.go @@ -61,12 +61,14 @@ type Daemon interface { // DaemonOpt allows to configure parameters of container daemons type DaemonOpt func(c *remote) error -// Start starts a containerd daemon and monitors it +// Start starts a containerd daemon and monitors it. +// It uses rootDir for persistent state and the daemon subdirectory under +// stateDir for runtime state. func Start(ctx context.Context, rootDir, stateDir string, opts ...DaemonOpt) (Daemon, error) { r := &remote{ Config: config.Config{ Version: 2, // FIXME(thaJeztah): update to v3 when we drop support for containerd v1. - Root: filepath.Join(rootDir, "daemon"), + Root: rootDir, State: filepath.Join(stateDir, "daemon"), GRPC: config.GRPCConfig{ //nolint:staticcheck // Deprecated in config v4, but required for config v3. Address: defaultGRPCAddress(stateDir), //nolint:staticcheck // Deprecated in config v4, but required for config v3. diff --git a/daemon/internal/libcontainerd/supervisor/remote_daemon_linux.go b/daemon/internal/containerd/server/supervisor/remote_daemon_linux.go similarity index 100% rename from daemon/internal/libcontainerd/supervisor/remote_daemon_linux.go rename to daemon/internal/containerd/server/supervisor/remote_daemon_linux.go diff --git a/daemon/internal/libcontainerd/supervisor/remote_daemon_options.go b/daemon/internal/containerd/server/supervisor/remote_daemon_options.go similarity index 100% rename from daemon/internal/libcontainerd/supervisor/remote_daemon_options.go rename to daemon/internal/containerd/server/supervisor/remote_daemon_options.go diff --git a/daemon/internal/libcontainerd/supervisor/remote_daemon_windows.go b/daemon/internal/containerd/server/supervisor/remote_daemon_windows.go similarity index 100% rename from daemon/internal/libcontainerd/supervisor/remote_daemon_windows.go rename to daemon/internal/containerd/server/supervisor/remote_daemon_windows.go diff --git a/daemon/internal/libcontainerd/supervisor/utils_linux.go b/daemon/internal/containerd/server/supervisor/utils_linux.go similarity index 100% rename from daemon/internal/libcontainerd/supervisor/utils_linux.go rename to daemon/internal/containerd/server/supervisor/utils_linux.go diff --git a/daemon/internal/libcontainerd/supervisor/utils_windows.go b/daemon/internal/containerd/server/supervisor/utils_windows.go similarity index 100% rename from daemon/internal/libcontainerd/supervisor/utils_windows.go rename to daemon/internal/containerd/server/supervisor/utils_windows.go diff --git a/go.mod b/go.mod index 678977c6ba..75fedc96de 100644 --- a/go.mod +++ b/go.mod @@ -32,8 +32,10 @@ require ( github.com/containerd/errdefs v1.0.0 github.com/containerd/fifo v1.1.0 github.com/containerd/log v0.1.0 - github.com/containerd/nri v0.12.1 + github.com/containerd/nri v0.12.0 github.com/containerd/platforms v1.0.0-rc.4 + github.com/containerd/plugin v1.1.0 + github.com/containerd/ttrpc v1.2.9 github.com/containerd/typeurl/v2 v2.3.0 github.com/coreos/go-systemd/v22 v22.7.0 github.com/cpuguy83/tar2go v0.3.1 @@ -154,6 +156,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/checkpoint-restore/checkpointctl v1.5.0 // indirect github.com/cilium/ebpf v0.17.3 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/container-storage-interface/spec v1.5.0 // indirect @@ -163,9 +166,8 @@ require ( github.com/containerd/go-cni v1.1.13 // indirect github.com/containerd/go-runc v1.1.0 // indirect github.com/containerd/nydus-snapshotter v0.15.15 // indirect - github.com/containerd/plugin v1.1.0 // indirect + github.com/containerd/otelttrpc v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect - github.com/containerd/ttrpc v1.2.9 // indirect github.com/containernetworking/cni v1.3.0 // indirect github.com/containernetworking/plugins v1.9.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect @@ -180,6 +182,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fernet/fernet-go v0.0.0-20240119011108-303da6aec611 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.25.2 // indirect @@ -230,9 +233,12 @@ require ( github.com/hiddeco/sshsig v0.2.0 // indirect github.com/in-toto/attestation v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/intel/goresctrl v0.12.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/klauspost/compress v1.19.1 // indirect github.com/knqyf263/go-plugin v0.9.0 // indirect + github.com/mdlayher/socket v0.5.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/sys/capability v0.4.0 // indirect github.com/morikuni/aec v1.1.0 // indirect @@ -271,6 +277,7 @@ require ( github.com/transparency-dev/formats v0.1.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/weppos/publicsuffix-go v0.30.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 // indirect github.com/zmap/zlint/v3 v3.5.0 // indirect @@ -303,8 +310,11 @@ require ( google.golang.org/api v0.283.0 // indirect google.golang.org/genproto v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apimachinery v0.36.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/yaml v1.6.0 // indirect tags.cncf.io/container-device-interface/specs-go v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index b260ed7051..3450ac3d0a 100644 --- a/go.sum +++ b/go.sum @@ -135,6 +135,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/checkpointctl v1.5.0 h1:Uu+D2cOf/GUyCMk23Y8L69P6YoATTe6pH+Au64O3y28= +github.com/checkpoint-restore/checkpointctl v1.5.0/go.mod h1:y5HRs1ZWQUZGyEuthlTHmTJN9PUMOjlaH6JvVaNq9kE= github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg= github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= @@ -177,10 +179,12 @@ github.com/containerd/go-runc v1.1.0 h1:OX4f+/i2y5sUT7LhmcJH7GYrjjhHa1QI4e8yO0gG github.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/nri v0.12.1 h1:Nkp14W/mdhP0ze83ja/O437du6NQA33W5Y0O+ar3aKM= -github.com/containerd/nri v0.12.1/go.mod h1:TGAfPLH4a+qwbv0PxsefPiR+PobYecDj2aXMtz7GQcg= +github.com/containerd/nri v0.12.0 h1:RvZtyCM64XOB1UmMAFOlfReTTwCd+hE2IEQ5XEpkKA0= +github.com/containerd/nri v0.12.0/go.mod h1:TGAfPLH4a+qwbv0PxsefPiR+PobYecDj2aXMtz7GQcg= github.com/containerd/nydus-snapshotter v0.15.15 h1:kVYbFpYA4K43qxGVoc/VBwRXLAVWn4X9mdwGrR+HsLk= github.com/containerd/nydus-snapshotter v0.15.15/go.mod h1:L96yO+4iE6qqDiqXKhxMXBoPeaE7JgzXir9yanUVuOY= +github.com/containerd/otelttrpc v0.1.0 h1:UOX68eVTE8H/T45JveIg+I22Ev2aFj4qPITCmXsskjw= +github.com/containerd/otelttrpc v0.1.0/go.mod h1:XhoA2VvaGPW1clB2ULwrBZfXVuEWuyOd2NUD1IM0yTg= github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4= github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A= github.com/containerd/plugin v1.1.0 h1:O+7lczNJVMy8rz0YNx3xGB8tTf5qY4i5abF041Ew19U= @@ -263,6 +267,8 @@ github.com/fluent/fluent-logger-golang v1.10.1/go.mod h1:qOuXG4ZMrXaSTk12ua+uAb2 github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= @@ -473,6 +479,8 @@ github.com/in-toto/in-toto-golang v0.11.0/go.mod h1:u3PjTnwFKjp5a1YCcw8SJg0G+tMe github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/intel/goresctrl v0.12.0 h1:F44m7jiVgOdqWfTTWaREF+5HTeX3i06qhvpuzpnrBko= +github.com/intel/goresctrl v0.12.0/go.mod h1:5GWtmPY4BWl/a9rU8apGED9Xul5b5WoLtg/qOWaghWU= github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2 h1:36qep4gxKs+JgeHGWeQ040RyZdt9kQlLglL1rFVn/oQ= github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b h1:ZGiXF8sz7PDk6RgkP+A/SFfUD0ZR/AgG6SpRNEDKZy8= @@ -520,6 +528,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mistifyio/go-zfs/v4 v4.0.0 h1:sU0+5dX45tdDK5xNZ3HBi95nxUc48FS92qbIZEvpAg4= @@ -788,6 +798,8 @@ github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8L github.com/weppos/publicsuffix-go v0.30.0 h1:QHPZ2GRu/YE7cvejH9iyavPOkVCB4dNxp2ZvtT+vQLY= github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY= github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -1039,6 +1051,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1053,6 +1067,8 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= +k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= kernel.org/pub/linux/libs/security/libcap/cap v1.2.78 h1:jgqg4gyu2BaYW9L6uzEtGLf8GNREwk/z4UFdwt5F3pE= @@ -1063,6 +1079,10 @@ pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= resenje.org/singleflight v0.4.3 h1:l7foFYg8X/VEHPxWs1K/Pw77807RMVzvXgWGb0J1sdM= resenje.org/singleflight v0.4.3/go.mod h1:lAgQK7VfjG6/pgredbQfmV0RvG/uVhKo6vSuZ0vCWfk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= diff --git a/integration-cli/requirements_test.go b/integration-cli/requirements_test.go index a13b5b0a4b..5e7dd97cee 100644 --- a/integration-cli/requirements_test.go +++ b/integration-cli/requirements_test.go @@ -143,8 +143,14 @@ func RegistryHosting() bool { return err == nil } +// RuntimeIsWindowsContainerd returns whether the containerd runtime is used on +// Windows. +// It is true when either the legacy DOCKER_WINDOWS_CONTAINERD_RUNTIME=1 env +// var is set, or when the embedded-containerd feature is enabled via +// TEST_INTEGRATION_CONTAINERD_EMBEDDED (which also uses containerd). func RuntimeIsWindowsContainerd() bool { - return os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME") == "1" + return os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME") == "1" || + (runtime.GOOS == "windows" && os.Getenv("TEST_INTEGRATION_CONTAINERD_EMBEDDED") != "") } func SwarmInactive() bool { diff --git a/internal/testutil/daemon/daemon.go b/internal/testutil/daemon/daemon.go index 46ed6c553b..b30a00c2d6 100644 --- a/internal/testutil/daemon/daemon.go +++ b/internal/testutil/daemon/daemon.go @@ -56,6 +56,13 @@ const ( var errDaemonNotStarted = errors.New("daemon not started") +// containerdEmbeddedFromEnv reports whether test daemons should run containerd +// in-process, via the embedded-containerd feature. It is controlled by +// TEST_INTEGRATION_CONTAINERD_EMBEDDED. +func containerdEmbeddedFromEnv() bool { + return os.Getenv("TEST_INTEGRATION_CONTAINERD_EMBEDDED") != "" +} + // SockRoot holds the path of the default docker integration daemon socket var SockRoot = filepath.Join(os.TempDir(), "docker-integration") @@ -88,6 +95,7 @@ type Daemon struct { args []string extraEnv []string containerdSocket string + containerdEmbedded bool usernsRemap string rootlessUser *user.User rootlessXDGRuntimeDir string @@ -142,12 +150,13 @@ func NewDaemon(workingDir string, ops ...Option) (*Daemon, error) { storageDriver: storageDriver, userlandProxy: userlandProxy, // dxr stands for docker-execroot (shortened for avoiding unix(7) path length limitation) - execRoot: filepath.Join(os.TempDir(), "dxr", id), - dockerdBinary: defaultDockerdBinary, - swarmListenAddr: defaultSwarmListenAddr, - SwarmPort: DefaultSwarmPort, - log: nopLog{}, - containerdSocket: defaultContainerdSocket, + execRoot: filepath.Join(os.TempDir(), "dxr", id), + dockerdBinary: defaultDockerdBinary, + swarmListenAddr: defaultSwarmListenAddr, + SwarmPort: DefaultSwarmPort, + log: nopLog{}, + containerdSocket: defaultContainerdSocket, + containerdEmbedded: containerdEmbeddedFromEnv(), } for _, op := range ops { @@ -537,7 +546,9 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { "--containerd-namespace", d.id, "--containerd-plugins-namespace", d.id+"p", ) - if d.containerdSocket != "" { + if d.containerdEmbedded { + d.args = append(d.args, "--feature", "embedded-containerd") + } else if d.containerdSocket != "" { d.args = append(d.args, "--containerd", d.containerdSocket) } diff --git a/internal/testutil/environment/environment.go b/internal/testutil/environment/environment.go index 7b2c74daa0..99e1b5b7c3 100644 --- a/internal/testutil/environment/environment.go +++ b/internal/testutil/environment/environment.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "testing" @@ -156,9 +157,13 @@ func (e *Execution) IsUserNamespace() bool { return root != "" } -// RuntimeIsWindowsContainerd returns whether containerd runtime is used on Windows +// RuntimeIsWindowsContainerd returns whether the containerd runtime is used on Windows. +// It is true when either the legacy DOCKER_WINDOWS_CONTAINERD_RUNTIME=1 env +// var is set, or when the embedded-containerd feature is enabled via +// TEST_INTEGRATION_CONTAINERD_EMBEDDED (which also uses containerd). func (e *Execution) RuntimeIsWindowsContainerd() bool { - return os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME") == "1" + return os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME") == "1" || + (runtime.GOOS == "windows" && os.Getenv("TEST_INTEGRATION_CONTAINERD_EMBEDDED") != "") } // IsRootless returns whether the rootless mode is enabled diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go b/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go new file mode 100644 index 0000000000..a75235201c --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go @@ -0,0 +1,167 @@ +//go:build windows + +package security + +import ( + "fmt" + "os" + "unsafe" + + "golang.org/x/sys/windows" +) + +type ( + accessMask uint32 + accessMode uint32 + desiredAccess uint32 + inheritMode uint32 + objectType uint32 + shareMode uint32 + securityInformation uint32 + trusteeForm uint32 + trusteeType uint32 + + explicitAccess struct { + accessPermissions accessMask + accessMode accessMode + inheritance inheritMode + trustee trustee + } + + trustee struct { + multipleTrustee *trustee + multipleTrusteeOperation int32 + trusteeForm trusteeForm + trusteeType trusteeType + name uintptr + } +) + +const ( + accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ + + accessModeGrant accessMode = 1 + + desiredAccessReadControl desiredAccess = 0x20000 + desiredAccessWriteDac desiredAccess = 0x40000 + + //cspell:disable-next-line + gvmga = "GrantVmGroupAccess:" + + inheritModeNoInheritance inheritMode = 0x0 + inheritModeSubContainersAndObjectsInherit inheritMode = 0x3 + + objectTypeFileObject objectType = 0x1 + + securityInformationDACL securityInformation = 0x4 + + shareModeRead shareMode = 0x1 + shareModeWrite shareMode = 0x2 + + sidVMGroup = "S-1-5-83-0" + + trusteeFormIsSID trusteeForm = 0 + + trusteeTypeWellKnownGroup trusteeType = 5 +) + +// GrantVMGroupAccess sets the DACL for a specified file or directory to +// include Grant ACE entries for the VM Group SID. This is a golang re- +// implementation of the same function in vmcompute, just not exported in +// RS5. Which kind of sucks. Sucks a lot :/ +// +//revive:disable-next-line:var-naming VM, not Vm +func GrantVmGroupAccess(name string) error { + // Stat (to determine if `name` is a directory). + s, err := os.Stat(name) + if err != nil { + return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err) + } + + // Get a handle to the file/directory. Must defer Close on success. + fd, err := createFile(name, s.IsDir()) + if err != nil { + return err // Already wrapped + } + defer windows.CloseHandle(fd) //nolint:errcheck + + // Get the current DACL and Security Descriptor. Must defer LocalFree on success. + ot := objectTypeFileObject + si := securityInformationDACL + sd := uintptr(0) + origDACL := uintptr(0) + if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { + return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) + } + defer windows.LocalFree(windows.Handle(sd)) //nolint:errcheck + + // Generate a new DACL which is the current DACL with the required ACEs added. + // Must defer LocalFree on success. + newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL) + if err != nil { + return err // Already wrapped + } + defer windows.LocalFree(windows.Handle(newDACL)) //nolint:errcheck + + // And finally use SetSecurityInfo to apply the updated DACL. + if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { + return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err) + } + + return nil +} + +// createFile is a helper function to call [Nt]CreateFile to get a handle to +// the file or directory. +func createFile(name string, isDir bool) (windows.Handle, error) { + namep, err := windows.UTF16FromString(name) + if err != nil { + return windows.InvalidHandle, fmt.Errorf("could not convernt name to UTF-16: %w", err) + } + da := uint32(desiredAccessReadControl | desiredAccessWriteDac) + sm := uint32(shareModeRead | shareModeWrite) + fa := uint32(windows.FILE_ATTRIBUTE_NORMAL) + if isDir { + fa |= windows.FILE_FLAG_BACKUP_SEMANTICS + } + fd, err := windows.CreateFile(&namep[0], da, sm, nil, windows.OPEN_EXISTING, fa, 0) + if err != nil { + return windows.InvalidHandle, fmt.Errorf("%s windows.CreateFile %s: %w", gvmga, name, err) + } + return fd, nil +} + +// generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added. +// The caller is responsible for LocalFree of the returned DACL on success. +func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) { + // Generate pointers to the SIDs based on the string SIDs + sid, err := windows.StringToSid(sidVMGroup) + if err != nil { + return 0, fmt.Errorf("%s windows.StringToSid %s %s: %w", gvmga, name, sidVMGroup, err) + } + + inheritance := inheritModeNoInheritance + if isDir { + inheritance = inheritModeSubContainersAndObjectsInherit + } + + eaArray := []explicitAccess{ + { + accessPermissions: accessMaskDesiredPermission, + accessMode: accessModeGrant, + inheritance: inheritance, + trustee: trustee{ + trusteeForm: trusteeFormIsSID, + trusteeType: trusteeTypeWellKnownGroup, + name: uintptr(unsafe.Pointer(sid)), + }, + }, + } + + modifiedDACL := uintptr(0) + if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil { + return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err) + } + + return modifiedDACL, nil +} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go b/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go new file mode 100644 index 0000000000..d8f3e5e0de --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go @@ -0,0 +1,7 @@ +package security + +//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go syscall_windows.go + +//sys getSecurityInfo(handle windows.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo +//sys setSecurityInfo(handle windows.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo +//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) = advapi32.SetEntriesInAclW diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go new file mode 100644 index 0000000000..e49f17ae62 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go @@ -0,0 +1,69 @@ +//go:build windows + +// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. + +package security + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + return e +} + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + + procGetSecurityInfo = modadvapi32.NewProc("GetSecurityInfo") + procSetEntriesInAclW = modadvapi32.NewProc("SetEntriesInAclW") + procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo") +) + +func getSecurityInfo(handle windows.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) { + r0, _, _ := syscall.SyscallN(procGetSecurityInfo.Addr(), uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor))) + if r0 != 0 { + win32err = syscall.Errno(r0) + } + return +} + +func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) { + r0, _, _ := syscall.SyscallN(procSetEntriesInAclW.Addr(), uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl))) + if r0 != 0 { + win32err = syscall.Errno(r0) + } + return +} + +func setSecurityInfo(handle windows.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) { + r0, _, _ := syscall.SyscallN(procSetSecurityInfo.Addr(), uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl)) + if r0 != 0 { + win32err = syscall.Errno(r0) + } + return +} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go b/vendor/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go new file mode 100644 index 0000000000..6e91c4bbc5 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go @@ -0,0 +1,268 @@ +package dmverity + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "fmt" + "io" + "os" + + "github.com/pkg/errors" + + "github.com/Microsoft/hcsshim/ext4/internal/compactext4" + "github.com/Microsoft/hcsshim/internal/memory" +) + +const ( + blockSize = compactext4.BlockSize + // MerkleTreeBufioSize is a default buffer size to use with bufio.Reader + MerkleTreeBufioSize = memory.MiB // 1MB + // RecommendedVHDSizeGB is the recommended size in GB for VHDs, which is not a hard limit. + RecommendedVHDSizeGB = 128 * memory.GiB + // VeritySignature is a value written to dm-verity super-block. + VeritySignature = "verity" +) + +var ( + salt = bytes.Repeat([]byte{0}, 32) + sbSize = binary.Size(dmveritySuperblock{}) +) + +var ( + ErrSuperBlockReadFailure = errors.New("failed to read dm-verity super block") + ErrSuperBlockParseFailure = errors.New("failed to parse dm-verity super block") + ErrRootHashReadFailure = errors.New("failed to read dm-verity root hash") + ErrNotVeritySuperBlock = errors.New("invalid dm-verity super-block signature") +) + +type dmveritySuperblock struct { + /* (0) "verity\0\0" */ + Signature [8]byte + /* (8) superblock version, 1 */ + Version uint32 + /* (12) 0 - Chrome OS, 1 - normal */ + HashType uint32 + /* (16) UUID of hash device */ + UUID [16]byte + /* (32) Name of the hash algorithm (e.g., sha256) */ + Algorithm [32]byte + /* (64) The data block size in bytes */ + DataBlockSize uint32 + /* (68) The hash block size in bytes */ + HashBlockSize uint32 + /* (72) The number of data blocks */ + DataBlocks uint64 + /* (80) Size of the salt */ + SaltSize uint16 + /* (82) Padding */ + _ [6]byte + /* (88) The salt */ + Salt [256]byte + /* (344) Padding */ + _ [168]byte +} + +// VerityInfo is minimal exported version of dmveritySuperblock +type VerityInfo struct { + // Offset in blocks on hash device + HashOffsetInBlocks int64 + // Set to true, when dm-verity super block is also written on the hash device + SuperBlock bool + RootDigest string + Salt string + Algorithm string + DataBlockSize uint32 + HashBlockSize uint32 + DataBlocks uint64 + Version uint32 +} + +// MerkleTree constructs dm-verity hash-tree for a given io.Reader with a fixed salt (0-byte) and algorithm (sha256). +func MerkleTree(r io.Reader) ([]byte, error) { + layers := make([][]byte, 0) + currentLevel := r + + for { + nextLevel := bytes.NewBuffer(make([]byte, 0)) + for { + block := make([]byte, blockSize) + if _, err := io.ReadFull(currentLevel, block); err != nil { + if err == io.EOF { + break + } + return nil, errors.Wrap(err, "failed to read data block") + } + h := hash2(salt, block) + nextLevel.Write(h) + } + + if nextLevel.Len()%blockSize != 0 { + padding := bytes.Repeat([]byte{0}, blockSize-(nextLevel.Len()%blockSize)) + nextLevel.Write(padding) + } + + layers = append(layers, nextLevel.Bytes()) + currentLevel = bufio.NewReaderSize(nextLevel, MerkleTreeBufioSize) + + // This means that only root hash remains and our job is done + if nextLevel.Len() == blockSize { + break + } + } + + tree := bytes.NewBuffer(make([]byte, 0)) + for i := len(layers) - 1; i >= 0; i-- { + if _, err := tree.Write(layers[i]); err != nil { + return nil, errors.Wrap(err, "failed to write merkle tree") + } + } + + return tree.Bytes(), nil +} + +// RootHash computes root hash of dm-verity hash-tree +func RootHash(tree []byte) []byte { + return hash2(salt, tree[:blockSize]) +} + +// NewDMVeritySuperblock returns a dm-verity superblock for a device with a given size, salt, algorithm and versions are +// fixed. +func NewDMVeritySuperblock(size uint64) *dmveritySuperblock { + superblock := &dmveritySuperblock{ + Version: 1, + HashType: 1, + UUID: generateUUID(), + DataBlockSize: blockSize, + HashBlockSize: blockSize, + DataBlocks: size / blockSize, + SaltSize: uint16(len(salt)), + } + + copy(superblock.Signature[:], VeritySignature) + copy(superblock.Algorithm[:], "sha256") + copy(superblock.Salt[:], salt) + + return superblock +} + +func hash2(a, b []byte) []byte { + h := sha256.New() + h.Write(append(a, b...)) + return h.Sum(nil) +} + +func generateUUID() [16]byte { + res := [16]byte{} + if _, err := rand.Read(res[:]); err != nil { + panic(err) + } + return res +} + +// ReadDMVerityInfo extracts dm-verity super block information and merkle tree root hash +func ReadDMVerityInfo(vhdPath string, offsetInBytes int64) (*VerityInfo, error) { + vhd, err := os.OpenFile(vhdPath, os.O_RDONLY, 0) + if err != nil { + return nil, err + } + defer vhd.Close() + + // Skip the ext4 data to get to dm-verity super block + if s, err := vhd.Seek(offsetInBytes, io.SeekStart); err != nil || s != offsetInBytes { + if err != nil { + return nil, errors.Wrap(err, "failed to seek dm-verity super block") + } + return nil, errors.Errorf("failed to seek dm-verity super block: expected bytes=%d, actual=%d", offsetInBytes, s) + } + + return ReadDMVerityInfoReader(vhd) +} + +func ReadDMVerityInfoReader(r io.Reader) (*VerityInfo, error) { + block := make([]byte, blockSize) + if s, err := r.Read(block); err != nil || s != blockSize { + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrSuperBlockReadFailure, err) + } + return nil, fmt.Errorf("unexpected bytes read expected=%d actual=%d: %w", blockSize, s, ErrSuperBlockReadFailure) + } + + dmvSB := &dmveritySuperblock{} + b := bytes.NewBuffer(block) + if err := binary.Read(b, binary.LittleEndian, dmvSB); err != nil { + return nil, fmt.Errorf("%w: %w", ErrSuperBlockParseFailure, err) + } + + if string(bytes.Trim(dmvSB.Signature[:], "\x00")[:]) != VeritySignature { + return nil, ErrNotVeritySuperBlock + } + + if s, err := r.Read(block); err != nil || s != blockSize { + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrRootHashReadFailure, err) + } + return nil, fmt.Errorf("unexpected bytes read expected=%d, actual=%d: %w", blockSize, s, ErrRootHashReadFailure) + } + + rootHash := hash2(dmvSB.Salt[:dmvSB.SaltSize], block) + return &VerityInfo{ + RootDigest: fmt.Sprintf("%x", rootHash), + Algorithm: string(bytes.Trim(dmvSB.Algorithm[:], "\x00")), + Salt: fmt.Sprintf("%x", dmvSB.Salt[:dmvSB.SaltSize]), + HashOffsetInBlocks: int64(dmvSB.DataBlocks), + SuperBlock: true, + DataBlocks: dmvSB.DataBlocks, + DataBlockSize: dmvSB.DataBlockSize, + HashBlockSize: blockSize, + Version: dmvSB.Version, + }, nil +} + +// ComputeAndWriteHashDevice builds merkle tree from a given io.ReadSeeker and +// writes the result hash device (dm-verity super-block combined with merkle +// tree) to io.Writer. +func ComputeAndWriteHashDevice(r io.ReadSeeker, w io.Writer) error { + // save current reader position + currBytePos, err := r.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + + // reset to the beginning to find the device size + if _, err := r.Seek(0, io.SeekStart); err != nil { + return err + } + + tree, err := MerkleTree(r) + if err != nil { + return errors.Wrap(err, "failed to build merkle tree") + } + + devSize, err := r.Seek(0, io.SeekEnd) + if err != nil { + return err + } + + // reset reader to initial position + if _, err := r.Seek(currBytePos, io.SeekStart); err != nil { + return err + } + + dmVeritySB := NewDMVeritySuperblock(uint64(devSize)) + if err := binary.Write(w, binary.LittleEndian, dmVeritySB); err != nil { + return errors.Wrap(err, "failed to write dm-verity super-block") + } + // write super-block padding + padding := bytes.Repeat([]byte{0}, blockSize-(sbSize%blockSize)) + if _, err = w.Write(padding); err != nil { + return err + } + // write tree + if _, err := w.Write(tree); err != nil { + return errors.Wrap(err, "failed to write merkle tree") + } + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go b/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go new file mode 100644 index 0000000000..7a8dceddf0 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go @@ -0,0 +1,1351 @@ +package compactext4 + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "path" + "sort" + "strings" + "time" + + "github.com/Microsoft/hcsshim/ext4/internal/format" + "github.com/Microsoft/hcsshim/internal/memory" +) + +// Writer writes a compact ext4 file system. +// +// It expects all paths to use directory separator '/', even on Windows. +type Writer struct { + f io.ReadWriteSeeker + bw *bufio.Writer + inodes []*inode + curName string + curInode *inode + pos int64 + dataWritten, dataMax int64 + err error + initialized bool + supportInlineData bool + maxDiskSize int64 + gdBlocks uint32 +} + +// Mode flags for Linux files. +const ( + S_IXOTH = format.S_IXOTH + S_IWOTH = format.S_IWOTH + S_IROTH = format.S_IROTH + S_IXGRP = format.S_IXGRP + S_IWGRP = format.S_IWGRP + S_IRGRP = format.S_IRGRP + S_IXUSR = format.S_IXUSR + S_IWUSR = format.S_IWUSR + S_IRUSR = format.S_IRUSR + S_ISVTX = format.S_ISVTX + S_ISGID = format.S_ISGID + S_ISUID = format.S_ISUID + S_IFIFO = format.S_IFIFO + S_IFCHR = format.S_IFCHR + S_IFDIR = format.S_IFDIR + S_IFBLK = format.S_IFBLK + S_IFREG = format.S_IFREG + S_IFLNK = format.S_IFLNK + S_IFSOCK = format.S_IFSOCK + + TypeMask = format.TypeMask +) + +type inode struct { + Size int64 + Atime, Ctime, Mtime, Crtime uint64 + Number format.InodeNumber + Mode uint16 + Uid, Gid uint32 + LinkCount uint32 + XattrBlock uint32 + BlockCount uint32 + Devmajor, Devminor uint32 + Flags format.InodeFlag + Data []byte + XattrInline []byte + Children directory +} + +func (node *inode) FileType() uint16 { + return node.Mode & format.TypeMask +} + +func (node *inode) IsDir() bool { + return node.FileType() == S_IFDIR +} + +// A File represents a file to be added to an ext4 file system. +type File struct { + Linkname string + Size int64 + Mode uint16 + Uid, Gid uint32 + Atime, Ctime, Mtime, Crtime time.Time + Devmajor, Devminor uint32 + Xattrs map[string][]byte +} + +const ( + inodeFirst = 11 + inodeLostAndFound = inodeFirst + + BlockSize = 4096 + blocksPerGroup = BlockSize * 8 + inodeSize = 256 + maxInodesPerGroup = BlockSize * 8 // Limited by the inode bitmap + inodesPerGroupIncrement = BlockSize / inodeSize + + defaultMaxDiskSize = 16 * memory.GiB // 16GB + maxMaxDiskSize = 16 * 1024 * 1024 * 1024 * 1024 // 16TB + + groupDescriptorSize = 32 // Use the small group descriptor + groupsPerDescriptorBlock = BlockSize / groupDescriptorSize + + maxFileSize = 128 * memory.GiB // 128GB file size maximum for now + smallSymlinkSize = 59 // max symlink size that goes directly in the inode + maxBlocksPerExtent = 0x8000 // maximum number of blocks in an extent + inodeDataSize = 60 + inodeUsedSize = 152 // fields through CrtimeExtra + inodeExtraSize = inodeSize - inodeUsedSize + xattrInodeOverhead = 4 + 4 // magic number + empty next entry value + xattrBlockOverhead = 32 + 4 // header + empty next entry value + inlineDataXattrOverhead = xattrInodeOverhead + 16 + 4 // entry + "data" + inlineDataSize = inodeDataSize + inodeExtraSize - inlineDataXattrOverhead +) + +type exceededMaxSizeError struct { + Size int64 +} + +func (err exceededMaxSizeError) Error() string { + return fmt.Sprintf("disk exceeded maximum size of %d bytes", err.Size) +} + +var directoryEntrySize = binary.Size(format.DirectoryEntry{}) +var extraIsize = uint16(inodeUsedSize - 128) + +type directory map[string]*inode + +func splitFirst(p string) (string, string) { + n := strings.IndexByte(p, '/') + if n >= 0 { + return p[:n], p[n+1:] + } + return p, "" +} + +func (w *Writer) findPath(root *inode, p string) *inode { + inode := root + for inode != nil && len(p) != 0 { + name, rest := splitFirst(p) + p = rest + inode = inode.Children[name] + } + return inode +} + +func timeToFsTime(t time.Time) uint64 { + if t.IsZero() { + return 0 + } + s := t.Unix() + if s < -0x80000000 { + return 0x80000000 + } + if s > 0x37fffffff { + return 0x37fffffff + } + return uint64(s) | uint64(t.Nanosecond())<<34 +} + +func fsTimeToTime(t uint64) time.Time { + if t == 0 { + return time.Time{} + } + s := int64(t & 0x3ffffffff) + if s > 0x7fffffff && s < 0x100000000 { + s = int64(int32(uint32(s))) + } + return time.Unix(s, int64(t>>34)) +} + +func (w *Writer) getInode(i format.InodeNumber) *inode { + if i == 0 || int(i) > len(w.inodes) { + return nil + } + return w.inodes[i-1] +} + +var xattrPrefixes = []struct { + Index uint8 + Prefix string +}{ + {2, "system.posix_acl_access"}, + {3, "system.posix_acl_default"}, + {8, "system.richacl"}, + {7, "system."}, + {1, "user."}, + {4, "trusted."}, + {6, "security."}, +} + +func compressXattrName(name string) (uint8, string) { + for _, p := range xattrPrefixes { + if strings.HasPrefix(name, p.Prefix) { + return p.Index, name[len(p.Prefix):] + } + } + return 0, name +} + +func decompressXattrName(index uint8, name string) string { + for _, p := range xattrPrefixes { + if index == p.Index { + return p.Prefix + name + } + } + return name +} + +func hashXattrEntry(name string, value []byte) uint32 { + var hash uint32 + for i := 0; i < len(name); i++ { + hash = (hash << 5) ^ (hash >> 27) ^ uint32(name[i]) + } + + for i := 0; i+3 < len(value); i += 4 { + hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(value[i:i+4]) + } + + if len(value)%4 != 0 { + var last [4]byte + copy(last[:], value[len(value)&^3:]) + hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(last[:]) + } + return hash +} + +type xattr struct { + Name string + Index uint8 + Value []byte +} + +func (x *xattr) EntryLen() int { + return (len(x.Name)+3)&^3 + 16 +} + +func (x *xattr) ValueLen() int { + return (len(x.Value) + 3) &^ 3 +} + +type xattrState struct { + inode, block []xattr + inodeLeft, blockLeft int +} + +func (s *xattrState) init() { + s.inodeLeft = inodeExtraSize - xattrInodeOverhead + s.blockLeft = BlockSize - xattrBlockOverhead +} + +func (s *xattrState) addXattr(name string, value []byte) bool { + index, name := compressXattrName(name) + x := xattr{ + Index: index, + Name: name, + Value: value, + } + length := x.EntryLen() + x.ValueLen() + if s.inodeLeft >= length { + s.inode = append(s.inode, x) + s.inodeLeft -= length + } else if s.blockLeft >= length { + s.block = append(s.block, x) + s.blockLeft -= length + } else { + return false + } + return true +} + +func putXattrs(xattrs []xattr, b []byte, offsetDelta uint16) { + offset := uint16(len(b)) + offsetDelta + eb := b + db := b + for _, xattr := range xattrs { + vl := xattr.ValueLen() + offset -= uint16(vl) + eb[0] = uint8(len(xattr.Name)) + eb[1] = xattr.Index + binary.LittleEndian.PutUint16(eb[2:], offset) + binary.LittleEndian.PutUint32(eb[8:], uint32(len(xattr.Value))) + binary.LittleEndian.PutUint32(eb[12:], hashXattrEntry(xattr.Name, xattr.Value)) + copy(eb[16:], xattr.Name) + eb = eb[xattr.EntryLen():] + copy(db[len(db)-vl:], xattr.Value) + db = db[:len(db)-vl] + } +} + +func getXattrs(b []byte, xattrs map[string][]byte, offsetDelta uint16) { + eb := b + for len(eb) != 0 { + nameLen := eb[0] + if nameLen == 0 { + break + } + index := eb[1] + offset := binary.LittleEndian.Uint16(eb[2:]) - offsetDelta + valueLen := binary.LittleEndian.Uint32(eb[8:]) + attr := xattr{ + Index: index, + Name: string(eb[16 : 16+nameLen]), + Value: b[offset : uint32(offset)+valueLen], + } + xattrs[decompressXattrName(index, attr.Name)] = attr.Value + eb = eb[attr.EntryLen():] + } +} + +func (w *Writer) writeXattrs(inode *inode, state *xattrState) error { + // Write the inline attributes. + if len(state.inode) != 0 { + inode.XattrInline = make([]byte, inodeExtraSize) + binary.LittleEndian.PutUint32(inode.XattrInline[0:], format.XAttrHeaderMagic) // Magic + putXattrs(state.inode, inode.XattrInline[4:], 0) + } + + // Write the block attributes. If there was previously an xattr block, then + // rewrite it even if it is now empty. + if len(state.block) != 0 || inode.XattrBlock != 0 { + sort.Slice(state.block, func(i, j int) bool { + return state.block[i].Index < state.block[j].Index || + len(state.block[i].Name) < len(state.block[j].Name) || + state.block[i].Name < state.block[j].Name + }) + + var b [BlockSize]byte + binary.LittleEndian.PutUint32(b[0:], format.XAttrHeaderMagic) // Magic + binary.LittleEndian.PutUint32(b[4:], 1) // ReferenceCount + binary.LittleEndian.PutUint32(b[8:], 1) // Blocks + putXattrs(state.block, b[32:], 32) + + orig := w.block() + if inode.XattrBlock == 0 { + inode.XattrBlock = orig + inode.BlockCount++ + } else { + // Reuse the original block. + w.seekBlock(inode.XattrBlock) + defer w.seekBlock(orig) + } + + if _, err := w.write(b[:]); err != nil { + return err + } + } + + return nil +} + +func (w *Writer) write(b []byte) (int, error) { + if w.err != nil { + return 0, w.err + } + if w.pos+int64(len(b)) > w.maxDiskSize { + w.err = exceededMaxSizeError{w.maxDiskSize} + return 0, w.err + } + n, err := w.bw.Write(b) + w.pos += int64(n) + w.err = err + return n, err +} + +func (w *Writer) zero(n int64) (int64, error) { + if w.err != nil { + return 0, w.err + } + if w.pos+int64(n) > w.maxDiskSize { + w.err = exceededMaxSizeError{w.maxDiskSize} + return 0, w.err + } + n, err := io.CopyN(w.bw, zero, n) + w.pos += n + w.err = err + return n, err +} + +func (w *Writer) makeInode(f *File, node *inode) (*inode, error) { + mode := f.Mode + if mode&format.TypeMask == 0 { + mode |= format.S_IFREG + } + typ := mode & format.TypeMask + ino := format.InodeNumber(len(w.inodes) + 1) + if node == nil { + node = &inode{ + Number: ino, + } + if typ == S_IFDIR { + node.Children = make(directory) + node.LinkCount = 1 // A directory is linked to itself. + } + } else if node.Flags&format.InodeFlagExtents != 0 { + // Since we cannot deallocate or reuse blocks, don't allow updates that + // would invalidate data that has already been written. + return nil, errors.New("cannot overwrite file with non-inline data") + } + node.Mode = mode + node.Uid = f.Uid + node.Gid = f.Gid + node.Flags = format.InodeFlagHugeFile + node.Atime = timeToFsTime(f.Atime) + node.Ctime = timeToFsTime(f.Ctime) + node.Mtime = timeToFsTime(f.Mtime) + node.Crtime = timeToFsTime(f.Crtime) + node.Devmajor = f.Devmajor + node.Devminor = f.Devminor + node.Data = nil + if f.Xattrs == nil { + f.Xattrs = make(map[string][]byte) + } + + // copy over existing xattrs first, we need to merge existing xattrs and the passed xattrs. + existingXattrs := make(map[string][]byte) + if len(node.XattrInline) > 0 { + getXattrs(node.XattrInline[4:], existingXattrs, 0) + } + node.XattrInline = nil + + var xstate xattrState + xstate.init() + + var size int64 + switch typ { + case format.S_IFREG: + size = f.Size + if f.Size > maxFileSize { + return nil, fmt.Errorf("file too big: %d > %d", f.Size, int64(maxFileSize)) + } + if f.Size <= inlineDataSize && w.supportInlineData { + node.Data = make([]byte, f.Size) + extra := 0 + if f.Size > inodeDataSize { + extra = int(f.Size - inodeDataSize) + } + // Add a dummy entry for now. + if !xstate.addXattr("system.data", node.Data[:extra]) { + panic("not enough room for inline data") + } + node.Flags |= format.InodeFlagInlineData + } + case format.S_IFLNK: + node.Mode |= 0777 // Symlinks should appear as ugw rwx + size = int64(len(f.Linkname)) + if size <= smallSymlinkSize { + // Special case: small symlinks go directly in Block without setting + // an inline data flag. + node.Data = make([]byte, len(f.Linkname)) + copy(node.Data, f.Linkname) + } + case format.S_IFDIR, format.S_IFIFO, format.S_IFSOCK, format.S_IFCHR, format.S_IFBLK: + default: + return nil, fmt.Errorf("invalid mode %o", mode) + } + + // merge xattrs but prefer currently passed over existing + for name, data := range existingXattrs { + if _, ok := f.Xattrs[name]; !ok { + f.Xattrs[name] = data + } + } + + // Accumulate the extended attributes. + if len(f.Xattrs) != 0 { + // Sort the xattrs to avoid non-determinism in map iteration. + var xattrs []string + for name := range f.Xattrs { + xattrs = append(xattrs, name) + } + sort.Strings(xattrs) + for _, name := range xattrs { + if !xstate.addXattr(name, f.Xattrs[name]) { + return nil, fmt.Errorf("could not fit xattr %s", name) + } + } + } + + if err := w.writeXattrs(node, &xstate); err != nil { + return nil, err + } + + node.Size = size + if typ == format.S_IFLNK && size > smallSymlinkSize { + // Write the link name as data. + w.startInode("", node, size) + if _, err := w.Write([]byte(f.Linkname)); err != nil { + return nil, err + } + if err := w.finishInode(); err != nil { + return nil, err + } + } + + if int(node.Number-1) >= len(w.inodes) { + w.inodes = append(w.inodes, node) + } + return node, nil +} + +func (w *Writer) root() *inode { + return w.getInode(format.InodeRoot) +} + +func (w *Writer) lookup(name string, mustExist bool) (*inode, *inode, string, error) { + root := w.root() + cleanname := path.Clean("/" + name)[1:] + if len(cleanname) == 0 { + return root, root, "", nil + } + dirname, childname := path.Split(cleanname) + if len(childname) == 0 || len(childname) > 0xff { + return nil, nil, "", fmt.Errorf("%s: invalid name", name) + } + dir := w.findPath(root, dirname) + if dir == nil || !dir.IsDir() { + return nil, nil, "", fmt.Errorf("%s: path not found", name) + } + child := dir.Children[childname] + if child == nil && mustExist { + return nil, nil, "", fmt.Errorf("%s: file not found", name) + } + return dir, child, childname, nil +} + +// MakeParents ensures that all the parent directories in the path specified by `name` exists. If +// they don't exist it creates them (like `mkdir -p`). These non existing parent directories are created +// with the same permissions as that of it's parent directory. It is expected that the a +// call to make these parent directories will be made at a later point with the correct +// permissions, at that time the permissions of these directories will be updated. +// We treat Atime, Mtime, Ctime, and Crtime in the same way. +func (w *Writer) MakeParents(name string) error { + if err := w.finishInode(); err != nil { + return err + } + + // go through the directories in the path one by one and create the + // parent directories if they don't exist. + cleanname := path.Clean("/" + name)[1:] + parentDirs, _ := path.Split(cleanname) + currentPath := "" + root := w.root() + dirname := "" + for parentDirs != "" { + dirname, parentDirs = splitFirst(parentDirs) + currentPath += "/" + dirname + if _, ok := root.Children[dirname]; !ok { + f := &File{ + Mode: root.Mode, + Atime: fsTimeToTime(root.Atime), + Mtime: fsTimeToTime(root.Mtime), + Ctime: fsTimeToTime(root.Ctime), + Crtime: fsTimeToTime(root.Crtime), + Size: 0, + Uid: root.Uid, + Gid: root.Gid, + Devmajor: root.Devmajor, + Devminor: root.Devminor, + Xattrs: make(map[string][]byte), + } + if err := w.Create(currentPath, f); err != nil { + return fmt.Errorf("failed while creating parent directories: %w", err) + } + } + root = root.Children[dirname] + } + return nil +} + +// Create adds a file to the file system. +func (w *Writer) Create(name string, f *File) error { + if err := w.finishInode(); err != nil { + return err + } + dir, existing, childname, err := w.lookup(name, false) + if err != nil { + return err + } + var reuse *inode + if existing != nil { + if existing.IsDir() { + if f.Mode&TypeMask != S_IFDIR { + return fmt.Errorf("%s: cannot replace a directory with a file", name) + } + reuse = existing + } else if f.Mode&TypeMask == S_IFDIR { + return fmt.Errorf("%s: cannot replace a file with a directory", name) + } else if existing.LinkCount < 2 { + reuse = existing + } + } else { + if f.Mode&TypeMask == S_IFDIR && dir.LinkCount >= format.MaxLinks { + return fmt.Errorf("%s: exceeded parent directory maximum link count", name) + } + } + child, err := w.makeInode(f, reuse) + if err != nil { + return fmt.Errorf("%s: %w", name, err) + } + if existing != child { + if existing != nil { + existing.LinkCount-- + } + dir.Children[childname] = child + child.LinkCount++ + if child.IsDir() { + dir.LinkCount++ + } + } + if child.Mode&format.TypeMask == format.S_IFREG { + w.startInode(name, child, f.Size) + } + return nil +} + +// Link adds a hard link to the file system. +// We support creating hardlinks to symlinks themselves instead of what +// the symlinks link to, as this is what containerd does upstream. +func (w *Writer) Link(oldname, newname string) error { + if err := w.finishInode(); err != nil { + return err + } + newdir, existing, newchildname, err := w.lookup(newname, false) + if err != nil { + return err + } + if existing != nil && (existing.IsDir() || existing.LinkCount < 2) { + return fmt.Errorf("%s: cannot orphan existing file or directory", newname) + } + + _, oldfile, _, err := w.lookup(oldname, true) + if err != nil { + return err + } + switch oldfile.Mode & format.TypeMask { + case format.S_IFDIR: + return fmt.Errorf("%s: link target cannot be a directory: %s", newname, oldname) + } + + if existing != oldfile && oldfile.LinkCount >= format.MaxLinks { + return fmt.Errorf("%s: link target would exceed maximum link count: %s", newname, oldname) + } + + if existing != nil { + existing.LinkCount-- + } + oldfile.LinkCount++ + newdir.Children[newchildname] = oldfile + return nil +} + +// Stat returns information about a file that has been written. +func (w *Writer) Stat(name string) (*File, error) { + if err := w.finishInode(); err != nil { + return nil, err + } + _, node, _, err := w.lookup(name, true) + if err != nil { + return nil, err + } + f := &File{ + Size: node.Size, + Mode: node.Mode, + Uid: node.Uid, + Gid: node.Gid, + Atime: fsTimeToTime(node.Atime), + Ctime: fsTimeToTime(node.Ctime), + Mtime: fsTimeToTime(node.Mtime), + Crtime: fsTimeToTime(node.Crtime), + Devmajor: node.Devmajor, + Devminor: node.Devminor, + } + f.Xattrs = make(map[string][]byte) + if node.XattrBlock != 0 || len(node.XattrInline) != 0 { + if node.XattrBlock != 0 { + orig := w.block() + w.seekBlock(node.XattrBlock) + if w.err != nil { + return nil, w.err + } + var b [BlockSize]byte + _, err := w.f.Read(b[:]) + w.seekBlock(orig) + if err != nil { + return nil, err + } + getXattrs(b[32:], f.Xattrs, 32) + } + if len(node.XattrInline) != 0 { + getXattrs(node.XattrInline[4:], f.Xattrs, 0) + delete(f.Xattrs, "system.data") + } + } + if node.FileType() == S_IFLNK { + if node.Size > smallSymlinkSize { + return nil, fmt.Errorf("%s: cannot retrieve link information", name) + } + f.Linkname = string(node.Data) + } + return f, nil +} + +func (w *Writer) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + if w.dataWritten+int64(len(b)) > w.dataMax { + return 0, fmt.Errorf("%s: wrote too much: %d > %d", w.curName, w.dataWritten+int64(len(b)), w.dataMax) + } + + if w.curInode.Flags&format.InodeFlagInlineData != 0 { + copy(w.curInode.Data[w.dataWritten:], b) + w.dataWritten += int64(len(b)) + return len(b), nil + } + + n, err := w.write(b) + w.dataWritten += int64(n) + return n, err +} + +func (w *Writer) startInode(name string, inode *inode, size int64) { + if w.curInode != nil { + panic("inode already in progress") + } + w.curName = name + w.curInode = inode + w.dataWritten = 0 + w.dataMax = size +} + +func (w *Writer) block() uint32 { + return uint32(w.pos / BlockSize) +} + +func (w *Writer) seekBlock(block uint32) { + w.pos = int64(block) * BlockSize + if w.err != nil { + return + } + w.err = w.bw.Flush() + if w.err != nil { + return + } + _, w.err = w.f.Seek(w.pos, io.SeekStart) +} + +func (w *Writer) nextBlock() { + if w.pos%BlockSize != 0 { + // Simplify callers; w.err is updated on failure. + _, _ = w.zero(BlockSize - w.pos%BlockSize) + } +} + +func fillExtents(hdr *format.ExtentHeader, extents []format.ExtentLeafNode, startBlock, offset, inodeSize uint32) { + *hdr = format.ExtentHeader{ + Magic: format.ExtentHeaderMagic, + Entries: uint16(len(extents)), + Max: uint16(cap(extents)), + Depth: 0, + } + for i := range extents { + block := offset + uint32(i)*maxBlocksPerExtent + length := inodeSize - block + if length > maxBlocksPerExtent { + length = maxBlocksPerExtent + } + start := startBlock + block + extents[i] = format.ExtentLeafNode{ + Block: block, + Length: uint16(length), + StartLow: start, + } + } +} + +func (w *Writer) writeExtents(inode *inode) error { + start := w.pos - w.dataWritten + if start%BlockSize != 0 { + panic("unaligned") + } + w.nextBlock() + + startBlock := uint32(start / BlockSize) + blocks := w.block() - startBlock + usedBlocks := blocks + + const extentNodeSize = 12 + const extentsPerBlock = BlockSize/extentNodeSize - 1 + + extents := (blocks + maxBlocksPerExtent - 1) / maxBlocksPerExtent + var b bytes.Buffer + if extents == 0 { + // Nothing to do. + } else if extents <= 4 { + var root struct { + hdr format.ExtentHeader + extents [4]format.ExtentLeafNode + } + fillExtents(&root.hdr, root.extents[:extents], startBlock, 0, blocks) + _ = binary.Write(&b, binary.LittleEndian, root) + } else if extents <= 4*extentsPerBlock { + const extentsPerBlock = BlockSize/extentNodeSize - 1 + extentBlocks := extents/extentsPerBlock + 1 + usedBlocks += extentBlocks + var b2 bytes.Buffer + + var root struct { + hdr format.ExtentHeader + nodes [4]format.ExtentIndexNode + } + root.hdr = format.ExtentHeader{ + Magic: format.ExtentHeaderMagic, + Entries: uint16(extentBlocks), + Max: 4, + Depth: 1, + } + for i := uint32(0); i < extentBlocks; i++ { + root.nodes[i] = format.ExtentIndexNode{ + Block: i * extentsPerBlock * maxBlocksPerExtent, + LeafLow: w.block(), + } + extentsInBlock := extents - i*extentBlocks + if extentsInBlock > extentsPerBlock { + extentsInBlock = extentsPerBlock + } + + var node struct { + hdr format.ExtentHeader + extents [extentsPerBlock]format.ExtentLeafNode + _ [BlockSize - (extentsPerBlock+1)*extentNodeSize]byte + } + + offset := i * extentsPerBlock * maxBlocksPerExtent + fillExtents(&node.hdr, node.extents[:extentsInBlock], startBlock+offset, offset, blocks) + _ = binary.Write(&b2, binary.LittleEndian, node) + if _, err := w.write(b2.Next(BlockSize)); err != nil { + return err + } + } + _ = binary.Write(&b, binary.LittleEndian, root) + } else { + panic("file too big") + } + + inode.Data = b.Bytes() + inode.Flags |= format.InodeFlagExtents + inode.BlockCount += usedBlocks + return w.err +} + +func (w *Writer) finishInode() error { + if !w.initialized { + if err := w.init(); err != nil { + return err + } + } + if w.curInode == nil { + return nil + } + if w.dataWritten != w.dataMax { + return fmt.Errorf("did not write the right amount: %d != %d", w.dataWritten, w.dataMax) + } + + if w.dataMax != 0 && w.curInode.Flags&format.InodeFlagInlineData == 0 { + if err := w.writeExtents(w.curInode); err != nil { + return err + } + } + + w.dataWritten = 0 + w.dataMax = 0 + w.curInode = nil + return w.err +} + +func modeToFileType(mode uint16) format.FileType { + switch mode & format.TypeMask { + default: + return format.FileTypeUnknown + case format.S_IFREG: + return format.FileTypeRegular + case format.S_IFDIR: + return format.FileTypeDirectory + case format.S_IFCHR: + return format.FileTypeCharacter + case format.S_IFBLK: + return format.FileTypeBlock + case format.S_IFIFO: + return format.FileTypeFIFO + case format.S_IFSOCK: + return format.FileTypeSocket + case format.S_IFLNK: + return format.FileTypeSymbolicLink + } +} + +type constReader byte + +var zero = constReader(0) + +func (r constReader) Read(b []byte) (int, error) { + for i := range b { + b[i] = byte(r) + } + return len(b), nil +} + +func (w *Writer) writeDirectory(dir, parent *inode) error { + if err := w.finishInode(); err != nil { + return err + } + + // The size of the directory is not known yet. + w.startInode("", dir, 0x7fffffffffffffff) + left := BlockSize + finishBlock := func() error { + if left > 0 { + e := format.DirectoryEntry{ + RecordLength: uint16(left), + } + err := binary.Write(w, binary.LittleEndian, e) + if err != nil { + return err + } + left -= directoryEntrySize + if left < 4 { + panic("not enough space for trailing entry") + } + _, err = io.CopyN(w, zero, int64(left)) + if err != nil { + return err + } + } + left = BlockSize + return nil + } + + writeEntry := func(ino format.InodeNumber, name string) error { + rlb := directoryEntrySize + len(name) + rl := (rlb + 3) & ^3 + if left < rl+12 { + if err := finishBlock(); err != nil { + return err + } + } + e := format.DirectoryEntry{ + Inode: ino, + RecordLength: uint16(rl), + NameLength: uint8(len(name)), + FileType: modeToFileType(w.getInode(ino).Mode), + } + err := binary.Write(w, binary.LittleEndian, e) + if err != nil { + return err + } + _, err = w.Write([]byte(name)) + if err != nil { + return err + } + var zero [4]byte + _, err = w.Write(zero[:rl-rlb]) + if err != nil { + return err + } + left -= rl + return nil + } + if err := writeEntry(dir.Number, "."); err != nil { + return err + } + if err := writeEntry(parent.Number, ".."); err != nil { + return err + } + + // Follow e2fsck's convention and sort the children by inode number. + var children []string + for name := range dir.Children { + children = append(children, name) + } + sort.Slice(children, func(i, j int) bool { + left_num := dir.Children[children[i]].Number + right_num := dir.Children[children[j]].Number + + if left_num == right_num { + return children[i] < children[j] + } + return left_num < right_num + }) + + for _, name := range children { + child := dir.Children[name] + if err := writeEntry(child.Number, name); err != nil { + return err + } + } + if err := finishBlock(); err != nil { + return err + } + w.curInode.Size = w.dataWritten + w.dataMax = w.dataWritten + return nil +} + +func (w *Writer) writeDirectoryRecursive(dir, parent *inode) error { + if err := w.writeDirectory(dir, parent); err != nil { + return err + } + + // Follow e2fsck's convention and sort the children by inode number. + var children []string + for name := range dir.Children { + children = append(children, name) + } + sort.Slice(children, func(i, j int) bool { + left_num := dir.Children[children[i]].Number + right_num := dir.Children[children[j]].Number + + if left_num == right_num { + return children[i] < children[j] + } + return left_num < right_num + }) + + for _, name := range children { + child := dir.Children[name] + if child.IsDir() { + if err := w.writeDirectoryRecursive(child, dir); err != nil { + return err + } + } + } + return nil +} + +func (w *Writer) writeInodeTable(tableSize uint32) error { + var b bytes.Buffer + for _, inode := range w.inodes { + if inode != nil { + binode := format.Inode{ + Mode: inode.Mode, + Uid: uint16(inode.Uid & 0xffff), + Gid: uint16(inode.Gid & 0xffff), + SizeLow: uint32(inode.Size & 0xffffffff), + SizeHigh: uint32(inode.Size >> 32), + LinksCount: uint16(inode.LinkCount), + BlocksLow: inode.BlockCount, + Flags: inode.Flags, + XattrBlockLow: inode.XattrBlock, + UidHigh: uint16(inode.Uid >> 16), + GidHigh: uint16(inode.Gid >> 16), + ExtraIsize: uint16(inodeUsedSize - 128), + Atime: uint32(inode.Atime), + AtimeExtra: uint32(inode.Atime >> 32), + Ctime: uint32(inode.Ctime), + CtimeExtra: uint32(inode.Ctime >> 32), + Mtime: uint32(inode.Mtime), + MtimeExtra: uint32(inode.Mtime >> 32), + Crtime: uint32(inode.Crtime), + CrtimeExtra: uint32(inode.Crtime >> 32), + } + switch inode.Mode & format.TypeMask { + case format.S_IFDIR, format.S_IFREG, format.S_IFLNK: + n := copy(binode.Block[:], inode.Data) + if n < len(inode.Data) { + // Rewrite the first xattr with the data. + xattr := [1]xattr{{ + Name: "data", + Index: 7, // "system." + Value: inode.Data[n:], + }} + putXattrs(xattr[:], inode.XattrInline[4:], 0) + } + case format.S_IFBLK, format.S_IFCHR: + dev := inode.Devminor&0xff | inode.Devmajor<<8 | (inode.Devminor&0xffffff00)<<12 + binary.LittleEndian.PutUint32(binode.Block[4:], dev) + } + + _ = binary.Write(&b, binary.LittleEndian, binode) + b.Truncate(inodeUsedSize) + n, _ := b.Write(inode.XattrInline) + _, _ = io.CopyN(&b, zero, int64(inodeExtraSize-n)) + } else { + _, _ = io.CopyN(&b, zero, inodeSize) + } + if _, err := w.write(b.Next(inodeSize)); err != nil { + return err + } + } + rest := tableSize - uint32(len(w.inodes)*inodeSize) + if _, err := w.zero(int64(rest)); err != nil { + return err + } + return nil +} + +// NewWriter returns a Writer that writes an ext4 file system to the provided +// ReadWriteSeeker. +func NewWriter(f io.ReadWriteSeeker, opts ...Option) *Writer { + w := &Writer{ + f: f, + bw: bufio.NewWriterSize(f, 65536*8), + maxDiskSize: defaultMaxDiskSize, + } + for _, opt := range opts { + opt(w) + } + return w +} + +// An Option provides extra options to NewWriter. +type Option func(*Writer) + +// InlineData instructs the Writer to write small files into the inode +// structures directly. This creates smaller images but currently is not +// compatible with DAX. +func InlineData(w *Writer) { + w.supportInlineData = true +} + +// MaximumDiskSize instructs the writer to reserve enough metadata space for the +// specified disk size. If not provided, then 16GB is the default. +func MaximumDiskSize(size int64) Option { + return func(w *Writer) { + if size < 0 || size > maxMaxDiskSize { + w.maxDiskSize = maxMaxDiskSize + } else if size == 0 { + w.maxDiskSize = defaultMaxDiskSize + } else { + w.maxDiskSize = (size + BlockSize - 1) &^ (BlockSize - 1) + } + } +} + +func (w *Writer) init() error { + // Skip the defective block inode. + w.inodes = make([]*inode, 1, 32) + // Create the root directory. + root, _ := w.makeInode(&File{ + Mode: format.S_IFDIR | 0755, + }, nil) + root.LinkCount++ // The root is linked to itself. + // Skip until the first non-reserved inode. + w.inodes = append(w.inodes, make([]*inode, inodeFirst-len(w.inodes)-1)...) + maxBlocks := (w.maxDiskSize-1)/BlockSize + 1 + maxGroups := (maxBlocks-1)/blocksPerGroup + 1 + w.gdBlocks = uint32((maxGroups-1)/groupsPerDescriptorBlock + 1) + + // Skip past the superblock and block descriptor table. + w.seekBlock(1 + w.gdBlocks) + w.initialized = true + + // The lost+found directory is required to exist for e2fsck to pass. + if err := w.Create("lost+found", &File{Mode: format.S_IFDIR | 0700}); err != nil { + return err + } + return w.err +} + +func groupCount(blocks uint32, inodes uint32, inodesPerGroup uint32) uint32 { + inodeBlocksPerGroup := inodesPerGroup * inodeSize / BlockSize + dataBlocksPerGroup := blocksPerGroup - inodeBlocksPerGroup - 2 // save room for the bitmaps + + // Increase the block count to ensure there are enough groups for all the + // inodes. + minBlocks := (inodes-1)/inodesPerGroup*dataBlocksPerGroup + 1 + if blocks < minBlocks { + blocks = minBlocks + } + + return (blocks + dataBlocksPerGroup - 1) / dataBlocksPerGroup +} + +func bestGroupCount(blocks uint32, inodes uint32) (groups uint32, inodesPerGroup uint32) { + groups = 0xffffffff + for ipg := uint32(inodesPerGroupIncrement); ipg <= maxInodesPerGroup; ipg += inodesPerGroupIncrement { + g := groupCount(blocks, inodes, ipg) + if g < groups { + groups = g + inodesPerGroup = ipg + } + } + return +} + +func (w *Writer) Close() error { + if err := w.finishInode(); err != nil { + return err + } + root := w.root() + if err := w.writeDirectoryRecursive(root, root); err != nil { + return err + } + // Finish the last inode (probably a directory). + if err := w.finishInode(); err != nil { + return err + } + + // Write the inode table + inodeTableOffset := w.block() + groups, inodesPerGroup := bestGroupCount(inodeTableOffset, uint32(len(w.inodes))) + err := w.writeInodeTable(groups * inodesPerGroup * inodeSize) + if err != nil { + return err + } + + // Write the bitmaps. + bitmapOffset := w.block() + bitmapSize := groups * 2 + validDataSize := bitmapOffset + bitmapSize + diskSize := validDataSize + minSize := (groups-1)*blocksPerGroup + 1 + if diskSize < minSize { + diskSize = minSize + } + + usedGdBlocks := (groups-1)/groupsPerDescriptorBlock + 1 + if usedGdBlocks > w.gdBlocks { + return exceededMaxSizeError{w.maxDiskSize} + } + + gds := make([]format.GroupDescriptor, w.gdBlocks*groupsPerDescriptorBlock) + inodeTableSizePerGroup := inodesPerGroup * inodeSize / BlockSize + var totalUsedBlocks, totalUsedInodes uint32 + for g := uint32(0); g < groups; g++ { + var b [BlockSize * 2]byte + var dirCount, usedInodeCount, usedBlockCount uint16 + + // Block bitmap + if (g+1)*blocksPerGroup <= validDataSize { + // This group is fully allocated. + for j := range b[:BlockSize] { + b[j] = 0xff + } + usedBlockCount = blocksPerGroup + } else if g*blocksPerGroup < validDataSize { + for j := uint32(0); j < validDataSize-g*blocksPerGroup; j++ { + b[j/8] |= 1 << (j % 8) + usedBlockCount++ + } + } + if g == 0 { + // Unused group descriptor blocks should be cleared. + for j := 1 + usedGdBlocks; j < 1+w.gdBlocks; j++ { + b[j/8] &^= 1 << (j % 8) + usedBlockCount-- + } + } + if g == groups-1 && diskSize%blocksPerGroup != 0 { + // Blocks that aren't present in the disk should be marked as + // allocated. + for j := diskSize % blocksPerGroup; j < blocksPerGroup; j++ { + b[j/8] |= 1 << (j % 8) + usedBlockCount++ + } + } + // Inode bitmap + for j := uint32(0); j < inodesPerGroup; j++ { + ino := format.InodeNumber(1 + g*inodesPerGroup + j) + inode := w.getInode(ino) + if ino < inodeFirst || inode != nil { + b[BlockSize+j/8] |= 1 << (j % 8) + usedInodeCount++ + } + if inode != nil && inode.Mode&format.TypeMask == format.S_IFDIR { + dirCount++ + } + } + _, err := w.write(b[:]) + if err != nil { + return err + } + gds[g] = format.GroupDescriptor{ + BlockBitmapLow: bitmapOffset + 2*g, + InodeBitmapLow: bitmapOffset + 2*g + 1, + InodeTableLow: inodeTableOffset + g*inodeTableSizePerGroup, + UsedDirsCountLow: dirCount, + FreeInodesCountLow: uint16(inodesPerGroup) - usedInodeCount, + FreeBlocksCountLow: blocksPerGroup - usedBlockCount, + } + + totalUsedBlocks += uint32(usedBlockCount) + totalUsedInodes += uint32(usedInodeCount) + } + + // Zero up to the disk size. + _, err = w.zero(int64(diskSize-bitmapOffset-bitmapSize) * BlockSize) + if err != nil { + return err + } + + // Write the block descriptors + w.seekBlock(1) + if w.err != nil { + return w.err + } + err = binary.Write(w.bw, binary.LittleEndian, gds) + if err != nil { + return err + } + + // Write the super block + var blk [BlockSize]byte + b := bytes.NewBuffer(blk[:1024]) + sb := &format.SuperBlock{ + InodesCount: inodesPerGroup * groups, + BlocksCountLow: diskSize, + FreeBlocksCountLow: blocksPerGroup*groups - totalUsedBlocks, + FreeInodesCount: inodesPerGroup*groups - totalUsedInodes, + FirstDataBlock: 0, + LogBlockSize: 2, // 2^(10 + 2) + LogClusterSize: 2, + BlocksPerGroup: blocksPerGroup, + ClustersPerGroup: blocksPerGroup, + InodesPerGroup: inodesPerGroup, + Magic: format.SuperBlockMagic, + State: 1, // cleanly unmounted + Errors: 1, // continue on error? + CreatorOS: 0, // Linux + RevisionLevel: 1, // dynamic inode sizes + FirstInode: inodeFirst, + LpfInode: inodeLostAndFound, + InodeSize: inodeSize, + FeatureCompat: format.CompatSparseSuper2 | format.CompatExtAttr, + FeatureIncompat: format.IncompatFiletype | format.IncompatExtents | format.IncompatFlexBg, + FeatureRoCompat: format.RoCompatLargeFile | format.RoCompatHugeFile | format.RoCompatExtraIsize | format.RoCompatReadonly, + MinExtraIsize: extraIsize, + WantExtraIsize: extraIsize, + LogGroupsPerFlex: 31, + } + if w.supportInlineData { + sb.FeatureIncompat |= format.IncompatInlineData + } + _ = binary.Write(b, binary.LittleEndian, sb) + w.seekBlock(0) + if _, err := w.write(blk[:]); err != nil { + return err + } + w.seekBlock(diskSize) + return w.err +} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/internal/format/format.go b/vendor/github.com/Microsoft/hcsshim/ext4/internal/format/format.go new file mode 100644 index 0000000000..9dc4c4e164 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/ext4/internal/format/format.go @@ -0,0 +1,411 @@ +package format + +type SuperBlock struct { + InodesCount uint32 + BlocksCountLow uint32 + RootBlocksCountLow uint32 + FreeBlocksCountLow uint32 + FreeInodesCount uint32 + FirstDataBlock uint32 + LogBlockSize uint32 + LogClusterSize uint32 + BlocksPerGroup uint32 + ClustersPerGroup uint32 + InodesPerGroup uint32 + Mtime uint32 + Wtime uint32 + MountCount uint16 + MaxMountCount uint16 + Magic uint16 + State uint16 + Errors uint16 + MinorRevisionLevel uint16 + LastCheck uint32 + CheckInterval uint32 + CreatorOS uint32 + RevisionLevel uint32 + DefaultReservedUid uint16 + DefaultReservedGid uint16 + FirstInode uint32 + InodeSize uint16 + BlockGroupNr uint16 + FeatureCompat CompatFeature + FeatureIncompat IncompatFeature + FeatureRoCompat RoCompatFeature + UUID [16]uint8 + VolumeName [16]byte + LastMounted [64]byte + AlgorithmUsageBitmap uint32 + PreallocBlocks uint8 + PreallocDirBlocks uint8 + ReservedGdtBlocks uint16 + JournalUUID [16]uint8 + JournalInum uint32 + JournalDev uint32 + LastOrphan uint32 + HashSeed [4]uint32 + DefHashVersion uint8 + JournalBackupType uint8 + DescSize uint16 + DefaultMountOpts uint32 + FirstMetaBg uint32 + MkfsTime uint32 + JournalBlocks [17]uint32 + BlocksCountHigh uint32 + RBlocksCountHigh uint32 + FreeBlocksCountHigh uint32 + MinExtraIsize uint16 + WantExtraIsize uint16 + Flags uint32 + RaidStride uint16 + MmpInterval uint16 + MmpBlock uint64 + RaidStripeWidth uint32 + LogGroupsPerFlex uint8 + ChecksumType uint8 + ReservedPad uint16 + KbytesWritten uint64 + SnapshotInum uint32 + SnapshotID uint32 + SnapshotRBlocksCount uint64 + SnapshotList uint32 + ErrorCount uint32 + FirstErrorTime uint32 + FirstErrorInode uint32 + FirstErrorBlock uint64 + FirstErrorFunc [32]uint8 + FirstErrorLine uint32 + LastErrorTime uint32 + LastErrorInode uint32 + LastErrorLine uint32 + LastErrorBlock uint64 + LastErrorFunc [32]uint8 + MountOpts [64]uint8 + UserQuotaInum uint32 + GroupQuotaInum uint32 + OverheadBlocks uint32 + BackupBgs [2]uint32 + EncryptAlgos [4]uint8 + EncryptPwSalt [16]uint8 + LpfInode uint32 + ProjectQuotaInum uint32 + ChecksumSeed uint32 + WtimeHigh uint8 + MtimeHigh uint8 + MkfsTimeHigh uint8 + LastcheckHigh uint8 + FirstErrorTimeHigh uint8 + LastErrorTimeHigh uint8 + Pad [2]uint8 + Reserved [96]uint32 + Checksum uint32 +} + +const SuperBlockMagic uint16 = 0xef53 + +type CompatFeature uint32 +type IncompatFeature uint32 +type RoCompatFeature uint32 + +const ( + CompatDirPrealloc CompatFeature = 0x1 + CompatImagicInodes CompatFeature = 0x2 + CompatHasJournal CompatFeature = 0x4 + CompatExtAttr CompatFeature = 0x8 + CompatResizeInode CompatFeature = 0x10 + CompatDirIndex CompatFeature = 0x20 + CompatLazyBg CompatFeature = 0x40 + CompatExcludeInode CompatFeature = 0x80 + CompatExcludeBitmap CompatFeature = 0x100 + CompatSparseSuper2 CompatFeature = 0x200 + + IncompatCompression IncompatFeature = 0x1 + IncompatFiletype IncompatFeature = 0x2 + IncompatRecover IncompatFeature = 0x4 + IncompatJournalDev IncompatFeature = 0x8 + IncompatMetaBg IncompatFeature = 0x10 + IncompatExtents IncompatFeature = 0x40 + Incompat_64Bit IncompatFeature = 0x80 + IncompatMmp IncompatFeature = 0x100 + IncompatFlexBg IncompatFeature = 0x200 + IncompatEaInode IncompatFeature = 0x400 + IncompatDirdata IncompatFeature = 0x1000 + IncompatCsumSeed IncompatFeature = 0x2000 + IncompatLargedir IncompatFeature = 0x4000 + IncompatInlineData IncompatFeature = 0x8000 + IncompatEncrypt IncompatFeature = 0x10000 + + RoCompatSparseSuper RoCompatFeature = 0x1 + RoCompatLargeFile RoCompatFeature = 0x2 + RoCompatBtreeDir RoCompatFeature = 0x4 + RoCompatHugeFile RoCompatFeature = 0x8 + RoCompatGdtCsum RoCompatFeature = 0x10 + RoCompatDirNlink RoCompatFeature = 0x20 + RoCompatExtraIsize RoCompatFeature = 0x40 + RoCompatHasSnapshot RoCompatFeature = 0x80 + RoCompatQuota RoCompatFeature = 0x100 + RoCompatBigalloc RoCompatFeature = 0x200 + RoCompatMetadataCsum RoCompatFeature = 0x400 + RoCompatReplica RoCompatFeature = 0x800 + RoCompatReadonly RoCompatFeature = 0x1000 + RoCompatProject RoCompatFeature = 0x2000 +) + +type BlockGroupFlag uint16 + +const ( + BlockGroupInodeUninit BlockGroupFlag = 0x1 + BlockGroupBlockUninit BlockGroupFlag = 0x2 + BlockGroupInodeZeroed BlockGroupFlag = 0x4 +) + +type GroupDescriptor struct { + BlockBitmapLow uint32 + InodeBitmapLow uint32 + InodeTableLow uint32 + FreeBlocksCountLow uint16 + FreeInodesCountLow uint16 + UsedDirsCountLow uint16 + Flags BlockGroupFlag + ExcludeBitmapLow uint32 + BlockBitmapCsumLow uint16 + InodeBitmapCsumLow uint16 + ItableUnusedLow uint16 + Checksum uint16 +} + +type GroupDescriptor64 struct { + GroupDescriptor + BlockBitmapHigh uint32 + InodeBitmapHigh uint32 + InodeTableHigh uint32 + FreeBlocksCountHigh uint16 + FreeInodesCountHigh uint16 + UsedDirsCountHigh uint16 + ItableUnusedHigh uint16 + ExcludeBitmapHigh uint32 + BlockBitmapCsumHigh uint16 + InodeBitmapCsumHigh uint16 + Reserved uint32 +} + +const ( + S_IXOTH = 0x1 + S_IWOTH = 0x2 + S_IROTH = 0x4 + S_IXGRP = 0x8 + S_IWGRP = 0x10 + S_IRGRP = 0x20 + S_IXUSR = 0x40 + S_IWUSR = 0x80 + S_IRUSR = 0x100 + S_ISVTX = 0x200 + S_ISGID = 0x400 + S_ISUID = 0x800 + S_IFIFO = 0x1000 + S_IFCHR = 0x2000 + S_IFDIR = 0x4000 + S_IFBLK = 0x6000 + S_IFREG = 0x8000 + S_IFLNK = 0xA000 + S_IFSOCK = 0xC000 + + TypeMask uint16 = 0xF000 +) + +type InodeNumber uint32 + +const ( + InodeRoot = 2 +) + +type Inode struct { + Mode uint16 + Uid uint16 + SizeLow uint32 + Atime uint32 + Ctime uint32 + Mtime uint32 + Dtime uint32 + Gid uint16 + LinksCount uint16 + BlocksLow uint32 + Flags InodeFlag + Version uint32 + Block [60]byte + Generation uint32 + XattrBlockLow uint32 + SizeHigh uint32 + ObsoleteFragmentAddr uint32 + BlocksHigh uint16 + XattrBlockHigh uint16 + UidHigh uint16 + GidHigh uint16 + ChecksumLow uint16 + Reserved uint16 + ExtraIsize uint16 + ChecksumHigh uint16 + CtimeExtra uint32 + MtimeExtra uint32 + AtimeExtra uint32 + Crtime uint32 + CrtimeExtra uint32 + VersionHigh uint32 + Projid uint32 +} + +type InodeFlag uint32 + +const ( + InodeFlagSecRm InodeFlag = 0x1 + InodeFlagUnRm InodeFlag = 0x2 + InodeFlagCompressed InodeFlag = 0x4 + InodeFlagSync InodeFlag = 0x8 + InodeFlagImmutable InodeFlag = 0x10 + InodeFlagAppend InodeFlag = 0x20 + InodeFlagNoDump InodeFlag = 0x40 + InodeFlagNoAtime InodeFlag = 0x80 + InodeFlagDirtyCompressed InodeFlag = 0x100 + InodeFlagCompressedClusters InodeFlag = 0x200 + InodeFlagNoCompress InodeFlag = 0x400 + InodeFlagEncrypted InodeFlag = 0x800 + InodeFlagHashedIndex InodeFlag = 0x1000 + InodeFlagMagic InodeFlag = 0x2000 + InodeFlagJournalData InodeFlag = 0x4000 + InodeFlagNoTail InodeFlag = 0x8000 + InodeFlagDirSync InodeFlag = 0x10000 + InodeFlagTopDir InodeFlag = 0x20000 + InodeFlagHugeFile InodeFlag = 0x40000 + InodeFlagExtents InodeFlag = 0x80000 + InodeFlagEaInode InodeFlag = 0x200000 + InodeFlagEOFBlocks InodeFlag = 0x400000 + InodeFlagSnapfile InodeFlag = 0x01000000 + InodeFlagSnapfileDeleted InodeFlag = 0x04000000 + InodeFlagSnapfileShrunk InodeFlag = 0x08000000 + InodeFlagInlineData InodeFlag = 0x10000000 + InodeFlagProjectIDInherit InodeFlag = 0x20000000 + InodeFlagReserved InodeFlag = 0x80000000 +) + +const ( + MaxLinks = 65000 +) + +type ExtentHeader struct { + Magic uint16 + Entries uint16 + Max uint16 + Depth uint16 + Generation uint32 +} + +const ExtentHeaderMagic uint16 = 0xf30a + +type ExtentIndexNode struct { + Block uint32 + LeafLow uint32 + LeafHigh uint16 + Unused uint16 +} + +type ExtentLeafNode struct { + Block uint32 + Length uint16 + StartHigh uint16 + StartLow uint32 +} + +type ExtentTail struct { + Checksum uint32 +} + +type DirectoryEntry struct { + Inode InodeNumber + RecordLength uint16 + NameLength uint8 + FileType FileType + //Name []byte +} + +type FileType uint8 + +const ( + FileTypeUnknown FileType = 0x0 + FileTypeRegular FileType = 0x1 + FileTypeDirectory FileType = 0x2 + FileTypeCharacter FileType = 0x3 + FileTypeBlock FileType = 0x4 + FileTypeFIFO FileType = 0x5 + FileTypeSocket FileType = 0x6 + FileTypeSymbolicLink FileType = 0x7 +) + +type DirectoryEntryTail struct { + ReservedZero1 uint32 + RecordLength uint16 + ReservedZero2 uint8 + FileType uint8 + Checksum uint32 +} + +type DirectoryTreeRoot struct { + Dot DirectoryEntry + DotName [4]byte + DotDot DirectoryEntry + DotDotName [4]byte + ReservedZero uint32 + HashVersion uint8 + InfoLength uint8 + IndirectLevels uint8 + UnusedFlags uint8 + Limit uint16 + Count uint16 + Block uint32 + //Entries []DirectoryTreeEntry +} + +type DirectoryTreeNode struct { + FakeInode uint32 + FakeRecordLength uint16 + NameLength uint8 + FileType uint8 + Limit uint16 + Count uint16 + Block uint32 + //Entries []DirectoryTreeEntry +} + +type DirectoryTreeEntry struct { + Hash uint32 + Block uint32 +} + +type DirectoryTreeTail struct { + Reserved uint32 + Checksum uint32 +} + +type XAttrInodeBodyHeader struct { + Magic uint32 +} + +type XAttrHeader struct { + Magic uint32 + ReferenceCount uint32 + Blocks uint32 + Hash uint32 + Checksum uint32 + Reserved [3]uint32 +} + +const XAttrHeaderMagic uint32 = 0xea020000 + +type XAttrEntry struct { + NameLength uint8 + NameIndex uint8 + ValueOffset uint16 + ValueInum uint32 + ValueSize uint32 + Hash uint32 + //Name []byte +} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/tar2ext4.go b/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/tar2ext4.go new file mode 100644 index 0000000000..37e2226bf3 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/tar2ext4.go @@ -0,0 +1,369 @@ +package tar2ext4 + +import ( + "archive/tar" + "bufio" + "encoding/binary" + "fmt" + "io" + "os" + "path" + "strings" + + "github.com/Microsoft/hcsshim/ext4/dmverity" + "github.com/Microsoft/hcsshim/ext4/internal/compactext4" + "github.com/Microsoft/hcsshim/ext4/internal/format" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/pkg/errors" +) + +type params struct { + convertWhiteout bool + convertBackslash bool + appendVhdFooter bool + onlyAppendVhdFooter bool + appendDMVerity bool + ext4opts []compactext4.Option +} + +// Option is the type for optional parameters to Convert. +type Option func(*params) + +// ConvertWhiteout instructs the converter to convert OCI-style whiteouts +// (beginning with .wh.) to overlay-style whiteouts. +func ConvertWhiteout(p *params) { + p.convertWhiteout = true +} + +// ConvertBackslash instructs the converter to replace `\` in path names with `/`. +// This is useful if the tar file was created on Windows, where `\` is the filepath separator. +func ConvertBackslash(p *params) { + p.convertBackslash = true +} + +// AppendVhdFooter instructs the converter to add a fixed VHD footer to the +// file. +func AppendVhdFooter(p *params) { + p.appendVhdFooter = true +} + +// OnlyAppendVhdFooter instructs the converter not to convert but still to add a fixed VHD footer to the +// file. +func OnlyAppendVhdFooter(p *params) { + p.onlyAppendVhdFooter = true +} + +// AppendDMVerity instructs the converter to add a dmverity Merkle tree for +// the ext4 filesystem after the filesystem and before the optional VHD footer +func AppendDMVerity(p *params) { + p.appendDMVerity = true +} + +// InlineData instructs the converter to write small files into the inode +// structures directly. This creates smaller images but currently is not +// compatible with DAX. +func InlineData(p *params) { + p.ext4opts = append(p.ext4opts, compactext4.InlineData) +} + +// MaximumDiskSize instructs the writer to limit the disk size to the specified +// value. This also reserves enough metadata space for the specified disk size. +// If not provided, then 16GB is the default. +func MaximumDiskSize(size int64) Option { + return func(p *params) { + p.ext4opts = append(p.ext4opts, compactext4.MaximumDiskSize(size)) + } +} + +const ( + whiteoutPrefix = ".wh." + opaqueWhiteout = ".wh..wh..opq" +) + +// ConvertTarToExt4 writes a compact ext4 file system image that contains the files in the +// input tar stream. +func ConvertTarToExt4(r io.Reader, w io.ReadWriteSeeker, options ...Option) error { + var p params + for _, opt := range options { + opt(&p) + } + + t := tar.NewReader(bufio.NewReader(r)) + fs := compactext4.NewWriter(w, p.ext4opts...) + for { + hdr, err := t.Next() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return err + } + + name := hdr.Name + linkName := hdr.Linkname + if p.convertBackslash { + // compactext assumes all paths are `/` separated + // unconditionally replace all instances of `/`, regardless of GOOS + name = strings.ReplaceAll(name, `\`, "/") + linkName = strings.ReplaceAll(linkName, `\`, "/") + } + + if err = fs.MakeParents(name); err != nil { + return errors.Wrapf(err, "failed to ensure parent directories for %s", name) + } + + if p.convertWhiteout { + dir, file := path.Split(name) + if strings.HasPrefix(file, whiteoutPrefix) { + if file == opaqueWhiteout { + // Update the directory with the appropriate xattr. + f, err := fs.Stat(dir) + if err != nil { + return errors.Wrapf(err, "failed to stat parent directory of whiteout %s", file) + } + f.Xattrs["trusted.overlay.opaque"] = []byte("y") + err = fs.Create(dir, f) + if err != nil { + return errors.Wrapf(err, "failed to create opaque dir %s", file) + } + } else { + // Create an overlay-style whiteout. + f := &compactext4.File{ + Mode: compactext4.S_IFCHR, + Devmajor: 0, + Devminor: 0, + } + err = fs.Create(path.Join(dir, file[len(whiteoutPrefix):]), f) + if err != nil { + return errors.Wrapf(err, "failed to create whiteout file for %s", file) + } + } + + continue + } + } + + if hdr.Typeflag == tar.TypeLink { + err = fs.Link(linkName, name) + if err != nil { + return err + } + } else { + f := &compactext4.File{ + Mode: uint16(hdr.Mode), + Atime: hdr.AccessTime, + Mtime: hdr.ModTime, + Ctime: hdr.ChangeTime, + Crtime: hdr.ModTime, + Size: hdr.Size, + Uid: uint32(hdr.Uid), + Gid: uint32(hdr.Gid), + Linkname: linkName, + Devmajor: uint32(hdr.Devmajor), + Devminor: uint32(hdr.Devminor), + Xattrs: make(map[string][]byte), + } + for key, value := range hdr.PAXRecords { + const xattrPrefix = "SCHILY.xattr." + if strings.HasPrefix(key, xattrPrefix) { + f.Xattrs[key[len(xattrPrefix):]] = []byte(value) + } + } + + var typ uint16 + switch hdr.Typeflag { + case tar.TypeReg: + typ = compactext4.S_IFREG + case tar.TypeSymlink: + typ = compactext4.S_IFLNK + case tar.TypeChar: + typ = compactext4.S_IFCHR + case tar.TypeBlock: + typ = compactext4.S_IFBLK + case tar.TypeDir: + typ = compactext4.S_IFDIR + case tar.TypeFifo: + typ = compactext4.S_IFIFO + } + f.Mode &= ^compactext4.TypeMask + f.Mode |= typ + err = fs.Create(name, f) + if err != nil { + return err + } + _, err = io.Copy(fs, t) + if err != nil { + return err + } + } + } + return fs.Close() +} + +// Convert wraps ConvertTarToExt4 and conditionally computes (and appends) the file image's cryptographic +// hashes (merkle tree) or/and appends a VHD footer. +func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error { + var p params + for _, opt := range options { + opt(&p) + } + + if p.onlyAppendVhdFooter { + _, err := io.Copy(w, r) + if err != nil { + return err + } + return ConvertToVhd(w) + } + + if err := ConvertTarToExt4(r, w, options...); err != nil { + return err + } + + if p.appendDMVerity { + if err := dmverity.ComputeAndWriteHashDevice(w, w); err != nil { + return err + } + } + + if p.appendVhdFooter { + return ConvertToVhd(w) + } + return nil +} + +// ReadExt4SuperBlock reads and returns ext4 super block from given device. +func ReadExt4SuperBlock(devicePath string) (*format.SuperBlock, error) { + dev, err := os.OpenFile(devicePath, os.O_RDONLY, 0) + if err != nil { + return nil, err + } + defer dev.Close() + + return ReadExt4SuperBlockReadSeeker(dev) +} + +// ReadExt4SuperBlockReadSeeker reads and returns ext4 super block given +// an io.ReadSeeker. +// +// The layout on disk is as follows: +// | Group 0 padding | - 1024 bytes +// | ext4 SuperBlock | - 1 block +// | Group Descriptors | - many blocks +// | Reserved GDT Blocks | - many blocks +// | Data Block Bitmap | - 1 block +// | inode Bitmap | - 1 block +// | inode Table | - many blocks +// | Data Blocks | - many blocks +// +// More details can be found here https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout +// +// Our goal is to skip the Group 0 padding, read and return the ext4 SuperBlock +func ReadExt4SuperBlockReadSeeker(rsc io.ReadSeeker) (*format.SuperBlock, error) { + // save current reader position + currBytePos, err := rsc.Seek(0, io.SeekCurrent) + if err != nil { + return nil, err + } + + if _, err := rsc.Seek(1024, io.SeekCurrent); err != nil { + return nil, err + } + var sb format.SuperBlock + if err := binary.Read(rsc, binary.LittleEndian, &sb); err != nil { + return nil, err + } + + // reset the reader to initial position + if _, err := rsc.Seek(currBytePos, io.SeekStart); err != nil { + return nil, err + } + + if sb.Magic != format.SuperBlockMagic { + return nil, errors.New("not an ext4 file system") + } + return &sb, nil +} + +// IsDeviceExt4 is will read the device's superblock and determine if it is +// and ext4 superblock. +func IsDeviceExt4(devicePath string) bool { + // ReadExt4SuperBlock will check the superblock magic number for us, + // so we know if no error is returned, this is an ext4 device. + _, err := ReadExt4SuperBlock(devicePath) + if err != nil { + log.L.Warnf("failed to read Ext4 superblock: %s", err) + } + return err == nil +} + +// Ext4FileSystemSize reads ext4 superblock and returns the size of the underlying +// ext4 file system and its block size. +func Ext4FileSystemSize(r io.ReadSeeker) (int64, int, error) { + sb, err := ReadExt4SuperBlockReadSeeker(r) + if err != nil { + return 0, 0, fmt.Errorf("failed to read ext4 superblock: %w", err) + } + blockSize := 1024 * (1 << sb.LogBlockSize) + fsSize := int64(blockSize) * int64(sb.BlocksCountLow) + return fsSize, blockSize, nil +} + +// ConvertAndComputeRootDigest writes a compact ext4 file system image that contains the files in the +// input tar stream, computes the resulting file image's cryptographic hashes (merkle tree) and returns +// merkle tree root digest. Convert is called with minimal options: ConvertWhiteout and MaximumDiskSize +// set to dmverity.RecommendedVHDSizeGB. +func ConvertAndComputeRootDigest(r io.Reader) (string, error) { + out, err := os.CreateTemp("", "") + if err != nil { + return "", fmt.Errorf("failed to create temporary file: %w", err) + } + defer func() { + _ = os.Remove(out.Name()) + }() + defer out.Close() + + options := []Option{ + ConvertWhiteout, + MaximumDiskSize(dmverity.RecommendedVHDSizeGB), + } + if err := ConvertTarToExt4(r, out, options...); err != nil { + return "", fmt.Errorf("failed to convert tar to ext4: %w", err) + } + + if _, err := out.Seek(0, io.SeekStart); err != nil { + return "", fmt.Errorf("failed to seek start on temp file when creating merkle tree: %w", err) + } + + tree, err := dmverity.MerkleTree(bufio.NewReaderSize(out, dmverity.MerkleTreeBufioSize)) + if err != nil { + return "", fmt.Errorf("failed to create merkle tree: %w", err) + } + + hash := dmverity.RootHash(tree) + return fmt.Sprintf("%x", hash), nil +} + +// ConvertToVhd converts given io.WriteSeeker to VHD, by appending the VHD footer with a fixed size. +func ConvertToVhd(w io.WriteSeeker) error { + size, err := w.Seek(0, io.SeekEnd) + if err != nil { + return err + } + return binary.Write(w, binary.BigEndian, makeFixedVHDFooter(size)) +} + +// A convenience wrapper for ConverToVhd, instead of asking the caller to open the file and pass an io.WriteSeeker, this +// takes in a file path and appends the VHD footer to that file. +func ConvertFileToVhd(filePath string) error { + f, err := os.OpenFile(filePath, os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to open file `%s` : %w", filePath, err) + } + defer f.Close() + + if err := ConvertToVhd(f); err != nil { + return fmt.Errorf("failed to append VHD footer: %w", err) + } + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/vhdfooter.go b/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/vhdfooter.go new file mode 100644 index 0000000000..99f6e3a304 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/ext4/tar2ext4/vhdfooter.go @@ -0,0 +1,76 @@ +package tar2ext4 + +import ( + "bytes" + "crypto/rand" + "encoding/binary" +) + +// Constants for the VHD footer +const ( + cookieMagic = "conectix" + featureMask = 0x2 + fileFormatVersionMagic = 0x00010000 + fixedDataOffset = -1 + creatorVersionMagic = 0x000a0000 + diskTypeFixed = 2 +) + +type vhdFooter struct { + Cookie [8]byte + Features uint32 + FileFormatVersion uint32 + DataOffset int64 + TimeStamp uint32 + CreatorApplication [4]byte + CreatorVersion uint32 + CreatorHostOS [4]byte + OriginalSize int64 + CurrentSize int64 + DiskGeometry uint32 + DiskType uint32 + Checksum uint32 + UniqueID [16]uint8 + SavedState uint8 + Reserved [427]uint8 +} + +func makeFixedVHDFooter(size int64) *vhdFooter { + footer := &vhdFooter{ + Features: featureMask, + FileFormatVersion: fileFormatVersionMagic, + DataOffset: fixedDataOffset, + CreatorVersion: creatorVersionMagic, + OriginalSize: size, + CurrentSize: size, + DiskType: diskTypeFixed, + UniqueID: generateUUID(), + } + copy(footer.Cookie[:], cookieMagic) + footer.Checksum = calculateCheckSum(footer) + return footer +} + +func calculateCheckSum(footer *vhdFooter) uint32 { + oldchk := footer.Checksum + footer.Checksum = 0 + + buf := &bytes.Buffer{} + _ = binary.Write(buf, binary.BigEndian, footer) + + var chk uint32 + bufBytes := buf.Bytes() + for i := 0; i < len(bufBytes); i++ { + chk += uint32(bufBytes[i]) + } + footer.Checksum = oldchk + return uint32(^chk) +} + +func generateUUID() [16]byte { + res := [16]byte{} + if _, err := rand.Read(res[:]); err != nil { + panic(err) + } + return res +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/block_cim_writer.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/block_cim_writer.go new file mode 100644 index 0000000000..16f7c9552d --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/block_cim_writer.go @@ -0,0 +1,157 @@ +//go:build windows + +package cim + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/wclayer" + "github.com/Microsoft/hcsshim/pkg/cimfs" +) + +// A BlockCIMLayerWriter implements the CIMLayerWriter interface to allow writing +// container image layers in the blocked cim format. +type BlockCIMLayerWriter struct { + *cimLayerWriter + // the layer that we are writing + layer *cimfs.BlockCIM + // parent layers + parentLayers []*cimfs.BlockCIM + // added files maintains a map of all files that have been added to this layer + addedFiles map[string]struct{} +} + +var _ CIMLayerWriter = &BlockCIMLayerWriter{} + +// NewBlockCIMLayerWriterWithOpts returns a writer for writing image layers in the block CIM format. The writer's behavior can be +// controlled with the supports opts. +func NewBlockCIMLayerWriterWithOpts(ctx context.Context, layer *cimfs.BlockCIM, parentLayers []*cimfs.BlockCIM, opts ...cimfs.BlockCIMOpt) (_ *BlockCIMLayerWriter, err error) { + if layer.Type != cimfs.BlockCIMTypeSingleFile { + // we only support writing single file CIMs for now because in layer + // writing process we still need to write some files (registry hives) + // outside the CIM. We currently use the parent directory of the CIM (i.e + // the parent directory of block path in this case) for this. This can't + // be reliably done with the block device CIM since the block path + // provided will be a volume path. However, once we get rid of hive rollup + // step during layer import we should be able to support block device + // CIMs. + return nil, ErrBlockCIMWriterNotSupported + } + + parentLayerPaths := make([]string, 0, len(parentLayers)) + for _, pl := range parentLayers { + if pl.Type != layer.Type { + return nil, ErrBlockCIMParentTypeMismatch + } + parentLayerPaths = append(parentLayerPaths, filepath.Dir(pl.BlockPath)) + } + + // We always want to write layers with consistent flag + bcimOpts := append([]cimfs.BlockCIMOpt{cimfs.WithConsistentCIM()}, opts...) + + cim, err := cimfs.CreateBlockCIMWithOptions(ctx, layer, bcimOpts...) + if err != nil { + return nil, fmt.Errorf("error in creating a new cim: %w", err) + } + defer func() { + if err != nil { + cErr := cim.Close() + if cErr != nil { + log.G(ctx).WithError(err).Warnf("failed to close cim after error: %s", cErr) + } + } + }() + + // std file writer writes registry hives outside the CIM for 2 reasons. 1. We can + // merge the hives of this layer with the parent layer hives and then write the + // merged hives into the CIM. 2. When importing child layer of this layer, we + // have access to the merges hives of this layer. + sfw, err := newStdFileWriter(filepath.Dir(layer.BlockPath), parentLayerPaths) + if err != nil { + return nil, fmt.Errorf("error in creating new standard file writer: %w", err) + } + + return &BlockCIMLayerWriter{ + layer: layer, + parentLayers: parentLayers, + addedFiles: make(map[string]struct{}), + cimLayerWriter: &cimLayerWriter{ + ctx: ctx, + cimWriter: cim, + stdFileWriter: sfw, + layerPath: filepath.Dir(layer.BlockPath), + parentLayerPaths: parentLayerPaths, + }, + }, nil +} + +// NewBlockCIMLayerWriter writes the layer files in the block CIM format. +func NewBlockCIMLayerWriter(ctx context.Context, layer *cimfs.BlockCIM, parentLayers []*cimfs.BlockCIM) (_ *BlockCIMLayerWriter, err error) { + return NewBlockCIMLayerWriterWithOpts(ctx, layer, parentLayers) +} + +// Add adds a file to the layer with given metadata. +func (cw *BlockCIMLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo, fileSize int64, securityDescriptor []byte, extendedAttributes []byte, reparseData []byte) error { + cw.addedFiles[name] = struct{}{} + if name == wclayer.UtilityVMPath && len(cw.parentLayers) > 0 { + // If there are UtilityVM files in non base layers, we will have to merge + // those files with the parent layer UtilityVM files - either during image + // pull or at runtime (i.e when starting the UVM). In order to merge at + // image pull time, we will have to read parent layer block CIMs and copy + // all the UtilityVM files from those CIMs into this block CIM one by one + // i.e effectively merge all parent layer UtilityVM files in this + // layer. Or we will need to be able to boot the UtilityVM with merged + // block CIMs. None of these options are implemented yet so log a + // warning. However, this shouldn't cause issues with most of the standard + // use cases because usually the pod is a nanoserver image and that is + // always a single layer. + log.G(cw.ctx).Warn("UtilityVM files in non base layers is not supported for block CIMs") + } + return cw.cimLayerWriter.Add(name, fileInfo, fileSize, securityDescriptor, extendedAttributes, reparseData) +} + +// Remove removes a file that was present in a parent layer from the layer. +func (cw *BlockCIMLayerWriter) Remove(name string) error { + // set active write to nil so that we panic if layer tar is incorrectly formatted. + cw.activeWriter = nil + err := cw.cimWriter.AddTombstone(name) + if err != nil { + return fmt.Errorf("failed to remove file : %w", err) + } + return nil +} + +// AddLink adds a hard link to the layer. Note that the link added here is evaluated only +// at the CIM merge time. So an invalid link will not throw an error here. +func (cw *BlockCIMLayerWriter) AddLink(name string, target string) error { + // set active write to nil so that we panic if layer tar is incorrectly formatted. + cw.activeWriter = nil + + // when adding links to a block CIM, we need to know if the target file is present + // in this same block CIM or if it is coming from one of the parent layers. If the + // file is in the same CIM we add a standard hard link. If the file is not in the + // same CIM we add a special type of link called merged link. This merged link is + // resolved when all the individual block CIM layers are merged. In order to + // reliably know if the target is a part of the CIM or not, we wait until all + // files are added and then lookup the added entries in a map to make the + // decision. + pendingLinkOp := func(c *cimfs.CimFsWriter) error { + if _, ok := cw.addedFiles[target]; ok { + // target was added in this layer - add a normal link. Once a + // hardlink is added that hardlink also becomes a valid target for + // other links so include it in the map. + cw.addedFiles[name] = struct{}{} + return c.AddLink(target, name) + } else { + // target is from a parent layer - add a merged link + return c.AddMergedLink(target, name) + } + } + cw.pendingOps = append(cw.pendingOps, pendingCimOpFunc(pendingLinkOp)) + return nil + +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/common.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/common.go new file mode 100644 index 0000000000..40640c5ff3 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/common.go @@ -0,0 +1,204 @@ +//go:build windows + +package cim + +import ( + "context" + "fmt" + "io" + "path/filepath" + "strings" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/internal/wclayer" + "github.com/Microsoft/hcsshim/pkg/cimfs" +) + +var ( + ErrBlockCIMWriterNotSupported = fmt.Errorf("writing block device CIM isn't supported") + ErrBlockCIMParentTypeMismatch = fmt.Errorf("parent layer block CIM type doesn't match with extraction layer") + ErrBlockCIMIntegrityMismatch = fmt.Errorf("verified CIMs can not be mixed with non verified CIMs") +) + +type hive struct { + name string + base string + delta string +} + +var ( + hives = []hive{ + {"SYSTEM", "SYSTEM_BASE", "SYSTEM_DELTA"}, + {"SOFTWARE", "SOFTWARE_BASE", "SOFTWARE_DELTA"}, + {"SAM", "SAM_BASE", "SAM_DELTA"}, + {"SECURITY", "SECURITY_BASE", "SECURITY_DELTA"}, + {"DEFAULT", "DEFAULTUSER_BASE", "DEFAULTUSER_DELTA"}, + } +) + +// CIMLayerWriter is an interface that supports writing a new container image layer to the +// CIM format +type CIMLayerWriter interface { + // Add adds a file to the layer with given metadata. + Add(string, *winio.FileBasicInfo, int64, []byte, []byte, []byte) error + // AddLink adds a hard link to the layer. The target must already have been added. + AddLink(string, string) error + // AddAlternateStream adds an alternate stream to a file + AddAlternateStream(string, uint64) error + // Remove removes a file that was present in a parent layer from the layer. + Remove(string) error + // Write writes data to the current file. The data must be in the format of a Win32 + // backup stream. + Write([]byte) (int, error) + // Close finishes the layer writing process and releases any resources. + Close(context.Context) error +} + +func isDeltaOrBaseHive(path string) bool { + for _, hv := range hives { + if strings.EqualFold(path, filepath.Join(wclayer.HivesPath, hv.delta)) || + strings.EqualFold(path, filepath.Join(wclayer.RegFilesPath, hv.name)) { + return true + } + } + return false +} + +// checks if this particular file should be written with a stdFileWriter instead of +// using the cimWriter. +func isStdFile(path string) bool { + return (isDeltaOrBaseHive(path) || + path == filepath.Join(wclayer.UtilityVMPath, wclayer.RegFilesPath, "SYSTEM") || + path == filepath.Join(wclayer.UtilityVMPath, wclayer.RegFilesPath, "SOFTWARE") || + path == wclayer.BcdFilePath || path == wclayer.BootMgrFilePath) +} + +// cimLayerWriter is a base struct that is further extended by forked cim writer & blocked +// cim writer to provide full functionality of writing layers. +type cimLayerWriter struct { + ctx context.Context + // Handle to the layer cim - writes to the cim file + cimWriter *cimfs.CimFsWriter + // Handle to the writer for writing files in the local filesystem + stdFileWriter *stdFileWriter + // reference to currently active writer either cimWriter or stdFileWriter + activeWriter io.Writer + // denotes if this layer has the UtilityVM directory + hasUtilityVM bool + // path to the layer (i.e layer's directory) as provided by the caller. + // Even if a layer is stored as a cim in the cim directory, some files associated + // with a layer are still stored in this path. + layerPath string + // parent layer paths + parentLayerPaths []string + // some files are written outside the cim during initial import (via stdFileWriter) because we need to + // make some modifications to these files before writing them to the cim. The pendingOps slice + // maintains a list of such delayed modifications to the layer cim. These modifications are applied at + // the very end of layer import process. + pendingOps []pendingCimOp +} + +// Add adds a file to the layer with given metadata. +func (cw *cimLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo, fileSize int64, securityDescriptor []byte, extendedAttributes []byte, reparseData []byte) error { + if name == wclayer.UtilityVMPath { + cw.hasUtilityVM = true + } + if isStdFile(name) { + // create a pending op for this file + cw.pendingOps = append(cw.pendingOps, &addOp{ + pathInCim: name, + hostPath: filepath.Join(cw.layerPath, name), + fileInfo: fileInfo, + securityDescriptor: securityDescriptor, + extendedAttributes: extendedAttributes, + reparseData: reparseData, + }) + if err := cw.stdFileWriter.Add(name); err != nil { + return err + } + cw.activeWriter = cw.stdFileWriter + } else { + if err := cw.cimWriter.AddFile(name, fileInfo, fileSize, securityDescriptor, extendedAttributes, reparseData); err != nil { + return err + } + cw.activeWriter = cw.cimWriter + } + return nil +} + +// AddLink adds a hard link to the layer. The target must already have been added. +func (cw *cimLayerWriter) AddLink(name string, target string) error { + // set active write to nil so that we panic if layer tar is incorrectly formatted. + cw.activeWriter = nil + if isStdFile(target) { + // If this is a link to a std file it will have to be added later once the + // std file is written to the CIM. Create a pending op for this + cw.pendingOps = append(cw.pendingOps, &linkOp{ + oldPath: target, + newPath: name, + }) + return nil + } else if isStdFile(name) { + // None of the predefined std files are links. If they show up as links this is unexpected + // behavior. Error out. + return fmt.Errorf("unexpected link %s in layer", name) + } else { + return cw.cimWriter.AddLink(target, name) + } +} + +// AddAlternateStream creates another alternate stream at the given +// path. Any writes made after this call will go to that stream. +func (cw *cimLayerWriter) AddAlternateStream(name string, size uint64) error { + if isStdFile(name) { + // As of now there is no known case of std file having multiple data streams. + // If such a file is encountered our assumptions are wrong. Error out. + return fmt.Errorf("unexpected alternate stream %s in layer", name) + } + + if err := cw.cimWriter.CreateAlternateStream(name, size); err != nil { + return err + } + cw.activeWriter = cw.cimWriter + return nil +} + +// Write writes data to the current file. The data must be in the format of a Win32 +// backup stream. +func (cw *cimLayerWriter) Write(b []byte) (int, error) { + return cw.activeWriter.Write(b) +} + +// Close finishes the layer writing process and releases any resources. +func (cw *cimLayerWriter) Close(ctx context.Context) (retErr error) { + if err := cw.stdFileWriter.Close(ctx); err != nil { + return err + } + + // cimWriter must be closed even if there are errors. + defer func() { + if err := cw.cimWriter.Close(); retErr == nil { + retErr = err + } + }() + + // We don't support running UtilityVM with CIM layers yet. + processUtilityVM := false + + if len(cw.parentLayerPaths) == 0 { + if err := cw.processBaseLayer(ctx, processUtilityVM); err != nil { + return fmt.Errorf("process base layer: %w", err) + } + } else { + if err := cw.processNonBaseLayer(ctx, processUtilityVM); err != nil { + return fmt.Errorf("process non base layer: %w", err) + } + } + + for _, op := range cw.pendingOps { + if err := op.apply(cw.cimWriter); err != nil { + return fmt.Errorf("apply pending operations: %w", err) + } + } + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/doc.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/doc.go new file mode 100644 index 0000000000..d814659965 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/doc.go @@ -0,0 +1,3 @@ +// This package provides utilities for working with container image layers in the cim format +// via the wclayer APIs. +package cim diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/file_writer.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/file_writer.go new file mode 100644 index 0000000000..9e5e8dd456 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/file_writer.go @@ -0,0 +1,93 @@ +//go:build windows + +package cim + +import ( + "context" + "fmt" + "os" + "path/filepath" + "syscall" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/internal/safefile" + "github.com/Microsoft/hcsshim/internal/winapi" +) + +// stdFileWriter writes the files of a layer to the layer folder instead of writing them inside the cim. +// For some files (like the Hive files or some UtilityVM files) it is necessary to write them as a normal file +// first, do some modifications on them (for example merging of hives or processing of UtilityVM files) +// and then write the modified versions into the cim. This writer is used for such files. +type stdFileWriter struct { + activeFile *os.File + // parent layer paths + parentLayerPaths []string + // path to the current layer + path string + // the open handle to the path directory + root *os.File +} + +func newStdFileWriter(root string, parentRoots []string) (sfw *stdFileWriter, err error) { + sfw = &stdFileWriter{ + path: root, + parentLayerPaths: parentRoots, + } + sfw.root, err = safefile.OpenRoot(root) + if err != nil { + return + } + return +} + +func (sfw *stdFileWriter) closeActiveFile() (err error) { + if sfw.activeFile != nil { + err = sfw.activeFile.Close() + sfw.activeFile = nil + } + return +} + +// Adds a new file or an alternate data stream to an existing file inside the layer directory. +func (sfw *stdFileWriter) Add(name string) error { + if err := sfw.closeActiveFile(); err != nil { + return err + } + + // The directory of this file might be created inside the cim. + // make sure we have the same parent directory chain here + if err := safefile.MkdirAllRelative(filepath.Dir(name), sfw.root); err != nil { + return fmt.Errorf("failed to create file %s: %w", name, err) + } + + f, err := safefile.OpenRelative( + name, + sfw.root, + syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER, + syscall.FILE_SHARE_READ, + winapi.FILE_CREATE, + 0, + ) + if err != nil { + return fmt.Errorf("error creating file %s: %w", name, err) + } + sfw.activeFile = f + return nil +} + +// Write writes data to the current file. The data must be in the format of a Win32 +// backup stream. +func (sfw *stdFileWriter) Write(b []byte) (int, error) { + return sfw.activeFile.Write(b) +} + +// Close finishes the layer writing process and releases any resources. +func (sfw *stdFileWriter) Close(ctx context.Context) error { + if err := sfw.closeActiveFile(); err != nil { + return fmt.Errorf("failed to close active file %s : %w", sfw.activeFile.Name(), err) + } + if err := sfw.root.Close(); err != nil { + return fmt.Errorf("failed to close root dir: %w", err) + } + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/forked_cim_writer.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/forked_cim_writer.go new file mode 100644 index 0000000000..7da052b515 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/forked_cim_writer.go @@ -0,0 +1,78 @@ +//go:build windows + +package cim + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/pkg/cimfs" +) + +// A ForkedCimLayerWriter implements the wclayer.LayerWriter interface to allow writing container +// image layers in the cim format. +// A cim layer consist of cim files (which are usually stored in the `cim-layers` directory and +// some other files which are stored in the directory of that layer (i.e the `path` directory). +type ForkedCimLayerWriter struct { + *cimLayerWriter +} + +var _ CIMLayerWriter = &ForkedCimLayerWriter{} + +func NewForkedCimLayerWriter(ctx context.Context, layerPath, cimPath string, parentLayerPaths, parentLayerCimPaths []string) (_ *ForkedCimLayerWriter, err error) { + if !cimfs.IsCimFSSupported() { + return nil, fmt.Errorf("CimFs not supported on this build") + } + + parentCim := "" + if len(parentLayerPaths) > 0 { + // We only need to provide parent CIM name, it is assumed that both parent CIM + // and newly created CIM are present in the same directory. + parentCim = filepath.Base(parentLayerCimPaths[0]) + } + + cim, err := cimfs.Create(filepath.Dir(cimPath), parentCim, filepath.Base(cimPath)) + if err != nil { + return nil, fmt.Errorf("error in creating a new cim: %w", err) + } + defer func() { + if err != nil { + cErr := cim.Close() + if cErr != nil { + log.G(ctx).WithError(err).Warnf("failed to close cim after error: %s", cErr) + } + cErr = cimfs.DestroyCim(ctx, cimPath) + if cErr != nil { + log.G(ctx).WithError(err).Warnf("failed to cleanup cim after error: %s", cErr) + } + } + }() + + sfw, err := newStdFileWriter(layerPath, parentLayerPaths) + if err != nil { + return nil, fmt.Errorf("error in creating new standard file writer: %w", err) + } + return &ForkedCimLayerWriter{ + cimLayerWriter: &cimLayerWriter{ + parentLayerPaths: parentLayerPaths, + ctx: ctx, + cimWriter: cim, + stdFileWriter: sfw, + layerPath: layerPath, + }, + }, nil +} + +// Remove removes a file that was present in a parent layer from the layer. +func (cw *ForkedCimLayerWriter) Remove(name string) error { + // set active write to nil so that we panic if layer tar is incorrectly formatted. + cw.activeWriter = nil + err := cw.cimWriter.Unlink(name) + if err == nil || os.IsNotExist(err) { + return nil + } + return fmt.Errorf("failed to remove file: %w", err) +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/mount.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/mount.go new file mode 100644 index 0000000000..89fa13ccdc --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/mount.go @@ -0,0 +1,150 @@ +//go:build windows + +package cim + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/oc" + cimfs "github.com/Microsoft/hcsshim/pkg/cimfs" + "github.com/sirupsen/logrus" + "go.opencensus.io/trace" +) + +var cimMountNamespace guid.GUID = guid.GUID{Data1: 0x6827367b, Data2: 0xc388, Data3: 0x4e9b, Data4: [8]byte{0x96, 0x1c, 0x6d, 0x2c, 0x93, 0x6c}} + +// MountForkedCimLayer mounts the cim at path `cimPath` and returns the mount location of +// that cim. The containerID is used to generate the volumeID for the volume at which +// this CIM is mounted. containerID is used so that if the shim process crashes for any +// reason, the mounted cim can be correctly cleaned up during `shim delete` call. +func MountForkedCimLayer(ctx context.Context, cimPath, containerID string) (string, error) { + volumeGUID, err := guid.NewV5(cimMountNamespace, []byte(containerID)) + if err != nil { + return "", fmt.Errorf("generated cim mount GUID: %w", err) + } + + vol, err := cimfs.Mount(cimPath, volumeGUID, 0) + if err != nil { + return "", err + } + return vol, nil +} + +// MountBlockCIMLayer mounts the given block cim and returns the mount +// location of that cim. The containerID is used to generate the volumeID for the volume +// at which this CIM is mounted. containerID is used so that if the shim process crashes +// for any reason, the mounted cim can be correctly cleaned up during `shim delete` call. +func MountBlockCIMLayer(ctx context.Context, layer *cimfs.BlockCIM, containerID string) (_ string, err error) { + ctx, span := oc.StartSpan(ctx, "MountBlockCIMLayer") + defer func() { + oc.SetSpanStatus(span, err) + span.End() + }() + span.AddAttributes( + trace.StringAttribute("layer", layer.String())) + + var mountFlags uint32 + switch layer.Type { + case cimfs.BlockCIMTypeDevice: + mountFlags |= cimfs.CimMountBlockDeviceCim + case cimfs.BlockCIMTypeSingleFile: + mountFlags |= cimfs.CimMountSingleFileCim + default: + return "", fmt.Errorf("invalid BlockCIMType for merged layer: %w", os.ErrInvalid) + } + + volumeGUID, err := guid.NewV5(cimMountNamespace, []byte(containerID)) + if err != nil { + return "", fmt.Errorf("generated cim mount GUID: %w", err) + } + + cimPath := filepath.Join(layer.BlockPath, layer.CimName) + + log.G(ctx).WithFields(logrus.Fields{ + "flags": mountFlags, + "volume": volumeGUID.String(), + }).Debug("mounting block layer CIM") + + vol, err := cimfs.Mount(cimPath, volumeGUID, mountFlags) + if err != nil { + return "", err + } + return vol, nil +} + +// MergeMountBlockCIMLayer mounts the given merged block cim and returns the mount +// location of that cim. The containerID is used to generate the volumeID for the volume +// at which this CIM is mounted. containerID is used so that if the shim process crashes +// for any reason, the mounted cim can be correctly cleaned up during `shim delete` call. +// parentLayers MUST be in the base to topmost order. I.e base layer should be at index 0 +// and immediate parent MUST be at the last index. +func MergeMountBlockCIMLayer(ctx context.Context, mergedLayer *cimfs.BlockCIM, parentLayers []*cimfs.BlockCIM, containerID string) (_ string, err error) { + _, span := oc.StartSpan(ctx, "MergeMountBlockCIMLayer") + defer func() { + oc.SetSpanStatus(span, err) + span.End() + }() + span.AddAttributes( + trace.StringAttribute("merged layer", mergedLayer.String()), + trace.StringAttribute("parent layers", fmt.Sprintf("%v", parentLayers))) + + var mountFlags uint32 + switch mergedLayer.Type { + case cimfs.BlockCIMTypeDevice: + mountFlags |= cimfs.CimMountBlockDeviceCim + case cimfs.BlockCIMTypeSingleFile: + mountFlags |= cimfs.CimMountSingleFileCim + default: + return "", fmt.Errorf("invalid BlockCIMType for merged layer: %w", os.ErrInvalid) + } + + volumeGUID, err := guid.NewV5(cimMountNamespace, []byte(containerID)) + if err != nil { + return "", fmt.Errorf("generated cim mount GUID: %w", err) + } + + return cimfs.MountMergedBlockCIMs(mergedLayer, parentLayers, mountFlags, volumeGUID) +} + +// Unmounts the cim mounted at the given volume +func UnmountCimLayer(ctx context.Context, volume string) error { + return cimfs.Unmount(volume) +} + +func CleanupContainerMounts(containerID string) error { + volumeGUID, err := guid.NewV5(cimMountNamespace, []byte(containerID)) + if err != nil { + return fmt.Errorf("generated cim mount GUID: %w", err) + } + + volPath := fmt.Sprintf("\\\\?\\Volume{%s}\\", volumeGUID.String()) + + log.L.WithFields(logrus.Fields{ + "volume": volPath, + "containerID": containerID, + }).Debug("cleanup container CIM mounts") + + if _, err := os.Stat(volPath); err == nil { + err = cimfs.Unmount(volPath) + if err != nil { + return err + } + } + return nil +} + +// LayerID provides a unique GUID for each mounted CIM volume. +func LayerID(vol string) (string, error) { + // since each mounted volume has a unique GUID, just return the same GUID as ID + if !strings.HasPrefix(vol, "\\\\?\\Volume{") || !strings.HasSuffix(vol, "}\\") { + return "", fmt.Errorf("volume path %s is not in the expected format", vol) + } else { + return strings.TrimSuffix(strings.TrimPrefix(vol, "\\\\?\\Volume{"), "}\\"), nil + } +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/pending.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/pending.go new file mode 100644 index 0000000000..f2185a0998 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/pending.go @@ -0,0 +1,75 @@ +//go:build windows + +package cim + +import ( + "fmt" + "io" + "os" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/pkg/cimfs" + "golang.org/x/sys/windows" +) + +type pendingCimOp interface { + apply(cw *cimfs.CimFsWriter) error +} + +type pendingCimOpFunc func(cw *cimfs.CimFsWriter) error + +func (f pendingCimOpFunc) apply(cw *cimfs.CimFsWriter) error { + return f(cw) + +} + +// add op represents a pending operation of adding a new file inside the cim +type addOp struct { + // path inside the cim at which the file should be added + pathInCim string + // host path where this file was temporarily written. + hostPath string + // other file metadata fields that were provided during the add call. + fileInfo *winio.FileBasicInfo + securityDescriptor []byte + extendedAttributes []byte + reparseData []byte +} + +func (o *addOp) apply(cw *cimfs.CimFsWriter) error { + f, err := os.Open(o.hostPath) + if err != nil { + return fmt.Errorf("open file %s: %w", o.hostPath, err) + } + defer f.Close() + + fs, err := f.Stat() + if err != nil { + return fmt.Errorf("stat file %s: %w", o.hostPath, err) + } + + if err := cw.AddFile(o.pathInCim, o.fileInfo, fs.Size(), o.securityDescriptor, o.extendedAttributes, o.reparseData); err != nil { + return fmt.Errorf("cim add file %s: %w", o.hostPath, err) + } + + if o.fileInfo.FileAttributes != windows.FILE_ATTRIBUTE_DIRECTORY { + written, err := io.Copy(cw, f) + if err != nil { + return fmt.Errorf("write file %s inside cim: %w", o.hostPath, err) + } else if written != fs.Size() { + return fmt.Errorf("short write to cim for file %s, expected %d bytes wrote %d", o.hostPath, fs.Size(), written) + } + } + return nil +} + +// linkOp represents a pending link file operation inside the cim +type linkOp struct { + // old & new paths inside the cim where the link should be created + oldPath string + newPath string +} + +func (o *linkOp) apply(cw *cimfs.CimFsWriter) error { + return cw.AddLink(o.oldPath, o.newPath) +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/process.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/process.go new file mode 100644 index 0000000000..ace81122bc --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/process.go @@ -0,0 +1,138 @@ +//go:build windows + +package cim + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/internal/wclayer" + "golang.org/x/sys/windows" +) + +// processUtilityVMLayer will handle processing of UVM specific files when we start +// supporting UVM based containers with CimFS in the future. +func processUtilityVMLayer(ctx context.Context, layerPath string) error { + return nil +} + +// processBaseLayerHives make the base layer specific modifications on the hives and emits equivalent the +// pendingCimOps that should be applied on the CIM. In base layer we need to create hard links from registry +// hives under Files/Windows/Sysetm32/config into Hives/*_BASE. This function creates these links outside so +// that the registry hives under Hives/ are available during children layers import. Then we write these hive +// files inside the cim and create links inside the cim. +func processBaseLayerHives(layerPath string) ([]pendingCimOp, error) { + pendingOps := []pendingCimOp{} + + // make hives directory both outside and in the cim + if err := os.Mkdir(filepath.Join(layerPath, wclayer.HivesPath), 0755); err != nil { + return pendingOps, fmt.Errorf("hives directory creation: %w", err) + } + + hivesDirInfo := &winio.FileBasicInfo{ + FileAttributes: windows.FILE_ATTRIBUTE_DIRECTORY, + } + pendingOps = append(pendingOps, &addOp{ + pathInCim: wclayer.HivesPath, + hostPath: filepath.Join(layerPath, wclayer.HivesPath), + fileInfo: hivesDirInfo, + }) + + // add hard links from base hive files. + for _, hv := range hives { + oldHivePathRelative := filepath.Join(wclayer.RegFilesPath, hv.name) + newHivePathRelative := filepath.Join(wclayer.HivesPath, hv.base) + if err := os.Link(filepath.Join(layerPath, oldHivePathRelative), filepath.Join(layerPath, newHivePathRelative)); err != nil { + return pendingOps, fmt.Errorf("hive link creation: %w", err) + } + + pendingOps = append(pendingOps, &linkOp{ + oldPath: oldHivePathRelative, + newPath: newHivePathRelative, + }) + } + return pendingOps, nil +} + +// processLayoutFile creates a file named "layout" in the root of the base layer. This allows certain +// container startup related functions to understand that the hives are a part of the container rootfs. +func processLayoutFile(layerPath string) ([]pendingCimOp, error) { + fileContents := "vhd-with-hives\n" + if err := os.WriteFile(filepath.Join(layerPath, "layout"), []byte(fileContents), 0755); err != nil { + return []pendingCimOp{}, fmt.Errorf("write layout file: %w", err) + } + + layoutFileInfo := &winio.FileBasicInfo{ + FileAttributes: windows.FILE_ATTRIBUTE_NORMAL, + } + + op := &addOp{ + pathInCim: "layout", + hostPath: filepath.Join(layerPath, "layout"), + fileInfo: layoutFileInfo, + } + return []pendingCimOp{op}, nil +} + +// Some of the layer files that are generated during the processBaseLayer call must be added back +// inside the cim, some registry file links must be updated. This function takes care of all those +// steps. This function opens the cim file for writing and updates it. +func (cw *cimLayerWriter) processBaseLayer(ctx context.Context, processUtilityVM bool) (err error) { + if processUtilityVM { + if err = processUtilityVMLayer(ctx, cw.layerPath); err != nil { + return fmt.Errorf("process utilityVM layer: %w", err) + } + } + + ops, err := processBaseLayerHives(cw.layerPath) + if err != nil { + return err + } + cw.pendingOps = append(cw.pendingOps, ops...) + + ops, err = processLayoutFile(cw.layerPath) + if err != nil { + return err + } + cw.pendingOps = append(cw.pendingOps, ops...) + return nil +} + +// processNonBaseLayer takes care of the processing required for a non base layer. As of now +// the only processing required for non base layer is to merge the delta registry hives of the +// non-base layer with it's parent layer. +func (cw *cimLayerWriter) processNonBaseLayer(ctx context.Context, processUtilityVM bool) (err error) { + for _, hv := range hives { + baseHive := filepath.Join(wclayer.HivesPath, hv.base) + deltaHive := filepath.Join(wclayer.HivesPath, hv.delta) + _, err := os.Stat(filepath.Join(cw.layerPath, deltaHive)) + // merge with parent layer if delta exists. + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("stat delta hive %s: %w", filepath.Join(cw.layerPath, deltaHive), err) + } else if err == nil { + // merge base hive of parent layer with the delta hive of this layer and write it as + // the base hive of this layer. + err = mergeHive(filepath.Join(cw.parentLayerPaths[0], baseHive), filepath.Join(cw.layerPath, deltaHive), filepath.Join(cw.layerPath, baseHive)) + if err != nil { + return err + } + + // the newly created merged file must be added to the cim + cw.pendingOps = append(cw.pendingOps, &addOp{ + pathInCim: baseHive, + hostPath: filepath.Join(cw.layerPath, baseHive), + fileInfo: &winio.FileBasicInfo{ + FileAttributes: windows.FILE_ATTRIBUTE_NORMAL, + }, + }) + } + } + + if processUtilityVM { + return processUtilityVMLayer(ctx, cw.layerPath) + } + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/registry.go b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/registry.go new file mode 100644 index 0000000000..c95b03ca37 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/wclayer/cim/registry.go @@ -0,0 +1,49 @@ +//go:build windows + +package cim + +import ( + "fmt" + + "github.com/Microsoft/hcsshim/internal/winapi" + "github.com/Microsoft/hcsshim/osversion" + "github.com/pkg/errors" +) + +// mergeHive merges the hive located at parentHivePath with the hive located at deltaHivePath and stores +// the result into the file at mergedHivePath. If a file already exists at path `mergedHivePath` then it +// throws an error. +func mergeHive(parentHivePath, deltaHivePath, mergedHivePath string) (err error) { + var baseHive, deltaHive, mergedHive winapi.ORHKey + if err := winapi.OROpenHive(parentHivePath, &baseHive); err != nil { + return fmt.Errorf("failed to open base hive %s: %w", parentHivePath, err) + } + defer func() { + err2 := winapi.ORCloseHive(baseHive) + if err == nil { + err = errors.Wrap(err2, "failed to close base hive") + } + }() + if err := winapi.OROpenHive(deltaHivePath, &deltaHive); err != nil { + return fmt.Errorf("failed to open delta hive %s: %w", deltaHivePath, err) + } + defer func() { + err2 := winapi.ORCloseHive(deltaHive) + if err == nil { + err = errors.Wrap(err2, "failed to close delta hive") + } + }() + if err := winapi.ORMergeHives([]winapi.ORHKey{baseHive, deltaHive}, &mergedHive); err != nil { + return fmt.Errorf("failed to merge hives: %w", err) + } + defer func() { + err2 := winapi.ORCloseHive(mergedHive) + if err == nil { + err = errors.Wrap(err2, "failed to close merged hive") + } + }() + if err := winapi.ORSaveHive(mergedHive, mergedHivePath, uint32(osversion.Get().MajorVersion), uint32(osversion.Get().MinorVersion)); err != nil { + return fmt.Errorf("failed to save hive: %w", err) + } + return +} diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cim_writer_windows.go b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cim_writer_windows.go new file mode 100644 index 0000000000..0117b9f139 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cim_writer_windows.go @@ -0,0 +1,542 @@ +//go:build windows +// +build windows + +package cimfs + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "unsafe" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/winapi" + winapitypes "github.com/Microsoft/hcsshim/internal/winapi/types" + "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +// CimFsWriter represents a writer to a single CimFS filesystem instance. On disk, the +// image is composed of a filesystem file and several object ID and region files. +// Note: The CimFsWriter isn't thread safe! +type CimFsWriter struct { + // name of this cim. Usually a .cim file will be created to represent this cim. + name string + // handle is the CIMFS_IMAGE_HANDLE that must be passed when calling CIMFS APIs. + handle winapitypes.FsHandle + // name of the active file i.e the file to which we are currently writing. + activeName string + // stream to currently active file. + activeStream winapitypes.StreamHandle + // amount of bytes that can be written to the activeStream. + activeLeft uint64 + // if true the CIM will be sealed after the writer is closed. + sealOnClose bool +} + +// Create creates a new cim image. The CimFsWriter returned can then be used to do +// operations on this cim. If `oldFSName` is provided the new image is "forked" from the +// CIM with name `oldFSName` located under `imagePath`. +func Create(imagePath string, oldFSName string, newFSName string) (_ *CimFsWriter, err error) { + var oldNameBytes *uint16 + // CimCreateImage API call has different behavior if the value of oldNameBytes / newNameBytes + // is empty than if it is nil. So we have to convert those strings into *uint16 here. + fsName := oldFSName + if oldFSName != "" { + oldNameBytes, err = windows.UTF16PtrFromString(oldFSName) + if err != nil { + return nil, err + } + } + var newNameBytes *uint16 + if newFSName != "" { + fsName = newFSName + newNameBytes, err = windows.UTF16PtrFromString(newFSName) + if err != nil { + return nil, err + } + } + var handle winapitypes.FsHandle + if err := winapi.CimCreateImage(imagePath, oldNameBytes, newNameBytes, &handle); err != nil { + return nil, fmt.Errorf("failed to create cim image at path %s, oldName: %s, newName: %s: %w", imagePath, oldFSName, newFSName, err) + } + return &CimFsWriter{handle: handle, name: filepath.Join(imagePath, fsName)}, nil +} + +// blockCIMConfig represents options for creating or merging block CIMs +type blockCIMConfig struct { + // ensures that the generted CIM is identical every time when created from the same source data. + // This is mostly required for image layers. Dissabled by default. + consistentCIM bool + // enables data integrity checking, which means the CIM will be verified and sealed on close. + // This is useful for ensuring that the CIM is tamper-proof. Disabled by default. + dataIntegrity bool +} + +// BlockCIMOpt is a function type for configuring block CIM creation options +type BlockCIMOpt func(*blockCIMConfig) error + +// enabled consistent CIM creation, this ensures that CIMs created from identical source data will always be identical (i.e. SHA256 digest of the CIM will remain same) +func WithConsistentCIM() BlockCIMOpt { + return func(opts *blockCIMConfig) error { + opts.consistentCIM = true + return nil + } +} + +// WithDataIntegrity enables data integrity checking (verified CIM with sealing on close) +func WithDataIntegrity() BlockCIMOpt { + return func(opts *blockCIMConfig) error { + opts.dataIntegrity = true + return nil + } +} + +// CreateBlockCIMWithOptions creates a new block CIM with the specified options and opens it for writing. +// The CimFsWriter returned can then be used to add/remove files to/from this CIM. +func CreateBlockCIMWithOptions(ctx context.Context, bCIM *BlockCIM, options ...BlockCIMOpt) (_ *CimFsWriter, err error) { + // Apply default options + config := &blockCIMConfig{} + + // Apply provided options + for _, option := range options { + if err = option(config); err != nil { + return nil, err + } + } + + // Validate options + if bCIM.BlockPath == "" || bCIM.CimName == "" { + return nil, fmt.Errorf("both blockPath & name must be non empty: %w", os.ErrInvalid) + } + + if bCIM.Type == BlockCIMTypeNone { + return nil, fmt.Errorf("invalid block CIM type `%d`: %w", bCIM.Type, os.ErrInvalid) + } + + // Check OS support + if !IsBlockCimWriteSupported() { + return nil, fmt.Errorf("block CIM not supported on this OS version") + } + + if config.dataIntegrity && !IsVerifiedCimWriteSupported() { + return nil, fmt.Errorf("verified CIMs are not supported on this OS version") + } + + // Build create flags based on options + var createFlags uint32 + if config.consistentCIM { + createFlags |= CimCreateFlagConsistentCim + } + if config.dataIntegrity { + createFlags |= CimCreateFlagVerifiedCim + } + + switch bCIM.Type { + case BlockCIMTypeDevice: + createFlags |= CimCreateFlagBlockDeviceCim + case BlockCIMTypeSingleFile: + createFlags |= CimCreateFlagSingleFileCim + default: + return nil, fmt.Errorf("invalid block CIM type `%d`: %w", bCIM.Type, os.ErrInvalid) + } + + winapi.LogCimDLLSupport() + + var newNameUTF16 *uint16 + newNameUTF16, err = windows.UTF16PtrFromString(bCIM.CimName) + if err != nil { + return nil, err + } + + var handle winapitypes.FsHandle + if err := winapi.CimCreateImage2(bCIM.BlockPath, createFlags, nil, newNameUTF16, &handle); err != nil { + return nil, fmt.Errorf("failed to create block CIM at path %s,%s: %w", bCIM.BlockPath, bCIM.CimName, err) + } + + return &CimFsWriter{ + handle: handle, + name: filepath.Join(bCIM.BlockPath, bCIM.CimName), + sealOnClose: config.dataIntegrity, // Seal on close if data integrity is enabled + }, nil +} + +// Create creates a new block CIM and opens it for writing. The CimFsWriter +// returned can then be used to add/remove files to/from this CIM. +func CreateBlockCIM(blockPath, name string, blockType BlockCIMType) (_ *CimFsWriter, err error) { + return CreateBlockCIMWithOptions(context.Background(), &BlockCIM{ + Type: blockType, + BlockPath: blockPath, + CimName: name, + }, WithConsistentCIM()) +} + +// CreateAlternateStream creates alternate stream of given size at the given path inside the cim. This will +// replace the current active stream. Always, finish writing current active stream and then create an +// alternate stream. +func (c *CimFsWriter) CreateAlternateStream(path string, size uint64) (err error) { + err = c.closeStream() + if err != nil { + return err + } + err = winapi.CimCreateAlternateStream(c.handle, path, size, &c.activeStream) + if err != nil { + return fmt.Errorf("failed to create alternate stream for path %s: %w", path, err) + } + c.activeName = path + return nil +} + +// closes the currently active stream. +func (c *CimFsWriter) closeStream() error { + if c.activeStream == 0 { + return nil + } + err := winapi.CimCloseStream(c.activeStream) + if err == nil && c.activeLeft > 0 { + // Validate here because CimCloseStream does not and this improves error + // reporting. Otherwise the error will occur in the context of + // cimWriteStream. + err = fmt.Errorf("incomplete write, %d bytes left in the stream %s", c.activeLeft, c.activeName) + } + if err != nil { + err = &PathError{Cim: c.name, Op: "closeStream", Path: c.activeName, Err: err} + } + c.activeLeft = 0 + c.activeStream = 0 + c.activeName = "" + return err +} + +// AddFile adds a new file to the image. The file is added at the specified path. After +// calling this function, the file is set as the active stream for the image, so data can +// be written by calling `Write`. +func (c *CimFsWriter) AddFile(path string, info *winio.FileBasicInfo, fileSize int64, securityDescriptor []byte, extendedAttributes []byte, reparseData []byte) error { + err := c.closeStream() + if err != nil { + return err + } + fileMetadata := &winapitypes.CimFsFileMetadata{ + Attributes: info.FileAttributes, + FileSize: fileSize, + CreationTime: info.CreationTime, + LastWriteTime: info.LastWriteTime, + ChangeTime: info.ChangeTime, + LastAccessTime: info.LastAccessTime, + } + if len(securityDescriptor) == 0 { + // Passing an empty security descriptor creates a CIM in a weird state. + // Pass the NULL DACL. + securityDescriptor = nullSd + } + fileMetadata.SecurityDescriptorBuffer = unsafe.Pointer(&securityDescriptor[0]) + fileMetadata.SecurityDescriptorSize = uint32(len(securityDescriptor)) + if len(reparseData) > 0 { + fileMetadata.ReparseDataBuffer = unsafe.Pointer(&reparseData[0]) + fileMetadata.ReparseDataSize = uint32(len(reparseData)) + } + if len(extendedAttributes) > 0 { + fileMetadata.ExtendedAttributes = unsafe.Pointer(&extendedAttributes[0]) + fileMetadata.EACount = uint32(len(extendedAttributes)) + } + // remove the trailing `\` if present, otherwise it trips off the cim writer + path = strings.TrimSuffix(path, "\\") + err = winapi.CimCreateFile(c.handle, path, fileMetadata, &c.activeStream) + if err != nil { + return &PathError{Cim: c.name, Op: "addFile", Path: path, Err: err} + } + c.activeName = path + if info.FileAttributes&(windows.FILE_ATTRIBUTE_DIRECTORY) == 0 { + c.activeLeft = uint64(fileSize) + } + return nil +} + +// Write writes bytes to the active stream. +func (c *CimFsWriter) Write(p []byte) (int, error) { + if c.activeStream == 0 { + return 0, fmt.Errorf("no active stream") + } + if uint64(len(p)) > c.activeLeft { + return 0, &PathError{Cim: c.name, Op: "write", Path: c.activeName, Err: fmt.Errorf("wrote too much")} + } + err := winapi.CimWriteStream(c.activeStream, uintptr(unsafe.Pointer(&p[0])), uint32(len(p))) + if err != nil { + err = &PathError{Cim: c.name, Op: "write", Path: c.activeName, Err: err} + return 0, err + } + c.activeLeft -= uint64(len(p)) + return len(p), nil +} + +// AddLink adds a hard link at `newPath` that points to `oldPath`. +func (c *CimFsWriter) AddLink(oldPath string, newPath string) error { + err := c.closeStream() + if err != nil { + return err + } + err = winapi.CimCreateHardLink(c.handle, newPath, oldPath) + if err != nil { + err = &LinkError{Cim: c.name, Op: "addLink", Old: oldPath, New: newPath, Err: err} + } + return err +} + +// AddMergedLink adds a hard link at `newPath` that points to `oldPath` in the +// image. However unlike AddLink this link is resolved at merge time. This allows us to +// create links to files that are in other CIMs. +func (c *CimFsWriter) AddMergedLink(oldPath string, newPath string) error { + err := c.closeStream() + if err != nil { + return err + } + err = winapi.CimCreateMergeLink(c.handle, newPath, oldPath) + if err != nil { + err = &LinkError{Cim: c.name, Op: "addMergedLink", Old: oldPath, New: newPath, Err: err} + } + return err +} + +// Unlink deletes the file at `path` from the image. Note that the file MUST have been +// already added to the image. +func (c *CimFsWriter) Unlink(path string) error { + err := c.closeStream() + if err != nil { + return err + } + return winapi.CimDeletePath(c.handle, path) +} + +// Adds a tombstone at given path. This ensures that when the the CIMs are merged, the +// file at this path from lower layers won't show up in a mounted CIM. In case of Unlink, +// the file from the lower layers still shows up after merge. +func (c *CimFsWriter) AddTombstone(path string) error { + err := c.closeStream() + if err != nil { + return err + } + + return winapi.CimTombstoneFile(c.handle, path) +} + +func (c *CimFsWriter) commit() error { + err := c.closeStream() + if err != nil { + return err + } + err = winapi.CimCommitImage(c.handle) + if err != nil { + err = &OpError{Cim: c.name, Op: "commit", Err: err} + } + return err +} + +// Close closes the CimFS filesystem. +func (c *CimFsWriter) Close() (err error) { + if c.handle == 0 { + return fmt.Errorf("invalid writer") + } + if err = c.commit(); err != nil { + return &OpError{Cim: c.name, Op: "commit", Err: err} + } + err = winapi.CimCloseImage(c.handle) + c.handle = 0 + if err != nil { + return &OpError{Cim: c.name, Op: "close", Err: err} + } + if c.sealOnClose { + if err = sealBlockCIM(filepath.Dir(c.name)); err != nil { + return &OpError{Cim: filepath.Dir(c.name), Op: "seal", Err: err} + } + } + return nil +} + +// DestroyCim finds out the region files, object files of this cim and then delete the +// region files, object files and the .cim file itself. Note that any other +// CIMs that were forked off of this CIM would become unusable after this operation. This +// should not be used for block CIMs, os.Remove is sufficient for block CIMs. +func DestroyCim(ctx context.Context, cimPath string) (retErr error) { + regionFilePaths, err := getRegionFilePaths(ctx, cimPath) + if err != nil { + log.G(ctx).WithError(err).Warnf("get region files for cim %s", cimPath) + if retErr == nil { //nolint:govet // nilness: consistency with below + retErr = err + } + } + objectFilePaths, err := getObjectIDFilePaths(ctx, cimPath) + if err != nil { + log.G(ctx).WithError(err).Warnf("get objectid file for cim %s", cimPath) + if retErr == nil { + retErr = err + } + } + + log.G(ctx).WithFields(logrus.Fields{ + "cimPath": cimPath, + "regionFiles": regionFilePaths, + "objectFiles": objectFilePaths, + }).Debug("destroy cim") + + for _, regFilePath := range regionFilePaths { + if err := os.Remove(regFilePath); err != nil { + log.G(ctx).WithError(err).Warnf("remove file %s", regFilePath) + if retErr == nil { + retErr = err + } + } + } + + for _, objFilePath := range objectFilePaths { + if err := os.Remove(objFilePath); err != nil { + log.G(ctx).WithError(err).Warnf("remove file %s", objFilePath) + if retErr == nil { + retErr = err + } + } + } + + if err := os.Remove(cimPath); err != nil { + log.G(ctx).WithError(err).Warnf("remove file %s", cimPath) + if retErr == nil { + retErr = err + } + } + return retErr +} + +// GetCimUsage returns the total disk usage in bytes by the cim at path `cimPath`. +func GetCimUsage(ctx context.Context, cimPath string) (uint64, error) { + regionFilePaths, err := getRegionFilePaths(ctx, cimPath) + if err != nil { + return 0, fmt.Errorf("get region file paths for cim %s: %w", cimPath, err) + } + objectFilePaths, err := getObjectIDFilePaths(ctx, cimPath) + if err != nil { + return 0, fmt.Errorf("get objectid file for cim %s: %w", cimPath, err) + } + + var totalUsage uint64 + for _, f := range append(regionFilePaths, objectFilePaths...) { + fi, err := os.Stat(f) + if err != nil { + return 0, fmt.Errorf("stat file %s: %w", f, err) + } + totalUsage += uint64(fi.Size()) + } + return totalUsage, nil +} + +// MergeBlockCIMs creates a new merged BlockCIM from the provided source BlockCIMs. CIM +// at index 0 is considered to be topmost CIM and the CIM at index `length-1` is +// considered the base CIM. (i.e file with the same path in CIM at index 0 will shadow +// files with the same path at all other CIMs) When mounting this merged CIM the source +// CIMs MUST be provided in the exact same order. +func MergeBlockCIMsWithOpts(ctx context.Context, mergedCIM *BlockCIM, sourceCIMs []*BlockCIM, opts ...BlockCIMOpt) (err error) { + if !IsMergedCimWriteSupported() { + return fmt.Errorf("merged CIMs aren't supported on this OS version") + } else if len(sourceCIMs) < 2 { + return fmt.Errorf("need at least 2 source CIMs, got %d: %w", len(sourceCIMs), os.ErrInvalid) + } + + // Apply default options + config := &blockCIMConfig{} + + // Apply provided options + for _, opt := range opts { + if err = opt(config); err != nil { + return err + } + } + + for _, sCIM := range sourceCIMs { + if sCIM.Type != mergedCIM.Type { + return fmt.Errorf("source CIM (%s) type MUST match with merged CIM type: %w", sCIM.String(), os.ErrInvalid) + } + } + + var mergeFlags uint32 + switch mergedCIM.Type { + case BlockCIMTypeDevice: + mergeFlags = CimMergeFlagBlockDevice + case BlockCIMTypeSingleFile: + mergeFlags = CimMergeFlagSingleFile + default: + return fmt.Errorf("invalid block CIM type `%d`: %w", mergedCIM.Type, os.ErrInvalid) + } + + if config.dataIntegrity { + mergeFlags |= CimMergeFlagVerifiedCim + } + + cim, err := CreateBlockCIMWithOptions(ctx, mergedCIM, opts...) + if err != nil { + return fmt.Errorf("create merged CIM: %w", err) + } + defer func() { + cErr := cim.Close() + if err == nil { + err = cErr + } + }() + + // CimAddFsToMergedImage expects that topmost CIM is added first and the bottom + // most CIM is added last. + for _, sCIM := range sourceCIMs { + fullPath := filepath.Join(sCIM.BlockPath, sCIM.CimName) + if err := winapi.CimAddFsToMergedImage2(cim.handle, fullPath, mergeFlags); err != nil { + return fmt.Errorf("add cim to merged image: %w", err) + } + } + return nil +} + +// MergeBlockCIMs creates a new merged BlockCIM from the provided source BlockCIMs. CIM +// at index 0 is considered to be topmost CIM and the CIM at index `length-1` is +// considered the base CIM. (i.e file with the same path in CIM at index 0 will shadow +// files with the same path at all other CIMs) When mounting this merged CIM the source +// CIMs MUST be provided in the exact same order. +func MergeBlockCIMs(mergedCIM *BlockCIM, sourceCIMs []*BlockCIM) (err error) { + return MergeBlockCIMsWithOpts(context.Background(), mergedCIM, sourceCIMs, WithConsistentCIM()) +} + +// sealBlockCIM seals a blockCIM at the given path so that no further modifications are allowed on it. This also writes a +// root hash in the block header so that in future any reads happening on the CIM can be easily verified against this root hash +// to detect tampering. +func sealBlockCIM(blockPath string) error { + var hashSize, fixedHeaderSize uint64 + hashBuf := make([]byte, cimHashSize) + + // the blockPath could be a path to a block device or a file. In either case there should be no trailing backslash. + blockPath = strings.TrimSuffix(blockPath, "\\") + + if err := winapi.CimSealImage(blockPath, &hashSize, &fixedHeaderSize, &hashBuf[0]); err != nil { + return fmt.Errorf("failed to seal block CIM: %w", err) + } else if hashSize != cimHashSize { + return fmt.Errorf("unexpected cim hash size %d", hashSize) + } + return nil +} + +// GetVerificationInfo returns the root digest of the given block CIM. This is only +// applicable for CIMs that are sealed after writing. +func GetVerificationInfo(blockPath string) ([]byte, error) { + var ( + isSealed uint32 + hashSize uint64 + signatureSize uint64 + fixedHeaderSize uint64 + hash = make([]byte, cimHashSize) + ) + if err := winapi.CimGetVerificationInformation(blockPath, &isSealed, &hashSize, &signatureSize, &fixedHeaderSize, &hash[0], nil); err != nil { + return nil, fmt.Errorf("failed to get verification info from the CIM: %w", err) + } else if hashSize != cimHashSize { + return nil, fmt.Errorf("unexpected cim hash size %d", hashSize) + } else if isSealed == 0 { + return nil, fmt.Errorf("cim is not sealed") + } + return hash, nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cimfs.go b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cimfs.go new file mode 100644 index 0000000000..beae1f289b --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cimfs.go @@ -0,0 +1,161 @@ +//go:build windows +// +build windows + +package cimfs + +import ( + "github.com/Microsoft/hcsshim/internal/winapi/cimfs" + "github.com/Microsoft/hcsshim/internal/winapi/cimwriter" + + "path/filepath" + + "github.com/Microsoft/hcsshim/osversion" + "github.com/sirupsen/logrus" +) + +func IsCimFSSupported() bool { + rv, err := osversion.BuildRevision() + if err != nil { + logrus.WithError(err).Warn("get build revision") + } + build := osversion.Build() + // CimFS support is backported to LTSC2022 starting with revision 2031 and should + // otherwise be available on all builds >= V25H1Server + return (build >= osversion.V25H1Server || (build == osversion.V21H2Server && rv >= 2031)) && cimfs.Supported() +} + +// IsBlockCimSupported returns true if block formatted CIMs (i.e block device CIM & +// single file CIM) are supported on the current OS build. +func IsBlockCimSupported() bool { + build := osversion.Build() + // TODO(ambarve): Currently we are checking against a higher build number since there is no + // official build with block CIM support yet. Once we have that build, we should + // update the build number here. + return build >= 27766 && cimfs.Supported() +} + +// IsBlockCimWriteSupported returns true if block formatted CIMs (i.e block device CIM & +// single file CIM) are supported on the current OS build or if CimWriter is present. +func IsBlockCimWriteSupported() bool { + // TODO(ambarve): Currently we are checking against a higher build number since there is no + // official build with block CIM support yet. Once we have that build, we should + // update the build number here. + return IsBlockCimSupported() || cimwriter.Supported() +} + +// IsBlockCimMountSupported returns true if block formatted CIMs (i.e block device CIM & +// single file CIM) are supported on the current OS build. +func IsBlockCimMountSupported() bool { + // TODO(ambarve): Currently we are checking against a higher build number since there is no + // official build with block CIM support yet. Once we have that build, we should + // update the build number here. + return IsBlockCimSupported() +} + +// IsVerifiedCimSupported returns true if block CIM format supports also writing verification information in the CIM. +func IsVerifiedCimSupported() bool { + build := osversion.Build() + // TODO(ambarve): Currently we are checking against a higher build number since there is no + // official build with block CIM support yet. Once we have that build, we should + // update the build number here. + return build >= 27800 && cimfs.Supported() +} + +// IsVerifiedCimWriteSupported returns true if block CIM format supports also writing verification information in the CIM. +func IsVerifiedCimWriteSupported() bool { + // TODO(ambarve): Currently we are checking against a higher build number since there is no + // official build with block CIM support yet. Once we have that build, we should + // update the build number here. + return IsVerifiedCimSupported() || cimwriter.Supported() +} + +// IsVerifiedCimMountSupported returns true if block CIM format supports mounting. +func IsVerifiedCimMountSupported() bool { + // TODO(ambarve): Currently we are checking against a higher build number since there is no + // official build with block CIM support yet. Once we have that build, we should + // update the build number here. + return IsVerifiedCimSupported() +} + +func IsMergedCimSupported() bool { + // The merged CIM support was originally added before block CIM support. However, + // some of the merged CIM features that we use (e.g. merged hard links) were added + // later along with block CIM support. So use the same check as block CIM here. + return IsBlockCimSupported() +} + +func IsMergedCimWriteSupported() bool { + // The merged CIM support was originally added before block CIM support. However, + // some of the merged CIM features that we use (e.g. merged hard links) were added + // later along with block CIM support. So use the same check as block CIM here. + return IsBlockCimWriteSupported() +} + +func IsMergedCimMountSupported() bool { + // The merged CIM support was originally added before block CIM support. However, + // some of the merged CIM features that we use (e.g. merged hard links) were added + // later along with block CIM support. So use the same check as block CIM here. + return IsBlockCimMountSupported() +} + +type BlockCIMType uint32 + +const ( + BlockCIMTypeNone BlockCIMType = iota + BlockCIMTypeSingleFile + BlockCIMTypeDevice + + CimMountFlagNone uint32 = 0x0 + CimMountFlagEnableDax uint32 = 0x2 + CimMountBlockDeviceCim uint32 = 0x10 + CimMountSingleFileCim uint32 = 0x20 + CimMountVerifiedCim uint32 = 0x80 + + CimCreateFlagNone uint32 = 0x0 + CimCreateFlagDoNotExpandPEImages uint32 = 0x1 + CimCreateFlagFixedSizeChunks uint32 = 0x2 + CimCreateFlagBlockDeviceCim uint32 = 0x4 + CimCreateFlagSingleFileCim uint32 = 0x8 + CimCreateFlagConsistentCim uint32 = 0x10 + CimCreateFlagVerifiedCim uint32 = 0x40 + + CimMergeFlagNone uint32 = 0x0 + CimMergeFlagSingleFile uint32 = 0x1 + CimMergeFlagBlockDevice uint32 = 0x2 + CimMergeFlagVerifiedCim uint32 = 0x4 +) + +// BlockCIM represents a CIM stored in a block formatted way. +// +// A CIM usually is made up of a .cim file and multiple region & objectID +// files. Currently, all of these files are stored together in the same directory. To +// refer to such a CIM, we provide the path to the `.cim` file and the corresponding +// region & objectID files are assumed to be present right next to it. In this case the +// directory on the host's filesystem which holds one or more such CIMs is the container +// for those CIMs. +// +// Using multiple files for a single CIM can be very limiting. (For example, if you want +// to do a remote mount for a CIM layer, you now need to mount multiple files for a single +// layer). In such cases having a single container which contains all of the CIM related +// data is a great option. For this reason, CimFS has added support for a new type of a +// CIM named BlockCIM. A BlockCIM is a CIM for which the container used to store all of +// the CIM files is a block device or a binary file formatted like a block device. Such a +// block device (or a binary file) doesn't have a separate filesystem (like NTFS or FAT32) +// on it. Instead it is formatted in such a way that CimFS driver can read the blocks and +// find out which CIMs are present on that block device. The CIMs stored on a raw block +// device are sometimes referred to as block device CIMs and CIMs stored on the block +// formatted single file are referred as single file CIMs. +type BlockCIM struct { + Type BlockCIMType + // BlockPath is a path to the block device or the single file which contains the + // CIM. + BlockPath string + // Since a block device CIM or a single file CIM can container multiple CIMs, we + // refer to an individual CIM using its name. + CimName string +} + +// added for logging convenience +func (b *BlockCIM) String() string { + return filepath.Join(b.BlockPath, b.CimName) +} diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/common.go b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/common.go new file mode 100644 index 0000000000..ab988aff06 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/common.go @@ -0,0 +1,138 @@ +//go:build windows +// +build windows + +package cimfs + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "os" + "path/filepath" + + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/pkg/cimfs/format" +) + +const ( + cimHashSize = 32 // size of a hash of a verified CIM in bytes +) + +var ( + // Equivalent to SDDL of "D:NO_ACCESS_CONTROL". + nullSd = []byte{1, 0, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +) + +type OpError struct { + Cim string + Op string + Err error +} + +func (e *OpError) Error() string { + s := "cim " + e.Op + " " + e.Cim + s += ": " + e.Err.Error() + return s +} + +// PathError is the error type returned by most functions in this package. +type PathError struct { + Cim string + Op string + Path string + Err error +} + +func (e *PathError) Error() string { + s := "cim " + e.Op + " " + e.Cim + s += ":" + e.Path + s += ": " + e.Err.Error() + return s +} + +type LinkError struct { + Cim string + Op string + Old string + New string + Err error +} + +func (e *LinkError) Error() string { + return "cim " + e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error() +} + +func validateHeader(h *format.CommonHeader) error { + if !bytes.Equal(h.Magic[:], format.MagicValue[:]) { + return fmt.Errorf("not a cim file") + } + if h.Version.Major > format.CurrentVersion.Major || h.Version.Major < format.MinSupportedVersion.Major { + return fmt.Errorf("unsupported cim version. cim version %v must be between %v & %v", h.Version, format.MinSupportedVersion, format.CurrentVersion) + } + return nil +} + +func readFilesystemHeader(f *os.File) (format.FilesystemHeader, error) { + var fsh format.FilesystemHeader + + if err := binary.Read(f, binary.LittleEndian, &fsh); err != nil { + return fsh, fmt.Errorf("reading filesystem header: %w", err) + } + + if err := validateHeader(&fsh.Common); err != nil { + return fsh, fmt.Errorf("validating filesystem header: %w", err) + } + return fsh, nil +} + +// Returns the paths of all the objectID files associated with the cim at `cimPath`. +func getObjectIDFilePaths(ctx context.Context, cimPath string) ([]string, error) { + f, err := os.Open(cimPath) + if err != nil { + return []string{}, fmt.Errorf("open cim file %s: %w", cimPath, err) + } + defer f.Close() + + fsh, err := readFilesystemHeader(f) + if err != nil { + return []string{}, fmt.Errorf("readingp cim header: %w", err) + } + + paths := []string{} + for i := 0; i < int(fsh.Regions.Count); i++ { + path := filepath.Join(filepath.Dir(cimPath), fmt.Sprintf("%s_%v_%d", format.ObjectIDFileName, fsh.Regions.ID, i)) + if _, err := os.Stat(path); err == nil { + paths = append(paths, path) + } else { + log.G(ctx).WithError(err).Warnf("stat for object file %s", path) + } + + } + return paths, nil +} + +// Returns the paths of all the region files associated with the cim at `cimPath`. +func getRegionFilePaths(ctx context.Context, cimPath string) ([]string, error) { + f, err := os.Open(cimPath) + if err != nil { + return []string{}, fmt.Errorf("open cim file %s: %w", cimPath, err) + } + defer f.Close() + + fsh, err := readFilesystemHeader(f) + if err != nil { + return []string{}, fmt.Errorf("reading cim header: %w", err) + } + + paths := []string{} + for i := 0; i < int(fsh.Regions.Count); i++ { + path := filepath.Join(filepath.Dir(cimPath), fmt.Sprintf("%s_%v_%d", format.RegionFileName, fsh.Regions.ID, i)) + if _, err := os.Stat(path); err == nil { + paths = append(paths, path) + } else { + log.G(ctx).WithError(err).Warnf("stat for region file %s", path) + } + } + return paths, nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/doc.go b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/doc.go new file mode 100644 index 0000000000..c40f38cc69 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/doc.go @@ -0,0 +1,99 @@ +/* +This package provides simple go wrappers on top of the win32 CIMFS APIs. + +Details about CimFS & related win32 APIs can be found here: +https://learn.microsoft.com/en-us/windows/win32/api/_cimfs/ + +Details about how CimFS is being used in containerd can be found here: +https://github.com/containerd/containerd/issues/8346 + +CIM types: +Currently we support 2 types of CIMs: + - Standard/classic (for the lack of a better term) CIMs. + - Block CIMs. + +Standard CIMs store all the contents of a CIM in one or more region & objectID files. This +means a single CIM is made up of a `.cim` file, one or more region files and one or more +objectID files. All of these files MUST be present in the same directory in order for that +CIM to work. Block CIMs store all the data of a CIM in a single block device. A VHD can be +such a block device. For convenience CimFS also allows using a block formatted file as a +block device. + +Standard CIMs can be created with the `func Create(imagePath string, oldFSName string, +newFSName string) (_ *CimFsWriter, err error)` function defined in this package, whereas +block CIMs can be created with the `func CreateBlockCIM(blockPath, oldName, newName +string, blockType BlockCIMType) (_ *CimFsWriter, err error)` function. + +Verified CIMs: +A block CIM can also provide integrity checking (via a hash/Merkel tree, +similar to dm-verity on Linux). If a CIM is written and sealed, it generates a +root hash of all of its contents and shares it back with the client. Any +verified CIM can be mounted by passing a hash that we expect to be its root +hash. All read operations on such a mounted CIM will then validate that the +generated root hash matches with the one that was provided at mount time. If it +doesn't match the read fails. This allows us to guarantee that the CIM based +layered aren't being modified underneath us. + +Forking & Merging CIMs: +In container world, CIMs are used for storing container image layers. Usually, one layer +is stored in one CIM. This means we need a way to combine multiple CIMs to create the +rootfs of a container. This can be achieved either by forking the CIMs or merging the +CIMs. + +Forking CIMs: +Forking means every time a CIM is created for a non-base layer, we fork it off of a parent +layer CIM. This ensures that contents that are written to this CIM are merged with that of +parent layer CIMs at the time of CIM creation itself. When such a CIM is mounted we get a +combined view of the contents of this CIM as well as the parent CIM from which this CIM +was forked. However, this means that all the CIMs MUST be stored in the same directory in +order for forked CIMs to work. And every non-base layer CIM is dependent on all of its +parent layer CIMs. + +Merging CIMs: +If we create one or more CIMs without forking them at the time of creation, we can still +merge those CIMs later to create a new special type of CIM called merged CIM. When +mounted, this merged CIM provides a view of the combined contents of all the layers that +were merged. The advantage of this approach is that each layer CIM (also referred to as +source CIMs in the context of merging CIMs) can be created & stored independent of its +parent CIMs. (Currently we only support merging block CIMs). + +In order to create a merged CIM we need at least 2 non-forked block CIMs (we can not merge +forked & non-forked CIMs), these CIMs are also referred to as source CIMs. We first create +a new CIM (for storing the merge) via the `CreateBlockCIM` API, then call +`CimAddFsToMergedImage2` repeatedly to add the source CIMs one by one to the merged +CIM. Closing the handle on this new CIM commits it automatically. The order in which +source CIMs are added matters. A source CIM that was added before another source CIM takes +precedence when merging the CIM contents. Crating this merged CIM only combines the +metadata of all the source CIMs, however the actual data isn't copied to the merged +CIM. This is why when mounting the merged CIM, we still need to provide paths to the +source CIMs. + +`CimMergeMountImage` is used to mount a merged CIM. This API expects an array of paths of +the merged CIM and all the source CIMs. Note that the array MUST include the merged CIM +path at the 0th index and all the source CIMs in the same order in which they were added +at the time of creation of the merged CIM. For example, if we merged CIMs 1.cim & 2.cim by +first adding 1.cim (via CimAddFsToMergedImage) and then adding 2.cim, then the array +should be [merged.cim, 1.cim, 2.cim] + +Merged CIM specific APIs. + +`CimTombstoneFile`: is used for creating a tombstone file in a CIM. Tombstone file is +similar to a whiteout file used in case of overlayFS. A tombstone's primary use case is +for merged CIMs. When multiple source CIMs are merged, a tombstone file/directory ensures +that any files with the same path in the lower layers (i.e source CIMs that are added +after the CIM that has a tombstone) do not show up in the mounted filesystem view. For +example, imagine 1.cim has a file at path `foo/bar.txt` and 2.cim has a tombstone at path +`foo/bar.txt`. If a merged CIM is created by first adding 2.cim (via +CimAddFsToMergedImage) and then adding 1.cim and then when that merged CIM is mounted, +`foo/bar.txt` will not show up in the mounted filesystem. A tombstone isn't required when +using forked CIMs, because we can just call `CimDeletePath` to remove a file from the +lower layers in that case. However, that doesn't work for merged CIMs since at the time of +writing one of the source CIMs, we can't delete files from other source CIMs. + +`CimCreateMergeLink`: is used to create a file link that is resolved at the time of +merging CIMs. This is required if we want to create a hardlink in one source CIM that +points to a file in another source CIM. Such a hardlink can not be resolved at the time of +writing the source CIM. It can only be resolved at the time of merge. This API allows us +to create such cross layer hard links. +*/ +package cimfs diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/doc.go b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/doc.go new file mode 100644 index 0000000000..a85e64710e --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/doc.go @@ -0,0 +1,4 @@ +// format package maintains some basic structures to allows us to read header of a cim file. This is mostly +// required to understand the region & objectid files associated with a particular cim. Otherwise, we don't +// need to parse the cim format. +package format diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/format.go b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/format.go new file mode 100644 index 0000000000..72dea683eb --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/format.go @@ -0,0 +1,61 @@ +//go:build windows +// +build windows + +package format + +import "github.com/Microsoft/go-winio/pkg/guid" + +const ( + RegionFileName = "region" + ObjectIDFileName = "objectid" +) + +// Magic specifies the magic number at the beginning of a file. +type Magic [8]uint8 + +var MagicValue = Magic([8]uint8{'c', 'i', 'm', 'f', 'i', 'l', 'e', '0'}) + +type Version struct { + Major, Minor uint32 +} + +var CurrentVersion = Version{3, 0} + +var MinSupportedVersion = Version{2, 0} + +type FileType uint8 + +// RegionOffset encodes an offset to objects as index of the region file +// containing the object and the byte offset within that file. +type RegionOffset uint64 + +// CommonHeader is the common header for all CIM-related files. +type CommonHeader struct { + Magic Magic + HeaderLength uint32 + Type FileType + Reserved uint8 + Reserved2 uint16 + Version Version + Reserved3 uint64 +} + +type RegionSet struct { + ID guid.GUID + Count uint16 + Reserved uint16 + Reserved1 uint32 +} + +// FilesystemHeader is the header for a filesystem file. +// +// The filesystem file points to the filesystem object inside a region +// file and specifies regions sets. +type FilesystemHeader struct { + Common CommonHeader + Regions RegionSet + FilesystemOffset RegionOffset + Reserved uint32 + Reserved1 uint16 + ParentCount uint16 +} diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/mount_cim.go b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/mount_cim.go new file mode 100644 index 0000000000..bfb5e753e0 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/cimfs/mount_cim.go @@ -0,0 +1,214 @@ +//go:build windows +// +build windows + +package cimfs + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/Microsoft/hcsshim/internal/winapi" + winapitypes "github.com/Microsoft/hcsshim/internal/winapi/types" + "github.com/pkg/errors" + "golang.org/x/sys/windows" +) + +type MountError struct { + Cim string + Op string + VolumeGUID guid.GUID + Err error +} + +func (e *MountError) Error() string { + s := "cim " + e.Op + if e.Cim != "" { + s += " " + e.Cim + } + s += " " + e.VolumeGUID.String() + ": " + e.Err.Error() + return s +} + +const ( + VolumePathFormat = "\\\\?\\Volume{%s}\\" +) + +// Mount mounts the given cim at a volume with given GUID. Returns the full volume +// path if mount is successful. +func Mount(cimPath string, volumeGUID guid.GUID, mountFlags uint32) (string, error) { + if err := winapi.CimMountImage(filepath.Dir(cimPath), filepath.Base(cimPath), mountFlags, &volumeGUID); err != nil { + return "", &MountError{Cim: cimPath, Op: "Mount", VolumeGUID: volumeGUID, Err: err} + } + return fmt.Sprintf(VolumePathFormat, volumeGUID.String()), nil +} + +// Unmount unmounts the cim at mounted at path `volumePath`. +func Unmount(volumePath string) error { + // The path is expected to be in the \\?\Volume{GUID}\ format + if volumePath[len(volumePath)-1] != '\\' { + volumePath += "\\" + } + + if !strings.HasPrefix(volumePath, "\\\\?\\Volume{") || !strings.HasSuffix(volumePath, "}\\") { + return errors.Errorf("volume path %s is not in the expected format", volumePath) + } + + trimmedStr := strings.TrimPrefix(volumePath, "\\\\?\\Volume{") + trimmedStr = strings.TrimSuffix(trimmedStr, "}\\") + + volGUID, err := guid.FromString(trimmedStr) + if err != nil { + return errors.Wrapf(err, "guid parsing failed for %s", trimmedStr) + } + + if err := winapi.CimDismountImage(&volGUID); err != nil { + return &MountError{VolumeGUID: volGUID, Op: "Unmount", Err: err} + } + + return nil +} + +// MountMergedBlockCIMs mounts the given merged BlockCIM (usually created with +// `MergeBlockCIMs`) at a volume with given GUID. The `sourceCIMs` MUST be identical +// to the `sourceCIMs` passed to `MergeBlockCIMs` when creating this merged CIM. +func MountMergedBlockCIMs(mergedCIM *BlockCIM, sourceCIMs []*BlockCIM, mountFlags uint32, volumeGUID guid.GUID) (string, error) { + if !IsMergedCimMountSupported() { + return "", fmt.Errorf("merged CIMs aren't supported on this OS version") + } else if len(sourceCIMs) < 2 { + return "", fmt.Errorf("need at least 2 source CIMs, got %d: %w", len(sourceCIMs), os.ErrInvalid) + } + + switch mergedCIM.Type { + case BlockCIMTypeDevice: + mountFlags |= CimMountBlockDeviceCim + case BlockCIMTypeSingleFile: + mountFlags |= CimMountSingleFileCim + default: + return "", fmt.Errorf("invalid block CIM type `%d`", mergedCIM.Type) + } + + for _, sCIM := range sourceCIMs { + if sCIM.Type != mergedCIM.Type { + return "", fmt.Errorf("source CIM (%s) type doesn't match with merged CIM type: %w", sCIM.String(), os.ErrInvalid) + } + } + + // win32 mount merged CIM API expects an array of all CIMs. 0th entry in the array + // should be the merged CIM. All remaining entries should be the source CIM paths + // in the same order that was used while creating the merged CIM. + allcims := append([]*BlockCIM{mergedCIM}, sourceCIMs...) + cimsToMerge := []winapitypes.CimFsImagePath{} + for _, bcim := range allcims { + // Trailing backslashes cause problems-remove those + imageDir, err := windows.UTF16PtrFromString(strings.TrimRight(bcim.BlockPath, `\`)) + if err != nil { + return "", fmt.Errorf("convert string to utf16: %w", err) + } + cimName, err := windows.UTF16PtrFromString(bcim.CimName) + if err != nil { + return "", fmt.Errorf("convert string to utf16: %w", err) + } + + cimsToMerge = append(cimsToMerge, winapitypes.CimFsImagePath{ + ImageDir: imageDir, + ImageName: cimName, + }) + } + + if err := winapi.CimMergeMountImage(uint32(len(cimsToMerge)), &cimsToMerge[0], mountFlags, &volumeGUID); err != nil { + return "", &MountError{Cim: filepath.Join(mergedCIM.BlockPath, mergedCIM.CimName), Op: "MountMerged", Err: err} + } + return fmt.Sprintf(VolumePathFormat, volumeGUID.String()), nil +} + +// Mounts a verified block CIM with the provided root hash. The root hash is usually +// returned when the CIM is sealed or the root hash can be queried from a block CIM. +// Every read on the mounted volume will be verified to match against the provided root +// hash if it doesn't, the read will fail. The CIM MUST have been created with the +// verified creation flag. +func MountVerifiedBlockCIM(bCIM *BlockCIM, mountFlags uint32, volumeGUID guid.GUID, rootHash []byte) (string, error) { + if len(rootHash) != cimHashSize { + return "", fmt.Errorf("unexpected root hash size %d, expected size is %d", len(rootHash), cimHashSize) + } + + // The CimMountVerifiedCim flag should only be used when using the regular mount + // CIM API. That flag is required to tell that API that this is a verified + // CIM. This API doesn't need that flag as it is already assumed that the CIM is + // verified. + switch bCIM.Type { + case BlockCIMTypeDevice: + mountFlags |= CimMountBlockDeviceCim + case BlockCIMTypeSingleFile: + mountFlags |= CimMountSingleFileCim + default: + return "", fmt.Errorf("invalid block CIM type `%d`: %w", bCIM.Type, os.ErrInvalid) + } + + if err := winapi.CimMountVerifiedImage(bCIM.BlockPath, bCIM.CimName, mountFlags, &volumeGUID, cimHashSize, &rootHash[0]); err != nil { + return "", &MountError{Cim: bCIM.String(), Op: "MountVerifiedCIM", Err: err} + } + return fmt.Sprintf("\\\\?\\Volume{%s}\\", volumeGUID.String()), nil +} + +// MountMergedVerifiedBlockCIMs mounts the given merged verified BlockCIM (usually created +// with `MergeBlockCIMs`) at a volume with given GUID, with the given root hash. The +// `sourceCIMs` MUST be identical to the `sourceCIMs` passed to `MergeBlockCIMs` when +// creating this merged CIM. The root hash is usually returned when the CIM is sealed or +// the root hash can be queried from a block CIM. In case of merged CIMs, the root hash of +// the merged CIM should be passed here. Every read on the mounted volume will be verified +// to match against the provided root hash if it doesn't, the read will fail. The source +// CIMs and the merged CIM MUST have been created with the verified creation flag. +func MountMergedVerifiedBlockCIMs(mergedCIM *BlockCIM, sourceCIMs []*BlockCIM, mountFlags uint32, volumeGUID guid.GUID, rootHash []byte) (string, error) { + if !IsVerifiedCimMountSupported() { + return "", fmt.Errorf("verified CIMs aren't supported on this OS version") + } else if len(sourceCIMs) < 2 { + return "", fmt.Errorf("need at least 2 source CIMs, got %d: %w", len(sourceCIMs), os.ErrInvalid) + } else if len(rootHash) != cimHashSize { + return "", fmt.Errorf("unexpected root hash size %d, expected size is %d", len(rootHash), cimHashSize) + } + + switch mergedCIM.Type { + case BlockCIMTypeDevice: + mountFlags |= CimMountBlockDeviceCim + case BlockCIMTypeSingleFile: + mountFlags |= CimMountSingleFileCim + default: + return "", fmt.Errorf("invalid block CIM type `%d`", mergedCIM.Type) + } + + for _, sCIM := range sourceCIMs { + if sCIM.Type != mergedCIM.Type { + return "", fmt.Errorf("source CIM (%s) type doesn't match with merged CIM type: %w", sCIM.String(), os.ErrInvalid) + } + } + + // win32 mount merged CIM API expects an array of all CIMs. 0th entry in the array + // should be the merged CIM. All remaining entries should be the source CIM paths + // in the same order that was used while creating the merged CIM. + allcims := append([]*BlockCIM{mergedCIM}, sourceCIMs...) + cimsToMerge := []winapitypes.CimFsImagePath{} + for _, bcim := range allcims { + // Trailing backslashes cause problems-remove those + imageDir, err := windows.UTF16PtrFromString(strings.TrimRight(bcim.BlockPath, `\`)) + if err != nil { + return "", fmt.Errorf("convert string to utf16: %w", err) + } + cimName, err := windows.UTF16PtrFromString(bcim.CimName) + if err != nil { + return "", fmt.Errorf("convert string to utf16: %w", err) + } + + cimsToMerge = append(cimsToMerge, winapitypes.CimFsImagePath{ + ImageDir: imageDir, + ImageName: cimName, + }) + } + + if err := winapi.CimMergeMountVerifiedImage(uint32(len(cimsToMerge)), &cimsToMerge[0], mountFlags, &volumeGUID, cimHashSize, &rootHash[0]); err != nil { + return "", &MountError{Cim: filepath.Join(mergedCIM.BlockPath, mergedCIM.CimName), Op: "MountMergedVerified", Err: err} + } + return fmt.Sprintf(VolumePathFormat, volumeGUID.String()), nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/pkg/ociwclayer/cim/import.go b/vendor/github.com/Microsoft/hcsshim/pkg/ociwclayer/cim/import.go new file mode 100644 index 0000000000..5816bc737e --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/pkg/ociwclayer/cim/import.go @@ -0,0 +1,372 @@ +//go:build windows +// +build windows + +package cim + +import ( + "archive/tar" + "bufio" + "context" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + + "github.com/Microsoft/go-winio/backuptar" + "github.com/Microsoft/hcsshim/ext4/tar2ext4" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/wclayer/cim" + "github.com/Microsoft/hcsshim/pkg/cimfs" + "github.com/Microsoft/hcsshim/pkg/ociwclayer" + "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +// ImportCimLayerFromTar reads a layer from an OCI layer tar stream and extracts it into +// the CIM format at the specified path. +// `layerPath` is the directory which can be used to store intermediate files generated during layer extraction (and these file are also used when extracting children layers of this layer) +// `cimPath` is the path to the CIM in which layer files must be stored. Note that region & object files are created when writing to a CIM, these files will be created next to the `cimPath`. +// `parentLayerCimPaths` are paths to the parent layer CIMs, ordered from highest to lowest, i.e the CIM at `parentLayerCimPaths[0]` will be the immediate parent of the layer that is being extracted here. +// `parentLayerPaths` are paths to the parent layer directories. Ordered from highest to lowest. +// +// This function returns the total size of the layer's files, in bytes. +func ImportCimLayerFromTar(ctx context.Context, r io.Reader, layerPath, cimPath string, parentLayerPaths, parentLayerCimPaths []string) (_ int64, err error) { + log.G(ctx).WithFields(logrus.Fields{ + "layer path": layerPath, + "layer cim path": cimPath, + "parent layer paths": strings.Join(parentLayerPaths, ", "), + "parent layer CIM paths": strings.Join(parentLayerCimPaths, ", "), + }).Debug("Importing cim layer from tar") + + err = os.MkdirAll(layerPath, 0755) + if err != nil { + return 0, err + } + + w, err := cim.NewForkedCimLayerWriter(ctx, layerPath, cimPath, parentLayerPaths, parentLayerCimPaths) + if err != nil { + return 0, err + } + + n, err := writeCimLayerFromTar(ctx, r, w) + cerr := w.Close(ctx) + if err != nil { + return 0, err + } + if cerr != nil { + return 0, cerr + } + return n, nil +} + +type blockCIMLayerImportConfig struct { + // import layers with integrity enabled CIMs + dataIntegrity bool + // parent layers + parentLayers []*cimfs.BlockCIM + // append VHD footer to the import CIMs + appendVHDFooter bool +} + +// BlockCIMOpt is a function type for configuring block CIM creation options +type BlockCIMLayerImportOpt func(*blockCIMLayerImportConfig) error + +func WithLayerIntegrity() BlockCIMLayerImportOpt { + return func(opts *blockCIMLayerImportConfig) error { + opts.dataIntegrity = true + return nil + } +} + +func WithVHDFooter() BlockCIMLayerImportOpt { + return func(opts *blockCIMLayerImportConfig) error { + opts.appendVHDFooter = true + return nil + } +} + +func WithParentLayers(parentLayers []*cimfs.BlockCIM) BlockCIMLayerImportOpt { + return func(opts *blockCIMLayerImportConfig) error { + opts.parentLayers = parentLayers + return nil + } +} + +func GetIntegrityChecksum(ctx context.Context, blockPath string, pathName string) (string, error) { + log.G(ctx).Debugf("writing integrity checksum file for block CIM `%s`", blockPath) + // for convenience write a file that has the hex encoded root digest of the generated verified CIM. + // this same hex string can be used in the confidential policy. + // also return the integrity checksum as a string for integrity-vhd tooling. + digest, err := cimfs.GetVerificationInfo(blockPath) + if err != nil { + return "", fmt.Errorf("failed to query verified info of the CIM layer: %w", err) + } + + digestStr := hex.EncodeToString(digest) + + // only create a file if a path name is provided + if pathName != "" { + digestFile, err := os.Create(filepath.Join(filepath.Dir(blockPath), pathName)) + if err != nil { + return "", fmt.Errorf("failed to create verification info file: %w", err) + } + defer digestFile.Close() + + if wn, err := digestFile.WriteString(digestStr); err != nil { + return "", fmt.Errorf("failed to write verification info: %w", err) + } else if wn != len(digestStr) { + return "", fmt.Errorf("incomplete write of verification info: %w", err) + } + } + return digestStr, nil +} + +func ImportBlockCIMLayerWithOpts(ctx context.Context, r io.Reader, layer *cimfs.BlockCIM, opts ...BlockCIMLayerImportOpt) (_ int64, err error) { + log.G(ctx).WithField("layer", layer).Debug("Importing block CIM layer from tar") + + err = os.MkdirAll(filepath.Dir(layer.BlockPath), 0755) + if err != nil { + return 0, err + } + + config := &blockCIMLayerImportConfig{} + for _, opt := range opts { + if err := opt(config); err != nil { + return 0, fmt.Errorf("block CIM import config option failure: %w", err) + } + } + + log.G(ctx).WithField("config", *config).Debug("layer import config") + + bcimWriterOpts := []cimfs.BlockCIMOpt{} + if config.dataIntegrity { + bcimWriterOpts = append(bcimWriterOpts, cimfs.WithDataIntegrity()) + } + + w, err := cim.NewBlockCIMLayerWriterWithOpts(ctx, layer, config.parentLayers, bcimWriterOpts...) + if err != nil { + return 0, err + } + + n, err := writeCimLayerFromTar(ctx, r, w) + cerr := w.Close(ctx) + if err != nil { + return 0, err + } + if cerr != nil { + return 0, cerr + } + + if config.appendVHDFooter { + log.G(ctx).Debugf("appending VHD footer to block CIM at `%s`", layer.BlockPath) + if err = tar2ext4.ConvertFileToVhd(layer.BlockPath); err != nil { + return 0, fmt.Errorf("append VHD footer to block CIM: %w", err) + } + } + + if config.dataIntegrity { + if _, err = GetIntegrityChecksum(ctx, layer.BlockPath, "integrity_checksum"); err != nil { + return 0, err + } + } + + return n, nil +} + +// ImportSingleFileCimLayerFromTar reads a layer from an OCI layer tar stream and extracts +// it into the SingleFileCIM format. +func ImportSingleFileCimLayerFromTar(ctx context.Context, r io.Reader, layer *cimfs.BlockCIM, parentLayers []*cimfs.BlockCIM) (_ int64, err error) { + return ImportBlockCIMLayerWithOpts(ctx, r, layer, WithParentLayers(parentLayers)) +} + +func writeCimLayerFromTar(ctx context.Context, r io.Reader, w cim.CIMLayerWriter) (int64, error) { + tr := tar.NewReader(r) + buf := bufio.NewWriter(w) + size := int64(0) + + // Iterate through the files in the archive. + hdr, loopErr := tr.Next() + for loopErr == nil { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + + // Note: path is used instead of filepath to prevent OS specific handling + // of the tar path + base := path.Base(hdr.Name) + if strings.HasPrefix(base, ociwclayer.WhiteoutPrefix) { + name := path.Join(path.Dir(hdr.Name), base[len(ociwclayer.WhiteoutPrefix):]) + if rErr := w.Remove(filepath.FromSlash(name)); rErr != nil { + return 0, rErr + } + hdr, loopErr = tr.Next() + } else if hdr.Typeflag == tar.TypeLink { + if linkErr := w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname)); linkErr != nil { + return 0, linkErr + } + hdr, loopErr = tr.Next() + } else { + name, fileSize, fileInfo, err := backuptar.FileInfoFromHeader(hdr) + if err != nil { + return 0, err + } + sddl, err := backuptar.SecurityDescriptorFromTarHeader(hdr) + if err != nil { + return 0, err + } + eadata, err := backuptar.ExtendedAttributesFromTarHeader(hdr) + if err != nil { + return 0, err + } + + var reparse []byte + // As of now the only valid reparse data in a layer will be for a symlink. If file is + // a symlink set reparse attribute and ensure reparse data buffer isn't + // empty. Otherwise remove the reparse attributed. + fileInfo.FileAttributes &^= uint32(windows.FILE_ATTRIBUTE_REPARSE_POINT) + if hdr.Typeflag == tar.TypeSymlink { + reparse = backuptar.EncodeReparsePointFromTarHeader(hdr) + if len(reparse) > 0 { + fileInfo.FileAttributes |= uint32(windows.FILE_ATTRIBUTE_REPARSE_POINT) + } + } + + if addErr := w.Add(filepath.FromSlash(name), fileInfo, fileSize, sddl, eadata, reparse); addErr != nil { + return 0, addErr + } + if hdr.Typeflag == tar.TypeReg { + if _, cpErr := io.Copy(buf, tr); cpErr != nil { + return 0, cpErr + } + } + size += fileSize + + // Copy all the alternate data streams and return the next non-ADS header. + var ahdr *tar.Header + for { + ahdr, loopErr = tr.Next() + if loopErr != nil { + break + } + + if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") { + hdr = ahdr + break + } + + // stream names have following format: '::$DATA' + // $DATA is one of the valid types of streams. We currently only support + // data streams so fail if this is some other type of stream. + if !strings.HasSuffix(ahdr.Name, ":$DATA") { + return 0, fmt.Errorf("stream types other than $DATA are not supported, found: %s", ahdr.Name) + } + + if addErr := w.AddAlternateStream(filepath.FromSlash(ahdr.Name), uint64(ahdr.Size)); addErr != nil { + return 0, addErr + } + + if _, cpErr := io.Copy(buf, tr); cpErr != nil { + return 0, cpErr + } + } + } + + if flushErr := buf.Flush(); flushErr != nil { + if loopErr == nil { + loopErr = flushErr + } else { + log.G(ctx).WithError(flushErr).Warn("flush buffer during layer write failed") + } + } + } + if !errors.Is(loopErr, io.EOF) { + return 0, loopErr + } + return size, nil +} + +// MergeBlockCIMLayersWithOpts create a new block CIM at mergedCIM.BlockPath and then +// creates a new CIM inside that block CIM that merges all the contents of the provided +// sourceCIMs. Note that this is only a metadata merge, so when this merged CIM is being +// mounted, all the sourceCIMs must be provided too and they MUST be provided in the same +// order. Expected order of sourceCIMs is that the base layer should be at the last index +// and the topmost layer should be at 0'th index. Merge operation can take a long time in +// certain situations, this function respects context deadlines in such cases. This +// function is NOT thread safe, it is caller's responsibility to handle thread safety. +func MergeBlockCIMLayersWithOpts(ctx context.Context, sourceCIMs []*cimfs.BlockCIM, mergedCIM *cimfs.BlockCIM, opts ...BlockCIMLayerImportOpt) (retErr error) { + log.G(ctx).WithFields(logrus.Fields{ + "source CIMs": sourceCIMs, + "merged CIM": mergedCIM, + }).Debug("Merging block CIM layers") + + // check if a merged CIM already exists + _, err := os.Stat(mergedCIM.BlockPath) + if err == nil { + return os.ErrExist + } + + // Apply configuration options + config := &blockCIMLayerImportConfig{} + for _, opt := range opts { + if err := opt(config); err != nil { + return fmt.Errorf("apply merge option: %w", err) + } + } + + // Prepare options for the underlying cimfs.MergeBlockCIMsWithOpts call + cimfsOpts := []cimfs.BlockCIMOpt{cimfs.WithConsistentCIM()} + if config.dataIntegrity { + cimfsOpts = append(cimfsOpts, cimfs.WithDataIntegrity()) + } + + // Ensure the directory for the merged CIM exists + if err := os.MkdirAll(filepath.Dir(mergedCIM.BlockPath), 0755); err != nil { + return fmt.Errorf("create directory for merged CIM: %w", err) + } + defer func() { + if retErr != nil { + if rmErr := os.Remove(mergedCIM.BlockPath); rmErr != nil { + log.G(ctx).WithError(retErr).Warnf("error in cleanup on failure: %s", rmErr) + } + } + }() + + // Run the merge operation in a goroutine to handle context cancellation + // The merge operation can take a long time, so we need to respect context deadlines + errCh := make(chan error, 1) + go func() { + defer close(errCh) + err := cimfs.MergeBlockCIMsWithOpts(ctx, mergedCIM, sourceCIMs, cimfsOpts...) + errCh <- err + }() + + // Wait for either the merge to complete or context to be cancelled + select { + case <-ctx.Done(): + err = ctx.Err() + case err = <-errCh: + } + if err != nil { + return fmt.Errorf("merge block CIMs failed: %w", err) + } + + // Handle VHD footer if requested + if config.appendVHDFooter { + log.G(ctx).Debugf("appending VHD footer to block CIM at `%s`", mergedCIM.BlockPath) + if err = tar2ext4.ConvertFileToVhd(mergedCIM.BlockPath); err != nil { + return fmt.Errorf("append VHD footer to block CIM: %w", err) + } + } + if config.dataIntegrity { + if _, err = GetIntegrityChecksum(ctx, mergedCIM.BlockPath, "merged_integrity_checksum"); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/checkpoint-restore/checkpointctl/LICENSE b/vendor/github.com/checkpoint-restore/checkpointctl/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/vendor/github.com/checkpoint-restore/checkpointctl/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/vendor/github.com/checkpoint-restore/checkpointctl/lib/annotations.go b/vendor/github.com/checkpoint-restore/checkpointctl/lib/annotations.go new file mode 100644 index 0000000000..8d0ad98036 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/checkpointctl/lib/annotations.go @@ -0,0 +1,65 @@ +package metadata + +const ( + // CheckpointAnnotationEngine specifies the name of the container engine (e.g., podman, cri-o). + CheckpointAnnotationEngine = "org.criu.checkpoint.engine.name" + + // CheckpointAnnotationEngineVersion specifies the version of the container engine. + CheckpointAnnotationEngineVersion = "org.criu.checkpoint.engine.version" + + // CheckpointAnnotationName specifies the name of the container associated with the checkpoint. + CheckpointAnnotationName = "org.criu.checkpoint.container.name" + + // CheckpointAnnotationPod specifies the name of the pod associated with the checkpoint. + CheckpointAnnotationPod = "org.criu.checkpoint.pod.name" + + // CheckpointAnnotationPodID specifies the ID of the pod associated with the checkpoint. + CheckpointAnnotationPodID = "org.criu.checkpoint.pod.id" + + // CheckpointAnnotationNamespace specifies the namespace of the pod associated with the checkpoint. + CheckpointAnnotationNamespace = "org.criu.checkpoint.pod.namespace" + + // CheckpointAnnotationRootfsImageName specifies the name of the root filesystem image associated with the checkpoint. + CheckpointAnnotationRootfsImageName = "org.criu.checkpoint.rootfsImageName" + + // CheckpointAnnotationRootfsImageUserRequested specifies the name of the root filesystem image requested by the user. + CheckpointAnnotationRootfsImageUserRequested = "org.criu.checkpoint.rootfsImageUserRequested" + + // CheckpointAnnotationRootfsImageSha specifies the SHA hash of the root filesystem image associated with the checkpoint. + CheckpointAnnotationRootfsImageSha = "org.criu.checkpoint.rootfsImageSha" + + // CheckpointAnnotationRootfsImageID specifies the ID of the root filesystem image associated with the checkpoint. + CheckpointAnnotationRootfsImageID = "org.criu.checkpoint.rootfsImageID" + + // CheckpointAnnotationRawImageName specifies the original unprocessed name of the image used to create the container. + CheckpointAnnotationRawImageName = "org.criu.checkpoint.rawImageName" + + // CheckpointAnnotationRuntimeName specifies the runtime used on the host where the checkpoint was created. + CheckpointAnnotationRuntimeName = "org.criu.checkpoint.runtime.name" + + // CheckpointAnnotationRuntimeVersion specifies the version of the runtime used on the host where the checkpoint was created. + CheckpointAnnotationRuntimeVersion = "org.criu.checkpoint.runtime.version" + + // CheckpointAnnotationCriuVersion specifies the version of CRIU used on the host where the checkpoint was created. + CheckpointAnnotationCriuVersion = "org.criu.checkpoint.criu.version" + + // CheckpointAnnotationConmonVersion specifies the version of conmon used on the host where the checkpoint was created. + CheckpointAnnotationConmonVersion = "org.criu.checkpoint.conmon.version" + + // CheckpointAnnotationHostArch specifies the CPU architecture of the host where the checkpoint was created. + CheckpointAnnotationHostArch = "org.criu.checkpoint.host.arch" + + // CheckpointAnnotationHostKernel specifies the kernel version used by the host where the checkpoint was created. + CheckpointAnnotationHostKernel = "org.criu.checkpoint.host.kernel" + + // CheckpointAnnotationCgroupVersion specifies the cgroup version used by the host where the checkpoint was created. + CheckpointAnnotationCgroupVersion = "org.criu.checkpoint.cgroup.version" + + // CheckpointAnnotationDistributionVersion specifies the name of the host distribution on which the checkpoint was created. + // This annotation is particularly useful because some distributions may include non-upstream patches + // that can cause CRIU (Checkpoint/Restore in Userspace) to fail. + CheckpointAnnotationDistributionName = "org.criu.checkpoint.distribution.name" + + // CheckpointAnnotationDistributionVersion specifies the version of the host distribution on which the checkpoint was created. + CheckpointAnnotationDistributionVersion = "org.criu.checkpoint.distribution.version" +) diff --git a/vendor/github.com/checkpoint-restore/checkpointctl/lib/metadata.go b/vendor/github.com/checkpoint-restore/checkpointctl/lib/metadata.go new file mode 100644 index 0000000000..1d68f887f9 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/checkpointctl/lib/metadata.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 + +package metadata + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +const ( + // container archive + ConfigDumpFile = "config.dump" + SpecDumpFile = "spec.dump" + StatusDumpFile = "status.dump" + NetworkStatusFile = "network.status" + CheckpointDirectory = "checkpoint" + CheckpointVolumesDirectory = "volumes" + DevShmCheckpointTar = "devshm-checkpoint.tar" + RootFsDiffTar = "rootfs-diff.tar" + DeletedFilesFile = "deleted.files" + DumpLogFile = "dump.log" + RestoreLogFile = "restore.log" + // pod archive + PodOptionsFile = "pod.options" + PodDumpFile = "pod.dump" + // containerd only + StatusFile = "status" + // CRIU Images + PagesPrefix = "pages-" + AmdgpuPagesPrefix = "amdgpu-pages-" +) + +// This is a reduced copy of what Podman uses to store checkpoint metadata +type ContainerConfig struct { + ID string `json:"id"` + Name string `json:"name"` + RootfsImage string `json:"rootfsImage,omitempty"` + RootfsImageRef string `json:"rootfsImageRef,omitempty"` + RootfsImageName string `json:"rootfsImageName,omitempty"` + OCIRuntime string `json:"runtime,omitempty"` + CreatedTime time.Time `json:"createdTime"` + CheckpointedAt time.Time `json:"checkpointedTime"` + RestoredAt time.Time `json:"restoredTime"` + Restored bool `json:"restored"` +} + +type Spec struct { + Annotations map[string]string `json:"annotations,omitempty"` +} + +type ContainerdStatus struct { + CreatedAt int64 + StartedAt int64 + FinishedAt int64 + ExitCode int32 + Pid uint32 + Reason string + Message string +} + +// This structure is used by the KubernetesContainerCheckpointMetadata structure +type KubernetesCheckpoint struct { + Archive string `json:"archive,omitempty"` + Size int64 `json:"size,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` +} + +// This structure is the basis for Kubernetes to track how many checkpoints +// for a certain container have been created. +type KubernetesContainerCheckpointMetadata struct { + PodFullName string `json:"podFullName,omitempty"` + ContainerName string `json:"containerName,omitempty"` + TotalSize int64 `json:"totalSize,omitempty"` + Checkpoints []KubernetesCheckpoint `json:"checkpoints"` +} + +func ReadContainerCheckpointSpecDump(checkpointDirectory string) (*spec.Spec, string, error) { + var specDump spec.Spec + specDumpFile, err := ReadJSONFile(&specDump, checkpointDirectory, SpecDumpFile) + + return &specDump, specDumpFile, err +} + +func ReadContainerCheckpointConfigDump(checkpointDirectory string) (*ContainerConfig, string, error) { + var containerConfig ContainerConfig + configDumpFile, err := ReadJSONFile(&containerConfig, checkpointDirectory, ConfigDumpFile) + + return &containerConfig, configDumpFile, err +} + +func ReadContainerCheckpointDeletedFiles(checkpointDirectory string) ([]string, string, error) { + var deletedFiles []string + deletedFilesFile, err := ReadJSONFile(&deletedFiles, checkpointDirectory, DeletedFilesFile) + + return deletedFiles, deletedFilesFile, err +} + +func ReadContainerCheckpointStatusFile(checkpointDirectory string) (*ContainerdStatus, string, error) { + var containerdStatus ContainerdStatus + statusFile, err := ReadJSONFile(&containerdStatus, checkpointDirectory, StatusFile) + + return &containerdStatus, statusFile, err +} + +// WriteJSONFile marshalls and writes the given data to a JSON file +func WriteJSONFile(v interface{}, dir, file string) (string, error) { + fileJSON, err := json.MarshalIndent(v, "", " ") + if err != nil { + return "", fmt.Errorf("error marshalling JSON: %w", err) + } + file = filepath.Join(dir, file) + if err := os.WriteFile(file, fileJSON, 0o600); err != nil { + return "", err + } + + return file, nil +} + +func ReadJSONFile(v interface{}, dir, file string) (string, error) { + file = filepath.Join(dir, file) + content, err := os.ReadFile(file) + if err != nil { + return "", err + } + if err = json.Unmarshal(content, v); err != nil { + return "", fmt.Errorf("failed to unmarshal %s: %w", file, err) + } + + return file, nil +} + +func ByteToString(b int64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + + return fmt.Sprintf("%.1f %ciB", + float64(b)/float64(div), "KMGTPE"[exp]) +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/bootstrap.pb.go b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/bootstrap.pb.go new file mode 100644 index 0000000000..831e052260 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/bootstrap.pb.go @@ -0,0 +1,621 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Bootstrap Protocol +// +// This protocol defines the interface between containerd and shims at startup. +// It replaces the previous scattered configuration mechanisms (CLI args, env vars, +// stdin JSON, spec.json annotations) with a single, versioned, extensible protocol. +// +// Flow: +// 1. containerd spawns the shim process +// 2. containerd writes BootstrapParams as JSON to shim's stdin +// 3. shim initializes and writes BootstrapResult as JSON to stdout +// 4. containerd connects to the address provided in BootstrapResult +// +// This design enables: +// - Forward/backward compatibility via version field +// - Typed extensibility via google.protobuf.Any and Extension +// - Clear capability negotiation between containerd and shims + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: runtime/bootstrap/v1/bootstrap.proto + +package bootstrap + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// LogLevel defines log verbosity. INFO = 0, more critical levels are +// positive, and more verbose levels are negative. This is an open enum; +// unknown numeric values are preserved on the wire. +type LogLevel int32 + +const ( + LogLevel_LOG_LEVEL_INFO LogLevel = 0 + LogLevel_LOG_LEVEL_TRACE LogLevel = -8 + LogLevel_LOG_LEVEL_DEBUG LogLevel = -4 + LogLevel_LOG_LEVEL_WARN LogLevel = 4 + LogLevel_LOG_LEVEL_ERROR LogLevel = 8 + LogLevel_LOG_LEVEL_FATAL LogLevel = 10 + LogLevel_LOG_LEVEL_PANIC LogLevel = 12 +) + +// Enum value maps for LogLevel. +var ( + LogLevel_name = map[int32]string{ + 0: "LOG_LEVEL_INFO", + -8: "LOG_LEVEL_TRACE", + -4: "LOG_LEVEL_DEBUG", + 4: "LOG_LEVEL_WARN", + 8: "LOG_LEVEL_ERROR", + 10: "LOG_LEVEL_FATAL", + 12: "LOG_LEVEL_PANIC", + } + LogLevel_value = map[string]int32{ + "LOG_LEVEL_INFO": 0, + "LOG_LEVEL_TRACE": -8, + "LOG_LEVEL_DEBUG": -4, + "LOG_LEVEL_WARN": 4, + "LOG_LEVEL_ERROR": 8, + "LOG_LEVEL_FATAL": 10, + "LOG_LEVEL_PANIC": 12, + } +) + +func (x LogLevel) Enum() *LogLevel { + p := new(LogLevel) + *p = x + return p +} + +func (x LogLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LogLevel) Descriptor() protoreflect.EnumDescriptor { + return file_runtime_bootstrap_v1_bootstrap_proto_enumTypes[0].Descriptor() +} + +func (LogLevel) Type() protoreflect.EnumType { + return &file_runtime_bootstrap_v1_bootstrap_proto_enumTypes[0] +} + +func (x LogLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LogLevel.Descriptor instead. +func (LogLevel) EnumDescriptor() ([]byte, []int) { + return file_runtime_bootstrap_v1_bootstrap_proto_rawDescGZIP(), []int{0} +} + +// Capability defines optional features that can be negotiated between +// containerd and shims. +type Capability int32 + +const ( + Capability_CAPABILITY_UNSPECIFIED Capability = 0 +) + +// Enum value maps for Capability. +var ( + Capability_name = map[int32]string{ + 0: "CAPABILITY_UNSPECIFIED", + } + Capability_value = map[string]int32{ + "CAPABILITY_UNSPECIFIED": 0, + } +) + +func (x Capability) Enum() *Capability { + p := new(Capability) + *p = x + return p +} + +func (x Capability) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Capability) Descriptor() protoreflect.EnumDescriptor { + return file_runtime_bootstrap_v1_bootstrap_proto_enumTypes[1].Descriptor() +} + +func (Capability) Type() protoreflect.EnumType { + return &file_runtime_bootstrap_v1_bootstrap_proto_enumTypes[1] +} + +func (x Capability) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Capability.Descriptor instead. +func (Capability) EnumDescriptor() ([]byte, []int) { + return file_runtime_bootstrap_v1_bootstrap_proto_rawDescGZIP(), []int{1} +} + +// BootstrapParams contains all configuration passed from containerd to shim at startup. +type BootstrapParams struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Container/sandbox ID + InstanceID string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + // Namespace for the container + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // Requested shim log level. + // INFO = 0, more critical levels are positive, and more verbose + // levels are negative. This is an open enum; shims should treat + // unrecognized values by mapping to the nearest known level. + LogLevel LogLevel `protobuf:"varint,3,opt,name=log_level,json=logLevel,proto3,enum=containerd.runtime.bootstrap.v1.LogLevel" json:"log_level,omitempty"` + // containerd daemon version that is launching this shim. + ContainerdVersion string `protobuf:"bytes,4,opt,name=containerd_version,json=containerdVersion,proto3" json:"containerd_version,omitempty"` + // Containerd's TTRPC API address (e.g., "unix:///run/containerd/containerd.sock.ttrpc") + ContainerdTtrpcAddress string `protobuf:"bytes,5,opt,name=containerd_ttrpc_address,json=containerdTtrpcAddress,proto3" json:"containerd_ttrpc_address,omitempty"` + // Containerd's gRPC API address (e.g., "unix:///run/containerd/containerd.sock") + ContainerdGrpcAddress string `protobuf:"bytes,6,opt,name=containerd_grpc_address,json=containerdGrpcAddress,proto3" json:"containerd_grpc_address,omitempty"` + // Path to containerd binary for event publishing + ContainerdBinary string `protobuf:"bytes,7,opt,name=containerd_binary,json=containerdBinary,proto3" json:"containerd_binary,omitempty"` + // Extensible configuration sections for new features + // Each section can contain arbitrary structured data identified by type URL + // Examples: CRI config, NRI config, sandbox config, etc. + Extensions []*Extension `protobuf:"bytes,8,rep,name=extensions,proto3" json:"extensions,omitempty"` + // Optional directory for the shim to place its unix socket. + // If empty, the shim defaults to a short, well-known path + // (e.g., /run/containerd/s). The path must be kept short because + // the socket filename is a 64-character SHA256 hash and unix + // socket paths are limited to 104-108 bytes depending on platform. + SocketDir *string `protobuf:"bytes,9,opt,name=socket_dir,json=socketDir,proto3,oneof" json:"socket_dir,omitempty"` +} + +func (x *BootstrapParams) Reset() { + *x = BootstrapParams{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BootstrapParams) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BootstrapParams) ProtoMessage() {} + +func (x *BootstrapParams) ProtoReflect() protoreflect.Message { + mi := &file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BootstrapParams.ProtoReflect.Descriptor instead. +func (*BootstrapParams) Descriptor() ([]byte, []int) { + return file_runtime_bootstrap_v1_bootstrap_proto_rawDescGZIP(), []int{0} +} + +func (x *BootstrapParams) GetInstanceID() string { + if x != nil { + return x.InstanceID + } + return "" +} + +func (x *BootstrapParams) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *BootstrapParams) GetLogLevel() LogLevel { + if x != nil { + return x.LogLevel + } + return LogLevel_LOG_LEVEL_INFO +} + +func (x *BootstrapParams) GetContainerdVersion() string { + if x != nil { + return x.ContainerdVersion + } + return "" +} + +func (x *BootstrapParams) GetContainerdTtrpcAddress() string { + if x != nil { + return x.ContainerdTtrpcAddress + } + return "" +} + +func (x *BootstrapParams) GetContainerdGrpcAddress() string { + if x != nil { + return x.ContainerdGrpcAddress + } + return "" +} + +func (x *BootstrapParams) GetContainerdBinary() string { + if x != nil { + return x.ContainerdBinary + } + return "" +} + +func (x *BootstrapParams) GetExtensions() []*Extension { + if x != nil { + return x.Extensions + } + return nil +} + +func (x *BootstrapParams) GetSocketDir() string { + if x != nil && x.SocketDir != nil { + return *x.SocketDir + } + return "" +} + +// Extension provides extensibility for new configuration types +// without changing the core BootstrapParams protocol +type Extension struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Configuration data with embedded type URL + // Examples of type URLs: + // - "containerd.io/cri.v1.PodSandboxConfig" + // - "containerd.io/nri.v1.PluginConfig" + // - "containerd.io/sandbox.v1.SandboxConfig" + Value *anypb.Any `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Extension) Reset() { + *x = Extension{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Extension) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Extension) ProtoMessage() {} + +func (x *Extension) ProtoReflect() protoreflect.Message { + mi := &file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Extension.ProtoReflect.Descriptor instead. +func (*Extension) Descriptor() ([]byte, []int) { + return file_runtime_bootstrap_v1_bootstrap_proto_rawDescGZIP(), []int{1} +} + +func (x *Extension) GetValue() *anypb.Any { + if x != nil { + return x.Value + } + return nil +} + +// BootstrapResult is returned by shim via stdout after successful startup +type BootstrapResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Version of shim parameters (expected 2 for shim v2) + Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + // Address where shim is listening (e.g., "unix:///run/containerd/shim.sock") + // Containerd will connect to this address for task operations + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + // Protocol used by shim: "ttrpc" or "grpc" + Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` + // Optional: Capabilities supported by this shim instance. + // Reserved for future use to allow optional capability negotiation + // between the daemon and shim. + Capabilities []Capability `protobuf:"varint,4,rep,packed,name=capabilities,proto3,enum=containerd.runtime.bootstrap.v1.Capability" json:"capabilities,omitempty"` + // Optional: Additional metadata from shim + Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *BootstrapResult) Reset() { + *x = BootstrapResult{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BootstrapResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BootstrapResult) ProtoMessage() {} + +func (x *BootstrapResult) ProtoReflect() protoreflect.Message { + mi := &file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BootstrapResult.ProtoReflect.Descriptor instead. +func (*BootstrapResult) Descriptor() ([]byte, []int) { + return file_runtime_bootstrap_v1_bootstrap_proto_rawDescGZIP(), []int{2} +} + +func (x *BootstrapResult) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *BootstrapResult) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *BootstrapResult) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +func (x *BootstrapResult) GetCapabilities() []Capability { + if x != nil { + return x.Capabilities + } + return nil +} + +func (x *BootstrapResult) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +var File_runtime_bootstrap_v1_bootstrap_proto protoreflect.FileDescriptor + +var file_runtime_bootstrap_v1_bootstrap_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, + 0x72, 0x61, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x73, + 0x74, 0x72, 0x61, 0x70, 0x2e, 0x76, 0x31, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xe5, 0x03, 0x0a, 0x0f, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x62, 0x6f, + 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x2d, 0x0a, + 0x12, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x18, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x5f, 0x74, 0x74, 0x72, 0x70, 0x63, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x54, 0x74, 0x72, 0x70, 0x63, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x47, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, + 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x5f, 0x62, 0x69, 0x6e, + 0x61, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x4a, 0x0a, 0x0a, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x76, + 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x65, 0x78, 0x74, + 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x6f, 0x63, 0x6b, 0x65, + 0x74, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x73, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x44, 0x69, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, + 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x22, 0x37, 0x0a, 0x09, 0x45, 0x78, + 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0xcb, 0x02, 0x0a, 0x0f, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, + 0x70, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x4f, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, + 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x2b, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0c, 0x63, 0x61, 0x70, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x5a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, + 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x2a, 0xad, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x12, + 0x0a, 0x0e, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x46, 0x4f, + 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, + 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, + 0x12, 0x1c, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x44, 0x45, + 0x42, 0x55, 0x47, 0x10, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x12, 0x12, + 0x0a, 0x0e, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x57, 0x41, 0x52, 0x4e, + 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, + 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x0a, 0x12, 0x13, 0x0a, 0x0f, + 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, + 0x0c, 0x2a, 0x28, 0x0a, 0x0a, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, + 0x1a, 0x0a, 0x16, 0x43, 0x41, 0x50, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x42, 0x45, 0x5a, 0x43, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x62, 0x6f, 0x6f, 0x74, + 0x73, 0x74, 0x72, 0x61, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, + 0x61, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_runtime_bootstrap_v1_bootstrap_proto_rawDescOnce sync.Once + file_runtime_bootstrap_v1_bootstrap_proto_rawDescData = file_runtime_bootstrap_v1_bootstrap_proto_rawDesc +) + +func file_runtime_bootstrap_v1_bootstrap_proto_rawDescGZIP() []byte { + file_runtime_bootstrap_v1_bootstrap_proto_rawDescOnce.Do(func() { + file_runtime_bootstrap_v1_bootstrap_proto_rawDescData = protoimpl.X.CompressGZIP(file_runtime_bootstrap_v1_bootstrap_proto_rawDescData) + }) + return file_runtime_bootstrap_v1_bootstrap_proto_rawDescData +} + +var file_runtime_bootstrap_v1_bootstrap_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_runtime_bootstrap_v1_bootstrap_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_runtime_bootstrap_v1_bootstrap_proto_goTypes = []interface{}{ + (LogLevel)(0), // 0: containerd.runtime.bootstrap.v1.LogLevel + (Capability)(0), // 1: containerd.runtime.bootstrap.v1.Capability + (*BootstrapParams)(nil), // 2: containerd.runtime.bootstrap.v1.BootstrapParams + (*Extension)(nil), // 3: containerd.runtime.bootstrap.v1.Extension + (*BootstrapResult)(nil), // 4: containerd.runtime.bootstrap.v1.BootstrapResult + nil, // 5: containerd.runtime.bootstrap.v1.BootstrapResult.MetadataEntry + (*anypb.Any)(nil), // 6: google.protobuf.Any +} +var file_runtime_bootstrap_v1_bootstrap_proto_depIdxs = []int32{ + 0, // 0: containerd.runtime.bootstrap.v1.BootstrapParams.log_level:type_name -> containerd.runtime.bootstrap.v1.LogLevel + 3, // 1: containerd.runtime.bootstrap.v1.BootstrapParams.extensions:type_name -> containerd.runtime.bootstrap.v1.Extension + 6, // 2: containerd.runtime.bootstrap.v1.Extension.value:type_name -> google.protobuf.Any + 1, // 3: containerd.runtime.bootstrap.v1.BootstrapResult.capabilities:type_name -> containerd.runtime.bootstrap.v1.Capability + 5, // 4: containerd.runtime.bootstrap.v1.BootstrapResult.metadata:type_name -> containerd.runtime.bootstrap.v1.BootstrapResult.MetadataEntry + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_runtime_bootstrap_v1_bootstrap_proto_init() } +func file_runtime_bootstrap_v1_bootstrap_proto_init() { + if File_runtime_bootstrap_v1_bootstrap_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BootstrapParams); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Extension); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BootstrapResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_runtime_bootstrap_v1_bootstrap_proto_msgTypes[0].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_runtime_bootstrap_v1_bootstrap_proto_rawDesc, + NumEnums: 2, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_runtime_bootstrap_v1_bootstrap_proto_goTypes, + DependencyIndexes: file_runtime_bootstrap_v1_bootstrap_proto_depIdxs, + EnumInfos: file_runtime_bootstrap_v1_bootstrap_proto_enumTypes, + MessageInfos: file_runtime_bootstrap_v1_bootstrap_proto_msgTypes, + }.Build() + File_runtime_bootstrap_v1_bootstrap_proto = out.File + file_runtime_bootstrap_v1_bootstrap_proto_rawDesc = nil + file_runtime_bootstrap_v1_bootstrap_proto_goTypes = nil + file_runtime_bootstrap_v1_bootstrap_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/bootstrap.proto b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/bootstrap.proto new file mode 100644 index 0000000000..6320f7f645 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/bootstrap.proto @@ -0,0 +1,130 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Bootstrap Protocol +// +// This protocol defines the interface between containerd and shims at startup. +// It replaces the previous scattered configuration mechanisms (CLI args, env vars, +// stdin JSON, spec.json annotations) with a single, versioned, extensible protocol. +// +// Flow: +// 1. containerd spawns the shim process +// 2. containerd writes BootstrapParams as JSON to shim's stdin +// 3. shim initializes and writes BootstrapResult as JSON to stdout +// 4. containerd connects to the address provided in BootstrapResult +// +// This design enables: +// - Forward/backward compatibility via version field +// - Typed extensibility via google.protobuf.Any and Extension +// - Clear capability negotiation between containerd and shims + +syntax = "proto3"; + +package containerd.runtime.bootstrap.v1; + +import "google/protobuf/any.proto"; + +option go_package = "github.com/containerd/containerd/api/runtime/bootstrap/v1;bootstrap"; + +// BootstrapParams contains all configuration passed from containerd to shim at startup. +message BootstrapParams { + // Container/sandbox ID + string instance_id = 1; + + // Namespace for the container + string namespace = 2; + + // Requested shim log level. + // INFO = 0, more critical levels are positive, and more verbose + // levels are negative. This is an open enum; shims should treat + // unrecognized values by mapping to the nearest known level. + LogLevel log_level = 3; + + // containerd daemon version that is launching this shim. + string containerd_version = 4; + + // Containerd's TTRPC API address (e.g., "unix:///run/containerd/containerd.sock.ttrpc") + string containerd_ttrpc_address = 5; + + // Containerd's gRPC API address (e.g., "unix:///run/containerd/containerd.sock") + string containerd_grpc_address = 6; + + // Path to containerd binary for event publishing + string containerd_binary = 7; + + // Extensible configuration sections for new features + // Each section can contain arbitrary structured data identified by type URL + // Examples: CRI config, NRI config, sandbox config, etc. + repeated Extension extensions = 8; + + // Optional directory for the shim to place its unix socket. + // If empty, the shim defaults to a short, well-known path + // (e.g., /run/containerd/s). The path must be kept short because + // the socket filename is a 64-character SHA256 hash and unix + // socket paths are limited to 104-108 bytes depending on platform. + optional string socket_dir = 9; +} + +// Extension provides extensibility for new configuration types +// without changing the core BootstrapParams protocol +message Extension { + // Configuration data with embedded type URL + // Examples of type URLs: + // - "containerd.io/cri.v1.PodSandboxConfig" + // - "containerd.io/nri.v1.PluginConfig" + // - "containerd.io/sandbox.v1.SandboxConfig" + google.protobuf.Any value = 1; +} + +// BootstrapResult is returned by shim via stdout after successful startup +message BootstrapResult { + // Version of shim parameters (expected 2 for shim v2) + int32 version = 1; + + // Address where shim is listening (e.g., "unix:///run/containerd/shim.sock") + // Containerd will connect to this address for task operations + string address = 2; + + // Protocol used by shim: "ttrpc" or "grpc" + string protocol = 3; + + // Optional: Capabilities supported by this shim instance. + // Reserved for future use to allow optional capability negotiation + // between the daemon and shim. + repeated Capability capabilities = 4; + + // Optional: Additional metadata from shim + map metadata = 5; +} + +// LogLevel defines log verbosity. INFO = 0, more critical levels are +// positive, and more verbose levels are negative. This is an open enum; +// unknown numeric values are preserved on the wire. +enum LogLevel { + LOG_LEVEL_INFO = 0; + LOG_LEVEL_TRACE = -8; + LOG_LEVEL_DEBUG = -4; + LOG_LEVEL_WARN = 4; + LOG_LEVEL_ERROR = 8; + LOG_LEVEL_FATAL = 10; + LOG_LEVEL_PANIC = 12; +} + +// Capability defines optional features that can be negotiated between +// containerd and shims. +enum Capability { + CAPABILITY_UNSPECIFIED = 0; +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/doc.go b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/doc.go new file mode 100644 index 0000000000..c6d9c66aa0 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/doc.go @@ -0,0 +1,17 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package bootstrap diff --git a/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/helpers.go b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/helpers.go new file mode 100644 index 0000000000..f73e571713 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/bootstrap/v1/helpers.go @@ -0,0 +1,91 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package bootstrap + +import ( + "fmt" + "strconv" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +// LogLevelFromString converts a log level string (e.g. "debug", "info") to a +// LogLevel enum value. The accepted strings are compatible with logrus level names. +func LogLevelFromString(s string) LogLevel { + switch s { + case "trace": + return LogLevel_LOG_LEVEL_TRACE + case "debug": + return LogLevel_LOG_LEVEL_DEBUG + case "info": + return LogLevel_LOG_LEVEL_INFO + case "warn", "warning": + return LogLevel_LOG_LEVEL_WARN + case "error": + return LogLevel_LOG_LEVEL_ERROR + case "fatal": + return LogLevel_LOG_LEVEL_FATAL + case "panic": + return LogLevel_LOG_LEVEL_PANIC + default: + if v, err := strconv.ParseInt(s, 10, 32); err == nil { + return LogLevel(v) + } + return LogLevel_LOG_LEVEL_INFO + } +} + +// AddExtension adds a new extension to the BootstrapParams. +// The message is wrapped in a google.protobuf.Any with its type URL automatically set. +// If the message is already an *anypb.Any, it is used directly without double-wrapping. +func (p *BootstrapParams) AddExtension(msg proto.Message) error { + var anyVal *anypb.Any + if a, ok := msg.(*anypb.Any); ok { + // Already an Any, use it directly + anyVal = a + } else { + var err error + anyVal, err = anypb.New(msg) + if err != nil { + return err + } + } + + p.Extensions = append(p.Extensions, &Extension{Value: anyVal}) + return nil +} + +// FindExtension finds an extension matching the type of dst and unmarshals it. +func (p *BootstrapParams) FindExtension(dst proto.Message) (bool, error) { + if p == nil { + return false, nil + } + + name := dst.ProtoReflect().Descriptor().FullName() + + for _, ext := range p.Extensions { + if ext.GetValue().MessageIs(dst) { + if err := ext.GetValue().UnmarshalTo(dst); err != nil { + return false, fmt.Errorf("failed to unmarshal extension %q: %w", name, err) + } + return true, nil + } + } + + return false, nil +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/doc.go b/vendor/github.com/containerd/containerd/api/runtime/task/v2/doc.go new file mode 100644 index 0000000000..f933dd8d41 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/doc.go @@ -0,0 +1,17 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package task diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.pb.go b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.pb.go new file mode 100644 index 0000000000..e62f4e70d5 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.pb.go @@ -0,0 +1,2331 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: runtime/task/v2/shim.proto + +package task + +import ( + types "github.com/containerd/containerd/api/types" + task "github.com/containerd/containerd/api/types/task" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CreateTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Bundle string `protobuf:"bytes,2,opt,name=bundle,proto3" json:"bundle,omitempty"` + Rootfs []*types.Mount `protobuf:"bytes,3,rep,name=rootfs,proto3" json:"rootfs,omitempty"` + Terminal bool `protobuf:"varint,4,opt,name=terminal,proto3" json:"terminal,omitempty"` + Stdin string `protobuf:"bytes,5,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,6,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,7,opt,name=stderr,proto3" json:"stderr,omitempty"` + Checkpoint string `protobuf:"bytes,8,opt,name=checkpoint,proto3" json:"checkpoint,omitempty"` + ParentCheckpoint string `protobuf:"bytes,9,opt,name=parent_checkpoint,json=parentCheckpoint,proto3" json:"parent_checkpoint,omitempty"` + Options *anypb.Any `protobuf:"bytes,10,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *CreateTaskRequest) Reset() { + *x = CreateTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskRequest) ProtoMessage() {} + +func (x *CreateTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskRequest.ProtoReflect.Descriptor instead. +func (*CreateTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{0} +} + +func (x *CreateTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CreateTaskRequest) GetBundle() string { + if x != nil { + return x.Bundle + } + return "" +} + +func (x *CreateTaskRequest) GetRootfs() []*types.Mount { + if x != nil { + return x.Rootfs + } + return nil +} + +func (x *CreateTaskRequest) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *CreateTaskRequest) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *CreateTaskRequest) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *CreateTaskRequest) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *CreateTaskRequest) GetCheckpoint() string { + if x != nil { + return x.Checkpoint + } + return "" +} + +func (x *CreateTaskRequest) GetParentCheckpoint() string { + if x != nil { + return x.ParentCheckpoint + } + return "" +} + +func (x *CreateTaskRequest) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +type CreateTaskResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` +} + +func (x *CreateTaskResponse) Reset() { + *x = CreateTaskResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTaskResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskResponse) ProtoMessage() {} + +func (x *CreateTaskResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskResponse.ProtoReflect.Descriptor instead. +func (*CreateTaskResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateTaskResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +type DeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{2} +} + +func (x *DeleteRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *DeleteRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type DeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` + ExitStatus uint32 `protobuf:"varint,2,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` +} + +func (x *DeleteResponse) Reset() { + *x = DeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResponse) ProtoMessage() {} + +func (x *DeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{3} +} + +func (x *DeleteResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *DeleteResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *DeleteResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +type ExecProcessRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Terminal bool `protobuf:"varint,3,opt,name=terminal,proto3" json:"terminal,omitempty"` + Stdin string `protobuf:"bytes,4,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,5,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,6,opt,name=stderr,proto3" json:"stderr,omitempty"` + Spec *anypb.Any `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` +} + +func (x *ExecProcessRequest) Reset() { + *x = ExecProcessRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecProcessRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecProcessRequest) ProtoMessage() {} + +func (x *ExecProcessRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecProcessRequest.ProtoReflect.Descriptor instead. +func (*ExecProcessRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{4} +} + +func (x *ExecProcessRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ExecProcessRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *ExecProcessRequest) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *ExecProcessRequest) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *ExecProcessRequest) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *ExecProcessRequest) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *ExecProcessRequest) GetSpec() *anypb.Any { + if x != nil { + return x.Spec + } + return nil +} + +type ExecProcessResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ExecProcessResponse) Reset() { + *x = ExecProcessResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecProcessResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecProcessResponse) ProtoMessage() {} + +func (x *ExecProcessResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecProcessResponse.ProtoReflect.Descriptor instead. +func (*ExecProcessResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{5} +} + +type ResizePtyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Width uint32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` + Height uint32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` +} + +func (x *ResizePtyRequest) Reset() { + *x = ResizePtyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResizePtyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResizePtyRequest) ProtoMessage() {} + +func (x *ResizePtyRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResizePtyRequest.ProtoReflect.Descriptor instead. +func (*ResizePtyRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{6} +} + +func (x *ResizePtyRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ResizePtyRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *ResizePtyRequest) GetWidth() uint32 { + if x != nil { + return x.Width + } + return 0 +} + +func (x *ResizePtyRequest) GetHeight() uint32 { + if x != nil { + return x.Height + } + return 0 +} + +type StateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StateRequest) Reset() { + *x = StateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateRequest) ProtoMessage() {} + +func (x *StateRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateRequest.ProtoReflect.Descriptor instead. +func (*StateRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{7} +} + +func (x *StateRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StateRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type StateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Bundle string `protobuf:"bytes,2,opt,name=bundle,proto3" json:"bundle,omitempty"` + Pid uint32 `protobuf:"varint,3,opt,name=pid,proto3" json:"pid,omitempty"` + Status task.Status `protobuf:"varint,4,opt,name=status,proto3,enum=containerd.v1.types.Status" json:"status,omitempty"` + Stdin string `protobuf:"bytes,5,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,6,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,7,opt,name=stderr,proto3" json:"stderr,omitempty"` + Terminal bool `protobuf:"varint,8,opt,name=terminal,proto3" json:"terminal,omitempty"` + ExitStatus uint32 `protobuf:"varint,9,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` + ExecID string `protobuf:"bytes,11,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StateResponse) Reset() { + *x = StateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateResponse) ProtoMessage() {} + +func (x *StateResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateResponse.ProtoReflect.Descriptor instead. +func (*StateResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{8} +} + +func (x *StateResponse) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StateResponse) GetBundle() string { + if x != nil { + return x.Bundle + } + return "" +} + +func (x *StateResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *StateResponse) GetStatus() task.Status { + if x != nil { + return x.Status + } + return task.Status(0) +} + +func (x *StateResponse) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *StateResponse) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *StateResponse) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *StateResponse) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *StateResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *StateResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +func (x *StateResponse) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type KillRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Signal uint32 `protobuf:"varint,3,opt,name=signal,proto3" json:"signal,omitempty"` + All bool `protobuf:"varint,4,opt,name=all,proto3" json:"all,omitempty"` +} + +func (x *KillRequest) Reset() { + *x = KillRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KillRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KillRequest) ProtoMessage() {} + +func (x *KillRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KillRequest.ProtoReflect.Descriptor instead. +func (*KillRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{9} +} + +func (x *KillRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *KillRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *KillRequest) GetSignal() uint32 { + if x != nil { + return x.Signal + } + return 0 +} + +func (x *KillRequest) GetAll() bool { + if x != nil { + return x.All + } + return false +} + +type CloseIORequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Stdin bool `protobuf:"varint,3,opt,name=stdin,proto3" json:"stdin,omitempty"` +} + +func (x *CloseIORequest) Reset() { + *x = CloseIORequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseIORequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseIORequest) ProtoMessage() {} + +func (x *CloseIORequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseIORequest.ProtoReflect.Descriptor instead. +func (*CloseIORequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{10} +} + +func (x *CloseIORequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CloseIORequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *CloseIORequest) GetStdin() bool { + if x != nil { + return x.Stdin + } + return false +} + +type PidsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *PidsRequest) Reset() { + *x = PidsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PidsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PidsRequest) ProtoMessage() {} + +func (x *PidsRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PidsRequest.ProtoReflect.Descriptor instead. +func (*PidsRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{11} +} + +func (x *PidsRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type PidsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Processes []*task.ProcessInfo `protobuf:"bytes,1,rep,name=processes,proto3" json:"processes,omitempty"` +} + +func (x *PidsResponse) Reset() { + *x = PidsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PidsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PidsResponse) ProtoMessage() {} + +func (x *PidsResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PidsResponse.ProtoReflect.Descriptor instead. +func (*PidsResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{12} +} + +func (x *PidsResponse) GetProcesses() []*task.ProcessInfo { + if x != nil { + return x.Processes + } + return nil +} + +type CheckpointTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Options *anypb.Any `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *CheckpointTaskRequest) Reset() { + *x = CheckpointTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckpointTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckpointTaskRequest) ProtoMessage() {} + +func (x *CheckpointTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckpointTaskRequest.ProtoReflect.Descriptor instead. +func (*CheckpointTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{13} +} + +func (x *CheckpointTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CheckpointTaskRequest) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *CheckpointTaskRequest) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +type UpdateTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Resources *anypb.Any `protobuf:"bytes,2,opt,name=resources,proto3" json:"resources,omitempty"` + Annotations map[string]string `protobuf:"bytes,3,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *UpdateTaskRequest) Reset() { + *x = UpdateTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTaskRequest) ProtoMessage() {} + +func (x *UpdateTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTaskRequest.ProtoReflect.Descriptor instead. +func (*UpdateTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{14} +} + +func (x *UpdateTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *UpdateTaskRequest) GetResources() *anypb.Any { + if x != nil { + return x.Resources + } + return nil +} + +func (x *UpdateTaskRequest) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +type StartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StartRequest) Reset() { + *x = StartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartRequest) ProtoMessage() {} + +func (x *StartRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartRequest.ProtoReflect.Descriptor instead. +func (*StartRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{15} +} + +func (x *StartRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StartRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type StartResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` +} + +func (x *StartResponse) Reset() { + *x = StartResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartResponse) ProtoMessage() {} + +func (x *StartResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartResponse.ProtoReflect.Descriptor instead. +func (*StartResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{16} +} + +func (x *StartResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +type WaitRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *WaitRequest) Reset() { + *x = WaitRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WaitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitRequest) ProtoMessage() {} + +func (x *WaitRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitRequest.ProtoReflect.Descriptor instead. +func (*WaitRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{17} +} + +func (x *WaitRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *WaitRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type WaitResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ExitStatus uint32 `protobuf:"varint,1,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` +} + +func (x *WaitResponse) Reset() { + *x = WaitResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WaitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitResponse) ProtoMessage() {} + +func (x *WaitResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitResponse.ProtoReflect.Descriptor instead. +func (*WaitResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{18} +} + +func (x *WaitResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *WaitResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +type StatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *StatsRequest) Reset() { + *x = StatsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsRequest) ProtoMessage() {} + +func (x *StatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead. +func (*StatsRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{19} +} + +func (x *StatsRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type StatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats *anypb.Any `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *StatsResponse) Reset() { + *x = StatsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsResponse) ProtoMessage() {} + +func (x *StatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead. +func (*StatsResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{20} +} + +func (x *StatsResponse) GetStats() *anypb.Any { + if x != nil { + return x.Stats + } + return nil +} + +type ConnectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ConnectRequest) Reset() { + *x = ConnectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectRequest) ProtoMessage() {} + +func (x *ConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead. +func (*ConnectRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{21} +} + +func (x *ConnectRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type ConnectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ShimPid uint32 `protobuf:"varint,1,opt,name=shim_pid,json=shimPid,proto3" json:"shim_pid,omitempty"` + TaskPid uint32 `protobuf:"varint,2,opt,name=task_pid,json=taskPid,proto3" json:"task_pid,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *ConnectResponse) Reset() { + *x = ConnectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectResponse) ProtoMessage() {} + +func (x *ConnectResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectResponse.ProtoReflect.Descriptor instead. +func (*ConnectResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{22} +} + +func (x *ConnectResponse) GetShimPid() uint32 { + if x != nil { + return x.ShimPid + } + return 0 +} + +func (x *ConnectResponse) GetTaskPid() uint32 { + if x != nil { + return x.TaskPid + } + return 0 +} + +func (x *ConnectResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type ShutdownRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Now bool `protobuf:"varint,2,opt,name=now,proto3" json:"now,omitempty"` +} + +func (x *ShutdownRequest) Reset() { + *x = ShutdownRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShutdownRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShutdownRequest) ProtoMessage() {} + +func (x *ShutdownRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShutdownRequest.ProtoReflect.Descriptor instead. +func (*ShutdownRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{23} +} + +func (x *ShutdownRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ShutdownRequest) GetNow() bool { + if x != nil { + return x.Now + } + return false +} + +type PauseRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *PauseRequest) Reset() { + *x = PauseRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PauseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PauseRequest) ProtoMessage() {} + +func (x *PauseRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PauseRequest.ProtoReflect.Descriptor instead. +func (*PauseRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{24} +} + +func (x *PauseRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type ResumeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ResumeRequest) Reset() { + *x = ResumeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResumeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResumeRequest) ProtoMessage() {} + +func (x *ResumeRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResumeRequest.ProtoReflect.Descriptor instead. +func (*ResumeRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{25} +} + +func (x *ResumeRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +var File_runtime_task_v2_shim_proto protoreflect.FileDescriptor + +var file_runtime_task_v2_shim_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x76, + 0x32, 0x2f, 0x73, 0x68, 0x69, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, + 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, + 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xcb, 0x02, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x74, + 0x66, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, + 0x74, 0x64, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x64, 0x65, 0x72, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x10, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x26, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x38, 0x0a, 0x0d, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, + 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, + 0x63, 0x49, 0x64, 0x22, 0x7c, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, + 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x22, 0xc9, 0x01, 0x0a, 0x12, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, + 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, + 0x64, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, + 0x65, 0x72, 0x72, 0x12, 0x28, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x15, 0x0a, + 0x13, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x69, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, + 0x37, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0xd3, 0x02, 0x0a, 0x0d, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x03, 0x70, 0x69, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x64, + 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, + 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, + 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x69, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x60, + 0x0a, 0x0b, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x12, 0x10, + 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6c, 0x6c, + 0x22, 0x4f, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x64, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, + 0x6e, 0x22, 0x1d, 0x0a, 0x0b, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x4e, 0x0a, 0x0c, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x22, 0x6b, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, + 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf1, 0x01, + 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, + 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x37, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x21, 0x0a, 0x0d, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x36, 0x0a, + 0x0b, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, + 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x68, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, + 0x1e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x3b, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2a, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x20, 0x0a, 0x0e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x61, + 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x68, 0x69, 0x6d, 0x50, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x74, 0x61, 0x73, 0x6b, 0x50, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x33, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x03, 0x6e, 0x6f, 0x77, 0x22, 0x1e, 0x0a, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0x8a, 0x0a, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, + 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, + 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, + 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x04, 0x50, 0x69, 0x64, 0x73, 0x12, 0x1f, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x41, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, + 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x21, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4f, 0x0a, 0x0a, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x29, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x04, 0x4b, 0x69, + 0x6c, 0x6c, 0x12, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x46, 0x0a, 0x04, 0x45, + 0x78, 0x65, 0x63, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x09, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, 0x79, + 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, + 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, + 0x0a, 0x07, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, + 0x0a, 0x04, 0x57, 0x61, 0x69, 0x74, 0x12, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x61, 0x69, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x61, 0x69, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x53, + 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x68, 0x75, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x76, 0x32, 0x3b, 0x74, 0x61, 0x73, + 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_runtime_task_v2_shim_proto_rawDescOnce sync.Once + file_runtime_task_v2_shim_proto_rawDescData = file_runtime_task_v2_shim_proto_rawDesc +) + +func file_runtime_task_v2_shim_proto_rawDescGZIP() []byte { + file_runtime_task_v2_shim_proto_rawDescOnce.Do(func() { + file_runtime_task_v2_shim_proto_rawDescData = protoimpl.X.CompressGZIP(file_runtime_task_v2_shim_proto_rawDescData) + }) + return file_runtime_task_v2_shim_proto_rawDescData +} + +var file_runtime_task_v2_shim_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_runtime_task_v2_shim_proto_goTypes = []interface{}{ + (*CreateTaskRequest)(nil), // 0: containerd.task.v2.CreateTaskRequest + (*CreateTaskResponse)(nil), // 1: containerd.task.v2.CreateTaskResponse + (*DeleteRequest)(nil), // 2: containerd.task.v2.DeleteRequest + (*DeleteResponse)(nil), // 3: containerd.task.v2.DeleteResponse + (*ExecProcessRequest)(nil), // 4: containerd.task.v2.ExecProcessRequest + (*ExecProcessResponse)(nil), // 5: containerd.task.v2.ExecProcessResponse + (*ResizePtyRequest)(nil), // 6: containerd.task.v2.ResizePtyRequest + (*StateRequest)(nil), // 7: containerd.task.v2.StateRequest + (*StateResponse)(nil), // 8: containerd.task.v2.StateResponse + (*KillRequest)(nil), // 9: containerd.task.v2.KillRequest + (*CloseIORequest)(nil), // 10: containerd.task.v2.CloseIORequest + (*PidsRequest)(nil), // 11: containerd.task.v2.PidsRequest + (*PidsResponse)(nil), // 12: containerd.task.v2.PidsResponse + (*CheckpointTaskRequest)(nil), // 13: containerd.task.v2.CheckpointTaskRequest + (*UpdateTaskRequest)(nil), // 14: containerd.task.v2.UpdateTaskRequest + (*StartRequest)(nil), // 15: containerd.task.v2.StartRequest + (*StartResponse)(nil), // 16: containerd.task.v2.StartResponse + (*WaitRequest)(nil), // 17: containerd.task.v2.WaitRequest + (*WaitResponse)(nil), // 18: containerd.task.v2.WaitResponse + (*StatsRequest)(nil), // 19: containerd.task.v2.StatsRequest + (*StatsResponse)(nil), // 20: containerd.task.v2.StatsResponse + (*ConnectRequest)(nil), // 21: containerd.task.v2.ConnectRequest + (*ConnectResponse)(nil), // 22: containerd.task.v2.ConnectResponse + (*ShutdownRequest)(nil), // 23: containerd.task.v2.ShutdownRequest + (*PauseRequest)(nil), // 24: containerd.task.v2.PauseRequest + (*ResumeRequest)(nil), // 25: containerd.task.v2.ResumeRequest + nil, // 26: containerd.task.v2.UpdateTaskRequest.AnnotationsEntry + (*types.Mount)(nil), // 27: containerd.types.Mount + (*anypb.Any)(nil), // 28: google.protobuf.Any + (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp + (task.Status)(0), // 30: containerd.v1.types.Status + (*task.ProcessInfo)(nil), // 31: containerd.v1.types.ProcessInfo + (*emptypb.Empty)(nil), // 32: google.protobuf.Empty +} +var file_runtime_task_v2_shim_proto_depIdxs = []int32{ + 27, // 0: containerd.task.v2.CreateTaskRequest.rootfs:type_name -> containerd.types.Mount + 28, // 1: containerd.task.v2.CreateTaskRequest.options:type_name -> google.protobuf.Any + 29, // 2: containerd.task.v2.DeleteResponse.exited_at:type_name -> google.protobuf.Timestamp + 28, // 3: containerd.task.v2.ExecProcessRequest.spec:type_name -> google.protobuf.Any + 30, // 4: containerd.task.v2.StateResponse.status:type_name -> containerd.v1.types.Status + 29, // 5: containerd.task.v2.StateResponse.exited_at:type_name -> google.protobuf.Timestamp + 31, // 6: containerd.task.v2.PidsResponse.processes:type_name -> containerd.v1.types.ProcessInfo + 28, // 7: containerd.task.v2.CheckpointTaskRequest.options:type_name -> google.protobuf.Any + 28, // 8: containerd.task.v2.UpdateTaskRequest.resources:type_name -> google.protobuf.Any + 26, // 9: containerd.task.v2.UpdateTaskRequest.annotations:type_name -> containerd.task.v2.UpdateTaskRequest.AnnotationsEntry + 29, // 10: containerd.task.v2.WaitResponse.exited_at:type_name -> google.protobuf.Timestamp + 28, // 11: containerd.task.v2.StatsResponse.stats:type_name -> google.protobuf.Any + 7, // 12: containerd.task.v2.Task.State:input_type -> containerd.task.v2.StateRequest + 0, // 13: containerd.task.v2.Task.Create:input_type -> containerd.task.v2.CreateTaskRequest + 15, // 14: containerd.task.v2.Task.Start:input_type -> containerd.task.v2.StartRequest + 2, // 15: containerd.task.v2.Task.Delete:input_type -> containerd.task.v2.DeleteRequest + 11, // 16: containerd.task.v2.Task.Pids:input_type -> containerd.task.v2.PidsRequest + 24, // 17: containerd.task.v2.Task.Pause:input_type -> containerd.task.v2.PauseRequest + 25, // 18: containerd.task.v2.Task.Resume:input_type -> containerd.task.v2.ResumeRequest + 13, // 19: containerd.task.v2.Task.Checkpoint:input_type -> containerd.task.v2.CheckpointTaskRequest + 9, // 20: containerd.task.v2.Task.Kill:input_type -> containerd.task.v2.KillRequest + 4, // 21: containerd.task.v2.Task.Exec:input_type -> containerd.task.v2.ExecProcessRequest + 6, // 22: containerd.task.v2.Task.ResizePty:input_type -> containerd.task.v2.ResizePtyRequest + 10, // 23: containerd.task.v2.Task.CloseIO:input_type -> containerd.task.v2.CloseIORequest + 14, // 24: containerd.task.v2.Task.Update:input_type -> containerd.task.v2.UpdateTaskRequest + 17, // 25: containerd.task.v2.Task.Wait:input_type -> containerd.task.v2.WaitRequest + 19, // 26: containerd.task.v2.Task.Stats:input_type -> containerd.task.v2.StatsRequest + 21, // 27: containerd.task.v2.Task.Connect:input_type -> containerd.task.v2.ConnectRequest + 23, // 28: containerd.task.v2.Task.Shutdown:input_type -> containerd.task.v2.ShutdownRequest + 8, // 29: containerd.task.v2.Task.State:output_type -> containerd.task.v2.StateResponse + 1, // 30: containerd.task.v2.Task.Create:output_type -> containerd.task.v2.CreateTaskResponse + 16, // 31: containerd.task.v2.Task.Start:output_type -> containerd.task.v2.StartResponse + 3, // 32: containerd.task.v2.Task.Delete:output_type -> containerd.task.v2.DeleteResponse + 12, // 33: containerd.task.v2.Task.Pids:output_type -> containerd.task.v2.PidsResponse + 32, // 34: containerd.task.v2.Task.Pause:output_type -> google.protobuf.Empty + 32, // 35: containerd.task.v2.Task.Resume:output_type -> google.protobuf.Empty + 32, // 36: containerd.task.v2.Task.Checkpoint:output_type -> google.protobuf.Empty + 32, // 37: containerd.task.v2.Task.Kill:output_type -> google.protobuf.Empty + 32, // 38: containerd.task.v2.Task.Exec:output_type -> google.protobuf.Empty + 32, // 39: containerd.task.v2.Task.ResizePty:output_type -> google.protobuf.Empty + 32, // 40: containerd.task.v2.Task.CloseIO:output_type -> google.protobuf.Empty + 32, // 41: containerd.task.v2.Task.Update:output_type -> google.protobuf.Empty + 18, // 42: containerd.task.v2.Task.Wait:output_type -> containerd.task.v2.WaitResponse + 20, // 43: containerd.task.v2.Task.Stats:output_type -> containerd.task.v2.StatsResponse + 22, // 44: containerd.task.v2.Task.Connect:output_type -> containerd.task.v2.ConnectResponse + 32, // 45: containerd.task.v2.Task.Shutdown:output_type -> google.protobuf.Empty + 29, // [29:46] is the sub-list for method output_type + 12, // [12:29] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_runtime_task_v2_shim_proto_init() } +func file_runtime_task_v2_shim_proto_init() { + if File_runtime_task_v2_shim_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_runtime_task_v2_shim_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTaskResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecProcessRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecProcessResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResizePtyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KillRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseIORequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PidsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PidsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckpointTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WaitRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WaitResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShutdownRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PauseRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResumeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_runtime_task_v2_shim_proto_rawDesc, + NumEnums: 0, + NumMessages: 27, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_runtime_task_v2_shim_proto_goTypes, + DependencyIndexes: file_runtime_task_v2_shim_proto_depIdxs, + MessageInfos: file_runtime_task_v2_shim_proto_msgTypes, + }.Build() + File_runtime_task_v2_shim_proto = out.File + file_runtime_task_v2_shim_proto_rawDesc = nil + file_runtime_task_v2_shim_proto_goTypes = nil + file_runtime_task_v2_shim_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.proto b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.proto new file mode 100644 index 0000000000..6d9c36e03b --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.proto @@ -0,0 +1,200 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.task.v2; + +import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "types/mount.proto"; +import "types/task/task.proto"; + +option go_package = "github.com/containerd/containerd/api/runtime/task/v2;task"; + +// Shim service is launched for each container and is responsible for owning the IO +// for the container and its additional processes. The shim is also the parent of +// each container and allows reattaching to the IO and receiving the exit status +// for the container processes. +service Task { + rpc State(StateRequest) returns (StateResponse); + rpc Create(CreateTaskRequest) returns (CreateTaskResponse); + rpc Start(StartRequest) returns (StartResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc Pids(PidsRequest) returns (PidsResponse); + rpc Pause(PauseRequest) returns (google.protobuf.Empty); + rpc Resume(ResumeRequest) returns (google.protobuf.Empty); + rpc Checkpoint(CheckpointTaskRequest) returns (google.protobuf.Empty); + rpc Kill(KillRequest) returns (google.protobuf.Empty); + rpc Exec(ExecProcessRequest) returns (google.protobuf.Empty); + rpc ResizePty(ResizePtyRequest) returns (google.protobuf.Empty); + rpc CloseIO(CloseIORequest) returns (google.protobuf.Empty); + rpc Update(UpdateTaskRequest) returns (google.protobuf.Empty); + rpc Wait(WaitRequest) returns (WaitResponse); + rpc Stats(StatsRequest) returns (StatsResponse); + rpc Connect(ConnectRequest) returns (ConnectResponse); + rpc Shutdown(ShutdownRequest) returns (google.protobuf.Empty); +} + +message CreateTaskRequest { + string id = 1; + string bundle = 2; + repeated containerd.types.Mount rootfs = 3; + bool terminal = 4; + string stdin = 5; + string stdout = 6; + string stderr = 7; + string checkpoint = 8; + string parent_checkpoint = 9; + google.protobuf.Any options = 10; +} + +message CreateTaskResponse { + uint32 pid = 1; +} + +message DeleteRequest { + string id = 1; + string exec_id = 2; +} + +message DeleteResponse { + uint32 pid = 1; + uint32 exit_status = 2; + google.protobuf.Timestamp exited_at = 3; +} + +message ExecProcessRequest { + string id = 1; + string exec_id = 2; + bool terminal = 3; + string stdin = 4; + string stdout = 5; + string stderr = 6; + google.protobuf.Any spec = 7; +} + +message ExecProcessResponse {} + +message ResizePtyRequest { + string id = 1; + string exec_id = 2; + uint32 width = 3; + uint32 height = 4; +} + +message StateRequest { + string id = 1; + string exec_id = 2; +} + +message StateResponse { + string id = 1; + string bundle = 2; + uint32 pid = 3; + containerd.v1.types.Status status = 4; + string stdin = 5; + string stdout = 6; + string stderr = 7; + bool terminal = 8; + uint32 exit_status = 9; + google.protobuf.Timestamp exited_at = 10; + string exec_id = 11; +} + +message KillRequest { + string id = 1; + string exec_id = 2; + uint32 signal = 3; + bool all = 4; +} + +message CloseIORequest { + string id = 1; + string exec_id = 2; + bool stdin = 3; +} + +message PidsRequest { + string id = 1; +} + +message PidsResponse { + repeated containerd.v1.types.ProcessInfo processes = 1; +} + +message CheckpointTaskRequest { + string id = 1; + string path = 2; + google.protobuf.Any options = 3; +} + +message UpdateTaskRequest { + string id = 1; + google.protobuf.Any resources = 2; + map annotations = 3; +} + +message StartRequest { + string id = 1; + string exec_id = 2; +} + +message StartResponse { + uint32 pid = 1; +} + +message WaitRequest { + string id = 1; + string exec_id = 2; +} + +message WaitResponse { + uint32 exit_status = 1; + google.protobuf.Timestamp exited_at = 2; +} + +message StatsRequest { + string id = 1; +} + +message StatsResponse { + google.protobuf.Any stats = 1; +} + +message ConnectRequest { + string id = 1; +} + +message ConnectResponse { + uint32 shim_pid = 1; + uint32 task_pid = 2; + string version = 3; +} + +message ShutdownRequest { + string id = 1; + bool now = 2; +} + +message PauseRequest { + string id = 1; +} + +message ResumeRequest { + string id = 1; +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim_ttrpc.pb.go b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim_ttrpc.pb.go new file mode 100644 index 0000000000..822d7dadd6 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim_ttrpc.pb.go @@ -0,0 +1,301 @@ +// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT. +// source: runtime/task/v2/shim.proto +package task + +import ( + context "context" + ttrpc "github.com/containerd/ttrpc" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +type TTRPCTaskService interface { + State(context.Context, *StateRequest) (*StateResponse, error) + Create(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) + Start(context.Context, *StartRequest) (*StartResponse, error) + Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) + Pids(context.Context, *PidsRequest) (*PidsResponse, error) + Pause(context.Context, *PauseRequest) (*emptypb.Empty, error) + Resume(context.Context, *ResumeRequest) (*emptypb.Empty, error) + Checkpoint(context.Context, *CheckpointTaskRequest) (*emptypb.Empty, error) + Kill(context.Context, *KillRequest) (*emptypb.Empty, error) + Exec(context.Context, *ExecProcessRequest) (*emptypb.Empty, error) + ResizePty(context.Context, *ResizePtyRequest) (*emptypb.Empty, error) + CloseIO(context.Context, *CloseIORequest) (*emptypb.Empty, error) + Update(context.Context, *UpdateTaskRequest) (*emptypb.Empty, error) + Wait(context.Context, *WaitRequest) (*WaitResponse, error) + Stats(context.Context, *StatsRequest) (*StatsResponse, error) + Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) + Shutdown(context.Context, *ShutdownRequest) (*emptypb.Empty, error) +} + +func RegisterTTRPCTaskService(srv *ttrpc.Server, svc TTRPCTaskService) { + srv.RegisterService("containerd.task.v2.Task", &ttrpc.ServiceDesc{ + Methods: map[string]ttrpc.Method{ + "State": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StateRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.State(ctx, &req) + }, + "Create": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CreateTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Create(ctx, &req) + }, + "Start": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StartRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Start(ctx, &req) + }, + "Delete": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req DeleteRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Delete(ctx, &req) + }, + "Pids": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req PidsRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Pids(ctx, &req) + }, + "Pause": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req PauseRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Pause(ctx, &req) + }, + "Resume": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ResumeRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Resume(ctx, &req) + }, + "Checkpoint": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CheckpointTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Checkpoint(ctx, &req) + }, + "Kill": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req KillRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Kill(ctx, &req) + }, + "Exec": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ExecProcessRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Exec(ctx, &req) + }, + "ResizePty": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ResizePtyRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.ResizePty(ctx, &req) + }, + "CloseIO": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CloseIORequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.CloseIO(ctx, &req) + }, + "Update": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req UpdateTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Update(ctx, &req) + }, + "Wait": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req WaitRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Wait(ctx, &req) + }, + "Stats": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StatsRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Stats(ctx, &req) + }, + "Connect": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ConnectRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Connect(ctx, &req) + }, + "Shutdown": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ShutdownRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Shutdown(ctx, &req) + }, + }, + }) +} + +type ttrpctaskClient struct { + client *ttrpc.Client +} + +func NewTTRPCTaskClient(client *ttrpc.Client) TTRPCTaskService { + return &ttrpctaskClient{ + client: client, + } +} + +func (c *ttrpctaskClient) State(ctx context.Context, req *StateRequest) (*StateResponse, error) { + var resp StateResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "State", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Create(ctx context.Context, req *CreateTaskRequest) (*CreateTaskResponse, error) { + var resp CreateTaskResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Create", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Start(ctx context.Context, req *StartRequest) (*StartResponse, error) { + var resp StartResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Start", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) { + var resp DeleteResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Delete", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Pids(ctx context.Context, req *PidsRequest) (*PidsResponse, error) { + var resp PidsResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Pids", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Pause(ctx context.Context, req *PauseRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Pause", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Resume(ctx context.Context, req *ResumeRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Resume", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Checkpoint(ctx context.Context, req *CheckpointTaskRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Checkpoint", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Kill(ctx context.Context, req *KillRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Kill", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Exec(ctx context.Context, req *ExecProcessRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Exec", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) ResizePty(ctx context.Context, req *ResizePtyRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "ResizePty", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) CloseIO(ctx context.Context, req *CloseIORequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "CloseIO", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Update(ctx context.Context, req *UpdateTaskRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Update", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Wait(ctx context.Context, req *WaitRequest) (*WaitResponse, error) { + var resp WaitResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Wait", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Stats(ctx context.Context, req *StatsRequest) (*StatsResponse, error) { + var resp StatsResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Stats", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Connect(ctx context.Context, req *ConnectRequest) (*ConnectResponse, error) { + var resp ConnectResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Connect", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Shutdown(ctx context.Context, req *ShutdownRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Shutdown", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v3/doc.go b/vendor/github.com/containerd/containerd/api/runtime/task/v3/doc.go new file mode 100644 index 0000000000..f933dd8d41 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v3/doc.go @@ -0,0 +1,17 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package task diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim.pb.go b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim.pb.go new file mode 100644 index 0000000000..588b41c6a9 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim.pb.go @@ -0,0 +1,2331 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: runtime/task/v3/shim.proto + +package task + +import ( + types "github.com/containerd/containerd/api/types" + task "github.com/containerd/containerd/api/types/task" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CreateTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Bundle string `protobuf:"bytes,2,opt,name=bundle,proto3" json:"bundle,omitempty"` + Rootfs []*types.Mount `protobuf:"bytes,3,rep,name=rootfs,proto3" json:"rootfs,omitempty"` + Terminal bool `protobuf:"varint,4,opt,name=terminal,proto3" json:"terminal,omitempty"` + Stdin string `protobuf:"bytes,5,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,6,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,7,opt,name=stderr,proto3" json:"stderr,omitempty"` + Checkpoint string `protobuf:"bytes,8,opt,name=checkpoint,proto3" json:"checkpoint,omitempty"` + ParentCheckpoint string `protobuf:"bytes,9,opt,name=parent_checkpoint,json=parentCheckpoint,proto3" json:"parent_checkpoint,omitempty"` + Options *anypb.Any `protobuf:"bytes,10,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *CreateTaskRequest) Reset() { + *x = CreateTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskRequest) ProtoMessage() {} + +func (x *CreateTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskRequest.ProtoReflect.Descriptor instead. +func (*CreateTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{0} +} + +func (x *CreateTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CreateTaskRequest) GetBundle() string { + if x != nil { + return x.Bundle + } + return "" +} + +func (x *CreateTaskRequest) GetRootfs() []*types.Mount { + if x != nil { + return x.Rootfs + } + return nil +} + +func (x *CreateTaskRequest) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *CreateTaskRequest) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *CreateTaskRequest) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *CreateTaskRequest) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *CreateTaskRequest) GetCheckpoint() string { + if x != nil { + return x.Checkpoint + } + return "" +} + +func (x *CreateTaskRequest) GetParentCheckpoint() string { + if x != nil { + return x.ParentCheckpoint + } + return "" +} + +func (x *CreateTaskRequest) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +type CreateTaskResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` +} + +func (x *CreateTaskResponse) Reset() { + *x = CreateTaskResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTaskResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskResponse) ProtoMessage() {} + +func (x *CreateTaskResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskResponse.ProtoReflect.Descriptor instead. +func (*CreateTaskResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateTaskResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +type DeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{2} +} + +func (x *DeleteRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *DeleteRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type DeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` + ExitStatus uint32 `protobuf:"varint,2,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` +} + +func (x *DeleteResponse) Reset() { + *x = DeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResponse) ProtoMessage() {} + +func (x *DeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{3} +} + +func (x *DeleteResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *DeleteResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *DeleteResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +type ExecProcessRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Terminal bool `protobuf:"varint,3,opt,name=terminal,proto3" json:"terminal,omitempty"` + Stdin string `protobuf:"bytes,4,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,5,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,6,opt,name=stderr,proto3" json:"stderr,omitempty"` + Spec *anypb.Any `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` +} + +func (x *ExecProcessRequest) Reset() { + *x = ExecProcessRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecProcessRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecProcessRequest) ProtoMessage() {} + +func (x *ExecProcessRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecProcessRequest.ProtoReflect.Descriptor instead. +func (*ExecProcessRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{4} +} + +func (x *ExecProcessRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ExecProcessRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *ExecProcessRequest) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *ExecProcessRequest) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *ExecProcessRequest) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *ExecProcessRequest) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *ExecProcessRequest) GetSpec() *anypb.Any { + if x != nil { + return x.Spec + } + return nil +} + +type ExecProcessResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ExecProcessResponse) Reset() { + *x = ExecProcessResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecProcessResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecProcessResponse) ProtoMessage() {} + +func (x *ExecProcessResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecProcessResponse.ProtoReflect.Descriptor instead. +func (*ExecProcessResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{5} +} + +type ResizePtyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Width uint32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` + Height uint32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` +} + +func (x *ResizePtyRequest) Reset() { + *x = ResizePtyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResizePtyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResizePtyRequest) ProtoMessage() {} + +func (x *ResizePtyRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResizePtyRequest.ProtoReflect.Descriptor instead. +func (*ResizePtyRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{6} +} + +func (x *ResizePtyRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ResizePtyRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *ResizePtyRequest) GetWidth() uint32 { + if x != nil { + return x.Width + } + return 0 +} + +func (x *ResizePtyRequest) GetHeight() uint32 { + if x != nil { + return x.Height + } + return 0 +} + +type StateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StateRequest) Reset() { + *x = StateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateRequest) ProtoMessage() {} + +func (x *StateRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateRequest.ProtoReflect.Descriptor instead. +func (*StateRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{7} +} + +func (x *StateRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StateRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type StateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Bundle string `protobuf:"bytes,2,opt,name=bundle,proto3" json:"bundle,omitempty"` + Pid uint32 `protobuf:"varint,3,opt,name=pid,proto3" json:"pid,omitempty"` + Status task.Status `protobuf:"varint,4,opt,name=status,proto3,enum=containerd.v1.types.Status" json:"status,omitempty"` + Stdin string `protobuf:"bytes,5,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,6,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,7,opt,name=stderr,proto3" json:"stderr,omitempty"` + Terminal bool `protobuf:"varint,8,opt,name=terminal,proto3" json:"terminal,omitempty"` + ExitStatus uint32 `protobuf:"varint,9,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` + ExecID string `protobuf:"bytes,11,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StateResponse) Reset() { + *x = StateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateResponse) ProtoMessage() {} + +func (x *StateResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateResponse.ProtoReflect.Descriptor instead. +func (*StateResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{8} +} + +func (x *StateResponse) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StateResponse) GetBundle() string { + if x != nil { + return x.Bundle + } + return "" +} + +func (x *StateResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *StateResponse) GetStatus() task.Status { + if x != nil { + return x.Status + } + return task.Status(0) +} + +func (x *StateResponse) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *StateResponse) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *StateResponse) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *StateResponse) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *StateResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *StateResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +func (x *StateResponse) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type KillRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Signal uint32 `protobuf:"varint,3,opt,name=signal,proto3" json:"signal,omitempty"` + All bool `protobuf:"varint,4,opt,name=all,proto3" json:"all,omitempty"` +} + +func (x *KillRequest) Reset() { + *x = KillRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KillRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KillRequest) ProtoMessage() {} + +func (x *KillRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KillRequest.ProtoReflect.Descriptor instead. +func (*KillRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{9} +} + +func (x *KillRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *KillRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *KillRequest) GetSignal() uint32 { + if x != nil { + return x.Signal + } + return 0 +} + +func (x *KillRequest) GetAll() bool { + if x != nil { + return x.All + } + return false +} + +type CloseIORequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Stdin bool `protobuf:"varint,3,opt,name=stdin,proto3" json:"stdin,omitempty"` +} + +func (x *CloseIORequest) Reset() { + *x = CloseIORequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseIORequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseIORequest) ProtoMessage() {} + +func (x *CloseIORequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseIORequest.ProtoReflect.Descriptor instead. +func (*CloseIORequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{10} +} + +func (x *CloseIORequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CloseIORequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *CloseIORequest) GetStdin() bool { + if x != nil { + return x.Stdin + } + return false +} + +type PidsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *PidsRequest) Reset() { + *x = PidsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PidsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PidsRequest) ProtoMessage() {} + +func (x *PidsRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PidsRequest.ProtoReflect.Descriptor instead. +func (*PidsRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{11} +} + +func (x *PidsRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type PidsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Processes []*task.ProcessInfo `protobuf:"bytes,1,rep,name=processes,proto3" json:"processes,omitempty"` +} + +func (x *PidsResponse) Reset() { + *x = PidsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PidsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PidsResponse) ProtoMessage() {} + +func (x *PidsResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PidsResponse.ProtoReflect.Descriptor instead. +func (*PidsResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{12} +} + +func (x *PidsResponse) GetProcesses() []*task.ProcessInfo { + if x != nil { + return x.Processes + } + return nil +} + +type CheckpointTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Options *anypb.Any `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *CheckpointTaskRequest) Reset() { + *x = CheckpointTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckpointTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckpointTaskRequest) ProtoMessage() {} + +func (x *CheckpointTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckpointTaskRequest.ProtoReflect.Descriptor instead. +func (*CheckpointTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{13} +} + +func (x *CheckpointTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CheckpointTaskRequest) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *CheckpointTaskRequest) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +type UpdateTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Resources *anypb.Any `protobuf:"bytes,2,opt,name=resources,proto3" json:"resources,omitempty"` + Annotations map[string]string `protobuf:"bytes,3,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *UpdateTaskRequest) Reset() { + *x = UpdateTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTaskRequest) ProtoMessage() {} + +func (x *UpdateTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTaskRequest.ProtoReflect.Descriptor instead. +func (*UpdateTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{14} +} + +func (x *UpdateTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *UpdateTaskRequest) GetResources() *anypb.Any { + if x != nil { + return x.Resources + } + return nil +} + +func (x *UpdateTaskRequest) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +type StartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StartRequest) Reset() { + *x = StartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartRequest) ProtoMessage() {} + +func (x *StartRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartRequest.ProtoReflect.Descriptor instead. +func (*StartRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{15} +} + +func (x *StartRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StartRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type StartResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` +} + +func (x *StartResponse) Reset() { + *x = StartResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartResponse) ProtoMessage() {} + +func (x *StartResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartResponse.ProtoReflect.Descriptor instead. +func (*StartResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{16} +} + +func (x *StartResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +type WaitRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *WaitRequest) Reset() { + *x = WaitRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WaitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitRequest) ProtoMessage() {} + +func (x *WaitRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitRequest.ProtoReflect.Descriptor instead. +func (*WaitRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{17} +} + +func (x *WaitRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *WaitRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type WaitResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ExitStatus uint32 `protobuf:"varint,1,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` +} + +func (x *WaitResponse) Reset() { + *x = WaitResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WaitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitResponse) ProtoMessage() {} + +func (x *WaitResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitResponse.ProtoReflect.Descriptor instead. +func (*WaitResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{18} +} + +func (x *WaitResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *WaitResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +type StatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *StatsRequest) Reset() { + *x = StatsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsRequest) ProtoMessage() {} + +func (x *StatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead. +func (*StatsRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{19} +} + +func (x *StatsRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type StatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats *anypb.Any `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *StatsResponse) Reset() { + *x = StatsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsResponse) ProtoMessage() {} + +func (x *StatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead. +func (*StatsResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{20} +} + +func (x *StatsResponse) GetStats() *anypb.Any { + if x != nil { + return x.Stats + } + return nil +} + +type ConnectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ConnectRequest) Reset() { + *x = ConnectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectRequest) ProtoMessage() {} + +func (x *ConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead. +func (*ConnectRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{21} +} + +func (x *ConnectRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type ConnectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ShimPid uint32 `protobuf:"varint,1,opt,name=shim_pid,json=shimPid,proto3" json:"shim_pid,omitempty"` + TaskPid uint32 `protobuf:"varint,2,opt,name=task_pid,json=taskPid,proto3" json:"task_pid,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *ConnectResponse) Reset() { + *x = ConnectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectResponse) ProtoMessage() {} + +func (x *ConnectResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectResponse.ProtoReflect.Descriptor instead. +func (*ConnectResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{22} +} + +func (x *ConnectResponse) GetShimPid() uint32 { + if x != nil { + return x.ShimPid + } + return 0 +} + +func (x *ConnectResponse) GetTaskPid() uint32 { + if x != nil { + return x.TaskPid + } + return 0 +} + +func (x *ConnectResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type ShutdownRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Now bool `protobuf:"varint,2,opt,name=now,proto3" json:"now,omitempty"` +} + +func (x *ShutdownRequest) Reset() { + *x = ShutdownRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShutdownRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShutdownRequest) ProtoMessage() {} + +func (x *ShutdownRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShutdownRequest.ProtoReflect.Descriptor instead. +func (*ShutdownRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{23} +} + +func (x *ShutdownRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ShutdownRequest) GetNow() bool { + if x != nil { + return x.Now + } + return false +} + +type PauseRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *PauseRequest) Reset() { + *x = PauseRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PauseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PauseRequest) ProtoMessage() {} + +func (x *PauseRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PauseRequest.ProtoReflect.Descriptor instead. +func (*PauseRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{24} +} + +func (x *PauseRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type ResumeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ResumeRequest) Reset() { + *x = ResumeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v3_shim_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResumeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResumeRequest) ProtoMessage() {} + +func (x *ResumeRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v3_shim_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResumeRequest.ProtoReflect.Descriptor instead. +func (*ResumeRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v3_shim_proto_rawDescGZIP(), []int{25} +} + +func (x *ResumeRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +var File_runtime_task_v3_shim_proto protoreflect.FileDescriptor + +var file_runtime_task_v3_shim_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x76, + 0x33, 0x2f, 0x73, 0x68, 0x69, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, + 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, + 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xcb, 0x02, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x74, + 0x66, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, + 0x74, 0x64, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x64, 0x65, 0x72, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x10, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x26, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x38, 0x0a, 0x0d, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, + 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, + 0x63, 0x49, 0x64, 0x22, 0x7c, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, + 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x22, 0xc9, 0x01, 0x0a, 0x12, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, + 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, + 0x64, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, + 0x65, 0x72, 0x72, 0x12, 0x28, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x15, 0x0a, + 0x13, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x69, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, + 0x37, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0xd3, 0x02, 0x0a, 0x0d, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x03, 0x70, 0x69, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x64, + 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, + 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, + 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x69, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x60, + 0x0a, 0x0b, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x12, 0x10, + 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6c, 0x6c, + 0x22, 0x4f, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x64, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, + 0x6e, 0x22, 0x1d, 0x0a, 0x0b, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x4e, 0x0a, 0x0c, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x22, 0x6b, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, + 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf1, 0x01, + 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, + 0x33, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x37, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x21, 0x0a, 0x0d, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x36, 0x0a, + 0x0b, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, + 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x68, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, + 0x1e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x3b, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2a, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x20, 0x0a, 0x0e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x61, + 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x68, 0x69, 0x6d, 0x50, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x74, 0x61, 0x73, 0x6b, 0x50, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x33, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x03, 0x6e, 0x6f, 0x77, 0x22, 0x1e, 0x0a, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0x8a, 0x0a, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, + 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, + 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, + 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x04, 0x50, 0x69, 0x64, 0x73, 0x12, 0x1f, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x2e, 0x76, 0x33, 0x2e, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x41, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, + 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x21, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4f, 0x0a, 0x0a, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x29, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x04, 0x4b, 0x69, + 0x6c, 0x6c, 0x12, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x46, 0x0a, 0x04, 0x45, + 0x78, 0x65, 0x63, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x09, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, 0x79, + 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, + 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, + 0x0a, 0x07, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, + 0x0a, 0x04, 0x57, 0x61, 0x69, 0x74, 0x12, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x57, 0x61, 0x69, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x57, 0x61, 0x69, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x53, + 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x68, 0x75, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x76, 0x33, 0x3b, 0x74, 0x61, 0x73, + 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_runtime_task_v3_shim_proto_rawDescOnce sync.Once + file_runtime_task_v3_shim_proto_rawDescData = file_runtime_task_v3_shim_proto_rawDesc +) + +func file_runtime_task_v3_shim_proto_rawDescGZIP() []byte { + file_runtime_task_v3_shim_proto_rawDescOnce.Do(func() { + file_runtime_task_v3_shim_proto_rawDescData = protoimpl.X.CompressGZIP(file_runtime_task_v3_shim_proto_rawDescData) + }) + return file_runtime_task_v3_shim_proto_rawDescData +} + +var file_runtime_task_v3_shim_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_runtime_task_v3_shim_proto_goTypes = []interface{}{ + (*CreateTaskRequest)(nil), // 0: containerd.task.v3.CreateTaskRequest + (*CreateTaskResponse)(nil), // 1: containerd.task.v3.CreateTaskResponse + (*DeleteRequest)(nil), // 2: containerd.task.v3.DeleteRequest + (*DeleteResponse)(nil), // 3: containerd.task.v3.DeleteResponse + (*ExecProcessRequest)(nil), // 4: containerd.task.v3.ExecProcessRequest + (*ExecProcessResponse)(nil), // 5: containerd.task.v3.ExecProcessResponse + (*ResizePtyRequest)(nil), // 6: containerd.task.v3.ResizePtyRequest + (*StateRequest)(nil), // 7: containerd.task.v3.StateRequest + (*StateResponse)(nil), // 8: containerd.task.v3.StateResponse + (*KillRequest)(nil), // 9: containerd.task.v3.KillRequest + (*CloseIORequest)(nil), // 10: containerd.task.v3.CloseIORequest + (*PidsRequest)(nil), // 11: containerd.task.v3.PidsRequest + (*PidsResponse)(nil), // 12: containerd.task.v3.PidsResponse + (*CheckpointTaskRequest)(nil), // 13: containerd.task.v3.CheckpointTaskRequest + (*UpdateTaskRequest)(nil), // 14: containerd.task.v3.UpdateTaskRequest + (*StartRequest)(nil), // 15: containerd.task.v3.StartRequest + (*StartResponse)(nil), // 16: containerd.task.v3.StartResponse + (*WaitRequest)(nil), // 17: containerd.task.v3.WaitRequest + (*WaitResponse)(nil), // 18: containerd.task.v3.WaitResponse + (*StatsRequest)(nil), // 19: containerd.task.v3.StatsRequest + (*StatsResponse)(nil), // 20: containerd.task.v3.StatsResponse + (*ConnectRequest)(nil), // 21: containerd.task.v3.ConnectRequest + (*ConnectResponse)(nil), // 22: containerd.task.v3.ConnectResponse + (*ShutdownRequest)(nil), // 23: containerd.task.v3.ShutdownRequest + (*PauseRequest)(nil), // 24: containerd.task.v3.PauseRequest + (*ResumeRequest)(nil), // 25: containerd.task.v3.ResumeRequest + nil, // 26: containerd.task.v3.UpdateTaskRequest.AnnotationsEntry + (*types.Mount)(nil), // 27: containerd.types.Mount + (*anypb.Any)(nil), // 28: google.protobuf.Any + (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp + (task.Status)(0), // 30: containerd.v1.types.Status + (*task.ProcessInfo)(nil), // 31: containerd.v1.types.ProcessInfo + (*emptypb.Empty)(nil), // 32: google.protobuf.Empty +} +var file_runtime_task_v3_shim_proto_depIdxs = []int32{ + 27, // 0: containerd.task.v3.CreateTaskRequest.rootfs:type_name -> containerd.types.Mount + 28, // 1: containerd.task.v3.CreateTaskRequest.options:type_name -> google.protobuf.Any + 29, // 2: containerd.task.v3.DeleteResponse.exited_at:type_name -> google.protobuf.Timestamp + 28, // 3: containerd.task.v3.ExecProcessRequest.spec:type_name -> google.protobuf.Any + 30, // 4: containerd.task.v3.StateResponse.status:type_name -> containerd.v1.types.Status + 29, // 5: containerd.task.v3.StateResponse.exited_at:type_name -> google.protobuf.Timestamp + 31, // 6: containerd.task.v3.PidsResponse.processes:type_name -> containerd.v1.types.ProcessInfo + 28, // 7: containerd.task.v3.CheckpointTaskRequest.options:type_name -> google.protobuf.Any + 28, // 8: containerd.task.v3.UpdateTaskRequest.resources:type_name -> google.protobuf.Any + 26, // 9: containerd.task.v3.UpdateTaskRequest.annotations:type_name -> containerd.task.v3.UpdateTaskRequest.AnnotationsEntry + 29, // 10: containerd.task.v3.WaitResponse.exited_at:type_name -> google.protobuf.Timestamp + 28, // 11: containerd.task.v3.StatsResponse.stats:type_name -> google.protobuf.Any + 7, // 12: containerd.task.v3.Task.State:input_type -> containerd.task.v3.StateRequest + 0, // 13: containerd.task.v3.Task.Create:input_type -> containerd.task.v3.CreateTaskRequest + 15, // 14: containerd.task.v3.Task.Start:input_type -> containerd.task.v3.StartRequest + 2, // 15: containerd.task.v3.Task.Delete:input_type -> containerd.task.v3.DeleteRequest + 11, // 16: containerd.task.v3.Task.Pids:input_type -> containerd.task.v3.PidsRequest + 24, // 17: containerd.task.v3.Task.Pause:input_type -> containerd.task.v3.PauseRequest + 25, // 18: containerd.task.v3.Task.Resume:input_type -> containerd.task.v3.ResumeRequest + 13, // 19: containerd.task.v3.Task.Checkpoint:input_type -> containerd.task.v3.CheckpointTaskRequest + 9, // 20: containerd.task.v3.Task.Kill:input_type -> containerd.task.v3.KillRequest + 4, // 21: containerd.task.v3.Task.Exec:input_type -> containerd.task.v3.ExecProcessRequest + 6, // 22: containerd.task.v3.Task.ResizePty:input_type -> containerd.task.v3.ResizePtyRequest + 10, // 23: containerd.task.v3.Task.CloseIO:input_type -> containerd.task.v3.CloseIORequest + 14, // 24: containerd.task.v3.Task.Update:input_type -> containerd.task.v3.UpdateTaskRequest + 17, // 25: containerd.task.v3.Task.Wait:input_type -> containerd.task.v3.WaitRequest + 19, // 26: containerd.task.v3.Task.Stats:input_type -> containerd.task.v3.StatsRequest + 21, // 27: containerd.task.v3.Task.Connect:input_type -> containerd.task.v3.ConnectRequest + 23, // 28: containerd.task.v3.Task.Shutdown:input_type -> containerd.task.v3.ShutdownRequest + 8, // 29: containerd.task.v3.Task.State:output_type -> containerd.task.v3.StateResponse + 1, // 30: containerd.task.v3.Task.Create:output_type -> containerd.task.v3.CreateTaskResponse + 16, // 31: containerd.task.v3.Task.Start:output_type -> containerd.task.v3.StartResponse + 3, // 32: containerd.task.v3.Task.Delete:output_type -> containerd.task.v3.DeleteResponse + 12, // 33: containerd.task.v3.Task.Pids:output_type -> containerd.task.v3.PidsResponse + 32, // 34: containerd.task.v3.Task.Pause:output_type -> google.protobuf.Empty + 32, // 35: containerd.task.v3.Task.Resume:output_type -> google.protobuf.Empty + 32, // 36: containerd.task.v3.Task.Checkpoint:output_type -> google.protobuf.Empty + 32, // 37: containerd.task.v3.Task.Kill:output_type -> google.protobuf.Empty + 32, // 38: containerd.task.v3.Task.Exec:output_type -> google.protobuf.Empty + 32, // 39: containerd.task.v3.Task.ResizePty:output_type -> google.protobuf.Empty + 32, // 40: containerd.task.v3.Task.CloseIO:output_type -> google.protobuf.Empty + 32, // 41: containerd.task.v3.Task.Update:output_type -> google.protobuf.Empty + 18, // 42: containerd.task.v3.Task.Wait:output_type -> containerd.task.v3.WaitResponse + 20, // 43: containerd.task.v3.Task.Stats:output_type -> containerd.task.v3.StatsResponse + 22, // 44: containerd.task.v3.Task.Connect:output_type -> containerd.task.v3.ConnectResponse + 32, // 45: containerd.task.v3.Task.Shutdown:output_type -> google.protobuf.Empty + 29, // [29:46] is the sub-list for method output_type + 12, // [12:29] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_runtime_task_v3_shim_proto_init() } +func file_runtime_task_v3_shim_proto_init() { + if File_runtime_task_v3_shim_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_runtime_task_v3_shim_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTaskResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecProcessRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecProcessResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResizePtyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KillRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseIORequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PidsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PidsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckpointTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WaitRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WaitResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShutdownRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PauseRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v3_shim_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResumeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_runtime_task_v3_shim_proto_rawDesc, + NumEnums: 0, + NumMessages: 27, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_runtime_task_v3_shim_proto_goTypes, + DependencyIndexes: file_runtime_task_v3_shim_proto_depIdxs, + MessageInfos: file_runtime_task_v3_shim_proto_msgTypes, + }.Build() + File_runtime_task_v3_shim_proto = out.File + file_runtime_task_v3_shim_proto_rawDesc = nil + file_runtime_task_v3_shim_proto_goTypes = nil + file_runtime_task_v3_shim_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim.proto b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim.proto new file mode 100644 index 0000000000..2dffd52db8 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim.proto @@ -0,0 +1,200 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.task.v3; + +import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "types/mount.proto"; +import "types/task/task.proto"; + +option go_package = "github.com/containerd/containerd/api/runtime/task/v3;task"; + +// Shim service is launched for each container and is responsible for owning the IO +// for the container and its additional processes. The shim is also the parent of +// each container and allows reattaching to the IO and receiving the exit status +// for the container processes. +service Task { + rpc State(StateRequest) returns (StateResponse); + rpc Create(CreateTaskRequest) returns (CreateTaskResponse); + rpc Start(StartRequest) returns (StartResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc Pids(PidsRequest) returns (PidsResponse); + rpc Pause(PauseRequest) returns (google.protobuf.Empty); + rpc Resume(ResumeRequest) returns (google.protobuf.Empty); + rpc Checkpoint(CheckpointTaskRequest) returns (google.protobuf.Empty); + rpc Kill(KillRequest) returns (google.protobuf.Empty); + rpc Exec(ExecProcessRequest) returns (google.protobuf.Empty); + rpc ResizePty(ResizePtyRequest) returns (google.protobuf.Empty); + rpc CloseIO(CloseIORequest) returns (google.protobuf.Empty); + rpc Update(UpdateTaskRequest) returns (google.protobuf.Empty); + rpc Wait(WaitRequest) returns (WaitResponse); + rpc Stats(StatsRequest) returns (StatsResponse); + rpc Connect(ConnectRequest) returns (ConnectResponse); + rpc Shutdown(ShutdownRequest) returns (google.protobuf.Empty); +} + +message CreateTaskRequest { + string id = 1; + string bundle = 2; + repeated containerd.types.Mount rootfs = 3; + bool terminal = 4; + string stdin = 5; + string stdout = 6; + string stderr = 7; + string checkpoint = 8; + string parent_checkpoint = 9; + google.protobuf.Any options = 10; +} + +message CreateTaskResponse { + uint32 pid = 1; +} + +message DeleteRequest { + string id = 1; + string exec_id = 2; +} + +message DeleteResponse { + uint32 pid = 1; + uint32 exit_status = 2; + google.protobuf.Timestamp exited_at = 3; +} + +message ExecProcessRequest { + string id = 1; + string exec_id = 2; + bool terminal = 3; + string stdin = 4; + string stdout = 5; + string stderr = 6; + google.protobuf.Any spec = 7; +} + +message ExecProcessResponse {} + +message ResizePtyRequest { + string id = 1; + string exec_id = 2; + uint32 width = 3; + uint32 height = 4; +} + +message StateRequest { + string id = 1; + string exec_id = 2; +} + +message StateResponse { + string id = 1; + string bundle = 2; + uint32 pid = 3; + containerd.v1.types.Status status = 4; + string stdin = 5; + string stdout = 6; + string stderr = 7; + bool terminal = 8; + uint32 exit_status = 9; + google.protobuf.Timestamp exited_at = 10; + string exec_id = 11; +} + +message KillRequest { + string id = 1; + string exec_id = 2; + uint32 signal = 3; + bool all = 4; +} + +message CloseIORequest { + string id = 1; + string exec_id = 2; + bool stdin = 3; +} + +message PidsRequest { + string id = 1; +} + +message PidsResponse { + repeated containerd.v1.types.ProcessInfo processes = 1; +} + +message CheckpointTaskRequest { + string id = 1; + string path = 2; + google.protobuf.Any options = 3; +} + +message UpdateTaskRequest { + string id = 1; + google.protobuf.Any resources = 2; + map annotations = 3; +} + +message StartRequest { + string id = 1; + string exec_id = 2; +} + +message StartResponse { + uint32 pid = 1; +} + +message WaitRequest { + string id = 1; + string exec_id = 2; +} + +message WaitResponse { + uint32 exit_status = 1; + google.protobuf.Timestamp exited_at = 2; +} + +message StatsRequest { + string id = 1; +} + +message StatsResponse { + google.protobuf.Any stats = 1; +} + +message ConnectRequest { + string id = 1; +} + +message ConnectResponse { + uint32 shim_pid = 1; + uint32 task_pid = 2; + string version = 3; +} + +message ShutdownRequest { + string id = 1; + bool now = 2; +} + +message PauseRequest { + string id = 1; +} + +message ResumeRequest { + string id = 1; +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim_grpc.pb.go b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim_grpc.pb.go new file mode 100644 index 0000000000..c3d45cf835 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim_grpc.pb.go @@ -0,0 +1,684 @@ +//go:build !no_grpc + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc (unknown) +// source: runtime/task/v3/shim.proto + +package task + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// TaskClient is the client API for Task service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TaskClient interface { + State(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) + Create(ctx context.Context, in *CreateTaskRequest, opts ...grpc.CallOption) (*CreateTaskResponse, error) + Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*StartResponse, error) + Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) + Pids(ctx context.Context, in *PidsRequest, opts ...grpc.CallOption) (*PidsResponse, error) + Pause(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + Resume(ctx context.Context, in *ResumeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + Checkpoint(ctx context.Context, in *CheckpointTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + Kill(ctx context.Context, in *KillRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + Exec(ctx context.Context, in *ExecProcessRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + ResizePty(ctx context.Context, in *ResizePtyRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + CloseIO(ctx context.Context, in *CloseIORequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + Update(ctx context.Context, in *UpdateTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*WaitResponse, error) + Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) + Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectResponse, error) + Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type taskClient struct { + cc grpc.ClientConnInterface +} + +func NewTaskClient(cc grpc.ClientConnInterface) TaskClient { + return &taskClient{cc} +} + +func (c *taskClient) State(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) { + out := new(StateResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/State", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Create(ctx context.Context, in *CreateTaskRequest, opts ...grpc.CallOption) (*CreateTaskResponse, error) { + out := new(CreateTaskResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Create", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*StartResponse, error) { + out := new(StartResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Start", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) { + out := new(DeleteResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Delete", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Pids(ctx context.Context, in *PidsRequest, opts ...grpc.CallOption) (*PidsResponse, error) { + out := new(PidsResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Pids", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Pause(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Pause", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Resume(ctx context.Context, in *ResumeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Resume", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Checkpoint(ctx context.Context, in *CheckpointTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Checkpoint", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Kill(ctx context.Context, in *KillRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Kill", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Exec(ctx context.Context, in *ExecProcessRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Exec", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) ResizePty(ctx context.Context, in *ResizePtyRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/ResizePty", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) CloseIO(ctx context.Context, in *CloseIORequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/CloseIO", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Update(ctx context.Context, in *UpdateTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Update", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*WaitResponse, error) { + out := new(WaitResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Wait", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) { + out := new(StatsResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Stats", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectResponse, error) { + out := new(ConnectResponse) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Connect", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskClient) Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/containerd.task.v3.Task/Shutdown", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TaskServer is the server API for Task service. +// All implementations must embed UnimplementedTaskServer +// for forward compatibility +type TaskServer interface { + State(context.Context, *StateRequest) (*StateResponse, error) + Create(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) + Start(context.Context, *StartRequest) (*StartResponse, error) + Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) + Pids(context.Context, *PidsRequest) (*PidsResponse, error) + Pause(context.Context, *PauseRequest) (*emptypb.Empty, error) + Resume(context.Context, *ResumeRequest) (*emptypb.Empty, error) + Checkpoint(context.Context, *CheckpointTaskRequest) (*emptypb.Empty, error) + Kill(context.Context, *KillRequest) (*emptypb.Empty, error) + Exec(context.Context, *ExecProcessRequest) (*emptypb.Empty, error) + ResizePty(context.Context, *ResizePtyRequest) (*emptypb.Empty, error) + CloseIO(context.Context, *CloseIORequest) (*emptypb.Empty, error) + Update(context.Context, *UpdateTaskRequest) (*emptypb.Empty, error) + Wait(context.Context, *WaitRequest) (*WaitResponse, error) + Stats(context.Context, *StatsRequest) (*StatsResponse, error) + Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) + Shutdown(context.Context, *ShutdownRequest) (*emptypb.Empty, error) + mustEmbedUnimplementedTaskServer() +} + +// UnimplementedTaskServer must be embedded to have forward compatible implementations. +type UnimplementedTaskServer struct { +} + +func (UnimplementedTaskServer) State(context.Context, *StateRequest) (*StateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method State not implemented") +} +func (UnimplementedTaskServer) Create(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") +} +func (UnimplementedTaskServer) Start(context.Context, *StartRequest) (*StartResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Start not implemented") +} +func (UnimplementedTaskServer) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} +func (UnimplementedTaskServer) Pids(context.Context, *PidsRequest) (*PidsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Pids not implemented") +} +func (UnimplementedTaskServer) Pause(context.Context, *PauseRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Pause not implemented") +} +func (UnimplementedTaskServer) Resume(context.Context, *ResumeRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Resume not implemented") +} +func (UnimplementedTaskServer) Checkpoint(context.Context, *CheckpointTaskRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Checkpoint not implemented") +} +func (UnimplementedTaskServer) Kill(context.Context, *KillRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Kill not implemented") +} +func (UnimplementedTaskServer) Exec(context.Context, *ExecProcessRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Exec not implemented") +} +func (UnimplementedTaskServer) ResizePty(context.Context, *ResizePtyRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ResizePty not implemented") +} +func (UnimplementedTaskServer) CloseIO(context.Context, *CloseIORequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CloseIO not implemented") +} +func (UnimplementedTaskServer) Update(context.Context, *UpdateTaskRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") +} +func (UnimplementedTaskServer) Wait(context.Context, *WaitRequest) (*WaitResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Wait not implemented") +} +func (UnimplementedTaskServer) Stats(context.Context, *StatsRequest) (*StatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Stats not implemented") +} +func (UnimplementedTaskServer) Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Connect not implemented") +} +func (UnimplementedTaskServer) Shutdown(context.Context, *ShutdownRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Shutdown not implemented") +} +func (UnimplementedTaskServer) mustEmbedUnimplementedTaskServer() {} + +// UnsafeTaskServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TaskServer will +// result in compilation errors. +type UnsafeTaskServer interface { + mustEmbedUnimplementedTaskServer() +} + +func RegisterTaskServer(s grpc.ServiceRegistrar, srv TaskServer) { + s.RegisterService(&Task_ServiceDesc, srv) +} + +func _Task_State_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).State(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/State", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).State(ctx, req.(*StateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Create", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Create(ctx, req.(*CreateTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Start(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Start", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Start(ctx, req.(*StartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Delete", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Delete(ctx, req.(*DeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Pids_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PidsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Pids(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Pids", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Pids(ctx, req.(*PidsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Pause_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PauseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Pause(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Pause", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Pause(ctx, req.(*PauseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Resume_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResumeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Resume(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Resume", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Resume(ctx, req.(*ResumeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Checkpoint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckpointTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Checkpoint(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Checkpoint", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Checkpoint(ctx, req.(*CheckpointTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Kill_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(KillRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Kill(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Kill", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Kill(ctx, req.(*KillRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Exec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExecProcessRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Exec(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Exec", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Exec(ctx, req.(*ExecProcessRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_ResizePty_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResizePtyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).ResizePty(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/ResizePty", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).ResizePty(ctx, req.(*ResizePtyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_CloseIO_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CloseIORequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).CloseIO(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/CloseIO", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).CloseIO(ctx, req.(*CloseIORequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Update", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Update(ctx, req.(*UpdateTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Wait_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WaitRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Wait(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Wait", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Wait(ctx, req.(*WaitRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Stats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Stats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Stats", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Stats(ctx, req.(*StatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConnectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Connect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Connect", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Connect(ctx, req.(*ConnectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Task_Shutdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ShutdownRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServer).Shutdown(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.task.v3.Task/Shutdown", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServer).Shutdown(ctx, req.(*ShutdownRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Task_ServiceDesc is the grpc.ServiceDesc for Task service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Task_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "containerd.task.v3.Task", + HandlerType: (*TaskServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "State", + Handler: _Task_State_Handler, + }, + { + MethodName: "Create", + Handler: _Task_Create_Handler, + }, + { + MethodName: "Start", + Handler: _Task_Start_Handler, + }, + { + MethodName: "Delete", + Handler: _Task_Delete_Handler, + }, + { + MethodName: "Pids", + Handler: _Task_Pids_Handler, + }, + { + MethodName: "Pause", + Handler: _Task_Pause_Handler, + }, + { + MethodName: "Resume", + Handler: _Task_Resume_Handler, + }, + { + MethodName: "Checkpoint", + Handler: _Task_Checkpoint_Handler, + }, + { + MethodName: "Kill", + Handler: _Task_Kill_Handler, + }, + { + MethodName: "Exec", + Handler: _Task_Exec_Handler, + }, + { + MethodName: "ResizePty", + Handler: _Task_ResizePty_Handler, + }, + { + MethodName: "CloseIO", + Handler: _Task_CloseIO_Handler, + }, + { + MethodName: "Update", + Handler: _Task_Update_Handler, + }, + { + MethodName: "Wait", + Handler: _Task_Wait_Handler, + }, + { + MethodName: "Stats", + Handler: _Task_Stats_Handler, + }, + { + MethodName: "Connect", + Handler: _Task_Connect_Handler, + }, + { + MethodName: "Shutdown", + Handler: _Task_Shutdown_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "runtime/task/v3/shim.proto", +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim_ttrpc.pb.go b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim_ttrpc.pb.go new file mode 100644 index 0000000000..d06e0fba85 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v3/shim_ttrpc.pb.go @@ -0,0 +1,301 @@ +// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT. +// source: runtime/task/v3/shim.proto +package task + +import ( + context "context" + ttrpc "github.com/containerd/ttrpc" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +type TTRPCTaskService interface { + State(context.Context, *StateRequest) (*StateResponse, error) + Create(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) + Start(context.Context, *StartRequest) (*StartResponse, error) + Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) + Pids(context.Context, *PidsRequest) (*PidsResponse, error) + Pause(context.Context, *PauseRequest) (*emptypb.Empty, error) + Resume(context.Context, *ResumeRequest) (*emptypb.Empty, error) + Checkpoint(context.Context, *CheckpointTaskRequest) (*emptypb.Empty, error) + Kill(context.Context, *KillRequest) (*emptypb.Empty, error) + Exec(context.Context, *ExecProcessRequest) (*emptypb.Empty, error) + ResizePty(context.Context, *ResizePtyRequest) (*emptypb.Empty, error) + CloseIO(context.Context, *CloseIORequest) (*emptypb.Empty, error) + Update(context.Context, *UpdateTaskRequest) (*emptypb.Empty, error) + Wait(context.Context, *WaitRequest) (*WaitResponse, error) + Stats(context.Context, *StatsRequest) (*StatsResponse, error) + Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) + Shutdown(context.Context, *ShutdownRequest) (*emptypb.Empty, error) +} + +func RegisterTTRPCTaskService(srv *ttrpc.Server, svc TTRPCTaskService) { + srv.RegisterService("containerd.task.v3.Task", &ttrpc.ServiceDesc{ + Methods: map[string]ttrpc.Method{ + "State": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StateRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.State(ctx, &req) + }, + "Create": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CreateTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Create(ctx, &req) + }, + "Start": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StartRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Start(ctx, &req) + }, + "Delete": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req DeleteRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Delete(ctx, &req) + }, + "Pids": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req PidsRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Pids(ctx, &req) + }, + "Pause": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req PauseRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Pause(ctx, &req) + }, + "Resume": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ResumeRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Resume(ctx, &req) + }, + "Checkpoint": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CheckpointTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Checkpoint(ctx, &req) + }, + "Kill": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req KillRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Kill(ctx, &req) + }, + "Exec": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ExecProcessRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Exec(ctx, &req) + }, + "ResizePty": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ResizePtyRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.ResizePty(ctx, &req) + }, + "CloseIO": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CloseIORequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.CloseIO(ctx, &req) + }, + "Update": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req UpdateTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Update(ctx, &req) + }, + "Wait": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req WaitRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Wait(ctx, &req) + }, + "Stats": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StatsRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Stats(ctx, &req) + }, + "Connect": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ConnectRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Connect(ctx, &req) + }, + "Shutdown": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ShutdownRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Shutdown(ctx, &req) + }, + }, + }) +} + +type ttrpctaskClient struct { + client *ttrpc.Client +} + +func NewTTRPCTaskClient(client *ttrpc.Client) TTRPCTaskService { + return &ttrpctaskClient{ + client: client, + } +} + +func (c *ttrpctaskClient) State(ctx context.Context, req *StateRequest) (*StateResponse, error) { + var resp StateResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "State", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Create(ctx context.Context, req *CreateTaskRequest) (*CreateTaskResponse, error) { + var resp CreateTaskResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Create", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Start(ctx context.Context, req *StartRequest) (*StartResponse, error) { + var resp StartResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Start", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) { + var resp DeleteResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Delete", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Pids(ctx context.Context, req *PidsRequest) (*PidsResponse, error) { + var resp PidsResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Pids", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Pause(ctx context.Context, req *PauseRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Pause", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Resume(ctx context.Context, req *ResumeRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Resume", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Checkpoint(ctx context.Context, req *CheckpointTaskRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Checkpoint", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Kill(ctx context.Context, req *KillRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Kill", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Exec(ctx context.Context, req *ExecProcessRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Exec", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) ResizePty(ctx context.Context, req *ResizePtyRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "ResizePty", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) CloseIO(ctx context.Context, req *CloseIORequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "CloseIO", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Update(ctx context.Context, req *UpdateTaskRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Update", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Wait(ctx context.Context, req *WaitRequest) (*WaitResponse, error) { + var resp WaitResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Wait", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Stats(ctx context.Context, req *StatsRequest) (*StatsResponse, error) { + var resp StatsResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Stats", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Connect(ctx context.Context, req *ConnectRequest) (*ConnectResponse, error) { + var resp ConnectResponse + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Connect", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Shutdown(ctx context.Context, req *ShutdownRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v3.Task", "Shutdown", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} diff --git a/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/doc.go b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/doc.go new file mode 100644 index 0000000000..b374dde261 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/doc.go @@ -0,0 +1,23 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package events defines the ttrpc event service. +package events + +import types "github.com/containerd/containerd/api/types" + +// Deprecated: Use [types.Envelope]. +type Envelope = types.Envelope diff --git a/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.pb.go b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.pb.go new file mode 100644 index 0000000000..9aa8390f96 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.pb.go @@ -0,0 +1,182 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: services/ttrpc/events/v1/events.proto + +package events + +import ( + types "github.com/containerd/containerd/api/types" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ForwardRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Envelope *types.Envelope `protobuf:"bytes,1,opt,name=envelope,proto3" json:"envelope,omitempty"` +} + +func (x *ForwardRequest) Reset() { + *x = ForwardRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_services_ttrpc_events_v1_events_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ForwardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForwardRequest) ProtoMessage() {} + +func (x *ForwardRequest) ProtoReflect() protoreflect.Message { + mi := &file_services_ttrpc_events_v1_events_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForwardRequest.ProtoReflect.Descriptor instead. +func (*ForwardRequest) Descriptor() ([]byte, []int) { + return file_services_ttrpc_events_v1_events_proto_rawDescGZIP(), []int{0} +} + +func (x *ForwardRequest) GetEnvelope() *types.Envelope { + if x != nil { + return x.Envelope + } + return nil +} + +var File_services_ttrpc_events_v1_events_proto protoreflect.FileDescriptor + +var file_services_ttrpc_events_v1_events_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, + 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x23, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x48, 0x0a, 0x0e, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, + 0x0a, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x52, 0x08, 0x65, 0x6e, + 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x32, 0x60, 0x0a, 0x06, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x12, 0x56, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x33, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x76, + 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_services_ttrpc_events_v1_events_proto_rawDescOnce sync.Once + file_services_ttrpc_events_v1_events_proto_rawDescData = file_services_ttrpc_events_v1_events_proto_rawDesc +) + +func file_services_ttrpc_events_v1_events_proto_rawDescGZIP() []byte { + file_services_ttrpc_events_v1_events_proto_rawDescOnce.Do(func() { + file_services_ttrpc_events_v1_events_proto_rawDescData = protoimpl.X.CompressGZIP(file_services_ttrpc_events_v1_events_proto_rawDescData) + }) + return file_services_ttrpc_events_v1_events_proto_rawDescData +} + +var file_services_ttrpc_events_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_services_ttrpc_events_v1_events_proto_goTypes = []interface{}{ + (*ForwardRequest)(nil), // 0: containerd.services.events.ttrpc.v1.ForwardRequest + (*types.Envelope)(nil), // 1: containerd.types.Envelope + (*emptypb.Empty)(nil), // 2: google.protobuf.Empty +} +var file_services_ttrpc_events_v1_events_proto_depIdxs = []int32{ + 1, // 0: containerd.services.events.ttrpc.v1.ForwardRequest.envelope:type_name -> containerd.types.Envelope + 0, // 1: containerd.services.events.ttrpc.v1.Events.Forward:input_type -> containerd.services.events.ttrpc.v1.ForwardRequest + 2, // 2: containerd.services.events.ttrpc.v1.Events.Forward:output_type -> google.protobuf.Empty + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_services_ttrpc_events_v1_events_proto_init() } +func file_services_ttrpc_events_v1_events_proto_init() { + if File_services_ttrpc_events_v1_events_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_services_ttrpc_events_v1_events_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_ttrpc_events_v1_events_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_ttrpc_events_v1_events_proto_goTypes, + DependencyIndexes: file_services_ttrpc_events_v1_events_proto_depIdxs, + MessageInfos: file_services_ttrpc_events_v1_events_proto_msgTypes, + }.Build() + File_services_ttrpc_events_v1_events_proto = out.File + file_services_ttrpc_events_v1_events_proto_rawDesc = nil + file_services_ttrpc_events_v1_events_proto_goTypes = nil + file_services_ttrpc_events_v1_events_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.proto b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.proto new file mode 100644 index 0000000000..8b5527328d --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.proto @@ -0,0 +1,37 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.services.events.ttrpc.v1; + +import "google/protobuf/empty.proto"; +import "types/event.proto"; + +option go_package = "github.com/containerd/containerd/api/services/ttrpc/events/v1;events"; + +service Events { + // Forward sends an event that has already been packaged into an envelope + // with a timestamp and namespace. + // + // This is useful if earlier timestamping is required or when forwarding on + // behalf of another component, namespace or publisher. + rpc Forward(ForwardRequest) returns (google.protobuf.Empty); +} + +message ForwardRequest { + containerd.types.Envelope envelope = 1; +} diff --git a/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events_ttrpc.pb.go b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events_ttrpc.pb.go new file mode 100644 index 0000000000..944b1385b2 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/services/ttrpc/events/v1/events_ttrpc.pb.go @@ -0,0 +1,45 @@ +// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT. +// source: services/ttrpc/events/v1/events.proto +package events + +import ( + context "context" + ttrpc "github.com/containerd/ttrpc" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +type TTRPCEventsService interface { + Forward(context.Context, *ForwardRequest) (*emptypb.Empty, error) +} + +func RegisterTTRPCEventsService(srv *ttrpc.Server, svc TTRPCEventsService) { + srv.RegisterService("containerd.services.events.ttrpc.v1.Events", &ttrpc.ServiceDesc{ + Methods: map[string]ttrpc.Method{ + "Forward": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ForwardRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Forward(ctx, &req) + }, + }, + }) +} + +type ttrpceventsClient struct { + client *ttrpc.Client +} + +func NewTTRPCEventsClient(client *ttrpc.Client) TTRPCEventsService { + return &ttrpceventsClient{ + client: client, + } +} + +func (c *ttrpceventsClient) Forward(ctx context.Context, req *ForwardRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.services.events.ttrpc.v1.Events", "Forward", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply.go b/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply.go new file mode 100644 index 0000000000..67dccf9222 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply.go @@ -0,0 +1,210 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package apply + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "time" + + "github.com/containerd/containerd/v2/core/content" + "github.com/containerd/containerd/v2/core/diff" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/errdefs" + "github.com/containerd/log" + digest "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// NewFileSystemApplier returns an applier which simply mounts +// and applies diff onto the mounted filesystem. +func NewFileSystemApplier(cs content.Provider) diff.Applier { + return NewFileSystemApplierWithMountManager(cs, nil) +} + +// NewFileSystemApplierWithMountManager returns an applier which simply mounts and +// applies diff onto the mounted filesystem. +// An optional mount manager can be specified and it will take effect when applying. +func NewFileSystemApplierWithMountManager(cs content.Provider, mm mount.Manager) diff.Applier { + return &fsApplier{ + store: cs, + mount: mm, + } +} + +type fsApplier struct { + store content.Provider + mount mount.Manager +} + +var emptyDesc = ocispec.Descriptor{} + +// Apply applies the content associated with the provided digests onto the +// provided mounts. Archive content will be extracted and decompressed if +// necessary. +func (s *fsApplier) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) { + t1 := time.Now() + defer func() { + if err == nil { + log.G(ctx).WithFields(log.Fields{ + "d": time.Since(t1), + "digest": desc.Digest, + "size": desc.Size, + "media": desc.MediaType, + }).Debugf("diff applied") + } + }() + + var config diff.ApplyConfig + for _, o := range opts { + if err := o(ctx, desc, &config); err != nil { + return emptyDesc, fmt.Errorf("failed to apply config opt: %w", err) + } + } + + ra, err := s.store.ReaderAt(ctx, desc) + if err != nil { + return emptyDesc, fmt.Errorf("failed to get reader from content store: %w", err) + } + var r io.ReadCloser + if config.Progress != nil { + r = newProgressReader(ra, config.Progress) + } else { + r = newReadCloser(ra) + } + defer r.Close() + + var processors []diff.StreamProcessor + processor := diff.NewProcessorChain(desc.MediaType, r) + processors = append(processors, processor) + for { + if processor, err = diff.GetProcessor(ctx, processor, config.ProcessorPayloads); err != nil { + return emptyDesc, fmt.Errorf("failed to get stream processor for %s: %w", desc.MediaType, err) + } + processors = append(processors, processor) + if processor.MediaType() == ocispec.MediaTypeImageLayer { + break + } + } + defer processor.Close() + + digester := digest.Canonical.Digester() + rc := &readCounter{ + r: io.TeeReader(processor, digester.Hash()), + } + + // The number of `mounts` that need to be parsed by the mount manager + // will be more than 1 in reality; this is needed to work around some + // overlayfs/bind shortcuts in core/diff/apply/apply_linux.go + if s.mount != nil && len(mounts) > 1 { + var b [3]byte + // Ignore read failures, just decreases uniqueness + rand.Read(b[:]) + id := fmt.Sprintf("fs-diffapply-%d-%s", t1.Nanosecond(), base64.URLEncoding.EncodeToString(b[:])) + info, err := s.mount.Activate(ctx, id, mounts) + if err == nil { + defer s.mount.Deactivate(ctx, id) + mounts = info.System + } else if !errdefs.IsNotImplemented(err) { + return emptyDesc, fmt.Errorf("failed to activate mounts: %w", err) + } + } + + if err := apply(ctx, mounts, rc, config.SyncFs); err != nil { + return emptyDesc, err + } + + // Read any trailing data + if _, err := io.Copy(io.Discard, rc); err != nil { + return emptyDesc, err + } + + for _, p := range processors { + if ep, ok := p.(interface { + Err() error + }); ok { + if err := ep.Err(); err != nil { + return emptyDesc, err + } + } + } + + return ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageLayer, + Size: rc.c, + Digest: digester.Digest(), + }, nil +} + +type readCounter struct { + r io.Reader + c int64 +} + +func (rc *readCounter) Read(p []byte) (n int, err error) { + n, err = rc.r.Read(p) + if n > 0 { + rc.c += int64(n) + } + return +} + +type progressReader struct { + rc *readCounter + c io.Closer + p func(int64) +} + +func newProgressReader(ra content.ReaderAt, p func(int64)) io.ReadCloser { + return &progressReader{ + rc: &readCounter{ + r: content.NewReader(ra), + c: 0, + }, + c: ra, + p: p, + } +} + +func (pr *progressReader) Read(p []byte) (n int, err error) { + // Call the progress function with the current count, indicating + // the previously read content has been processed. Initial + // progress of 0 indicates start of processing. + pr.p(pr.rc.c) + n, err = pr.rc.Read(p) + return +} + +func (pr *progressReader) Close() error { + pr.p(pr.rc.c) + return pr.c.Close() +} + +type readCloser struct { + io.Reader + io.Closer +} + +func newReadCloser(ra content.ReaderAt) io.ReadCloser { + return &readCloser{ + Reader: content.NewReader(ra), + Closer: ra, + } +} diff --git a/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply_linux.go b/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply_linux.go new file mode 100644 index 0000000000..7856495f63 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply_linux.go @@ -0,0 +1,105 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package apply + +import ( + "context" + "fmt" + "io" + "os" + "strings" + + "github.com/moby/sys/userns" + "golang.org/x/sys/unix" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/pkg/archive" + "github.com/containerd/errdefs" +) + +func apply(ctx context.Context, mounts []mount.Mount, r io.Reader, sync bool) (retErr error) { + switch { + case len(mounts) == 1 && mounts[0].Type == "overlay": + // OverlayConvertWhiteout (mknod c 0 0) doesn't work in userns. + // https://github.com/containerd/containerd/issues/3762 + if userns.RunningInUserNS() { + break + } + path, parents, err := getOverlayPath(mounts[0].Options) + if err != nil { + if errdefs.IsInvalidArgument(err) { + break + } + return err + } + opts := []archive.ApplyOpt{ + archive.WithConvertWhiteout(archive.OverlayConvertWhiteout), + } + if len(parents) > 0 { + opts = append(opts, archive.WithParents(parents)) + } + _, err = archive.Apply(ctx, path, r, opts...) + if err == nil && sync { + err = doSyncFs(path) + } + return err + case sync && len(mounts) == 1 && mounts[0].Type == "bind": + defer func() { + if retErr != nil { + return + } + + retErr = doSyncFs(mounts[0].Source) + }() + } + return mount.WithTempMount(ctx, mounts, func(root string) error { + _, err := archive.Apply(ctx, root, r) + return err + }) +} + +func getOverlayPath(options []string) (upper string, lower []string, err error) { + const upperdirPrefix = "upperdir=" + const lowerdirPrefix = "lowerdir=" + + for _, o := range options { + if after, ok := strings.CutPrefix(o, upperdirPrefix); ok { + upper = after + } else if after, ok := strings.CutPrefix(o, lowerdirPrefix); ok { + lower = strings.Split(after, ":") + } + } + if upper == "" { + return "", nil, fmt.Errorf("upperdir not found: %w", errdefs.ErrInvalidArgument) + } + + return +} + +func doSyncFs(file string) error { + fd, err := os.Open(file) + if err != nil { + return fmt.Errorf("failed to open %s: %w", file, err) + } + defer fd.Close() + + err = unix.Syncfs(int(fd.Fd())) + if err != nil { + return fmt.Errorf("failed to syncfs for %s: %w", file, err) + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply_other.go b/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply_other.go new file mode 100644 index 0000000000..fc37d9234e --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/diff/apply/apply_other.go @@ -0,0 +1,51 @@ +//go:build !linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package apply + +import ( + "context" + "io" + "os" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/pkg/archive" +) + +func apply(ctx context.Context, mounts []mount.Mount, r io.Reader, _sync bool) error { + // TODO: for windows, how to sync? + + if !mount.HasBindMounts && len(mounts) == 1 && mounts[0].Type == "bind" { + opts := []archive.ApplyOpt{} + + if os.Getuid() != 0 { + opts = append(opts, archive.WithNoSameOwner()) + } + + path := mounts[0].Source + _, err := archive.Apply(ctx, path, r, opts...) + return err + + // TODO: Do we need to sync all the filesystems? + } + + return mount.WithTempMount(ctx, mounts, func(root string) error { + _, err := archive.Apply(ctx, root, r) + return err + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/events/exchange/exchange.go b/vendor/github.com/containerd/containerd/v2/core/events/exchange/exchange.go new file mode 100644 index 0000000000..9ece820683 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/events/exchange/exchange.go @@ -0,0 +1,248 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package exchange + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/pkg/filters" + "github.com/containerd/containerd/v2/pkg/identifiers" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/typeurl/v2" + goevents "github.com/docker/go-events" +) + +// Exchange broadcasts events +type Exchange struct { + broadcaster *goevents.Broadcaster +} + +// NewExchange returns a new event Exchange +func NewExchange() *Exchange { + return &Exchange{ + broadcaster: goevents.NewBroadcaster(), + } +} + +var _ events.Publisher = &Exchange{} +var _ events.Forwarder = &Exchange{} +var _ events.Subscriber = &Exchange{} + +// Forward accepts an envelope to be directly distributed on the exchange. +// +// This is useful when an event is forwarded on behalf of another namespace or +// when the event is propagated on behalf of another publisher. +func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) { + if err := validateEnvelope(envelope); err != nil { + return err + } + + defer func() { + logger := log.G(ctx).WithFields(log.Fields{ + "topic": envelope.Topic, + "ns": envelope.Namespace, + "type": envelope.Event.GetTypeUrl(), + }) + + if err != nil { + logger.WithError(err).Error("error forwarding event") + } else { + logger.Trace("event forwarded") + } + }() + + return e.broadcaster.Write(envelope) +} + +// Publish packages and sends an event. The caller will be considered the +// initial publisher of the event. This means the timestamp will be calculated +// at this point and this method may read from the calling context. +func (e *Exchange) Publish(ctx context.Context, topic string, event events.Event) (err error) { + var ( + namespace string + envelope events.Envelope + ) + + namespace, err = namespaces.NamespaceRequired(ctx) + if err != nil { + return fmt.Errorf("failed publishing event: %w", err) + } + if err := validateTopic(topic); err != nil { + return fmt.Errorf("envelope topic %q: %w", topic, err) + } + + encoded, err := typeurl.MarshalAny(event) + if err != nil { + return err + } + + envelope.Timestamp = time.Now().UTC() + envelope.Namespace = namespace + envelope.Topic = topic + envelope.Event = encoded + + defer func() { + logger := log.G(ctx).WithFields(log.Fields{ + "topic": envelope.Topic, + "ns": envelope.Namespace, + "type": envelope.Event.GetTypeUrl(), + }) + + if err != nil { + logger.WithError(err).Error("error publishing event") + } else { + logger.Trace("event published") + } + }() + + return e.broadcaster.Write(&envelope) +} + +// Subscribe to events on the exchange. Events are sent through the returned +// channel ch. If an error is encountered, it will be sent on channel errs and +// errs will be closed. To end the subscription, cancel the provided context. +// +// Zero or more filters may be provided as strings. Only events that match +// *any* of the provided filters will be sent on the channel. The filters use +// the standard containerd filters package syntax. +func (e *Exchange) Subscribe(ctx context.Context, fs ...string) (ch <-chan *events.Envelope, errs <-chan error) { + var ( + evch = make(chan *events.Envelope) + errq = make(chan error, 1) + channel = goevents.NewChannel(0) + queue = goevents.NewQueue(channel) + dst goevents.Sink = queue + ) + + closeAll := func() { + channel.Close() + queue.Close() + e.broadcaster.Remove(dst) + close(errq) + } + + ch = evch + errs = errq + + if len(fs) > 0 { + filter, err := filters.ParseAll(fs...) + if err != nil { + errq <- fmt.Errorf("failed parsing subscription filters: %w", err) + closeAll() + return + } + + dst = goevents.NewFilter(queue, goevents.MatcherFunc(func(gev goevents.Event) bool { + return filter.Match(adapt(gev)) + })) + } + + e.broadcaster.Add(dst) + + go func() { + defer closeAll() + + var err error + loop: + for { + select { + case ev := <-channel.C: + env, ok := ev.(*events.Envelope) + if !ok { + // TODO(stevvooe): For the most part, we are well protected + // from this condition. Both Forward and Publish protect + // from this. + err = fmt.Errorf("invalid envelope encountered %#v; please file a bug", ev) + break + } + + select { + case evch <- env: + case <-ctx.Done(): + break loop + } + case <-ctx.Done(): + break loop + } + } + + if err == nil { + if cerr := ctx.Err(); cerr != context.Canceled { + err = cerr + } + } + + errq <- err + }() + + return +} + +func validateTopic(topic string) error { + if topic == "" { + return fmt.Errorf("must not be empty: %w", errdefs.ErrInvalidArgument) + } + + if topic[0] != '/' { + return fmt.Errorf("must start with '/': %w", errdefs.ErrInvalidArgument) + } + + if len(topic) == 1 { + return fmt.Errorf("must have at least one component: %w", errdefs.ErrInvalidArgument) + } + + components := strings.SplitSeq(topic[1:], "/") + for component := range components { + if err := identifiers.Validate(component); err != nil { + return fmt.Errorf("failed validation on component %q: %w", component, err) + } + } + + return nil +} + +func validateEnvelope(envelope *events.Envelope) error { + if err := identifiers.Validate(envelope.Namespace); err != nil { + return fmt.Errorf("event envelope has invalid namespace: %w", err) + } + + if err := validateTopic(envelope.Topic); err != nil { + return fmt.Errorf("envelope topic %q: %w", envelope.Topic, err) + } + + if envelope.Timestamp.IsZero() { + return fmt.Errorf("timestamp must be set on forwarded event: %w", errdefs.ErrInvalidArgument) + } + + return nil +} + +func adapt(ev any) filters.Adaptor { + if adaptor, ok := ev.(filters.Adaptor); ok { + return adaptor + } + + return filters.AdapterFunc(func(fieldpath []string) (string, bool) { + return "", false + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/cgroups.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/cgroups.go new file mode 100644 index 0000000000..56e8020a8d --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/cgroups.go @@ -0,0 +1,98 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cgroups + +import ( + "context" + + "github.com/containerd/cgroups/v3" + "github.com/containerd/containerd/v2/core/events" + v1 "github.com/containerd/containerd/v2/core/metrics/cgroups/v1" + v2 "github.com/containerd/containerd/v2/core/metrics/cgroups/v2" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/version" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + metrics "github.com/docker/go-metrics" +) + +// Config for the cgroups monitor +type Config struct { + NoPrometheus bool `toml:"no_prometheus"` +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.TaskMonitorPlugin, + ID: "cgroups", + InitFn: New, + Requires: []plugin.Type{ + plugins.EventPlugin, + }, + Config: &Config{}, + ConfigMigration: func(ctx context.Context, configVersion int, pluginConfigs map[string]any) error { + if configVersion >= version.ConfigVersion { + return nil + } + // Previous plugin name + const pluginName = "io.containerd.monitor.v1.cgroups" + c, ok := pluginConfigs[pluginName] + if ok { + pluginConfigs[string(plugins.TaskMonitorPlugin)+".cgroups"] = c + delete(pluginConfigs, pluginName) + } + + return nil + }, + }) +} + +// New returns a new cgroups monitor +func New(ic *plugin.InitContext) (any, error) { + var ns *metrics.Namespace + config := ic.Config.(*Config) + if !config.NoPrometheus { + ns = metrics.NewNamespace("container", "", nil) + } + var ( + tm runtime.TaskMonitor + err error + ) + + ep, err := ic.GetSingle(plugins.EventPlugin) + if err != nil { + return nil, err + } + + if cgroups.Mode() == cgroups.Unified { + tm, err = v2.NewTaskMonitor(ic.Context, ep.(events.Publisher), ns) + } else { + tm, err = v1.NewTaskMonitor(ic.Context, ep.(events.Publisher), ns) + } + if err != nil { + return nil, err + } + if ns != nil { + metrics.Register(ns) + } + ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) + return tm, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/common/type.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/common/type.go new file mode 100644 index 0000000000..6a1d385add --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/common/type.go @@ -0,0 +1,32 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package common + +import ( + "context" + + "github.com/containerd/containerd/v2/pkg/protobuf/types" +) + +// Statable type that returns cgroup metrics +type Statable interface { + ID() string + Namespace() string + Stats(context.Context) (*types.Any, error) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/blkio.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/blkio.go new file mode 100644 index 0000000000..562539f274 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/blkio.go @@ -0,0 +1,132 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + "strconv" + + v1 "github.com/containerd/containerd/v2/core/metrics/types/v1" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var blkioMetrics = []*metric{ + { + name: "blkio_io_merged_recursive", + help: "The blkio io merged recursive", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"op", "device", "major", "minor"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Blkio == nil { + return nil + } + return blkioValues(stats.Blkio.IoMergedRecursive) + }, + }, + { + name: "blkio_io_queued_recursive", + help: "The blkio io queued recursive", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"op", "device", "major", "minor"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Blkio == nil { + return nil + } + return blkioValues(stats.Blkio.IoQueuedRecursive) + }, + }, + { + name: "blkio_io_service_bytes_recursive", + help: "The blkio io service bytes recursive", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + labels: []string{"op", "device", "major", "minor"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Blkio == nil { + return nil + } + return blkioValues(stats.Blkio.IoServiceBytesRecursive) + }, + }, + { + name: "blkio_io_service_time_recursive", + help: "The blkio io service time recursive", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"op", "device", "major", "minor"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Blkio == nil { + return nil + } + return blkioValues(stats.Blkio.IoServiceTimeRecursive) + }, + }, + { + name: "blkio_io_serviced_recursive", + help: "The blkio io serviced recursive", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"op", "device", "major", "minor"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Blkio == nil { + return nil + } + return blkioValues(stats.Blkio.IoServicedRecursive) + }, + }, + { + name: "blkio_io_time_recursive", + help: "The blkio io time recursive", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"op", "device", "major", "minor"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Blkio == nil { + return nil + } + return blkioValues(stats.Blkio.IoTimeRecursive) + }, + }, + { + name: "blkio_sectors_recursive", + help: "The blkio sectors recursive", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"op", "device", "major", "minor"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Blkio == nil { + return nil + } + return blkioValues(stats.Blkio.SectorsRecursive) + }, + }, +} + +func blkioValues(l []*v1.BlkIOEntry) []value { + var out []value + for _, e := range l { + out = append(out, value{ + v: float64(e.Value), + l: []string{e.Op, e.Device, strconv.FormatUint(e.Major, 10), strconv.FormatUint(e.Minor, 10)}, + }) + } + return out +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/cgroups.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/cgroups.go new file mode 100644 index 0000000000..e491f50e12 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/cgroups.go @@ -0,0 +1,95 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + "context" + + cgroups "github.com/containerd/cgroups/v3/cgroup1" + eventstypes "github.com/containerd/containerd/api/events" + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/docker/go-metrics" +) + +// NewTaskMonitor returns a new cgroups monitor +func NewTaskMonitor(ctx context.Context, publisher events.Publisher, ns *metrics.Namespace) (runtime.TaskMonitor, error) { + collector := NewCollector(ns) + oom, err := newOOMCollector(ns) + if err != nil { + return nil, err + } + return &cgroupsMonitor{ + collector: collector, + oom: oom, + context: ctx, + publisher: publisher, + }, nil +} + +type cgroupsMonitor struct { + collector *Collector + oom *oomCollector + context context.Context + publisher events.Publisher +} + +type cgroupTask interface { + Cgroup() (cgroups.Cgroup, error) +} + +func (m *cgroupsMonitor) Monitor(c runtime.Task, labels map[string]string) error { + if err := m.collector.Add(c, labels); err != nil { + return err + } + t, ok := c.(cgroupTask) + if !ok { + return nil + } + cg, err := t.Cgroup() + if err != nil { + if errdefs.IsNotFound(err) { + return nil + } + return err + } + err = m.oom.Add(c.ID(), c.Namespace(), cg, m.trigger) + if err == cgroups.ErrMemoryNotSupported { + log.L.WithError(err).Warn("OOM monitoring failed") + return nil + } + return err +} + +func (m *cgroupsMonitor) Stop(c runtime.Task) error { + m.collector.Remove(c) + return nil +} + +func (m *cgroupsMonitor) trigger(id, namespace string, cg cgroups.Cgroup) { + ctx := namespaces.WithNamespace(m.context, namespace) + if err := m.publisher.Publish(ctx, runtime.TaskOOMEventTopic, &eventstypes.TaskOOM{ + ContainerID: id, + }); err != nil { + log.G(m.context).WithError(err).Error("post OOM event") + } +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/cpu.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/cpu.go new file mode 100644 index 0000000000..a08f9e86e4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/cpu.go @@ -0,0 +1,146 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + "strconv" + + v1 "github.com/containerd/containerd/v2/core/metrics/types/v1" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var cpuMetrics = []*metric{ + { + name: "cpu_total", + help: "The total cpu time", + unit: metrics.Nanoseconds, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.Usage.Total), + }, + } + }, + }, + { + name: "cpu_kernel", + help: "The total kernel cpu time", + unit: metrics.Nanoseconds, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.Usage.Kernel), + }, + } + }, + }, + { + name: "cpu_user", + help: "The total user cpu time", + unit: metrics.Nanoseconds, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.Usage.User), + }, + } + }, + }, + { + name: "per_cpu", + help: "The total cpu time per cpu", + unit: metrics.Nanoseconds, + vt: prometheus.GaugeValue, + labels: []string{"cpu"}, + getValues: func(stats *v1.Metrics) []value { + if stats.CPU == nil { + return nil + } + var out []value + for i, v := range stats.CPU.Usage.PerCPU { + out = append(out, value{ + v: float64(v), + l: []string{strconv.Itoa(i)}, + }) + } + return out + }, + }, + { + name: "cpu_throttle_periods", + help: "The total cpu throttle periods", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.Throttling.Periods), + }, + } + }, + }, + { + name: "cpu_throttled_periods", + help: "The total cpu throttled periods", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.Throttling.ThrottledPeriods), + }, + } + }, + }, + { + name: "cpu_throttled_time", + help: "The total cpu throttled time", + unit: metrics.Nanoseconds, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.Throttling.ThrottledTime), + }, + } + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/hugetlb.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/hugetlb.go new file mode 100644 index 0000000000..36dcc92dc4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/hugetlb.go @@ -0,0 +1,88 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + v1 "github.com/containerd/containerd/v2/core/metrics/types/v1" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var hugetlbMetrics = []*metric{ + { + name: "hugetlb_usage", + help: "The hugetlb usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + labels: []string{"page"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Hugetlb == nil { + return nil + } + var out []value + for _, v := range stats.Hugetlb { + out = append(out, value{ + v: float64(v.Usage), + l: []string{v.Pagesize}, + }) + } + return out + }, + }, + { + name: "hugetlb_failcnt", + help: "The hugetlb failcnt", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"page"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Hugetlb == nil { + return nil + } + var out []value + for _, v := range stats.Hugetlb { + out = append(out, value{ + v: float64(v.Failcnt), + l: []string{v.Pagesize}, + }) + } + return out + }, + }, + { + name: "hugetlb_max", + help: "The hugetlb maximum usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + labels: []string{"page"}, + getValues: func(stats *v1.Metrics) []value { + if stats.Hugetlb == nil { + return nil + } + var out []value + for _, v := range stats.Hugetlb { + out = append(out, value{ + v: float64(v.Max), + l: []string{v.Pagesize}, + }) + } + return out + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/memory.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/memory.go new file mode 100644 index 0000000000..75dbfb9469 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/memory.go @@ -0,0 +1,796 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + v1 "github.com/containerd/containerd/v2/core/metrics/types/v1" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var memoryMetrics = []*metric{ + { + name: "memory_cache", + help: "The cache amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Cache), + }, + } + }, + }, + { + name: "memory_rss", + help: "The rss amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.RSS), + }, + } + }, + }, + { + name: "memory_rss_huge", + help: "The rss_huge amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.RSSHuge), + }, + } + }, + }, + { + name: "memory_mapped_file", + help: "The mapped_file amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.MappedFile), + }, + } + }, + }, + { + name: "memory_dirty", + help: "The dirty amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Dirty), + }, + } + }, + }, + { + name: "memory_writeback", + help: "The writeback amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Writeback), + }, + } + }, + }, + { + name: "memory_pgpgin", + help: "The pgpgin amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.PgPgIn), + }, + } + }, + }, + { + name: "memory_pgpgout", + help: "The pgpgout amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.PgPgOut), + }, + } + }, + }, + { + name: "memory_pgfault", + help: "The pgfault amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.PgFault), + }, + } + }, + }, + { + name: "memory_pgmajfault", + help: "The pgmajfault amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.PgMajFault), + }, + } + }, + }, + { + name: "memory_inactive_anon", + help: "The inactive_anon amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.InactiveAnon), + }, + } + }, + }, + { + name: "memory_active_anon", + help: "The active_anon amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.ActiveAnon), + }, + } + }, + }, + { + name: "memory_inactive_file", + help: "The inactive_file amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.InactiveFile), + }, + } + }, + }, + { + name: "memory_active_file", + help: "The active_file amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.ActiveFile), + }, + } + }, + }, + { + name: "memory_unevictable", + help: "The unevictable amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Unevictable), + }, + } + }, + }, + { + name: "memory_hierarchical_memory_limit", + help: "The hierarchical_memory_limit amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.HierarchicalMemoryLimit), + }, + } + }, + }, + { + name: "memory_hierarchical_memsw_limit", + help: "The hierarchical_memsw_limit amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.HierarchicalSwapLimit), + }, + } + }, + }, + { + name: "memory_total_cache", + help: "The total_cache amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalCache), + }, + } + }, + }, + { + name: "memory_total_rss", + help: "The total_rss amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalRSS), + }, + } + }, + }, + { + name: "memory_total_rss_huge", + help: "The total_rss_huge amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalRSSHuge), + }, + } + }, + }, + { + name: "memory_total_mapped_file", + help: "The total_mapped_file amount used", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalMappedFile), + }, + } + }, + }, + { + name: "memory_total_dirty", + help: "The total_dirty amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalDirty), + }, + } + }, + }, + { + name: "memory_total_writeback", + help: "The total_writeback amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalWriteback), + }, + } + }, + }, + { + name: "memory_total_pgpgin", + help: "The total_pgpgin amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalPgPgIn), + }, + } + }, + }, + { + name: "memory_total_pgpgout", + help: "The total_pgpgout amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalPgPgOut), + }, + } + }, + }, + { + name: "memory_total_pgfault", + help: "The total_pgfault amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalPgFault), + }, + } + }, + }, + { + name: "memory_total_pgmajfault", + help: "The total_pgmajfault amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalPgMajFault), + }, + } + }, + }, + { + name: "memory_total_inactive_anon", + help: "The total_inactive_anon amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalInactiveAnon), + }, + } + }, + }, + { + name: "memory_total_active_anon", + help: "The total_active_anon amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalActiveAnon), + }, + } + }, + }, + { + name: "memory_total_inactive_file", + help: "The total_inactive_file amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalInactiveFile), + }, + } + }, + }, + { + name: "memory_total_active_file", + help: "The total_active_file amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalActiveFile), + }, + } + }, + }, + { + name: "memory_total_unevictable", + help: "The total_unevictable amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.TotalUnevictable), + }, + } + }, + }, + { + name: "memory_usage_failcnt", + help: "The usage failcnt", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetUsage() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Usage.Failcnt), + }, + } + }, + }, + { + name: "memory_usage_limit", + help: "The memory limit", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetUsage() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Usage.Limit), + }, + } + }, + }, + { + name: "memory_usage_max", + help: "The memory maximum usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetUsage() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Usage.Max), + }, + } + }, + }, + { + name: "memory_usage_usage", + help: "The memory usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetUsage() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Usage.Usage), + }, + } + }, + }, + { + name: "memory_swap_failcnt", + help: "The swap failcnt", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetSwap() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Swap.Failcnt), + }, + } + }, + }, + { + name: "memory_swap_limit", + help: "The swap limit", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetSwap() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Swap.Limit), + }, + } + }, + }, + { + name: "memory_swap_max", + help: "The swap maximum usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetSwap() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Swap.Max), + }, + } + }, + }, + { + name: "memory_swap_usage", + help: "The swap usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetSwap() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Swap.Usage), + }, + } + }, + }, + { + name: "memory_kernel_failcnt", + help: "The kernel failcnt", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernel() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Kernel.Failcnt), + }, + } + }, + }, + { + name: "memory_kernel_limit", + help: "The kernel limit", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernel() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Kernel.Limit), + }, + } + }, + }, + { + name: "memory_kernel_max", + help: "The kernel maximum usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernel() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Kernel.Max), + }, + } + }, + }, + { + name: "memory_kernel_usage", + help: "The kernel usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernel() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Kernel.Usage), + }, + } + }, + }, + { + name: "memory_kerneltcp_failcnt", + help: "The kerneltcp failcnt", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernelTCP() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.KernelTCP.Failcnt), + }, + } + }, + }, + { + name: "memory_kerneltcp_limit", + help: "The kerneltcp limit", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernelTCP() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.KernelTCP.Limit), + }, + } + }, + }, + { + name: "memory_kerneltcp_max", + help: "The kerneltcp maximum usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernelTCP() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.KernelTCP.Max), + }, + } + }, + }, + { + name: "memory_kerneltcp_usage", + help: "The kerneltcp usage", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.GetMemory().GetKernelTCP() == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.KernelTCP.Usage), + }, + } + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/metric.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/metric.go new file mode 100644 index 0000000000..8c73918de1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/metric.go @@ -0,0 +1,64 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + v1 "github.com/containerd/containerd/v2/core/metrics/types/v1" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// IDName is the name that is used to identify the id being collected in the metric +var IDName = "container_id" + +type value struct { + v float64 + l []string +} + +type metric struct { + name string + help string + unit metrics.Unit + vt prometheus.ValueType + labels []string + // getValues returns the value and labels for the data + getValues func(stats *v1.Metrics) []value +} + +func (m *metric) desc(ns *metrics.Namespace) *prometheus.Desc { + // the namespace label is for containerd namespaces + return ns.NewDesc(m.name, m.help, m.unit, append([]string{IDName, "namespace"}, m.labels...)...) +} + +func (m *metric) collect(id, namespace string, stats *v1.Metrics, ns *metrics.Namespace, ch chan<- prometheus.Metric, block bool) { + values := m.getValues(stats) + for _, v := range values { + // block signals to block on the sending the metrics so none are missed + if block { + ch <- prometheus.MustNewConstMetric(m.desc(ns), m.vt, v.v, append([]string{id, namespace}, v.l...)...) + continue + } + // non-blocking metrics can be dropped if the chan is full + select { + case ch <- prometheus.MustNewConstMetric(m.desc(ns), m.vt, v.v, append([]string{id, namespace}, v.l...)...): + default: + } + } +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/metrics.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/metrics.go new file mode 100644 index 0000000000..dd2c509573 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/metrics.go @@ -0,0 +1,208 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + "context" + "fmt" + "sync" + + cgroups "github.com/containerd/cgroups/v3/cgroup1" + cmetrics "github.com/containerd/containerd/v2/core/metrics" + "github.com/containerd/containerd/v2/core/metrics/cgroups/common" + v1 "github.com/containerd/containerd/v2/core/metrics/types/v1" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/timeout" + "github.com/containerd/log" + "github.com/containerd/typeurl/v2" + "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// Trigger will be called when an event happens and provides the cgroup +// where the event originated from +type Trigger func(string, string, cgroups.Cgroup) + +// NewCollector registers the collector with the provided namespace and returns it so +// that cgroups can be added for collection +func NewCollector(ns *metrics.Namespace) *Collector { + if ns == nil { + return &Collector{} + } + // add machine cpus and memory info + c := &Collector{ + ns: ns, + tasks: make(map[string]entry), + } + c.metrics = append(c.metrics, pidMetrics...) + c.metrics = append(c.metrics, cpuMetrics...) + c.metrics = append(c.metrics, memoryMetrics...) + c.metrics = append(c.metrics, hugetlbMetrics...) + c.metrics = append(c.metrics, blkioMetrics...) + c.storedMetrics = make(chan prometheus.Metric, 100*len(c.metrics)) + ns.Add(c) + return c +} + +func taskID(id, namespace string) string { + return fmt.Sprintf("%s-%s", id, namespace) +} + +type entry struct { + task common.Statable + // ns is an optional child namespace that contains additional to parent labels. + // This can be used to append task specific labels to be able to differentiate the different containerd metrics. + ns *metrics.Namespace +} + +// Collector provides the ability to collect container stats and export +// them in the prometheus format +type Collector struct { + ns *metrics.Namespace + storedMetrics chan prometheus.Metric + + // TODO(fuweid): + // + // The Collector.Collect will be the field ns'Collect's callback, + // which be invoked periodically with internal lock. And Collector.Add + // might also invoke ns.Lock if the labels is not nil, which is easy to + // cause dead-lock. + // + // Goroutine X: + // + // ns.Collect + // ns.Lock + // Collector.Collect + // Collector.RLock + // + // + // Goroutine Y: + // + // Collector.Add + // ...(RLock/Lock) + // ns.Lock + // + // I think we should seek the way to decouple ns from Collector. + mu sync.RWMutex + tasks map[string]entry + metrics []*metric +} + +// Describe prometheus metrics +func (c *Collector) Describe(ch chan<- *prometheus.Desc) { + for _, m := range c.metrics { + ch <- m.desc(c.ns) + } +} + +// Collect prometheus metrics +func (c *Collector) Collect(ch chan<- prometheus.Metric) { + c.mu.RLock() + wg := &sync.WaitGroup{} + for _, t := range c.tasks { + wg.Add(1) + go c.collect(t, ch, true, wg) + } +storedLoop: + for { + // read stored metrics until the channel is flushed + select { + case m := <-c.storedMetrics: + ch <- m + default: + break storedLoop + } + } + c.mu.RUnlock() + wg.Wait() +} + +func (c *Collector) collect(entry entry, ch chan<- prometheus.Metric, block bool, wg *sync.WaitGroup) { + if wg != nil { + defer wg.Done() + } + + t := entry.task + ctx, cancel := timeout.WithContext(context.Background(), cmetrics.ShimStatsRequestTimeout) + stats, err := t.Stats(namespaces.WithNamespace(ctx, t.Namespace())) + cancel() + + if err != nil { + log.L.WithError(err).Errorf("stat task %s", t.ID()) + return + } + + s := &v1.Metrics{} + if err := typeurl.UnmarshalTo(stats, s); err != nil { + log.L.WithError(err).Errorf("unmarshal stats for %s", t.ID()) + return + } + + ns := entry.ns + if ns == nil { + ns = c.ns + } + for _, m := range c.metrics { + m.collect(t.ID(), t.Namespace(), s, ns, ch, block) + } +} + +// Add adds the provided cgroup and id so that metrics are collected and exported +func (c *Collector) Add(t common.Statable, labels map[string]string) error { + if c.ns == nil { + return nil + } + c.mu.RLock() + id := taskID(t.ID(), t.Namespace()) + _, ok := c.tasks[id] + c.mu.RUnlock() + if ok { + return nil // requests to collect metrics should be idempotent + } + + entry := entry{task: t} + if labels != nil { + entry.ns = c.ns.WithConstLabels(labels) + } + + c.mu.Lock() + c.tasks[id] = entry + c.mu.Unlock() + return nil +} + +// Remove removes the provided cgroup by id from the collector +func (c *Collector) Remove(t common.Statable) { + if c.ns == nil { + return + } + c.mu.Lock() + delete(c.tasks, taskID(t.ID(), t.Namespace())) + c.mu.Unlock() +} + +// RemoveAll statable items from the collector +func (c *Collector) RemoveAll() { + if c.ns == nil { + return + } + c.mu.Lock() + c.tasks = make(map[string]entry) + c.mu.Unlock() +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/oom.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/oom.go new file mode 100644 index 0000000000..cadc725104 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/oom.go @@ -0,0 +1,165 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + "sync" + "sync/atomic" + + "golang.org/x/sys/unix" + + cgroups "github.com/containerd/cgroups/v3/cgroup1" + "github.com/containerd/containerd/v2/pkg/sys" + "github.com/containerd/log" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +func newOOMCollector(ns *metrics.Namespace) (*oomCollector, error) { + fd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) + if err != nil { + return nil, err + } + var desc *prometheus.Desc + if ns != nil { + desc = ns.NewDesc("memory_oom", "The number of times a container has received an oom event", metrics.Total, "container_id", "namespace") + } + c := &oomCollector{ + fd: fd, + desc: desc, + set: make(map[uintptr]*oom), + } + if ns != nil { + ns.Add(c) + } + go c.start() + return c, nil +} + +type oomCollector struct { + mu sync.Mutex + + desc *prometheus.Desc + fd int + set map[uintptr]*oom +} + +type oom struct { + // count needs to stay the first member of this struct to ensure 64bits + // alignment on a 32bits machine (e.g. arm32). This is necessary as we use + // the sync/atomic operations on this field. + count atomic.Int64 + id string + namespace string + c cgroups.Cgroup + triggers []Trigger +} + +func (o *oomCollector) Add(id, namespace string, cg cgroups.Cgroup, triggers ...Trigger) error { + o.mu.Lock() + defer o.mu.Unlock() + fd, err := cg.OOMEventFD() + if err != nil { + return err + } + o.set[fd] = &oom{ + id: id, + c: cg, + triggers: triggers, + namespace: namespace, + } + event := unix.EpollEvent{ + Fd: int32(fd), + Events: unix.EPOLLHUP | unix.EPOLLIN | unix.EPOLLERR, + } + return unix.EpollCtl(o.fd, unix.EPOLL_CTL_ADD, int(fd), &event) +} + +func (o *oomCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- o.desc +} + +func (o *oomCollector) Collect(ch chan<- prometheus.Metric) { + o.mu.Lock() + defer o.mu.Unlock() + for _, t := range o.set { + c := t.count.Load() + ch <- prometheus.MustNewConstMetric(o.desc, prometheus.CounterValue, float64(c), t.id, t.namespace) + } +} + +// Close closes the epoll fd +func (o *oomCollector) Close() error { + return unix.Close(o.fd) +} + +func (o *oomCollector) start() { + var ( + n int + err error + events [128]unix.EpollEvent + ) + for { + if err := sys.IgnoringEINTR(func() error { + n, err = unix.EpollWait(o.fd, events[:], -1) + return err + }); err != nil { + log.L.WithError(err).Error("cgroups: epoll wait failed, OOM notifications disabled") + return + } + + for i := 0; i < n; i++ { + o.process(uintptr(events[i].Fd)) + } + } +} + +func (o *oomCollector) process(fd uintptr) { + // make sure to always flush the eventfd + flushEventfd(fd) + + o.mu.Lock() + info, ok := o.set[fd] + if !ok { + o.mu.Unlock() + return + } + o.mu.Unlock() + // if we received an event but it was caused by the cgroup being deleted and the fd + // being closed make sure we close our copy and remove the container from the set + if info.c.State() == cgroups.Deleted { + o.mu.Lock() + delete(o.set, fd) + o.mu.Unlock() + unix.Close(int(fd)) + return + } + info.count.Add(1) + for _, t := range info.triggers { + t(info.id, info.namespace, info.c) + } +} + +func flushEventfd(efd uintptr) error { + // Buffer must be >= 8 bytes for eventfd reads + // https://man7.org/linux/man-pages/man2/eventfd.2.html + var buf [8]byte + _, err := unix.Read(int(efd), buf[:]) + return err +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/pids.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/pids.go new file mode 100644 index 0000000000..c05b37fdff --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v1/pids.go @@ -0,0 +1,60 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + v1 "github.com/containerd/containerd/v2/core/metrics/types/v1" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var pidMetrics = []*metric{ + { + name: "pids", + help: "The limit to the number of pids allowed", + unit: metrics.Unit("limit"), + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Pids == nil { + return nil + } + return []value{ + { + v: float64(stats.Pids.Limit), + }, + } + }, + }, + { + name: "pids", + help: "The current number of pids", + unit: metrics.Unit("current"), + vt: prometheus.GaugeValue, + getValues: func(stats *v1.Metrics) []value { + if stats.Pids == nil { + return nil + } + return []value{ + { + v: float64(stats.Pids.Current), + }, + } + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/cgroups.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/cgroups.go new file mode 100644 index 0000000000..d84def68f1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/cgroups.go @@ -0,0 +1,55 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/docker/go-metrics" +) + +// NewTaskMonitor returns a new cgroups monitor +func NewTaskMonitor(ctx context.Context, publisher events.Publisher, ns *metrics.Namespace) (runtime.TaskMonitor, error) { + collector := NewCollector(ns) + return &cgroupsMonitor{ + collector: collector, + context: ctx, + publisher: publisher, + }, nil +} + +type cgroupsMonitor struct { + collector *Collector + context context.Context + publisher events.Publisher +} + +func (m *cgroupsMonitor) Monitor(c runtime.Task, labels map[string]string) error { + if err := m.collector.Add(c, labels); err != nil { + return err + } + return nil +} + +func (m *cgroupsMonitor) Stop(c runtime.Task) error { + m.collector.Remove(c) + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/cpu.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/cpu.go new file mode 100644 index 0000000000..e8e2dc11c5 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/cpu.go @@ -0,0 +1,124 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + v2 "github.com/containerd/containerd/v2/core/metrics/types/v2" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var cpuMetrics = []*metric{ + { + name: "cpu_usage_usec", + help: "Total cpu usage (cgroup v2)", + unit: metrics.Unit("microseconds"), + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.UsageUsec), + }, + } + }, + }, + { + name: "cpu_user_usec", + help: "Current cpu usage in user space (cgroup v2)", + unit: metrics.Unit("microseconds"), + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.UserUsec), + }, + } + }, + }, + { + name: "cpu_system_usec", + help: "Current cpu usage in kernel space (cgroup v2)", + unit: metrics.Unit("microseconds"), + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.SystemUsec), + }, + } + }, + }, + { + name: "cpu_nr_periods", + help: "Current cpu number of periods (only if controller is enabled)", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.NrPeriods), + }, + } + }, + }, + { + name: "cpu_nr_throttled", + help: "Total number of times tasks have been throttled (only if controller is enabled)", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.NrThrottled), + }, + } + }, + }, + { + name: "cpu_throttled_usec", + help: "Total time duration for which tasks have been throttled. (only if controller is enabled)", + unit: metrics.Unit("microseconds"), + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.CPU == nil { + return nil + } + return []value{ + { + v: float64(stats.CPU.ThrottledUsec), + }, + } + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/io.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/io.go new file mode 100644 index 0000000000..756ceabfbc --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/io.go @@ -0,0 +1,110 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "strconv" + + v2 "github.com/containerd/containerd/v2/core/metrics/types/v2" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var ioMetrics = []*metric{ + { + name: "io_rbytes", + help: "IO bytes read", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + labels: []string{"major", "minor"}, + getValues: func(stats *v2.Metrics) []value { + if stats.Io == nil { + return nil + } + var out []value + for _, e := range stats.Io.Usage { + out = append(out, value{ + v: float64(e.Rbytes), + l: []string{strconv.FormatUint(e.Major, 10), strconv.FormatUint(e.Minor, 10)}, + }) + } + return out + }, + }, + { + name: "io_wbytes", + help: "IO bytes written", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + labels: []string{"major", "minor"}, + getValues: func(stats *v2.Metrics) []value { + if stats.Io == nil { + return nil + } + var out []value + for _, e := range stats.Io.Usage { + out = append(out, value{ + v: float64(e.Wbytes), + l: []string{strconv.FormatUint(e.Major, 10), strconv.FormatUint(e.Minor, 10)}, + }) + } + return out + }, + }, + { + name: "io_rios", + help: "Number of read IOs", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"major", "minor"}, + getValues: func(stats *v2.Metrics) []value { + if stats.Io == nil { + return nil + } + var out []value + for _, e := range stats.Io.Usage { + out = append(out, value{ + v: float64(e.Rios), + l: []string{strconv.FormatUint(e.Major, 10), strconv.FormatUint(e.Minor, 10)}, + }) + } + return out + }, + }, + { + name: "io_wios", + help: "Number of write IOs", + unit: metrics.Total, + vt: prometheus.GaugeValue, + labels: []string{"major", "minor"}, + getValues: func(stats *v2.Metrics) []value { + if stats.Io == nil { + return nil + } + var out []value + for _, e := range stats.Io.Usage { + out = append(out, value{ + v: float64(e.Wios), + l: []string{strconv.FormatUint(e.Major, 10), strconv.FormatUint(e.Minor, 10)}, + }) + } + return out + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/memory.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/memory.go new file mode 100644 index 0000000000..b752162310 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/memory.go @@ -0,0 +1,605 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + v2 "github.com/containerd/containerd/v2/core/metrics/types/v2" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var memoryMetrics = []*metric{ + { + name: "memory_usage", + help: "Current memory usage (cgroup v2)", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Usage), + }, + } + }, + }, + { + name: "memory_usage_limit", + help: "Current memory usage limit (cgroup v2)", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.UsageLimit), + }, + } + }, + }, + { + name: "memory_swap_usage", + help: "Current swap usage (cgroup v2)", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.SwapUsage), + }, + } + }, + }, + { + name: "memory_swap_limit", + help: "Current swap usage limit (cgroup v2)", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.SwapLimit), + }, + } + }, + }, + + { + name: "memory_file_mapped", + help: "The file_mapped amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.FileMapped), + }, + } + }, + }, + { + name: "memory_file_dirty", + help: "The file_dirty amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.FileDirty), + }, + } + }, + }, + { + name: "memory_file_writeback", + help: "The file_writeback amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.FileWriteback), + }, + } + }, + }, + { + name: "memory_pgactivate", + help: "The pgactivate amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pgactivate), + }, + } + }, + }, + { + name: "memory_pgdeactivate", + help: "The pgdeactivate amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pgdeactivate), + }, + } + }, + }, + { + name: "memory_pgfault", + help: "The pgfault amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pgfault), + }, + } + }, + }, + { + name: "memory_pgmajfault", + help: "The pgmajfault amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pgmajfault), + }, + } + }, + }, + { + name: "memory_pglazyfree", + help: "The pglazyfree amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pglazyfree), + }, + } + }, + }, + { + name: "memory_pgrefill", + help: "The pgrefill amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pgrefill), + }, + } + }, + }, + { + name: "memory_pglazyfreed", + help: "The pglazyfreed amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pglazyfreed), + }, + } + }, + }, + { + name: "memory_pgscan", + help: "The pgscan amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pgscan), + }, + } + }, + }, + { + name: "memory_pgsteal", + help: "The pgsteal amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Pgsteal), + }, + } + }, + }, + { + name: "memory_inactive_anon", + help: "The inactive_anon amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.InactiveAnon), + }, + } + }, + }, + { + name: "memory_active_anon", + help: "The active_anon amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.ActiveAnon), + }, + } + }, + }, + { + name: "memory_inactive_file", + help: "The inactive_file amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.InactiveFile), + }, + } + }, + }, + { + name: "memory_active_file", + help: "The active_file amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.ActiveFile), + }, + } + }, + }, + { + name: "memory_unevictable", + help: "The unevictable amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Unevictable), + }, + } + }, + }, + { + name: "memory_anon", + help: "The anon amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Anon), + }, + } + }, + }, + { + name: "memory_file", + help: "The file amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.File), + }, + } + }, + }, + { + name: "memory_kernel_stack", + help: "The kernel_stack amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.KernelStack), + }, + } + }, + }, + { + name: "memory_slab", + help: "The slab amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Slab), + }, + } + }, + }, + { + name: "memory_sock", + help: "The sock amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Sock), + }, + } + }, + }, + { + name: "memory_shmem", + help: "The shmem amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.Shmem), + }, + } + }, + }, + { + name: "memory_anon_thp", + help: "The anon_thp amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.AnonThp), + }, + } + }, + }, + { + name: "memory_slab_reclaimable", + help: "The slab_reclaimable amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.SlabReclaimable), + }, + } + }, + }, + { + name: "memory_slab_unreclaimable", + help: "The slab_unreclaimable amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.SlabUnreclaimable), + }, + } + }, + }, + { + name: "memory_workingset_refault", + help: "The workingset_refault amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.WorkingsetRefault), + }, + } + }, + }, + { + name: "memory_workingset_activate", + help: "The workingset_activate amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.WorkingsetActivate), + }, + } + }, + }, + { + name: "memory_workingset_nodereclaim", + help: "The workingset_nodereclaim amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.WorkingsetNodereclaim), + }, + } + }, + }, + { + name: "memory_thp_fault_alloc", + help: "The thp_fault_alloc amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.ThpFaultAlloc), + }, + } + }, + }, + { + name: "memory_thp_collapse_alloc", + help: "The thp_collapse_alloc amount", + unit: metrics.Bytes, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Memory == nil { + return nil + } + return []value{ + { + v: float64(stats.Memory.ThpCollapseAlloc), + }, + } + }, + }, + { + name: "memory_oom", + help: "The number of times a container has received an oom event", + unit: metrics.Total, + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.MemoryEvents == nil { + return nil + } + return []value{ + { + v: float64(stats.MemoryEvents.Oom), + }, + } + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/metric.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/metric.go new file mode 100644 index 0000000000..b730d169c5 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/metric.go @@ -0,0 +1,64 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + v2 "github.com/containerd/containerd/v2/core/metrics/types/v2" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// IDName is the name that is used to identify the id being collected in the metric +var IDName = "container_id" + +type value struct { + v float64 + l []string +} + +type metric struct { + name string + help string + unit metrics.Unit + vt prometheus.ValueType + labels []string + // getValues returns the value and labels for the data + getValues func(stats *v2.Metrics) []value +} + +func (m *metric) desc(ns *metrics.Namespace) *prometheus.Desc { + // the namespace label is for containerd namespaces + return ns.NewDesc(m.name, m.help, m.unit, append([]string{IDName, "namespace"}, m.labels...)...) +} + +func (m *metric) collect(id, namespace string, stats *v2.Metrics, ns *metrics.Namespace, ch chan<- prometheus.Metric, block bool) { + values := m.getValues(stats) + for _, v := range values { + // block signals to block on the sending the metrics so none are missed + if block { + ch <- prometheus.MustNewConstMetric(m.desc(ns), m.vt, v.v, append([]string{id, namespace}, v.l...)...) + continue + } + // non-blocking metrics can be dropped if the chan is full + select { + case ch <- prometheus.MustNewConstMetric(m.desc(ns), m.vt, v.v, append([]string{id, namespace}, v.l...)...): + default: + } + } +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/metrics.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/metrics.go new file mode 100644 index 0000000000..29916326ca --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/metrics.go @@ -0,0 +1,198 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "fmt" + "sync" + + cmetrics "github.com/containerd/containerd/v2/core/metrics" + "github.com/containerd/containerd/v2/core/metrics/cgroups/common" + v2 "github.com/containerd/containerd/v2/core/metrics/types/v2" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/timeout" + "github.com/containerd/log" + "github.com/containerd/typeurl/v2" + "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// NewCollector registers the collector with the provided namespace and returns it so +// that cgroups can be added for collection +func NewCollector(ns *metrics.Namespace) *Collector { + if ns == nil { + return &Collector{} + } + c := &Collector{ + ns: ns, + tasks: make(map[string]entry), + } + c.metrics = append(c.metrics, pidMetrics...) + c.metrics = append(c.metrics, cpuMetrics...) + c.metrics = append(c.metrics, memoryMetrics...) + c.metrics = append(c.metrics, ioMetrics...) + c.storedMetrics = make(chan prometheus.Metric, 100*len(c.metrics)) + ns.Add(c) + return c +} + +func taskID(id, namespace string) string { + return fmt.Sprintf("%s-%s", id, namespace) +} + +type entry struct { + task common.Statable + // ns is an optional child namespace that contains additional to parent labels. + // This can be used to append task specific labels to be able to differentiate the different containerd metrics. + ns *metrics.Namespace +} + +// Collector provides the ability to collect container stats and export +// them in the prometheus format +type Collector struct { + ns *metrics.Namespace + storedMetrics chan prometheus.Metric + + // TODO(fuweid): + // + // The Collector.Collect will be the field ns'Collect's callback, + // which be invoked periodically with internal lock. And Collector.Add + // might also invoke ns.Lock if the labels is not nil, which is easy to + // cause dead-lock. + // + // Goroutine X: + // + // ns.Collect + // ns.Lock + // Collector.Collect + // Collector.RLock + // + // + // Goroutine Y: + // + // Collector.Add + // ...(RLock/Lock) + // ns.Lock + // + // I think we should seek the way to decouple ns from Collector. + mu sync.RWMutex + tasks map[string]entry + metrics []*metric +} + +// Describe prometheus metrics +func (c *Collector) Describe(ch chan<- *prometheus.Desc) { + for _, m := range c.metrics { + ch <- m.desc(c.ns) + } +} + +// Collect prometheus metrics +func (c *Collector) Collect(ch chan<- prometheus.Metric) { + c.mu.RLock() + wg := &sync.WaitGroup{} + for _, t := range c.tasks { + wg.Add(1) + go c.collect(t, ch, true, wg) + } +storedLoop: + for { + // read stored metrics until the channel is flushed + select { + case m := <-c.storedMetrics: + ch <- m + default: + break storedLoop + } + } + c.mu.RUnlock() + wg.Wait() +} + +func (c *Collector) collect(entry entry, ch chan<- prometheus.Metric, block bool, wg *sync.WaitGroup) { + if wg != nil { + defer wg.Done() + } + + t := entry.task + ctx, cancel := timeout.WithContext(context.Background(), cmetrics.ShimStatsRequestTimeout) + stats, err := t.Stats(namespaces.WithNamespace(ctx, t.Namespace())) + cancel() + + if err != nil { + log.L.WithError(err).Errorf("stat task %s", t.ID()) + return + } + + s := &v2.Metrics{} + if err := typeurl.UnmarshalTo(stats, s); err != nil { + log.L.WithError(err).Errorf("unmarshal stats for %s", t.ID()) + return + } + ns := entry.ns + if ns == nil { + ns = c.ns + } + for _, m := range c.metrics { + m.collect(t.ID(), t.Namespace(), s, ns, ch, block) + } +} + +// Add adds the provided cgroup and id so that metrics are collected and exported +func (c *Collector) Add(t common.Statable, labels map[string]string) error { + if c.ns == nil { + return nil + } + c.mu.RLock() + id := taskID(t.ID(), t.Namespace()) + _, ok := c.tasks[id] + c.mu.RUnlock() + if ok { + return nil // requests to collect metrics should be idempotent + } + entry := entry{task: t} + if labels != nil { + entry.ns = c.ns.WithConstLabels(labels) + } + c.mu.Lock() + c.tasks[id] = entry + c.mu.Unlock() + return nil +} + +// Remove removes the provided cgroup by id from the collector +func (c *Collector) Remove(t common.Statable) { + if c.ns == nil { + return + } + c.mu.Lock() + defer c.mu.Unlock() + delete(c.tasks, taskID(t.ID(), t.Namespace())) +} + +// RemoveAll statable items from the collector +func (c *Collector) RemoveAll() { + if c.ns == nil { + return + } + c.mu.Lock() + c.tasks = make(map[string]entry) + c.mu.Unlock() +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/pids.go b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/pids.go new file mode 100644 index 0000000000..ba2fbe6cb5 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/cgroups/v2/pids.go @@ -0,0 +1,60 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + v2 "github.com/containerd/containerd/v2/core/metrics/types/v2" + metrics "github.com/docker/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +var pidMetrics = []*metric{ + { + name: "pids", + help: "The limit to the number of pids allowed (cgroup v2)", + unit: metrics.Unit("limit"), + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Pids == nil { + return nil + } + return []value{ + { + v: float64(stats.Pids.Limit), + }, + } + }, + }, + { + name: "pids", + help: "The current number of pids (cgroup v2)", + unit: metrics.Unit("current"), + vt: prometheus.GaugeValue, + getValues: func(stats *v2.Metrics) []value { + if stats.Pids == nil { + return nil + } + return []value{ + { + v: float64(stats.Pids.Current), + }, + } + }, + }, +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/metrics.go b/vendor/github.com/containerd/containerd/v2/core/metrics/metrics.go new file mode 100644 index 0000000000..680db473e8 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/metrics.go @@ -0,0 +1,37 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package metrics + +import ( + "time" + + "github.com/containerd/containerd/v2/pkg/timeout" + "github.com/containerd/containerd/v2/version" + goMetrics "github.com/docker/go-metrics" +) + +const ( + ShimStatsRequestTimeout = "io.containerd.timeout.metrics.shimstats" +) + +func init() { + ns := goMetrics.NewNamespace("containerd", "", nil) + c := ns.NewLabeledCounter("build_info", "containerd build information", "version", "revision") + c.WithValues(version.Version, version.Revision).Inc() + goMetrics.Register(ns) + timeout.Set(ShimStatsRequestTimeout, 2*time.Second) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/types/v1/types.go b/vendor/github.com/containerd/containerd/v2/core/metrics/types/v1/types.go new file mode 100644 index 0000000000..6eaef03b12 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/types/v1/types.go @@ -0,0 +1,46 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1 + +import ( + v1 "github.com/containerd/cgroups/v3/cgroup1/stats" +) + +type ( + // Metrics alias + Metrics = v1.Metrics + // BlkIOEntry alias + BlkIOEntry = v1.BlkIOEntry + // MemoryStat alias + MemoryStat = v1.MemoryStat + // CPUStat alias + CPUStat = v1.CPUStat + // CPUUsage alias + CPUUsage = v1.CPUUsage + // BlkIOStat alias + BlkIOStat = v1.BlkIOStat + // PidsStat alias + PidsStat = v1.PidsStat + // RdmaStat alias + RdmaStat = v1.RdmaStat + // RdmaEntry alias + RdmaEntry = v1.RdmaEntry + // HugetlbStat alias + HugetlbStat = v1.HugetlbStat +) diff --git a/vendor/github.com/containerd/containerd/v2/core/metrics/types/v2/types.go b/vendor/github.com/containerd/containerd/v2/core/metrics/types/v2/types.go new file mode 100644 index 0000000000..2535d61245 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/metrics/types/v2/types.go @@ -0,0 +1,36 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + v2 "github.com/containerd/cgroups/v3/cgroup2/stats" +) + +type ( + // Metrics alias + Metrics = v2.Metrics + // MemoryStat alias + MemoryStat = v2.MemoryStat + // CPUStat alias + CPUStat = v2.CPUStat + // PidsStat alias + PidsStat = v2.PidsStat + // IOStat alias + IOStat = v2.IOStat +) diff --git a/vendor/github.com/containerd/containerd/v2/core/mount/manager/buckets.go b/vendor/github.com/containerd/containerd/v2/core/mount/manager/buckets.go new file mode 100644 index 0000000000..e1d8011a01 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/mount/manager/buckets.go @@ -0,0 +1,85 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package manager is used to manage mounts in a bolt database, normally +// backed by a tempfs. +/* +Database schema + + v1 + ╘══*namespace* + ├──mounts + │ ╘══*mount name* + │ ├──id : - Unique ID for mount (auto incrementing) + │ ├──createdat : - Created at + │ ├──updatedat : - Updated at + │ ├──lease : - Lease + │ ├──active + │ │ ╘══*order* + │ │ ├──mountedat : - Mounted at + │ │ ├──state : - (0 - unmounted, 1 - filesystem, 2 - device, 3 - process) + │ │ ├──type : - Mount type + │ │ ├──source : - Mount source + │ │ ├──target : - Mount target (relative to previous mount point) + │ │ ├──mp : - Mount point for filesystem mount + │ │ ├──mpg : - Whether the mount point is auto generated (NOT USED) + │ │ ├──dev : - Device active for this mount (NOT USED) + │ │ ├──pid : - Process created for this mount (NOT USED) + │ │ └──options : - Comma separate options + │ ├──system + │ │ ╘══*order* + │ │ ├──type : - Mount type + │ │ ├──source : - Mount source + │ │ ├──target : - Mount target (relative to previous mount point) + │ │ └──options : - Comma separate options + │ └──labels + │ ╘══*key* : - Label value + ├──leases + │ ╘══*lease id* + │ ╘══*mount name*: nil + └──unmountq (CURRENTLY NOT USED, may remove) + └──*mount name + auto-increment* + ├──type : - Mount type + ├──target : - Path to check && unmount + ├──rm : - Whether to remove target after unmount + ├──dev : - Device to check before unmount + ├──pid : - Process to check and kill + ├──target : - Path to unmount + ├──state : - (0 - unmounted, 1 - filesystem, 2 - device, 3 - process) + └─ order : - Order in which was mounted, unmount high to low +*/ +package manager + +var ( + bucketKeyID = []byte("id") + bucketKeyMounts = []byte("mounts") + bucketKeyLeases = []byte("leases") + bucketKeyLease = []byte("lease") + bucketKeyActive = []byte("active") + bucketKeySystem = []byte("system") + bucketKeyType = []byte("type") + bucketKeySource = []byte("source") + bucketKeyTarget = []byte("target") + bucketKeyOptions = []byte("options") + bucketKeyMountedAt = []byte("mat") + bucketKeyMountPoint = []byte("mp") + bucketKeyLabels = []byte("labels") + + labelGCContainerBackRef = []byte("containerd.io/gc.bref.container") + labelGCContentBackRef = []byte("containerd.io/gc.bref.content") + labelGCImageBackRef = []byte("containerd.io/gc.bref.image") + labelGCSnapBackRef = []byte("containerd.io/gc.bref.snapshot.") +) diff --git a/vendor/github.com/containerd/containerd/v2/core/mount/manager/format.go b/vendor/github.com/containerd/containerd/v2/core/mount/manager/format.go new file mode 100644 index 0000000000..01b9e0765a --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/mount/manager/format.go @@ -0,0 +1,127 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package manager + +import ( + "bytes" + "context" + "fmt" + "strings" + "text/template" + + "github.com/containerd/containerd/v2/core/mount" +) + +const formatCheck = "{{" + +type mountFormatter struct{} + +func (mountFormatter) Transform(_ context.Context, m mount.Mount, a []mount.ActiveMount) (mount.Mount, error) { + if sc := formatString(m.Source); sc != nil { + f, err := sc(a) + if err != nil { + return m, err + } + m.Source = f + } + + if tc := formatString(m.Target); tc != nil { + f, err := tc(a) + if err != nil { + return m, err + } + m.Target = f + } + + var o []string + for i := range m.Options { + if oc := formatString(m.Options[i]); oc != nil { + f, err := oc(a) + if err != nil { + return m, err + } + if o == nil { + o = make([]string, len(m.Options)) + copy(o, m.Options) + } + o[i] = f + } + } + if o != nil { + m.Options = o + } + return m, nil +} + +func formatString(s string) func([]mount.ActiveMount) (string, error) { + if !strings.Contains(s, formatCheck) { + return nil + } + + return func(a []mount.ActiveMount) (string, error) { + // TODO: The formatting is very easy, don't use template + fm := template.FuncMap{ + "source": func(i int) (string, error) { + if i < 0 || i >= len(a) { + return "", fmt.Errorf("index out of bounds: %d, has %d active mounts", i, len(a)) + } + return a[i].Source, nil + }, + "target": func(i int) (string, error) { + if i < 0 || i >= len(a) { + return "", fmt.Errorf("index out of bounds: %d, has %d active mounts", i, len(a)) + } + return a[i].Target, nil + }, + "mount": func(i int) (string, error) { + if i < 0 || i >= len(a) { + return "", fmt.Errorf("index out of bounds: %d, has %d active mounts", i, len(a)) + } + return a[i].MountPoint, nil + }, + "overlay": func(start, end int) (string, error) { + var dirs []string + if start > end { + if start >= len(a) || end < 0 { + return "", fmt.Errorf("invalid range: %d-%d, has %d active mounts", start, end, len(a)) + } + for i := start; i >= end; i-- { + dirs = append(dirs, a[i].MountPoint) + } + } else { + if start < 0 || end >= len(a) { + return "", fmt.Errorf("invalid range: %d-%d, has %d active mounts", start, end, len(a)) + } + for i := start; i <= end; i++ { + dirs = append(dirs, a[i].MountPoint) + } + } + return strings.Join(dirs, ":"), nil + }, + } + t, err := template.New("").Funcs(fm).Parse(s) + if err != nil { + return "", err + } + + buf := bytes.NewBuffer(nil) + if err := t.Execute(buf, nil); err != nil { + return "", err + } + return buf.String(), nil + } +} diff --git a/vendor/github.com/containerd/containerd/v2/core/mount/manager/manager.go b/vendor/github.com/containerd/containerd/v2/core/mount/manager/manager.go new file mode 100644 index 0000000000..66c45c65d1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/mount/manager/manager.go @@ -0,0 +1,1215 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package manager + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "sync" + "time" + + bolt "go.etcd.io/bbolt" + + "github.com/containerd/errdefs" + "github.com/containerd/log" + + "github.com/containerd/containerd/v2/core/leases" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/metadata/boltutil" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/internal/kmutex" + "github.com/containerd/containerd/v2/pkg/gc" + "github.com/containerd/containerd/v2/pkg/namespaces" +) + +type BoltManager interface { + mount.Manager + metadata.Collector + Sync(context.Context) error +} + +type managerOptions struct { + handlers map[string]mount.Handler + roots []*os.Root +} + +type Opt func(*managerOptions) error + +func WithMountHandler(name string, h mount.Handler) Opt { + return func(o *managerOptions) error { + if o.handlers == nil { + o.handlers = make(map[string]mount.Handler) + } + o.handlers[name] = h + return nil + } +} + +func WithAllowedRoot(root string) Opt { + return func(o *managerOptions) error { + r, err := os.OpenRoot(root) + if err != nil { + return err + } + o.roots = append(o.roots, r) + return nil + } +} + +func NewManager(db *bolt.DB, targetDir string, opts ...Opt) (mount.Manager, error) { + options := managerOptions{} + for _, o := range opts { + if err := o(&options); err != nil { + return nil, err + } + } + if err := os.MkdirAll(targetDir, 0700); err != nil { + return nil, err + } + tr, err := os.OpenRoot(targetDir) + if err != nil { + return nil, fmt.Errorf("failed to open target root %q: %w", targetDir, err) + } + rootMap := map[string]*os.Root{ + tr.Name(): tr, + } + for _, r := range options.roots { + rootMap[r.Name()] = r + } + + return &mountManager{ + db: db, + targets: tr, + handlers: options.handlers, + rootMap: rootMap, + activate: kmutex.New(), + }, nil +} + +type mountManager struct { + db *bolt.DB + targets *os.Root + handlers map[string]mount.Handler + rootMap map[string]*os.Root + + rwlock sync.RWMutex + activate kmutex.KeyedLocker +} + +func (mm *mountManager) Close() error { + var errs []error + for _, r := range mm.rootMap { + if err := r.Close(); err != nil { + errs = append(errs, err) + } + } + errs = append(errs, mm.db.Close()) + return errors.Join(errs...) +} + +func (mm *mountManager) Activate(ctx context.Context, name string, mounts []mount.Mount, opts ...mount.ActivateOpt) (info mount.ActivationInfo, retErr error) { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return mount.ActivationInfo{}, err + } + + // Serialize concurrent activations of the same name to prevent a + // racing Activate from misidentifying an in-progress activation as + // a stale record and destroying it. + if err := mm.activate.Lock(ctx, name); err != nil { + return mount.ActivationInfo{}, err + } + defer mm.activate.Unlock(name) + + log.G(ctx).WithField("name", name).WithField("mounts", mounts).Debugf("activating mount") + + lid, leased := leases.FromContext(ctx) + + var config mount.ActivateOptions + for _, opt := range opts { + opt(&config) + } + + shouldTransform := func(p string, t string) bool { + p = p + "/*" + for _, mt := range config.AllowMountTypes { + if mt == p || mt == t { + return false + } + } + return true + } + + shouldHandle := func(t string) bool { + return !slices.Contains(config.AllowMountTypes, t) + } + + transforms := map[string]mount.Transformer{ + "format": mountFormatter{}, + "mkfs": &mkfs{ + rootMap: mm.rootMap, + }, + "mkdir": &mkdir{ + rootMap: mm.rootMap, + }, + } + + start := time.Now() + // highest index of a mount + // first system mount is the first index which should be mounted by the system + var firstSystemMount = -1 + var mountConv [][]mount.Transformer + var handlers []mount.Handler + for i := range mounts { + mountType := mounts[i].Type + + // Check is the source needs transformation, any transform operation requires + // mounting with the mount manager. + for transformType, mt, ok := strings.Cut(mountType, "/"); ok; transformType, mt, ok = strings.Cut(mountType, "/") { + if tr, ok := transforms[transformType]; ok { + if shouldTransform(transformType, mounts[i].Type) { + // At least everything before this must be mounted + // by the mount manager + firstSystemMount = i + } + + if handlers == nil { + handlers = make([]mount.Handler, len(mounts)) + } + + if mountConv == nil { + mountConv = make([][]mount.Transformer, len(mounts)) + } + + mountConv[i] = append(mountConv[i], typeTransformer{ + Transformer: tr, + mountType: mt, + }) + + mountType = mt + } else { + log.G(ctx).Warnf("unknown transform %q for mount %v", transformType, mounts[i]) + break + } + } + + var handler mount.Handler + if mm.handlers != nil { + handler = mm.handlers[mountType] + } + + if handler != nil || config.Temporary { + if handlers == nil { + handlers = make([]mount.Handler, len(mounts)) + } + handlers[i] = handler + if shouldHandle(mountType) || config.Temporary { + firstSystemMount = i + 1 + } + } + } + // If no mounts are handled here, return not implemented and caller + // may just perform system mounts as normal. + if firstSystemMount == -1 { + return mount.ActivationInfo{}, errdefs.ErrNotImplemented + } + + // Get read lock to block GC context from starting + mm.rwlock.RLock() + defer mm.rwlock.RUnlock() + + var mid uint64 + var staleMID uint64 + + if err := mm.db.Update(func(tx *bolt.Tx) error { + v1bkt, err := tx.CreateBucketIfNotExists([]byte("v1")) + if err != nil { + return err + } + + nsbkt, err := v1bkt.CreateBucketIfNotExists([]byte(namespace)) + if err != nil { + return err + } + mbkt, err := nsbkt.CreateBucketIfNotExists(bucketKeyMounts) + if err != nil { + return err + } + bkt, err := mbkt.CreateBucket([]byte(name)) + if err != nil { + existing := mbkt.Bucket([]byte(name)) + if existing == nil { + return err + } + // If the mount is fully activated, return already exists + // so the caller can reuse the existing mount. + if existing.Bucket(bucketKeyActive) != nil { + return fmt.Errorf("mount %q: %w", name, errdefs.ErrAlreadyExists) + } + // The mount bucket exists but was never fully activated + // (e.g., process crashed between creating the bucket and + // completing activation). Clean up the stale entry and + // proceed with a fresh activation. Save the old mount ID + // so the target directory can be cleaned up after the + // transaction commits. + staleMID = readID(existing) + if lid := existing.Get(bucketKeyLease); len(lid) > 0 { + if lsbkt := nsbkt.Bucket(bucketKeyLeases); lsbkt != nil { + if lbkt := lsbkt.Bucket(lid); lbkt != nil { + if err := lbkt.Delete([]byte(name)); err != nil { + return err + } + } + } + } + if err := mbkt.DeleteBucket([]byte(name)); err != nil { + return err + } + bkt, err = mbkt.CreateBucket([]byte(name)) + if err != nil { + return err + } + } + + mid, err = v1bkt.NextSequence() + if err != nil { + return err + } + + idb, err := encodeID(mid) + if err != nil { + return err + } + if err = bkt.Put(bucketKeyID, idb); err != nil { + return err + } + + if err := boltutil.WriteLabels(bkt, config.Labels); err != nil { + return err + } + + if err := boltutil.WriteTimestamps(bkt, start, start); err != nil { + return err + } + + if leased { + if err = bkt.Put(bucketKeyLease, []byte(lid)); err != nil { + return err + } + + lsbkt, err := nsbkt.CreateBucketIfNotExists(bucketKeyLeases) + if err != nil { + return err + } + lbkt, err := lsbkt.CreateBucketIfNotExists([]byte(lid)) + if err != nil { + return err + } + if err := lbkt.Put([]byte(name), nil); err != nil { + return err + } + } + + // TODO: Store mount information including mountpoint + // Setup mounts now with generated targets + + return nil + }); err != nil { + return mount.ActivationInfo{}, err + } + + // If a stale incomplete activation was found, clean up its target + // directory which may contain leftover mounts from before a crash. + if staleMID != 0 { + staleTarget := filepath.Join(mm.targets.Name(), strconv.FormatUint(staleMID, 10)) + if err := unmountAll(ctx, staleTarget, mm.handlers); err != nil { + if os.IsNotExist(err) { + log.G(ctx).WithError(err).WithField("mountid", staleMID).Debug("stale activation target does not exist, skipping cleanup") + } else { + log.G(ctx).WithError(err).WithField("mountid", staleMID).Warn("failed to unmount stale activation target") + } + } + } + + defer func() { + // If error, rollback and remove by name + if retErr != nil { + if err := mm.db.Update(func(tx *bolt.Tx) error { + v1bkt := tx.Bucket([]byte("v1")) + if v1bkt == nil { + return fmt.Errorf("missing bucket: %w", errdefs.ErrUnknown) + } + + nsbkt := v1bkt.Bucket([]byte(namespace)) + if nsbkt == nil { + return fmt.Errorf("missing namespace %q bucket: %w", namespace, errdefs.ErrUnknown) + } + + mbkt := nsbkt.Bucket(bucketKeyMounts) + if mbkt == nil { + return fmt.Errorf("missing mounts bucket: %w", errdefs.ErrUnknown) + } + + if leased { + lsbkt := nsbkt.Bucket(bucketKeyLeases) + if lsbkt != nil { + lbkt := lsbkt.Bucket([]byte(lid)) + if lbkt != nil { + lbkt.Delete([]byte(name)) + } + if k, _ := lbkt.Cursor().First(); k == nil { + lsbkt.DeleteBucket([]byte(lid)) + } + } + + } + + return mbkt.DeleteBucket([]byte(name)) + }); err != nil { + log.G(ctx).WithError(err).WithField("name", name).Errorf("failed to rollback") + } + } + }() + + targetName := strconv.FormatUint(mid, 10) + if err := mm.targets.Mkdir(targetName, 0700); err != nil { + return mount.ActivationInfo{}, err + } + + var mounted []mount.ActiveMount + defer func() { + // If error, unmount all mounted + if retErr != nil { + for i, m := range mounted { + var err error + if h := handlers[i]; h != nil { + err = h.Unmount(ctx, m.MountPoint) + } else { + err = mount.Unmount(m.MountPoint, 0) + } + if err != nil { + log.G(ctx).WithError(err).WithField("MountPoint", m.MountPoint).Error("failed to cleanup mount after failed activation") + } + } + } + }() + + // Ensure directory order for cleanup when rare case of large number of mounts, + // this allows cleanup logic to just scan directories on cleanup. + formatMP := "%d" + formatType := "%d-type" + if firstSystemMount > 100 { + formatMP = "%03d" + formatType = "%03d-type" + } else if firstSystemMount > 10 { + formatMP = "%02d" + formatType = "%02d-type" + } + + for i, m := range mounts[:firstSystemMount] { + if mountConv != nil && mountConv[i] != nil { + for _, tr := range mountConv[i] { + newM, err := tr.Transform(ctx, m, mounted) + if err != nil { + return mount.ActivationInfo{}, err + } + m = newM + } + mounts[i] = m + } + + // Use cleanup order for directory names + ci := firstSystemMount - i + // TODO: Go 1.25 use targetbase.WriteFile + if err := os.WriteFile(filepath.Join(mm.targets.Name(), targetName, fmt.Sprintf(formatType, ci)), []byte(m.Type), 0600); err != nil { + return mount.ActivationInfo{}, err + } + + mname := fmt.Sprintf(formatMP, ci) + var active mount.ActiveMount + if h := handlers[i]; h != nil { + active, err = h.Mount(ctx, m, filepath.Join(mm.targets.Name(), targetName, mname), mounted) + if err != nil { + return mount.ActivationInfo{}, fmt.Errorf("mount handler failed %v: %w", m, err) + } + } else { + if err := mm.targets.Mkdir(filepath.Join(targetName, mname), 0700); err != nil { + return mount.ActivationInfo{}, err + } + mp := filepath.Join(mm.targets.Name(), targetName, mname) + if err := m.Mount(mp); err != nil { + return mount.ActivationInfo{}, fmt.Errorf("mount failed %v: %w", m, err) + } + t := time.Now() + active = mount.ActiveMount{ + Mount: m, + MountPoint: mp, + MountedAt: &t, + } + } + mounted = append(mounted, active) + } + + // If first system mount is converted, fill in the format + if mountConv != nil { + for _, tr := range mountConv[firstSystemMount] { + newM, err := tr.Transform(ctx, mounts[firstSystemMount], mounted) + if err != nil { + return mount.ActivationInfo{}, err + } + mounts[firstSystemMount] = newM + } + } + // If no system mounts, add a bind mount if temporary + // TODO: Add config for whether to add the bind mount? + if config.Temporary && firstSystemMount > 0 { + mounts = append(mounts, mount.Mount{ + Type: "bind", + Source: mounted[firstSystemMount-1].MountPoint, + Options: []string{"rbind"}, + }) + } + + info.Name = name + info.Active = mounted + info.System = mounts[firstSystemMount:] + info.Labels = config.Labels + + // Open another write transaction and update state, or another way to update state? + if err := mm.db.Update(func(tx *bolt.Tx) error { + v1bkt := tx.Bucket([]byte("v1")) + if v1bkt == nil { + return fmt.Errorf("missing v1 bucket: %w", errdefs.ErrUnknown) + } + + nsbkt := v1bkt.Bucket([]byte(namespace)) + if nsbkt == nil { + return fmt.Errorf("missing namespace %q bucket: %w", namespace, errdefs.ErrUnknown) + } + + mbkt := nsbkt.Bucket(bucketKeyMounts) + if mbkt == nil { + return fmt.Errorf("missing mounts bucket: %w", errdefs.ErrUnknown) + } + bkt := mbkt.Bucket([]byte(name)) + if bkt == nil { + return fmt.Errorf("missing mount %q bucket: %w", name, errdefs.ErrUnknown) + } + + abkt, err := bkt.CreateBucket(bucketKeyActive) + if err != nil { + return err + } + + for i, active := range mounted { + // Error is i > uint8 max + cur, err := abkt.CreateBucket([]byte{byte(i)}) + if err != nil { + return err + } + if err = putActiveMount(cur, active); err != nil { + return err + } + + } + + if err := boltutil.WriteTimestamps(bkt, start, time.Now()); err != nil { + return err + } + + if len(info.System) > 0 { + if len(info.System) > 255 { + return fmt.Errorf("too many system mounts (%d): maximum 255", len(info.System)) + } + sbkt, err := bkt.CreateBucket(bucketKeySystem) + if err != nil { + return err + } + for i, sm := range info.System { + cur, err := sbkt.CreateBucket([]byte{byte(i)}) + if err != nil { + return err + } + if err = putSystemMount(cur, sm); err != nil { + return err + } + } + } + + return nil + }); err != nil { + return mount.ActivationInfo{}, err + } + + return +} + +func encodeID(id uint64) ([]byte, error) { + var ( + buf [binary.MaxVarintLen64]byte + idEncoded = buf[:] + ) + idEncoded = idEncoded[:binary.PutUvarint(idEncoded, id)] + + if len(idEncoded) == 0 { + return nil, fmt.Errorf("failed encoding id = %v", id) + } + return idEncoded, nil +} + +func readID(bkt *bolt.Bucket) uint64 { + id, _ := binary.Uvarint(bkt.Get(bucketKeyID)) + return id +} + +func putActiveMount(bkt *bolt.Bucket, active mount.ActiveMount) error { + if err := bkt.Put(bucketKeyType, []byte(active.Type)); err != nil { + return err + } + + // TODO: Same if device? + if err := bkt.Put(bucketKeyMountPoint, []byte(active.MountPoint)); err != nil { + return err + } + + mountedAt, err := active.MountedAt.MarshalBinary() + if err != nil { + return err + } + if err := bkt.Put(bucketKeyMountedAt, mountedAt); err != nil { + return err + } + + // TODO: Add Source + // TODO: Add Target + // TODO: Add Options + + return nil +} + +func readActiveMount(bkt *bolt.Bucket) (mount.ActiveMount, error) { + var active mount.ActiveMount + active.Type = string(bkt.Get(bucketKeyType)) + active.MountPoint = string(bkt.Get(bucketKeyMountPoint)) + if v := bkt.Get(bucketKeyMountedAt); v != nil { + var mountedAt time.Time + if err := mountedAt.UnmarshalBinary(v); err != nil { + // TODO: Should this be skipped or otherwise logged and ignored? + return mount.ActiveMount{}, err + } + active.MountedAt = &mountedAt + } + + return active, nil +} + +func putSystemMount(bkt *bolt.Bucket, m mount.Mount) error { + if err := bkt.Put(bucketKeyType, []byte(m.Type)); err != nil { + return err + } + if err := bkt.Put(bucketKeySource, []byte(m.Source)); err != nil { + return err + } + if err := bkt.Put(bucketKeyTarget, []byte(m.Target)); err != nil { + return err + } + if len(m.Options) > 0 { + if err := bkt.Put(bucketKeyOptions, []byte(strings.Join(m.Options, "\x00"))); err != nil { + return err + } + } + return nil +} + +func readSystemMount(bkt *bolt.Bucket) mount.Mount { + m := mount.Mount{ + Type: string(bkt.Get(bucketKeyType)), + Source: string(bkt.Get(bucketKeySource)), + Target: string(bkt.Get(bucketKeyTarget)), + } + if v := bkt.Get(bucketKeyOptions); len(v) > 0 { + m.Options = strings.Split(string(v), "\x00") + } + return m +} + +func readActivationInfo(name string, bkt *bolt.Bucket) (mount.ActivationInfo, error) { + info := mount.ActivationInfo{ + Name: name, + } + if abkt := bkt.Bucket(bucketKeyActive); abkt != nil { + if err := abkt.ForEachBucket(func(k []byte) error { + active, err := readActiveMount(abkt.Bucket(k)) + if err != nil { + return err + } + info.Active = append(info.Active, active) + return nil + }); err != nil { + return mount.ActivationInfo{}, err + } + } + if sbkt := bkt.Bucket(bucketKeySystem); sbkt != nil { + if err := sbkt.ForEachBucket(func(k []byte) error { + info.System = append(info.System, readSystemMount(sbkt.Bucket(k))) + return nil + }); err != nil { + return mount.ActivationInfo{}, err + } + } + lbls, err := boltutil.ReadLabels(bkt) + if err != nil { + return mount.ActivationInfo{}, err + } + info.Labels = lbls + + return info, nil +} + +func getBucket(tx *bolt.Tx, keys ...[]byte) *bolt.Bucket { + bkt := tx.Bucket(keys[0]) + if bkt == nil { + return nil + } + + for _, key := range keys[1:] { + bkt = bkt.Bucket(key) + if bkt == nil { + return nil + } + } + + return bkt +} + +func (mm *mountManager) Deactivate(ctx context.Context, name string) error { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + + var ( + mid uint64 + allActive []mount.ActiveMount + ) + + // First in a single transaction, mark the mounts as deactivated + if err := mm.db.Update(func(tx *bolt.Tx) error { + v1bkt := tx.Bucket([]byte("v1")) + if v1bkt == nil { + return fmt.Errorf("missing v1 bucket: %w", errdefs.ErrNotFound) + } + + nsbkt := v1bkt.Bucket([]byte(namespace)) + if nsbkt == nil { + return fmt.Errorf("missing namespace %q bucket: %w", namespace, errdefs.ErrNotFound) + } + + mbkt := nsbkt.Bucket(bucketKeyMounts) + if mbkt == nil { + return fmt.Errorf("missing mounts bucket: %w", errdefs.ErrNotFound) + } + bkt := mbkt.Bucket([]byte(name)) + if bkt == nil { + return fmt.Errorf("missing mount %q bucket: %w", name, errdefs.ErrNotFound) + } + + mid = readID(bkt) + + lid := bkt.Get(bucketKeyLease) + if lid != nil { + lssbkt := nsbkt.Bucket(bucketKeyLeases) + if lssbkt != nil { + lsbkt := lssbkt.Bucket(lid) + if lsbkt != nil { + if err = lsbkt.Delete([]byte(name)); err != nil { + return err + } + } + } + } + + abkt := bkt.Bucket(bucketKeyActive) + if abkt != nil { + abkt.ForEachBucket(func(k []byte) error { + active, err := readActiveMount(abkt.Bucket(k)) + if err != nil { + return err + } + allActive = append(allActive, active) + return nil + }) + } + + if err = mbkt.DeleteBucket([]byte(name)); err != nil { + return err + } + + // TODO: Is unmountq really needed or just delete? + + return nil + }); err != nil { + return err + } + + // TODO: Should this also be backgrounded, no much can do on failure to unmount + var mountErrors error + for i := len(allActive) - 1; i >= 0; i-- { + var err error + if h := mm.handlers[allActive[i].Type]; h != nil { + err = h.Unmount(ctx, allActive[i].MountPoint) + } else { + err = mount.Unmount(allActive[i].MountPoint, 0) + } + if err != nil { + mountErrors = errors.Join(mountErrors, err) + } + } + if mountErrors != nil { + // Don't try to cleanup, GC will need to do the rest + return mountErrors + } + + // Run in background, GC would handle leftovers? + // Make configurable? + // TODO: In go 1.25, use mm.targets.RemoveAll() + if err := os.RemoveAll(filepath.Join(mm.targets.Name(), fmt.Sprintf("%d", mid))); err != nil { + // TODO: Only log here, cleanup would have to occur later + log.G(ctx).WithError(err).WithField("mountid", mid).Error("failed to cleanup mount target") + } + + return nil +} + +func (mm *mountManager) Info(ctx context.Context, name string) (mount.ActivationInfo, error) { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return mount.ActivationInfo{}, err + } + var info mount.ActivationInfo + if err := mm.db.View(func(tx *bolt.Tx) error { + bkt := getBucket(tx, []byte("v1"), []byte(namespace), bucketKeyMounts, []byte(name)) + if bkt == nil { + return fmt.Errorf("mount %q %w", name, errdefs.ErrNotFound) + } + var err error + info, err = readActivationInfo(name, bkt) + return err + }); err != nil { + return mount.ActivationInfo{}, err + } + return info, nil +} + +func (mm *mountManager) Update(context.Context, mount.ActivationInfo, ...string) (mount.ActivationInfo, error) { + return mount.ActivationInfo{}, errdefs.ErrNotImplemented +} + +func (mm *mountManager) List(ctx context.Context, filters ...string) ([]mount.ActivationInfo, error) { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + + var infos []mount.ActivationInfo + if err := mm.db.View(func(tx *bolt.Tx) error { + mbkt := getBucket(tx, []byte("v1"), []byte(namespace), bucketKeyMounts) + if mbkt == nil { + return nil + } + + return mbkt.ForEachBucket(func(k []byte) error { + info, err := readActivationInfo(string(k), mbkt.Bucket(k)) + if err != nil { + return err + } + infos = append(infos, info) + return nil + }) + }); err != nil { + return nil, err + } + return infos, nil +} + +func (mm *mountManager) StartCollection(ctx context.Context) (metadata.CollectionContext, error) { + // lock now and collection will unlock on cancel or finish + mm.rwlock.Lock() + + tx, err := mm.db.Begin(true) + if err != nil { + return nil, err + } + + return &collectionContext{ + ctx: ctx, + tx: tx, + manager: mm, + removed: map[string]map[string]struct{}{}, + }, nil +} + +func (mm *mountManager) ReferenceLabel() string { + return "mount" +} + +type collectionContext struct { + ctx context.Context + tx *bolt.Tx + manager *mountManager + removed map[string]map[string]struct{} +} + +func (cc *collectionContext) All(fn func(gc.Node)) { + v1bkt := cc.tx.Bucket([]byte("v1")) + if v1bkt == nil { + return + } + nsc := v1bkt.Cursor() + for nsk, nsv := nsc.First(); nsk != nil; nsk, nsv = nsc.Next() { + if nsv != nil { + continue + } + mntsbkt := v1bkt.Bucket(nsk).Bucket(bucketKeyMounts) + if mntsbkt == nil { + continue + } + mc := mntsbkt.Cursor() + for mk, mv := mc.First(); mk != nil; mk, mv = mc.Next() { + if mv != nil { + continue + } + fn(gc.Node{ + Type: metadata.ResourceMount, + Namespace: string(nsk), + Key: string(mk), + }) + } + } +} + +func gcnode(t gc.ResourceType, ns, key string) gc.Node { + return gc.Node{ + Type: t, + Namespace: ns, + Key: key, + } +} + +func (cc *collectionContext) ActiveWithBackRefs(ns string, fn func(gc.Node), bref func(gc.Node, gc.Node)) { + nsbkt := getBucket(cc.tx, []byte("v1"), []byte(ns), bucketKeyMounts) + if nsbkt != nil { + mc := nsbkt.Cursor() + for mk, mv := mc.First(); mk != nil; mk, mv = mc.Next() { + if mv != nil { + continue + } + n := gcnode(metadata.ResourceMount, ns, string(mk)) + lbkt := nsbkt.Bucket(mk).Bucket(bucketKeyLabels) + if lbkt != nil { + lc := lbkt.Cursor() + for _, h := range []struct { + key []byte + handler func([]byte, []byte) + }{ + { + key: labelGCContainerBackRef, + handler: func(k, v []byte) { + if ks := string(k); ks != string(labelGCContainerBackRef) { + // Allow reference naming separated by . or /, ignore names + if ks[len(labelGCContainerBackRef)] != '.' && ks[len(labelGCContainerBackRef)] != '/' { + return + } + } + + bref(gcnode(metadata.ResourceContainer, ns, string(v)), n) + }, + }, + { + key: labelGCContentBackRef, + handler: func(k, v []byte) { + if ks := string(k); ks != string(labelGCContentBackRef) { + // Allow reference naming separated by . or /, ignore names + if ks[len(labelGCContentBackRef)] != '.' && ks[len(labelGCContentBackRef)] != '/' { + return + } + } + + bref(gcnode(metadata.ResourceContent, ns, string(v)), n) + }, + }, + { + key: labelGCImageBackRef, + handler: func(k, v []byte) { + if ks := string(k); ks != string(labelGCImageBackRef) { + // Allow reference naming separated by . or /, ignore names + if ks[len(labelGCImageBackRef)] != '.' && ks[len(labelGCImageBackRef)] != '/' { + return + } + } + + bref(gcnode(metadata.ResourceImage, ns, string(v)), n) + }, + }, + { + key: labelGCSnapBackRef, + handler: func(k, v []byte) { + snapshotter := k[len(labelGCSnapBackRef):] + if i := bytes.IndexByte(snapshotter, '/'); i >= 0 { + snapshotter = snapshotter[:i] + } + bref(gcnode(metadata.ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, v)), n) + }, + }, + // TODO: Consider support for root/expire labels + } { + for k, v := lc.Seek(h.key); k != nil && bytes.HasPrefix(k, h.key); k, v = lc.Next() { + h.handler(k, v) + } + } + } + } + } +} + +func (cc *collectionContext) Active(ns string, fn func(gc.Node)) { + cc.ActiveWithBackRefs(ns, fn, func(gc.Node, gc.Node) {}) +} + +func (cc *collectionContext) Leased(ns, lease string, fn func(gc.Node)) { + bkt := getBucket(cc.tx, []byte("v1"), []byte(ns), []byte("leases"), []byte(lease)) + if bkt != nil { + c := bkt.Cursor() + for k, _ := c.First(); k != nil; k, _ = c.Next() { + fn(gc.Node{ + Type: metadata.ResourceMount, + Namespace: ns, + Key: string(k), + }) + } + } +} + +func (cc *collectionContext) Remove(n gc.Node) { + log.G(cc.ctx).WithFields(log.Fields{"namespace": n.Namespace, "name": n.Key}).Debugf("remove mount") + if n.Type != metadata.ResourceMount { + return + } + nmap, ok := cc.removed[n.Namespace] + if ok { + if _, ok = nmap[n.Key]; !ok { + nmap[n.Key] = struct{}{} + } + } else { + cc.removed[n.Namespace] = map[string]struct{}{ + n.Key: {}, + } + } +} + +func (cc *collectionContext) Cancel() (err error) { + err = cc.tx.Rollback() + cc.manager.rwlock.Unlock() + return +} + +func (cc *collectionContext) Finish() error { + // TODO: Get list of all remaining + remaining, err := cc.applyRemove() + if err != nil { + if rerr := cc.tx.Rollback(); rerr != nil { + err = errors.Join(err, rerr) + } + } else { + err = cc.tx.Commit() + } + if err != nil { + cc.manager.rwlock.Unlock() + return err + } + + // TODO: Consider using unmount q + cleanup, err := cc.getCleanupDirectories(remaining) + + cc.manager.rwlock.Unlock() + + if err != nil { + return err + } + + return cleanupAll(cc.ctx, cleanup, cc.manager.handlers) +} + +func (cc *collectionContext) applyRemove() (map[uint64]struct{}, error) { + remaining := map[uint64]struct{}{} + v1bkt := cc.tx.Bucket([]byte("v1")) + if v1bkt == nil { + return remaining, nil + } + nsc := v1bkt.Cursor() + for nsk, nsv := nsc.First(); nsk != nil; nsk, nsv = nsc.Next() { + if nsv != nil { + continue + } + removed := cc.removed[string(nsk)] + nsbkt := v1bkt.Bucket(nsk) + msbkt := nsbkt.Bucket(bucketKeyMounts) + if msbkt == nil { + continue + } + lsbkt := nsbkt.Bucket(bucketKeyLeases) + msc := msbkt.Cursor() + for msk, msv := msc.First(); msk != nil; msk, msv = msc.Next() { + if msv != nil { + continue + } + mbkt := msbkt.Bucket(msk) + var remove bool + if removed != nil { + _, remove = removed[string(msk)] + } + + if remove { + if lsbkt != nil { + lid := mbkt.Get(bucketKeyLease) + if len(lid) > 0 { + lbkt := lsbkt.Bucket(lid) + if lbkt != nil { + lbkt.Delete(msk) + if k, _ := lbkt.Cursor().First(); k == nil { + lsbkt.DeleteBucket(lid) + } + } + } + } + msbkt.DeleteBucket(msk) + } else { + remaining[readID(mbkt)] = struct{}{} + } + } + } + + return remaining, nil +} + +func (cc *collectionContext) getCleanupDirectories(remaining map[uint64]struct{}) ([]string, error) { + fd, err := cc.manager.targets.Open(".") + if err != nil { + return nil, err + } + defer fd.Close() + + dirs, err := fd.Readdirnames(0) + if err != nil { + return nil, err + } + + cleanup := []string{} + for _, d := range dirs { + id, err := strconv.ParseUint(d, 10, 64) + if err != nil { + continue + } + if _, ok := remaining[id]; ok { + continue + } + cleanup = append(cleanup, filepath.Join(cc.manager.targets.Name(), d)) + } + + return cleanup, nil +} + +func cleanupAll(ctx context.Context, roots []string, handlers map[string]mount.Handler) error { + var errs []error + for _, root := range roots { + if err := unmountAll(ctx, root, handlers); err != nil { + errs = append(errs, fmt.Errorf("unmount all failed during cleanup up %s: %w", root, err)) + } else { + log.G(ctx).WithField("root", root).Debugf("unmounted") + } + } + return errors.Join(errs...) +} + +func unmountAll(ctx context.Context, root string, handlers map[string]mount.Handler) error { + fd, err := os.Open(root) + if err != nil { + return err + } + + dirs, err := fd.Readdirnames(0) + fd.Close() + if err != nil { + return err + } + + var mountErrs []error + for i := len(dirs) - 1; i >= 0; { + var ( + d = dirs[i] + mp string + h mount.Handler + ) + i-- + + if strings.HasSuffix(d, "-type") { + name := d[:len(d)-5] + if i >= 0 && dirs[i] == name { + i-- + } + if b, rerr := os.ReadFile(filepath.Join(root, d)); rerr == nil { + h = handlers[string(b)] + } else { + return rerr + } + mp = filepath.Join(root, name) + } else { + mp = filepath.Join(root, d) + // If type file exists, continue and try again with "-type" file + if _, serr := os.Stat(mp + "-type"); serr == nil { + continue + } else if !os.IsNotExist(serr) { + return serr + } else { + log.G(ctx).WithField("mount", d).Infof("missing type file, attempting unmount with no handler") + } + } + + if h != nil { + err = h.Unmount(ctx, mp) + } else { + err = mount.Unmount(mp, 0) + } + if err != nil { + // TODO: Ignore already unmounted + mountErrs = append(mountErrs, fmt.Errorf("failure unmounting %s: %w", d, err)) + } + } + if len(mountErrs) > 0 { + return errors.Join(mountErrs...) + } + + return os.RemoveAll(root) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/mount/manager/mkdir.go b/vendor/github.com/containerd/containerd/v2/core/mount/manager/mkdir.go new file mode 100644 index 0000000000..bee933f2c0 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/mount/manager/mkdir.go @@ -0,0 +1,132 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package manager + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/containerd/errdefs" + + "github.com/containerd/containerd/v2/core/mount" +) + +// mkdir is a mount transformer that creates directories +// it can be used to ensure directories are created before +// overlay options are applied or any case that might need +// a directory +type mkdir struct { + rootMap map[string]*os.Root +} + +func (h *mkdir) Transform(ctx context.Context, m mount.Mount, _ []mount.ActiveMount) (mount.Mount, error) { + + var options []string + for _, o := range m.Options { + if mkdirOption, isMkdir := strings.CutPrefix(o, prefixMkdir); isMkdir { + // Format is X-containerd.mkdir.path=value[:mode[:uid:gid]] + + value, isPath := strings.CutPrefix(mkdirOption, "path=") + if !isPath { + return mount.Mount{}, fmt.Errorf("invalid mkdir option %q: %w", o, errdefs.ErrInvalidArgument) + } + parts := strings.SplitN(value, ":", 4) + var ( + dir string + mode os.FileMode = 0700 + luid = os.Getuid() + lgid = os.Getgid() + uid, gid = luid, lgid + err error + ) + switch len(parts) { + case 4: + gid, err = strconv.Atoi(parts[3]) + if err != nil { + return mount.Mount{}, fmt.Errorf("invalid gid %q: %w", parts[3], errdefs.ErrInvalidArgument) + } + uid, err = strconv.Atoi(parts[2]) + if err != nil { + return mount.Mount{}, fmt.Errorf("invalid uid %q: %w", parts[2], errdefs.ErrInvalidArgument) + } + fallthrough + case 2: + var p uint64 + p, err = strconv.ParseUint(parts[1], 8, 32) + if err == nil { + mode = os.FileMode(p) + if mode != mode&os.ModePerm { + return mount.Mount{}, fmt.Errorf("invalid mode %o", p) + } + } else { + return mount.Mount{}, fmt.Errorf("invalid mode %s: %w", parts[1], err) + } + fallthrough + case 1: + dir = parts[0] + default: + return mount.Mount{}, fmt.Errorf("invalid mkdir option %q: %w", o, errdefs.ErrInvalidArgument) + } + + var r *os.Root + var subpath string + + for path, root := range h.rootMap { + if strings.HasPrefix(dir, path) { + r = root + subpath = strings.TrimPrefix(dir, path) + subpath, _ = filepath.Rel("/", subpath) + break + } + } + if r == nil { + return mount.Mount{}, fmt.Errorf("no root %q configured for mkdir: %w", dir, errdefs.ErrNotImplemented) + } + + if st, err := r.Stat(subpath); err == nil { + if st.Mode()&os.ModePerm != mode { + // TODO: Chmod support added in go1.25 + return mount.Mount{}, fmt.Errorf("chmod not supported yet for mkdir handler: %w", errdefs.ErrNotImplemented) + } + // TODO: check ownership, chown support added in go1.25 + } else if os.IsNotExist(err) { + // TODO: MkdirAll added in go1.25 + if err := r.Mkdir(subpath, mode); err != nil { + return mount.Mount{}, fmt.Errorf("failed to create directory %q: %w", dir, err) + } + if luid != -1 && (luid != uid || lgid != gid) { + // TODO: Chown support added in go1.25 + //if err := r.Chown(subpath, uid, gid); err != nil { + // return fmt.Errorf("failed to chown directory %q: %w", m.Source, err) + //} + return mount.Mount{}, fmt.Errorf("chown not supported yet for mkdir handler: %w", errdefs.ErrNotImplemented) + } + } else { + return mount.Mount{}, fmt.Errorf("failed to stat %q: %w", dir, err) + } + } else { + options = append(options, o) + } + } + m.Options = options + + return m, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/mount/manager/mkfs.go b/vendor/github.com/containerd/containerd/v2/core/mount/manager/mkfs.go new file mode 100644 index 0000000000..761d4ef107 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/mount/manager/mkfs.go @@ -0,0 +1,149 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package manager + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/containerd/errdefs" + "github.com/containerd/log" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/docker/go-units" +) + +type mkfs struct { + rootMap map[string]*os.Root +} + +func (t *mkfs) Transform(ctx context.Context, m mount.Mount, a []mount.ActiveMount) (mount.Mount, error) { + var r *os.Root + var subpath string + + for path, root := range t.rootMap { + if strings.HasPrefix(m.Source, path) { + r = root + subpath = strings.TrimPrefix(m.Source, path) + subpath, _ = filepath.Rel("/", subpath) + break + } + } + if r == nil { + err := fmt.Errorf("no root %q configured for mkfs: %w", m.Source, errdefs.ErrNotImplemented) + log.G(ctx).WithError(err).Debugf("skipping mkfs") + return m, err + } + + log.G(ctx).Debugf("transforming mkfs mount: %+v", m) + + var ( + size int64 + id string + fs = "ext4" + ) + var options []string + for _, o := range m.Options { + if mkfsOption, isMkfs := strings.CutPrefix(o, prefixMkfs); isMkfs { + key, value, ok := strings.Cut(mkfsOption, "=") + if !ok { + key = o + value = "true" + } + switch key { + case "size": + var err error + size, err = units.RAMInBytes(value) + if err != nil { + return mount.Mount{}, fmt.Errorf("bad option %s: %w", key, err) + } + case "fs": + fs = value + case "uuid": + id = value + default: + return mount.Mount{}, fmt.Errorf("unknown mount option %s: %w", key, errdefs.ErrInvalidArgument) + } + + } else { + options = append(options, o) + } + } + m.Options = options + if size == 0 { + return mount.Mount{}, fmt.Errorf("mkfs requires mkfs.size option: %w", errdefs.ErrInvalidArgument) + } + + if _, err := r.Stat(subpath); err == nil { + // Check magic number + } else if os.IsNotExist(err) { + createArgs := []string{"-q"} + + // TODO: Pre-resolve the binaries to absolute path on startup for supported fs types + var binary string + + // Check fs + switch fs { + case "ext2", "ext3", "ext4": + binary = fmt.Sprintf("mkfs.%s", fs) + if id != "" { + createArgs = append(createArgs, []string{"-U", id}...) + } + case "xfs": + binary = "mkfs.xfs" + if id != "" { + createArgs = append(createArgs, []string{"-m", fmt.Sprintf("uuid=%s", id)}...) + } + default: + return mount.Mount{}, fmt.Errorf("unsupported filesystem %q: %w", fs, errdefs.ErrInvalidArgument) + } + + f, err := r.OpenFile(subpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0640) + if err != nil { + return mount.Mount{}, fmt.Errorf("failed to create file %q: %w", m.Source, err) + } + + createArgs = append(createArgs, f.Name()) + + err = f.Truncate(size) + f.Close() + if err != nil { + return mount.Mount{}, fmt.Errorf("failed to truncate file %q: %w", m.Source, err) + } + + if err := createWritableImage(ctx, binary, createArgs...); err != nil { + return mount.Mount{}, fmt.Errorf("failed format %q: %w", m.Source, err) + } + } else { + return mount.Mount{}, fmt.Errorf("failed to stat %q: %w", m.Source, err) + } + + return m, nil +} + +func createWritableImage(ctx context.Context, binary string, args ...string) error { + cmd := exec.CommandContext(ctx, binary, args...) + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s failed: %s: %w", filepath.Base(binary), out, err) + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/mount/manager/transformer.go b/vendor/github.com/containerd/containerd/v2/core/mount/manager/transformer.go new file mode 100644 index 0000000000..107412a6aa --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/mount/manager/transformer.go @@ -0,0 +1,42 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package manager + +import ( + "context" + + "github.com/containerd/containerd/v2/core/mount" +) + +const ( + // define mount options using X-containerd prefix as defined by + // https://man7.org/linux/man-pages/man8/mount.8.html + + prefixMkdir = "X-containerd.mkdir." + prefixMkfs = "X-containerd.mkfs." +) + +type typeTransformer struct { + mount.Transformer + + mountType string +} + +func (t typeTransformer) Transform(ctx context.Context, m mount.Mount, a []mount.ActiveMount) (mount.Mount, error) { + m.Type = t.mountType + return t.Transformer.Transform(ctx, m, a) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/events.go b/vendor/github.com/containerd/containerd/v2/core/runtime/events.go new file mode 100644 index 0000000000..73e52835a6 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/events.go @@ -0,0 +1,77 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runtime + +import ( + "github.com/containerd/containerd/api/events" + "github.com/containerd/log" +) + +const ( + // TaskCreateEventTopic for task create + TaskCreateEventTopic = "/tasks/create" + // TaskStartEventTopic for task start + TaskStartEventTopic = "/tasks/start" + // TaskOOMEventTopic for task oom + TaskOOMEventTopic = "/tasks/oom" + // TaskExitEventTopic for task exit + TaskExitEventTopic = "/tasks/exit" + // TaskDeleteEventTopic for task delete + TaskDeleteEventTopic = "/tasks/delete" + // TaskExecAddedEventTopic for task exec create + TaskExecAddedEventTopic = "/tasks/exec-added" + // TaskExecStartedEventTopic for task exec start + TaskExecStartedEventTopic = "/tasks/exec-started" + // TaskPausedEventTopic for task pause + TaskPausedEventTopic = "/tasks/paused" + // TaskResumedEventTopic for task resume + TaskResumedEventTopic = "/tasks/resumed" + // TaskCheckpointedEventTopic for task checkpoint + TaskCheckpointedEventTopic = "/tasks/checkpointed" + // TaskUnknownTopic for unknown task events + TaskUnknownTopic = "/tasks/?" +) + +// GetTopic converts an event from an interface type to the specific +// event topic id +func GetTopic(e any) string { + switch e.(type) { + case *events.TaskCreate: + return TaskCreateEventTopic + case *events.TaskStart: + return TaskStartEventTopic + case *events.TaskOOM: + return TaskOOMEventTopic + case *events.TaskExit: + return TaskExitEventTopic + case *events.TaskDelete: + return TaskDeleteEventTopic + case *events.TaskExecAdded: + return TaskExecAddedEventTopic + case *events.TaskExecStarted: + return TaskExecStartedEventTopic + case *events.TaskPaused: + return TaskPausedEventTopic + case *events.TaskResumed: + return TaskResumedEventTopic + case *events.TaskCheckpointed: + return TaskCheckpointedEventTopic + default: + log.L.Warnf("no topic for type %#v", e) + } + return TaskUnknownTopic +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/monitor.go b/vendor/github.com/containerd/containerd/v2/core/runtime/monitor.go new file mode 100644 index 0000000000..df5cf7f1ad --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/monitor.go @@ -0,0 +1,71 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runtime + +// TaskMonitor provides an interface for monitoring of containers within containerd +type TaskMonitor interface { + // Monitor adds the provided container to the monitor. + // Labels are optional (can be nil) key value pairs to be added to the metrics namespace. + Monitor(task Task, labels map[string]string) error + // Stop stops and removes the provided container from the monitor + Stop(task Task) error +} + +// NewMultiTaskMonitor returns a new TaskMonitor broadcasting to the provided monitors +func NewMultiTaskMonitor(monitors ...TaskMonitor) TaskMonitor { + return &multiTaskMonitor{ + monitors: monitors, + } +} + +// NewNoopMonitor is a task monitor that does nothing +func NewNoopMonitor() TaskMonitor { + return &noopTaskMonitor{} +} + +type noopTaskMonitor struct { +} + +func (mm *noopTaskMonitor) Monitor(c Task, labels map[string]string) error { + return nil +} + +func (mm *noopTaskMonitor) Stop(c Task) error { + return nil +} + +type multiTaskMonitor struct { + monitors []TaskMonitor +} + +func (mm *multiTaskMonitor) Monitor(task Task, labels map[string]string) error { + for _, m := range mm.monitors { + if err := m.Monitor(task, labels); err != nil { + return err + } + } + return nil +} + +func (mm *multiTaskMonitor) Stop(c Task) error { + for _, m := range mm.monitors { + if err := m.Stop(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/nsmap.go b/vendor/github.com/containerd/containerd/v2/core/runtime/nsmap.go new file mode 100644 index 0000000000..fec82113e3 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/nsmap.go @@ -0,0 +1,144 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runtime + +import ( + "context" + "fmt" + "sync" + + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/errdefs" +) + +type object interface { + ID() string +} + +// NSMap extends Map type with a notion of namespaces passed via Context. +type NSMap[T object] struct { + mu sync.RWMutex + objects map[string]map[string]T +} + +// NewNSMap returns a new NSMap +func NewNSMap[T object]() *NSMap[T] { + return &NSMap[T]{ + objects: make(map[string]map[string]T), + } +} + +// Get a task +func (m *NSMap[T]) Get(ctx context.Context, id string) (T, error) { + namespace, err := namespaces.NamespaceRequired(ctx) + var t T + if err != nil { + return t, err + } + + m.mu.RLock() + defer m.mu.RUnlock() + tasks, ok := m.objects[namespace] + if !ok { + return t, errdefs.ErrNotFound + } + t, ok = tasks[id] + if !ok { + return t, errdefs.ErrNotFound + } + return t, nil +} + +// GetAll objects under a namespace +func (m *NSMap[T]) GetAll(ctx context.Context, noNS bool) ([]T, error) { + m.mu.RLock() + defer m.mu.RUnlock() + var o []T + if noNS { + for ns := range m.objects { + for _, t := range m.objects[ns] { + o = append(o, t) + } + } + return o, nil + } + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + tasks, ok := m.objects[namespace] + if !ok { + return o, nil + } + for _, t := range tasks { + o = append(o, t) + } + return o, nil +} + +// Add a task +func (m *NSMap[T]) Add(ctx context.Context, t T) error { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + return m.AddWithNamespace(namespace, t) +} + +// AddWithNamespace adds a task with the provided namespace +func (m *NSMap[T]) AddWithNamespace(namespace string, t T) error { + id := t.ID() + + m.mu.Lock() + defer m.mu.Unlock() + if _, ok := m.objects[namespace]; !ok { + m.objects[namespace] = make(map[string]T) + } + if _, ok := m.objects[namespace][id]; ok { + return fmt.Errorf("%s: %w", id, errdefs.ErrAlreadyExists) + } + m.objects[namespace][id] = t + return nil +} + +// Delete a task +func (m *NSMap[T]) Delete(ctx context.Context, id string) { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return + } + + m.mu.Lock() + defer m.mu.Unlock() + tasks, ok := m.objects[namespace] + if ok { + delete(tasks, id) + } +} + +func (m *NSMap[T]) IsEmpty() bool { + m.mu.RLock() + defer m.mu.RUnlock() + + for ns := range m.objects { + if len(m.objects[ns]) > 0 { + return false + } + } + + return true +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/runtime.go b/vendor/github.com/containerd/containerd/v2/core/runtime/runtime.go new file mode 100644 index 0000000000..5fea15ea78 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/runtime.go @@ -0,0 +1,83 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runtime + +import ( + "context" + "time" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/typeurl/v2" +) + +// IO holds process IO information +type IO struct { + Stdin string + Stdout string + Stderr string + Terminal bool +} + +// CreateOpts contains task creation data +type CreateOpts struct { + // Spec is the OCI runtime spec + Spec typeurl.Any + // Rootfs mounts to perform to gain access to the container's filesystem + Rootfs []mount.Mount + // IO for the container's main process + IO IO + // Checkpoint digest to restore container state + Checkpoint string + // Mark this as a restore via a local checkpoint archive (most likely via CRI) + RestoreFromPath bool + // RuntimeOptions for the runtime + RuntimeOptions typeurl.Any + // TaskOptions received for the task + TaskOptions typeurl.Any + // Runtime name to use (e.g. `io.containerd.NAME.VERSION`). + // As an alternative full abs path to binary may be specified instead. + Runtime string + // SandboxID is an optional ID of sandbox this container belongs to + SandboxID string + // Address is an optional Address for Task API server + Address string + // Version is an optional Version of the Task API + Version uint32 +} + +// Exit information for a process +type Exit struct { + Pid uint32 + Status uint32 + Timestamp time.Time +} + +// PlatformRuntime is responsible for the creation and management of +// tasks and processes for a platform. +type PlatformRuntime interface { + // ID of the runtime + ID() string + // Create creates a task with the provided id and options. + Create(ctx context.Context, taskID string, opts CreateOpts) (Task, error) + // Get returns a task. + Get(ctx context.Context, taskID string) (Task, error) + // Tasks returns all the current tasks for the runtime. + // Any container runs at most one task at a time. + Tasks(ctx context.Context, all bool) ([]Task, error) + // Delete remove a task. + Delete(ctx context.Context, taskID string) (*Exit, error) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/task.go b/vendor/github.com/containerd/containerd/v2/core/runtime/task.go new file mode 100644 index 0000000000..1b2e6f7e7d --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/task.go @@ -0,0 +1,143 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runtime + +import ( + "context" + "time" + + "github.com/containerd/containerd/v2/pkg/protobuf/types" +) + +// TaskInfo provides task specific information +type TaskInfo struct { + ID string + Runtime string + Spec []byte + Namespace string +} + +// Process is a runtime object for an executing process inside a container +type Process interface { + // ID of the process + ID() string + // State returns the process state + State(ctx context.Context) (State, error) + // Kill signals a container + Kill(ctx context.Context, signal uint32, all bool) error + // ResizePty resizes the processes pty/console + ResizePty(ctx context.Context, size ConsoleSize) error + // CloseIO closes the processes IO + CloseIO(ctx context.Context) error + // Start the container's user defined process + Start(ctx context.Context) error + // Wait for the process to exit + Wait(ctx context.Context) (*Exit, error) +} + +// ExecProcess is a process spawned in container via Task.Exec call. +// The only difference from a regular `Process` is that exec process can delete self, +// while task process requires slightly more complex logic and needs to be deleted through the task manager. +type ExecProcess interface { + Process + + // Delete deletes the process + Delete(ctx context.Context) (*Exit, error) +} + +// Task is the runtime object for an executing container +type Task interface { + Process + + // PID of the process + PID(ctx context.Context) (uint32, error) + // Namespace that the task exists in + Namespace() string + // Pause pauses the container process + Pause(ctx context.Context) error + // Resume unpauses the container process + Resume(ctx context.Context) error + // Exec adds a process into the container + Exec(ctx context.Context, id string, opts ExecOpts) (ExecProcess, error) + // Pids returns all pids + Pids(ctx context.Context) ([]ProcessInfo, error) + // Checkpoint checkpoints a container to an image with live system data + Checkpoint(ctx context.Context, path string, opts *types.Any) error + // Update sets the provided resources to a running task + Update(ctx context.Context, resources *types.Any, annotations map[string]string) error + // Process returns a process within the task for the provided id + Process(ctx context.Context, id string) (ExecProcess, error) + // Stats returns runtime specific metrics for a task + Stats(ctx context.Context) (*types.Any, error) +} + +// ExecOpts provides additional options for additional processes running in a task +type ExecOpts struct { + Spec *types.Any + IO IO +} + +// ConsoleSize of a pty or windows terminal +type ConsoleSize struct { + Width uint32 + Height uint32 +} + +// Status is the runtime status of a task and/or process +type Status int + +const ( + // CreatedStatus when a process has been created + CreatedStatus Status = iota + 1 + // RunningStatus when a process is running + RunningStatus + // StoppedStatus when a process has stopped + StoppedStatus + // DeletedStatus when a process has been deleted + DeletedStatus + // PausedStatus when a process is paused + PausedStatus + // PausingStatus when a process is currently pausing + PausingStatus +) + +// State information for a process +type State struct { + // Status is the current status of the container + Status Status + // Pid is the main process id for the container + Pid uint32 + // ExitStatus of the process + // Only valid if the Status is Stopped + ExitStatus uint32 + // ExitedAt is the time at which the process exited + // Only valid if the Status is Stopped + ExitedAt time.Time + Stdin string + Stdout string + Stderr string + Terminal bool +} + +// ProcessInfo holds platform specific process information +type ProcessInfo struct { + // Pid is the process ID + Pid uint32 + // Info includes additional process information + // Info varies by platform + Info any +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/typeurl.go b/vendor/github.com/containerd/containerd/v2/core/runtime/typeurl.go new file mode 100644 index 0000000000..818a508917 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/typeurl.go @@ -0,0 +1,36 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runtime + +import ( + "strconv" + + "github.com/containerd/typeurl/v2" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-spec/specs-go/features" +) + +func init() { + const prefix = "types.containerd.io" + // register TypeUrls for commonly marshaled external types + major := strconv.Itoa(specs.VersionMajor) + typeurl.Register(&specs.Spec{}, prefix, "opencontainers/runtime-spec", major, "Spec") + typeurl.Register(&specs.Process{}, prefix, "opencontainers/runtime-spec", major, "Process") + typeurl.Register(&specs.LinuxResources{}, prefix, "opencontainers/runtime-spec", major, "LinuxResources") + typeurl.Register(&specs.WindowsResources{}, prefix, "opencontainers/runtime-spec", major, "WindowsResources") + typeurl.Register(&features.Features{}, prefix, "opencontainers/runtime-spec", major, "features", "Features") +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/binary.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/binary.go new file mode 100644 index 0000000000..a0c8848577 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/binary.go @@ -0,0 +1,220 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path/filepath" + gruntime "runtime" + + "github.com/containerd/containerd/api/runtime/task/v2" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/protobuf" + "github.com/containerd/containerd/v2/pkg/protobuf/proto" + "github.com/containerd/containerd/v2/pkg/protobuf/types" + client "github.com/containerd/containerd/v2/pkg/shim" + "github.com/containerd/log" +) + +type shimBinaryConfig struct { + runtime string + address string + ttrpcAddress string + socketDir string + env []string +} + +func shimBinary(bundle *Bundle, config shimBinaryConfig) *binary { + return &binary{ + bundle: bundle, + runtime: config.runtime, + containerdAddress: config.address, + containerdTTRPCAddress: config.ttrpcAddress, + socketDir: config.socketDir, + env: config.env, + } +} + +type binary struct { + runtime string + containerdAddress string + containerdTTRPCAddress string + socketDir string + bundle *Bundle + env []string +} + +func (b *binary) Start(ctx context.Context, opts *types.Any, onClose func()) (_ *shim, err error) { + // containerd daemon is the intended caller of client.Command; the deprecation + // targets external callers. + cmd, err := client.Command( //nolint:staticcheck // SA1019 + ctx, + &client.CommandConfig{ + ID: b.bundle.ID, + RuntimePath: b.runtime, + GRPCAddress: b.containerdAddress, + TTRPCAddress: b.containerdTTRPCAddress, + WorkDir: b.bundle.Path, + Opts: opts, + Env: b.env, + LogLevel: log.GetLevel(), + Action: "start", + SocketDir: b.socketDir, + }) + if err != nil { + return nil, err + } + // Windows needs a namespace when openShimLog + ns, _ := namespaces.Namespace(ctx) + shimCtx, cancelShimLog := context.WithCancel(namespaces.WithNamespace(context.Background(), ns)) + defer func() { + if err != nil { + cancelShimLog() + } + }() + f, err := openShimLog(shimCtx, b.bundle, client.AnonDialer) + if err != nil { + return nil, fmt.Errorf("open shim log pipe: %w", err) + } + defer func() { + if err != nil { + f.Close() + } + }() + // open the log pipe and block until the writer is ready + // this helps with synchronization of the shim + // copy the shim's logs to containerd's output + go func() { + defer f.Close() + _, err := io.Copy(os.Stderr, f) + // To prevent flood of error messages, the expected error + // should be reset, like os.ErrClosed or os.ErrNotExist, which + // depends on platform. + err = checkCopyShimLogError(ctx, err) + if err != nil { + log.G(ctx).WithError(err).Error("copy shim log") + } + }() + out, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("%s: %w", out, err) + } + response := bytes.TrimSpace(out) + + onCloseWithShimLog := func() { + onClose() + cancelShimLog() + f.Close() + } + // Save runtime binary path for restore. + if err := os.WriteFile(filepath.Join(b.bundle.Path, "shim-binary-path"), []byte(b.runtime), 0600); err != nil { + return nil, err + } + + params, err := parseStartResponse(response) + if err != nil { + return nil, err + } + + conn, err := makeConnection(ctx, b.bundle.ID, params, onCloseWithShimLog, client.AnonDialer) + if err != nil { + return nil, err + } + + // Save bootstrap configuration (so containerd can restore shims after restart). + if err := writeBootstrapParams(filepath.Join(b.bundle.Path, "bootstrap.json"), params); err != nil { + return nil, fmt.Errorf("failed to write bootstrap.json: %w", err) + } + // The address is in the form like ttrpc+unix:// or grpc+vsock://: + address := fmt.Sprintf("%s+%s", params.Protocol, params.Address) + return &shim{ + bundle: b.bundle, + client: conn, + address: address, + version: int(params.Version), + }, nil +} + +func (b *binary) Delete(ctx context.Context) (*runtime.Exit, error) { + log.G(ctx).WithField("id", b.bundle.ID).Info("cleaning up dead shim") + + // On Windows and FreeBSD, the current working directory of the shim should + // not be the bundle path during the delete operation. Instead, we invoke + // with the default work dir and forward the bundle path on the cmdline. + // Windows cannot delete the current working directory while an executable + // is in use with it. On FreeBSD, fork/exec can fail. + var bundlePath string + if gruntime.GOOS != "windows" && gruntime.GOOS != "freebsd" { + bundlePath = b.bundle.Path + } + + // containerd daemon is the intended caller of client.Command; the deprecation + // targets external callers. + cmd, err := client.Command(ctx, //nolint:staticcheck // SA1019 + &client.CommandConfig{ + ID: b.bundle.ID, + RuntimePath: b.runtime, + BundlePath: b.bundle.Path, + GRPCAddress: b.containerdAddress, + TTRPCAddress: b.containerdTTRPCAddress, + WorkDir: bundlePath, + Opts: nil, + LogLevel: log.GetLevel(), + Action: "delete", + }) + + if err != nil { + return nil, err + } + var ( + out = bytes.NewBuffer(nil) + errb = bytes.NewBuffer(nil) + ) + cmd.Stdout = out + cmd.Stderr = errb + if err := cmd.Run(); err != nil { + log.G(ctx).WithFields(log.Fields{ + "cmd": cmd.String(), + "error": err, + "id": b.bundle.ID, + }).Error("failed to delete dead shim") + return nil, fmt.Errorf("%s: %w", errb.String(), err) + } + if s := errb.String(); s != "" { + log.G(ctx).WithFields(log.Fields{ + "id": b.bundle.ID, + "warnings": s, + }).Warn("warnings while cleaning up dead shim") + } + var response task.DeleteResponse + if err := proto.Unmarshal(out.Bytes(), &response); err != nil { + return nil, err + } + if err := b.bundle.Delete(); err != nil { + return nil, err + } + return &runtime.Exit{ + Status: response.ExitStatus, + Timestamp: protobuf.FromTimestamp(response.ExitedAt), + Pid: response.Pid, + }, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bridge.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bridge.go new file mode 100644 index 0000000000..7c896f745c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bridge.go @@ -0,0 +1,330 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "fmt" + + "github.com/containerd/ttrpc" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" + + v2 "github.com/containerd/containerd/api/runtime/task/v2" + + api "github.com/containerd/containerd/api/runtime/task/v3" // Current version used by TaskServiceClient +) + +// TaskServiceClient exposes a client interface to shims, which aims to hide +// the underlying complexity and backward compatibility (v2 task service vs v3, TTRPC vs GRPC, etc). +type TaskServiceClient interface { + State(context.Context, *api.StateRequest) (*api.StateResponse, error) + Create(context.Context, *api.CreateTaskRequest) (*api.CreateTaskResponse, error) + Start(context.Context, *api.StartRequest) (*api.StartResponse, error) + Delete(context.Context, *api.DeleteRequest) (*api.DeleteResponse, error) + Pids(context.Context, *api.PidsRequest) (*api.PidsResponse, error) + Pause(context.Context, *api.PauseRequest) (*emptypb.Empty, error) + Resume(context.Context, *api.ResumeRequest) (*emptypb.Empty, error) + Checkpoint(context.Context, *api.CheckpointTaskRequest) (*emptypb.Empty, error) + Kill(context.Context, *api.KillRequest) (*emptypb.Empty, error) + Exec(context.Context, *api.ExecProcessRequest) (*emptypb.Empty, error) + ResizePty(context.Context, *api.ResizePtyRequest) (*emptypb.Empty, error) + CloseIO(context.Context, *api.CloseIORequest) (*emptypb.Empty, error) + Update(context.Context, *api.UpdateTaskRequest) (*emptypb.Empty, error) + Wait(context.Context, *api.WaitRequest) (*api.WaitResponse, error) + Stats(context.Context, *api.StatsRequest) (*api.StatsResponse, error) + Connect(context.Context, *api.ConnectRequest) (*api.ConnectResponse, error) + Shutdown(context.Context, *api.ShutdownRequest) (*emptypb.Empty, error) +} + +// NewTaskClient returns a new task client interface which handles both GRPC and TTRPC servers depending on the +// client object type passed in. +// +// Supported client types are: +// - *ttrpc.Client +// - grpc.ClientConnInterface +// +// Currently supported servers: +// - TTRPC v2 (compatibility with shims before 2.0) +// - TTRPC v3 +// - GRPC v3 +func NewTaskClient(client any, version int) (TaskServiceClient, error) { + switch c := client.(type) { + case *ttrpc.Client: + switch version { + case 2: + return &ttrpcV2Bridge{client: v2.NewTTRPCTaskClient(c)}, nil + case 3: + return api.NewTTRPCTaskClient(c), nil + default: + return nil, fmt.Errorf("containerd client supports only v2 and v3 TTRPC task client (got %d)", version) + } + + case grpc.ClientConnInterface: + if version != 3 { + return nil, fmt.Errorf("containerd client supports only v3 GRPC task service (got %d)", version) + } + + return &grpcV3Bridge{api.NewTaskClient(c)}, nil + default: + return nil, fmt.Errorf("unsupported shim client type %T", c) + } +} + +// ttrpcV2Bridge is a bridge from TTRPC v2 task service. +type ttrpcV2Bridge struct { + client v2.TTRPCTaskService +} + +var _ TaskServiceClient = (*ttrpcV2Bridge)(nil) + +func (b *ttrpcV2Bridge) State(ctx context.Context, request *api.StateRequest) (*api.StateResponse, error) { + resp, err := b.client.State(ctx, &v2.StateRequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + }) + + return &api.StateResponse{ + ID: resp.GetID(), + Bundle: resp.GetBundle(), + Pid: resp.GetPid(), + Status: resp.GetStatus(), + Stdin: resp.GetStdin(), + Stdout: resp.GetStdout(), + Stderr: resp.GetStderr(), + Terminal: resp.GetTerminal(), + ExitStatus: resp.GetExitStatus(), + ExitedAt: resp.GetExitedAt(), + ExecID: resp.GetExecID(), + }, err +} + +func (b *ttrpcV2Bridge) Create(ctx context.Context, request *api.CreateTaskRequest) (*api.CreateTaskResponse, error) { + resp, err := b.client.Create(ctx, &v2.CreateTaskRequest{ + ID: request.GetID(), + Bundle: request.GetBundle(), + Rootfs: request.GetRootfs(), + Terminal: request.GetTerminal(), + Stdin: request.GetStdin(), + Stdout: request.GetStdout(), + Stderr: request.GetStderr(), + Checkpoint: request.GetCheckpoint(), + ParentCheckpoint: request.GetParentCheckpoint(), + Options: request.GetOptions(), + }) + + return &api.CreateTaskResponse{Pid: resp.GetPid()}, err +} + +func (b *ttrpcV2Bridge) Start(ctx context.Context, request *api.StartRequest) (*api.StartResponse, error) { + resp, err := b.client.Start(ctx, &v2.StartRequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + }) + + return &api.StartResponse{Pid: resp.GetPid()}, err +} + +func (b *ttrpcV2Bridge) Delete(ctx context.Context, request *api.DeleteRequest) (*api.DeleteResponse, error) { + resp, err := b.client.Delete(ctx, &v2.DeleteRequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + }) + + return &api.DeleteResponse{ + Pid: resp.GetPid(), + ExitStatus: resp.GetExitStatus(), + ExitedAt: resp.GetExitedAt(), + }, err +} + +func (b *ttrpcV2Bridge) Pids(ctx context.Context, request *api.PidsRequest) (*api.PidsResponse, error) { + resp, err := b.client.Pids(ctx, &v2.PidsRequest{ID: request.GetID()}) + return &api.PidsResponse{Processes: resp.GetProcesses()}, err +} + +func (b *ttrpcV2Bridge) Pause(ctx context.Context, request *api.PauseRequest) (*emptypb.Empty, error) { + return b.client.Pause(ctx, &v2.PauseRequest{ID: request.GetID()}) +} + +func (b *ttrpcV2Bridge) Resume(ctx context.Context, request *api.ResumeRequest) (*emptypb.Empty, error) { + return b.client.Resume(ctx, &v2.ResumeRequest{ID: request.GetID()}) +} + +func (b *ttrpcV2Bridge) Checkpoint(ctx context.Context, request *api.CheckpointTaskRequest) (*emptypb.Empty, error) { + return b.client.Checkpoint(ctx, &v2.CheckpointTaskRequest{ + ID: request.GetID(), + Path: request.GetPath(), + Options: request.GetOptions(), + }) +} + +func (b *ttrpcV2Bridge) Kill(ctx context.Context, request *api.KillRequest) (*emptypb.Empty, error) { + return b.client.Kill(ctx, &v2.KillRequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + Signal: request.GetSignal(), + All: request.GetAll(), + }) +} + +func (b *ttrpcV2Bridge) Exec(ctx context.Context, request *api.ExecProcessRequest) (*emptypb.Empty, error) { + return b.client.Exec(ctx, &v2.ExecProcessRequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + Terminal: request.GetTerminal(), + Stdin: request.GetStdin(), + Stdout: request.GetStdout(), + Stderr: request.GetStderr(), + Spec: request.GetSpec(), + }) +} + +func (b *ttrpcV2Bridge) ResizePty(ctx context.Context, request *api.ResizePtyRequest) (*emptypb.Empty, error) { + return b.client.ResizePty(ctx, &v2.ResizePtyRequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + Width: request.GetWidth(), + Height: request.GetHeight(), + }) +} + +func (b *ttrpcV2Bridge) CloseIO(ctx context.Context, request *api.CloseIORequest) (*emptypb.Empty, error) { + return b.client.CloseIO(ctx, &v2.CloseIORequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + Stdin: request.GetStdin(), + }) +} + +func (b *ttrpcV2Bridge) Update(ctx context.Context, request *api.UpdateTaskRequest) (*emptypb.Empty, error) { + return b.client.Update(ctx, &v2.UpdateTaskRequest{ + ID: request.GetID(), + Resources: request.GetResources(), + Annotations: request.GetAnnotations(), + }) +} + +func (b *ttrpcV2Bridge) Wait(ctx context.Context, request *api.WaitRequest) (*api.WaitResponse, error) { + resp, err := b.client.Wait(ctx, &v2.WaitRequest{ + ID: request.GetID(), + ExecID: request.GetExecID(), + }) + + return &api.WaitResponse{ + ExitStatus: resp.GetExitStatus(), + ExitedAt: resp.GetExitedAt(), + }, err +} + +func (b *ttrpcV2Bridge) Stats(ctx context.Context, request *api.StatsRequest) (*api.StatsResponse, error) { + resp, err := b.client.Stats(ctx, &v2.StatsRequest{ID: request.GetID()}) + return &api.StatsResponse{Stats: resp.GetStats()}, err +} + +func (b *ttrpcV2Bridge) Connect(ctx context.Context, request *api.ConnectRequest) (*api.ConnectResponse, error) { + resp, err := b.client.Connect(ctx, &v2.ConnectRequest{ID: request.GetID()}) + + return &api.ConnectResponse{ + ShimPid: resp.GetShimPid(), + TaskPid: resp.GetTaskPid(), + Version: resp.GetVersion(), + }, err +} + +func (b *ttrpcV2Bridge) Shutdown(ctx context.Context, request *api.ShutdownRequest) (*emptypb.Empty, error) { + return b.client.Shutdown(ctx, &v2.ShutdownRequest{ + ID: request.GetID(), + Now: request.GetNow(), + }) +} + +// grpcV3Bridge implements task service client for v3 GRPC server. +// GRPC uses same request/response structures as TTRPC, so it just wraps GRPC calls. +type grpcV3Bridge struct { + client api.TaskClient +} + +var _ TaskServiceClient = (*grpcV3Bridge)(nil) + +func (g *grpcV3Bridge) State(ctx context.Context, request *api.StateRequest) (*api.StateResponse, error) { + return g.client.State(ctx, request) +} + +func (g *grpcV3Bridge) Create(ctx context.Context, request *api.CreateTaskRequest) (*api.CreateTaskResponse, error) { + return g.client.Create(ctx, request) +} + +func (g *grpcV3Bridge) Start(ctx context.Context, request *api.StartRequest) (*api.StartResponse, error) { + return g.client.Start(ctx, request) +} + +func (g *grpcV3Bridge) Delete(ctx context.Context, request *api.DeleteRequest) (*api.DeleteResponse, error) { + return g.client.Delete(ctx, request) +} + +func (g *grpcV3Bridge) Pids(ctx context.Context, request *api.PidsRequest) (*api.PidsResponse, error) { + return g.client.Pids(ctx, request) +} + +func (g *grpcV3Bridge) Pause(ctx context.Context, request *api.PauseRequest) (*emptypb.Empty, error) { + return g.client.Pause(ctx, request) +} + +func (g *grpcV3Bridge) Resume(ctx context.Context, request *api.ResumeRequest) (*emptypb.Empty, error) { + return g.client.Resume(ctx, request) +} + +func (g *grpcV3Bridge) Checkpoint(ctx context.Context, request *api.CheckpointTaskRequest) (*emptypb.Empty, error) { + return g.client.Checkpoint(ctx, request) +} + +func (g *grpcV3Bridge) Kill(ctx context.Context, request *api.KillRequest) (*emptypb.Empty, error) { + return g.client.Kill(ctx, request) +} + +func (g *grpcV3Bridge) Exec(ctx context.Context, request *api.ExecProcessRequest) (*emptypb.Empty, error) { + return g.client.Exec(ctx, request) +} + +func (g *grpcV3Bridge) ResizePty(ctx context.Context, request *api.ResizePtyRequest) (*emptypb.Empty, error) { + return g.client.ResizePty(ctx, request) +} + +func (g *grpcV3Bridge) CloseIO(ctx context.Context, request *api.CloseIORequest) (*emptypb.Empty, error) { + return g.client.CloseIO(ctx, request) +} + +func (g *grpcV3Bridge) Update(ctx context.Context, request *api.UpdateTaskRequest) (*emptypb.Empty, error) { + return g.client.Update(ctx, request) +} + +func (g *grpcV3Bridge) Wait(ctx context.Context, request *api.WaitRequest) (*api.WaitResponse, error) { + return g.client.Wait(ctx, request) +} + +func (g *grpcV3Bridge) Stats(ctx context.Context, request *api.StatsRequest) (*api.StatsResponse, error) { + return g.client.Stats(ctx, request) +} + +func (g *grpcV3Bridge) Connect(ctx context.Context, request *api.ConnectRequest) (*api.ConnectResponse, error) { + return g.client.Connect(ctx, request) +} + +func (g *grpcV3Bridge) Shutdown(ctx context.Context, request *api.ShutdownRequest) (*emptypb.Empty, error) { + return g.client.Shutdown(ctx, request) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle.go new file mode 100644 index 0000000000..3d301b25f1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle.go @@ -0,0 +1,170 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/pkg/identifiers" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/oci" + "github.com/containerd/typeurl/v2" + "github.com/opencontainers/runtime-spec/specs-go" +) + +// LoadBundle loads an existing bundle from disk +func LoadBundle(ctx context.Context, root, id string) (*Bundle, error) { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + return &Bundle{ + ID: id, + Path: filepath.Join(root, ns, id), + Namespace: ns, + }, nil +} + +// NewBundle returns a new bundle on disk +func NewBundle(ctx context.Context, root, state, id string, spec typeurl.Any) (b *Bundle, err error) { + if err := identifiers.Validate(id); err != nil { + return nil, fmt.Errorf("invalid task id %s: %w", id, err) + } + + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + work := filepath.Join(root, ns, id) + b = &Bundle{ + ID: id, + Path: filepath.Join(state, ns, id), + Namespace: ns, + } + var paths []string + defer func() { + if err != nil { + for _, d := range paths { + os.RemoveAll(d) + } + } + }() + // create state directory for the bundle + if err := os.MkdirAll(filepath.Dir(b.Path), 0711); err != nil { + return nil, err + } + if err := os.Mkdir(b.Path, 0700); err != nil { + return nil, err + } + if typeurl.Is(spec, &specs.Spec{}) { + if err := prepareBundleDirectoryPermissions(b.Path, spec.GetValue()); err != nil { + return nil, err + } + } + paths = append(paths, b.Path) + // create working directory for the bundle + if err := os.MkdirAll(filepath.Dir(work), 0711); err != nil { + return nil, err + } + rootfs := filepath.Join(b.Path, "rootfs") + if err := os.MkdirAll(rootfs, 0711); err != nil { + return nil, err + } + paths = append(paths, rootfs) + if err := os.Mkdir(work, 0711); err != nil { + if !os.IsExist(err) { + return nil, err + } + os.RemoveAll(work) + if err := os.Mkdir(work, 0711); err != nil { + return nil, err + } + } + paths = append(paths, work) + // symlink workdir + if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil { + return nil, err + } + // Spec may be nil for some sandboxers that do not initialize the spec, for + // example the shim sandboxer with a hostNetwork container. + if spec != nil { + if spec := spec.GetValue(); spec != nil { + // write the spec to the bundle + specPath := filepath.Join(b.Path, oci.ConfigFilename) + err = os.WriteFile(specPath, spec, 0666) + if err != nil { + return nil, fmt.Errorf("failed to write bundle spec: %w", err) + } + } + } + return b, nil +} + +// Bundle represents an OCI bundle +type Bundle struct { + // ID of the bundle + ID string + // Path to the bundle + Path string + // Namespace of the bundle + Namespace string +} + +// Delete a bundle atomically +func (b *Bundle) Delete() error { + work, werr := os.Readlink(filepath.Join(b.Path, "work")) + rootfs := filepath.Join(b.Path, "rootfs") + if err := mount.UnmountRecursive(rootfs, 0); err != nil { + return fmt.Errorf("unmount rootfs %s: %w", rootfs, err) + } + if err := os.Remove(rootfs); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove bundle rootfs: %w", err) + } + err := atomicDelete(b.Path) + if err == nil { + if werr == nil { + return atomicDelete(work) + } + return nil + } + // error removing the bundle path; still attempt removing work dir + var err2 error + if werr == nil { + err2 = atomicDelete(work) + if err2 == nil { + return err + } + } + return fmt.Errorf("failed to remove both bundle and workdir locations: %v: %w", err2, err) +} + +// atomicDelete renames the path to a hidden file before removal +func atomicDelete(path string) error { + // create a hidden dir for an atomic removal + atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path))) + if err := os.Rename(path, atomicPath); err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + return os.RemoveAll(atomicPath) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle_default.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle_default.go new file mode 100644 index 0000000000..01977adb4e --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle_default.go @@ -0,0 +1,23 @@ +//go:build !linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +// prepareBundleDirectoryPermissions prepares the permissions of the bundle +// directory according to the needs of the current platform. +func prepareBundleDirectoryPermissions(path string, spec []byte) error { return nil } diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle_linux.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle_linux.go new file mode 100644 index 0000000000..5f1915d776 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/bundle_linux.go @@ -0,0 +1,74 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + "os" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +// prepareBundleDirectoryPermissions prepares the permissions of the bundle +// directory according to the needs of the current platform. +// On Linux when user namespaces are enabled, the permissions are modified to +// allow the remapped root GID to access the bundle. +func prepareBundleDirectoryPermissions(path string, spec []byte) error { + gid, err := remappedGID(spec) + if err != nil { + return err + } + if gid == 0 { + return nil + } + if err := os.Chown(path, -1, int(gid)); err != nil { + return err + } + return os.Chmod(path, 0710) +} + +// ociSpecUserNS is a subset of specs.Spec used to reduce garbage during +// unmarshal. +type ociSpecUserNS struct { + Linux *linuxSpecUserNS +} + +// linuxSpecUserNS is a subset of specs.Linux used to reduce garbage during +// unmarshal. +type linuxSpecUserNS struct { + GIDMappings []specs.LinuxIDMapping +} + +// remappedGID reads the remapped GID 0 from the OCI spec, if it exists. If +// there is no remapping, remappedGID returns 0. If the spec cannot be parsed, +// remappedGID returns an error. +func remappedGID(spec []byte) (uint32, error) { + var ociSpec ociSpecUserNS + err := json.Unmarshal(spec, &ociSpec) + if err != nil { + return 0, err + } + if ociSpec.Linux == nil || len(ociSpec.Linux.GIDMappings) == 0 { + return 0, nil + } + for _, mapping := range ociSpec.Linux.GIDMappings { + if mapping.ContainerID == 0 { + return mapping.HostID, nil + } + } + return 0, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/manager_unix.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/manager_unix.go new file mode 100644 index 0000000000..be6ce6e112 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/manager_unix.go @@ -0,0 +1,27 @@ +//go:build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "github.com/containerd/platforms" +) + +func defaultPlatforms() []string { + return []string{platforms.DefaultString()} +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/manager_windows.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/manager_windows.go new file mode 100644 index 0000000000..b1bfbaa488 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/manager_windows.go @@ -0,0 +1,28 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "github.com/containerd/platforms" +) + +func defaultPlatforms() []string { + return []string{ + platforms.DefaultString(), + "linux/amd64", + } +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/process.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/process.go new file mode 100644 index 0000000000..bf3afc0856 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/process.go @@ -0,0 +1,161 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "errors" + + task "github.com/containerd/containerd/api/runtime/task/v3" + tasktypes "github.com/containerd/containerd/api/types/task" + "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/ttrpc" + + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/pkg/protobuf" +) + +type process struct { + id string + shim *shimTask +} + +func (p *process) ID() string { + return p.id +} + +func (p *process) Kill(ctx context.Context, signal uint32, _ bool) error { + _, err := p.shim.task.Kill(ctx, &task.KillRequest{ + Signal: signal, + ID: p.shim.ID(), + ExecID: p.id, + }) + if err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func statusFromProto(from tasktypes.Status) runtime.Status { + var status runtime.Status + switch from { + case tasktypes.Status_CREATED: + status = runtime.CreatedStatus + case tasktypes.Status_RUNNING: + status = runtime.RunningStatus + case tasktypes.Status_STOPPED: + status = runtime.StoppedStatus + case tasktypes.Status_PAUSED: + status = runtime.PausedStatus + case tasktypes.Status_PAUSING: + status = runtime.PausingStatus + } + return status +} + +func (p *process) State(ctx context.Context) (runtime.State, error) { + response, err := p.shim.task.State(ctx, &task.StateRequest{ + ID: p.shim.ID(), + ExecID: p.id, + }) + if err != nil { + if !errors.Is(err, ttrpc.ErrClosed) { + return runtime.State{}, errgrpc.ToNative(err) + } + return runtime.State{}, errdefs.ErrNotFound + } + return runtime.State{ + Pid: response.Pid, + Status: statusFromProto(response.Status), + Stdin: response.Stdin, + Stdout: response.Stdout, + Stderr: response.Stderr, + Terminal: response.Terminal, + ExitStatus: response.ExitStatus, + ExitedAt: protobuf.FromTimestamp(response.ExitedAt), + }, nil +} + +// ResizePty changes the side of the process's PTY to the provided width and height +func (p *process) ResizePty(ctx context.Context, size runtime.ConsoleSize) error { + _, err := p.shim.task.ResizePty(ctx, &task.ResizePtyRequest{ + ID: p.shim.ID(), + ExecID: p.id, + Width: size.Width, + Height: size.Height, + }) + if err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +// CloseIO closes the provided IO pipe for the process +func (p *process) CloseIO(ctx context.Context) error { + _, err := p.shim.task.CloseIO(ctx, &task.CloseIORequest{ + ID: p.shim.ID(), + ExecID: p.id, + Stdin: true, + }) + if err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +// Start the process +func (p *process) Start(ctx context.Context) error { + _, err := p.shim.task.Start(ctx, &task.StartRequest{ + ID: p.shim.ID(), + ExecID: p.id, + }) + if err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +// Wait on the process to exit and return the exit status and timestamp +func (p *process) Wait(ctx context.Context) (*runtime.Exit, error) { + response, err := p.shim.task.Wait(ctx, &task.WaitRequest{ + ID: p.shim.ID(), + ExecID: p.id, + }) + if err != nil { + return nil, errgrpc.ToNative(err) + } + return &runtime.Exit{ + Timestamp: protobuf.FromTimestamp(response.ExitedAt), + Status: response.ExitStatus, + }, nil +} + +func (p *process) Delete(ctx context.Context) (*runtime.Exit, error) { + response, err := p.shim.task.Delete(ctx, &task.DeleteRequest{ + ID: p.shim.ID(), + ExecID: p.id, + }) + if err != nil { + return nil, errgrpc.ToNative(err) + } + return &runtime.Exit{ + Status: response.ExitStatus, + Timestamp: protobuf.FromTimestamp(response.ExitedAt), + Pid: response.Pid, + }, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim.go new file mode 100644 index 0000000000..8d617130d2 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim.go @@ -0,0 +1,873 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "strings" + "time" + + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials/insecure" + + crmetadata "github.com/checkpoint-restore/checkpointctl/lib" + eventstypes "github.com/containerd/containerd/api/events" + bootapi "github.com/containerd/containerd/api/runtime/bootstrap/v1" + task "github.com/containerd/containerd/api/runtime/task/v3" + "github.com/containerd/containerd/api/types" + "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/log" + "github.com/containerd/otelttrpc" + "github.com/containerd/ttrpc" + "github.com/containerd/typeurl/v2" + + "github.com/containerd/containerd/v2/core/events/exchange" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/pkg/archive" + "github.com/containerd/containerd/v2/pkg/archive/compression" + "github.com/containerd/containerd/v2/pkg/atomicfile" + "github.com/containerd/containerd/v2/pkg/dialer" + "github.com/containerd/containerd/v2/pkg/identifiers" + "github.com/containerd/containerd/v2/pkg/protobuf" + "github.com/containerd/containerd/v2/pkg/protobuf/proto" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + client "github.com/containerd/containerd/v2/pkg/shim" + "github.com/containerd/containerd/v2/pkg/timeout" +) + +const ( + loadTimeout = "io.containerd.timeout.shim.load" + cleanupTimeout = "io.containerd.timeout.shim.cleanup" + shutdownTimeout = "io.containerd.timeout.shim.shutdown" +) + +func init() { + timeout.Set(loadTimeout, 5*time.Second) + timeout.Set(cleanupTimeout, 5*time.Second) + timeout.Set(shutdownTimeout, 3*time.Second) +} + +func loadShim(ctx context.Context, bundle *Bundle, onClose func()) (_ ShimInstance, retErr error) { + shimCtx, cancelShimLog := context.WithCancel(ctx) + defer func() { + if retErr != nil { + cancelShimLog() + } + }() + f, err := openShimLog(shimCtx, bundle, client.AnonReconnectDialer) + if err != nil { + return nil, fmt.Errorf("open shim log pipe when reload: %w", err) + } + defer func() { + if retErr != nil { + f.Close() + } + }() + // open the log pipe and block until the writer is ready + // this helps with synchronization of the shim + // copy the shim's logs to containerd's output + go func() { + defer f.Close() + _, err := io.Copy(os.Stderr, f) + // To prevent flood of error messages, the expected error + // should be reset, like os.ErrClosed or os.ErrNotExist, which + // depends on platform. + err = checkCopyShimLogError(shimCtx, err) + if err != nil { + log.G(shimCtx).WithError(err).Error("copy shim log after reload") + } + }() + onCloseWithShimLog := func() { + onClose() + cancelShimLog() + f.Close() + } + + params, err := restoreBootstrapParams(bundle.Path) + if err != nil { + return nil, fmt.Errorf("failed to read bootstrap.json when restoring bundle %q: %w", bundle.ID, err) + } + + conn, err := makeConnection(ctx, bundle.ID, params, onCloseWithShimLog, client.AnonReconnectDialer) + if err != nil { + return nil, fmt.Errorf("unable to make connection: %w", err) + } + + defer func() { + if retErr != nil { + conn.Close() + } + }() + + // The address is in the form like ttrpc+unix:// or grpc+vsock://: + address := fmt.Sprintf("%s+%s", params.Protocol, params.Address) + + shim := &shim{ + bundle: bundle, + client: conn, + address: address, + version: int(params.Version), + } + + return shim, nil +} + +func cleanupAfterDeadShim(ctx context.Context, id string, rt *runtime.NSMap[ShimInstance], events *exchange.Exchange, binaryCall *binary) { + ctx, cancel := timeout.WithContext(ctx, cleanupTimeout) + defer cancel() + + log.G(ctx).WithField("id", id).Info("cleaning up after shim disconnected") + response, err := binaryCall.Delete(ctx) + if err != nil { + log.G(ctx).WithError(err).WithField("id", id).Warn("failed to clean up after shim disconnected") + } + + if _, err := rt.Get(ctx, id); err != nil { + // Task was never started or was already successfully deleted + // No need to publish events + return + } + + var ( + pid uint32 + exitStatus uint32 + exitedAt time.Time + ) + if response != nil { + pid = response.Pid + exitStatus = response.Status + exitedAt = response.Timestamp + } else { + exitStatus = 255 + exitedAt = time.Now() + } + events.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{ + ContainerID: id, + ID: id, + Pid: pid, + ExitStatus: exitStatus, + ExitedAt: protobuf.ToTimestamp(exitedAt), + }) + + events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{ + ContainerID: id, + Pid: pid, + ExitStatus: exitStatus, + ExitedAt: protobuf.ToTimestamp(exitedAt), + }) +} + +// CurrentShimVersion is the latest shim version supported by containerd (e.g. TaskService v3). +const CurrentShimVersion = 3 + +// ShimInstance represents running shim process managed by ShimManager. +type ShimInstance interface { + io.Closer + + // ID of the shim. + ID() string + // Namespace of this shim. + Namespace() string + // Bundle is a file system path to shim's bundle. + Bundle() string + // Client returns the underlying TTRPC or GRPC client object for this shim. + // The underlying object can be either *ttrpc.Client or grpc.ClientConnInterface. + Client() any + // Delete will close the client and remove bundle from disk. + Delete(ctx context.Context) error + // Endpoint returns shim's endpoint information, + // including address and version. + Endpoint() (string, int) +} + +type clientVersionDowngrader interface { + // Downgrade is to lower shim's client version. + // + // Assume there is a running pod created by containerd-shim-runc-v2 from v1.7.x. + // After upgrading to v2.x, the containerd-shim-runc-v2 binary will support the + // sandbox API, and calling `shim start` for the existing running pod will return + // a version=3 address. However, that pod shim does not support the streaming IO API, + // so we should downgrade the shim version. + // + // Additionally, if a container record was created with v1.7.x, it will not have + // a SandboxID field in the metadata store. In the CRI case, this will cause + // the new shim client to use the v3 protocol to send requests to a running shim + // that still uses the v2 protocol, resulting in a failure to start. + // In this case, we should also downgrade the shim version and retry. + Downgrade() error +} + +func parseStartResponse(response []byte) (*bootapi.BootstrapResult, error) { + var result bootapi.BootstrapResult + + if err := proto.Unmarshal(response, &result); err == nil { + return &result, nil + } + + // Fallback to legacy parsing for backward compatibility with legacy shims that return the address as a plain string or JSON. + + var params client.BootstrapParams //nolint:staticcheck // Used for backward compatibility with legacy shims + if err := json.Unmarshal(response, ¶ms); err != nil || params.Version < 2 { + // Use TTRPC for legacy shims + params.Address = string(response) + params.Protocol = "ttrpc" + params.Version = 2 + } + + if params.Version > CurrentShimVersion { + return nil, fmt.Errorf("unsupported shim version (%d): %w", params.Version, errdefs.ErrNotImplemented) + } + + return &bootapi.BootstrapResult{ + Version: int32(params.Version), + Address: params.Address, + Protocol: params.Protocol, + }, nil +} + +// writeBootstrapParams writes shim's bootstrap configuration (e.g. how to connect, version, etc). +func writeBootstrapParams(path string, params *bootapi.BootstrapResult) error { + path, err := filepath.Abs(path) + if err != nil { + return err + } + + data, err := json.Marshal(¶ms) + if err != nil { + return err + } + + f, err := atomicfile.New(path, 0o644) + if err != nil { + return err + } + + _, err = f.Write(data) + if err != nil { + f.Cancel() + return err + } + + return f.Close() +} + +func readBootstrapParams(path string) (*bootapi.BootstrapResult, error) { + path, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + return parseStartResponse(data) +} + +// makeConnection creates a new TTRPC or GRPC connection using the address and +// protocol from params. Legacy plain-string or JSON bootstrap responses are +// normalized by parseStartResponse before calling this function. +// The dialer parameter controls connection behavior: use AnonDialer for newly +// started shims (retries if pipe doesn't exist yet) or AnonReconnectDialer for +// reconnecting to already-running shims (fails fast if pipe is missing). +func makeConnection(ctx context.Context, id string, params *bootapi.BootstrapResult, onClose func(), dialer func(string, time.Duration) (net.Conn, error)) (_ io.Closer, retErr error) { + log.G(ctx).WithFields(log.Fields{ + "address": params.Address, + "protocol": params.Protocol, + "version": params.Version, + }).Infof("connecting to shim %s", id) + + switch strings.ToLower(params.Protocol) { + case "ttrpc": + conn, err := client.Connect(params.Address, dialer) + if err != nil { + return nil, fmt.Errorf("failed to create TTRPC connection: %w", err) + } + defer func() { + if retErr != nil { + conn.Close() + } + }() + + return ttrpc.NewClient( + conn, + ttrpc.WithOnClose(onClose), + ttrpc.WithUnaryClientInterceptor(otelttrpc.UnaryClientInterceptor()), + ), nil + case "grpc": + gopts := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + } + return grpcDialContext(params.Address, onClose, gopts...) + default: + return nil, fmt.Errorf("unexpected protocol: %q", params.Protocol) + } +} + +// grpcDialContext and the underlying grpcConn type exist solely +// so we can have something similar to ttrpc.WithOnClose to have +// a callback run when the connection is severed or explicitly closed. +func grpcDialContext( + address string, + onClose func(), + gopts ...grpc.DialOption, +) (*grpcConn, error) { + // If grpc.WithBlock is specified in gopts this causes the connection to block waiting for + // a connection regardless of if the socket exists or has a listener when Dial begins. This + // specific behavior of WithBlock is mostly undesirable for shims, as if the socket isn't + // there when we go to load/connect there's likely an issue. However, getting rid of WithBlock is + // also undesirable as we don't want the background connection behavior, we want to ensure + // a connection before moving on. To bring this in line with the ttrpc connection behavior + // lets do an initial dial to ensure the shims socket is actually available. stat wouldn't suffice + // here as if the shim exited unexpectedly its socket may still be on the filesystem, but it'd return + // ECONNREFUSED which grpc.DialContext will happily trudge along through for the full timeout. + // + // This is especially helpful on restart of containerd as if the shim died while containerd + // was down, we end up waiting the full timeout. + conn, err := net.DialTimeout("unix", address, time.Second*10) + if err != nil { + return nil, err + } + conn.Close() + + target := dialer.DialAddress(address) + client, err := grpc.NewClient(target, gopts...) + if err != nil { + return nil, fmt.Errorf("failed to create GRPC connection: %w", err) + } + + done := make(chan struct{}) + go func() { + gctx := context.Background() + sourceState := connectivity.Ready + for { + if client.WaitForStateChange(gctx, sourceState) { + state := client.GetState() + if state == connectivity.Idle || state == connectivity.Shutdown { + break + } + // Could be transient failure. Lets see if we can get back to a working + // state. + log.G(gctx).WithFields(log.Fields{ + "state": state, + "addr": target, + }).Warn("shim grpc connection unexpected state") + sourceState = state + } + } + onClose() + close(done) + }() + + return &grpcConn{ + ClientConn: client, + onCloseDone: done, + }, nil +} + +type grpcConn struct { + *grpc.ClientConn + onCloseDone chan struct{} +} + +func (gc *grpcConn) UserOnCloseWait(ctx context.Context) error { + select { + case <-gc.onCloseDone: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +type shim struct { + bundle *Bundle + client any + address string + version int +} + +var _ ShimInstance = (*shim)(nil) +var _ clientVersionDowngrader = (*shim)(nil) + +// ID of the shim/task +func (s *shim) ID() string { + return s.bundle.ID +} + +func (s *shim) Endpoint() (string, int) { + return s.address, s.version +} + +func (s *shim) Downgrade() error { + if s.version >= CurrentShimVersion { + s.version-- + return nil + } + return fmt.Errorf("unable to downgrade because shim version (%d) is lower than CurrentShimVersion (%d)", + s.version, CurrentShimVersion) +} + +func (s *shim) Namespace() string { + return s.bundle.Namespace +} + +func (s *shim) Bundle() string { + return s.bundle.Path +} + +func (s *shim) Client() any { + return s.client +} + +// Close closes the underlying client connection. +func (s *shim) Close() error { + if ttrpcClient, ok := s.client.(*ttrpc.Client); ok { + return ttrpcClient.Close() + } + + if grpcClient, ok := s.client.(*grpcConn); ok { + return grpcClient.Close() + } + + return nil +} + +func (s *shim) Delete(ctx context.Context) error { + var result []error + + if ttrpcClient, ok := s.client.(*ttrpc.Client); ok { + if err := ttrpcClient.Close(); err != nil { + result = append(result, fmt.Errorf("failed to close ttrpc client: %w", err)) + } + + if err := ttrpcClient.UserOnCloseWait(ctx); err != nil { + result = append(result, fmt.Errorf("close wait error: %w", err)) + } + } + + if grpcClient, ok := s.client.(*grpcConn); ok { + if err := grpcClient.Close(); err != nil { + result = append(result, fmt.Errorf("failed to close grpc client: %w", err)) + } + + if err := grpcClient.UserOnCloseWait(ctx); err != nil { + result = append(result, fmt.Errorf("close wait error: %w", err)) + } + } + + if err := s.bundle.Delete(); err != nil { + log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to delete bundle") + result = append(result, fmt.Errorf("failed to delete bundle: %w", err)) + } + + return errors.Join(result...) +} + +var _ runtime.Task = &shimTask{} + +// shimTask wraps shim process and adds task service client for compatibility with existing shim manager. +type shimTask struct { + ShimInstance + task TaskServiceClient +} + +func newShimTask(shim ShimInstance) (*shimTask, error) { + _, version := shim.Endpoint() + taskClient, err := NewTaskClient(shim.Client(), version) + if err != nil { + return nil, err + } + + return &shimTask{ + ShimInstance: shim, + task: taskClient, + }, nil +} + +func (s *shimTask) Shutdown(ctx context.Context) error { + _, err := s.task.Shutdown(ctx, &task.ShutdownRequest{ + ID: s.ID(), + }) + if err != nil && !errors.Is(err, ttrpc.ErrClosed) { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) waitShutdown(ctx context.Context) error { + ctx, cancel := timeout.WithContext(ctx, shutdownTimeout) + defer cancel() + return s.Shutdown(ctx) +} + +// PID of the task +func (s *shimTask) PID(ctx context.Context) (uint32, error) { + response, err := s.task.Connect(ctx, &task.ConnectRequest{ + ID: s.ID(), + }) + if err != nil { + return 0, errgrpc.ToNative(err) + } + + return response.TaskPid, nil +} + +func (s *shimTask) delete(ctx context.Context, sandboxed bool, removeTask func(ctx context.Context, id string)) (*runtime.Exit, error) { + response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{ + ID: s.ID(), + }) + if shimErr != nil { + log.G(ctx).WithField("id", s.ID()).WithError(shimErr).Error("failed to delete task") + if !errors.Is(shimErr, ttrpc.ErrClosed) { + shimErr = errgrpc.ToNative(shimErr) + if !errdefs.IsNotFound(shimErr) { + return nil, shimErr + } + } + } + + // NOTE: If the shim has been killed and ttrpc connection has been + // closed, the shimErr will not be nil. For this case, the event + // subscriber, like moby/moby, might have received the exit or delete + // events. Just in case, we should allow ttrpc-callback-on-close to + // send the exit and delete events again. And the exit status will + // depend on result of shimV2.Delete. + // + // If not, the shim has been delivered the exit and delete events. + // So we should remove the record and prevent duplicate events from + // ttrpc-callback-on-close. + // + // TODO: It's hard to guarantee that the event is unique and sent only + // once. The moby/moby should not rely on that assumption that there is + // only one exit event. The moby/moby should handle the duplicate events. + // + // REF: https://github.com/containerd/containerd/issues/4769 + if shimErr == nil { + removeTask(ctx, s.ID()) + } + + const supportSandboxAPIVersion = 3 + if _, apiVer := s.ShimInstance.Endpoint(); apiVer < supportSandboxAPIVersion { + sandboxed = false + } + + // Don't shutdown sandbox as there may be other containers running. + // Let controller decide when to shutdown. + if !sandboxed { + if err := s.waitShutdown(ctx); err != nil { + // FIXME(fuweid): + // + // If the error is context canceled, should we use context.TODO() + // to wait for it? + log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to shutdown shim task and the shim might be leaked") + } + } + + if err := s.ShimInstance.Delete(ctx); err != nil { + log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to delete shim") + } + + // remove self from the runtime task list + // this seems dirty but it cleans up the API across runtimes, tasks, and the service + removeTask(ctx, s.ID()) + + if shimErr != nil { + return nil, shimErr + } + + return &runtime.Exit{ + Status: response.ExitStatus, + Timestamp: protobuf.FromTimestamp(response.ExitedAt), + Pid: response.Pid, + }, nil +} + +func (s *shimTask) Create(ctx context.Context, opts runtime.CreateOpts) (runtime.Task, error) { + topts := opts.TaskOptions + if topts == nil || topts.GetValue() == nil { + topts = opts.RuntimeOptions + } + request := &task.CreateTaskRequest{ + ID: s.ID(), + Bundle: s.Bundle(), + Stdin: opts.IO.Stdin, + Stdout: opts.IO.Stdout, + Stderr: opts.IO.Stderr, + Terminal: opts.IO.Terminal, + Checkpoint: opts.Checkpoint, + Options: typeurl.MarshalProto(topts), + } + for _, m := range opts.Rootfs { + request.Rootfs = append(request.Rootfs, &types.Mount{ + Type: m.Type, + Source: m.Source, + Target: m.Target, + Options: m.Options, + }) + } + + _, err := s.task.Create(ctx, request) + if err != nil { + return nil, errgrpc.ToNative(err) + } + + if opts.RestoreFromPath { + // Unpack rootfs-diff.tar if it exists. + // This needs to happen between the 'Create()' from above and before the 'Start()' from below. + rootfsDiff := filepath.Join(opts.Checkpoint, "..", crmetadata.RootFsDiffTar) + + _, err = os.Stat(rootfsDiff) + if err == nil { + rootfsDiffTar, err := os.Open(rootfsDiff) + if err != nil { + return nil, fmt.Errorf("failed to open rootfs-diff archive %s for import: %w", rootfsDiffTar.Name(), err) + } + defer func(f *os.File) { + if err := f.Close(); err != nil { + log.G(ctx).Errorf("Unable to close file %s: %q", f.Name(), err) + } + }(rootfsDiffTar) + + decompressed, err := compression.DecompressStream(rootfsDiffTar) + if err != nil { + return nil, fmt.Errorf("failed to decompress archive %s for import: %w", rootfsDiffTar.Name(), err) + } + + rootfs := filepath.Join(s.Bundle(), "rootfs") + _, err = archive.Apply( + ctx, + rootfs, + decompressed, + ) + + if err != nil { + return nil, fmt.Errorf("unpacking of rootfs-diff archive %s into %s failed: %w", rootfsDiffTar.Name(), rootfs, err) + } + log.G(ctx).Debugf("Unpacked checkpoint in %s", rootfs) + } + // (adrianreber): This is unclear to me. But it works (and it is necessary). + // This is probably connected to my misunderstanding why + // restoring a container goes through Create(). + log.G(ctx).Infof("About to start with opts.Checkpoint %s", opts.Checkpoint) + err = s.Start(ctx) + if err != nil { + return nil, err + } + } + + return s, nil +} + +func (s *shimTask) Pause(ctx context.Context) error { + if _, err := s.task.Pause(ctx, &task.PauseRequest{ + ID: s.ID(), + }); err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) Resume(ctx context.Context) error { + if _, err := s.task.Resume(ctx, &task.ResumeRequest{ + ID: s.ID(), + }); err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) Start(ctx context.Context) error { + _, err := s.task.Start(ctx, &task.StartRequest{ + ID: s.ID(), + }) + if err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) Kill(ctx context.Context, signal uint32, all bool) error { + if _, err := s.task.Kill(ctx, &task.KillRequest{ + ID: s.ID(), + Signal: signal, + All: all, + }); err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.ExecProcess, error) { + if err := identifiers.Validate(id); err != nil { + return nil, fmt.Errorf("invalid exec id %s: %w", id, err) + } + request := &task.ExecProcessRequest{ + ID: s.ID(), + ExecID: id, + Stdin: opts.IO.Stdin, + Stdout: opts.IO.Stdout, + Stderr: opts.IO.Stderr, + Terminal: opts.IO.Terminal, + Spec: opts.Spec, + } + if _, err := s.task.Exec(ctx, request); err != nil { + return nil, errgrpc.ToNative(err) + } + return &process{ + id: id, + shim: s, + }, nil +} + +func (s *shimTask) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) { + resp, err := s.task.Pids(ctx, &task.PidsRequest{ + ID: s.ID(), + }) + if err != nil { + return nil, errgrpc.ToNative(err) + } + var processList []runtime.ProcessInfo + for _, p := range resp.Processes { + processList = append(processList, runtime.ProcessInfo{ + Pid: p.Pid, + Info: p.Info, + }) + } + return processList, nil +} + +func (s *shimTask) ResizePty(ctx context.Context, size runtime.ConsoleSize) error { + _, err := s.task.ResizePty(ctx, &task.ResizePtyRequest{ + ID: s.ID(), + Width: size.Width, + Height: size.Height, + }) + if err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) CloseIO(ctx context.Context) error { + _, err := s.task.CloseIO(ctx, &task.CloseIORequest{ + ID: s.ID(), + Stdin: true, + }) + if err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) Wait(ctx context.Context) (*runtime.Exit, error) { + taskPid, err := s.PID(ctx) + if err != nil { + return nil, err + } + response, err := s.task.Wait(ctx, &task.WaitRequest{ + ID: s.ID(), + }) + if err != nil { + return nil, errgrpc.ToNative(err) + } + return &runtime.Exit{ + Pid: taskPid, + Timestamp: protobuf.FromTimestamp(response.ExitedAt), + Status: response.ExitStatus, + }, nil +} + +func (s *shimTask) Checkpoint(ctx context.Context, path string, options *ptypes.Any) error { + request := &task.CheckpointTaskRequest{ + ID: s.ID(), + Path: path, + Options: options, + } + if _, err := s.task.Checkpoint(ctx, request); err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) Update(ctx context.Context, resources *ptypes.Any, annotations map[string]string) error { + if _, err := s.task.Update(ctx, &task.UpdateTaskRequest{ + ID: s.ID(), + Resources: resources, + Annotations: annotations, + }); err != nil { + return errgrpc.ToNative(err) + } + return nil +} + +func (s *shimTask) Stats(ctx context.Context) (*ptypes.Any, error) { + response, err := s.task.Stats(ctx, &task.StatsRequest{ + ID: s.ID(), + }) + if err != nil { + return nil, errgrpc.ToNative(err) + } + return response.Stats, nil +} + +func (s *shimTask) Process(ctx context.Context, id string) (runtime.ExecProcess, error) { + p := &process{ + id: id, + shim: s, + } + if _, err := p.State(ctx); err != nil { + return nil, err + } + return p, nil +} + +func (s *shimTask) State(ctx context.Context) (runtime.State, error) { + response, err := s.task.State(ctx, &task.StateRequest{ + ID: s.ID(), + }) + if err != nil { + if errdefs.IsDeadlineExceeded(err) { + return runtime.State{}, err + } + if !errors.Is(err, ttrpc.ErrClosed) { + return runtime.State{}, errgrpc.ToNative(err) + } + return runtime.State{}, errdefs.ErrNotFound + } + return runtime.State{ + Pid: response.Pid, + Status: statusFromProto(response.Status), + Stdin: response.Stdin, + Stdout: response.Stdout, + Stderr: response.Stderr, + Terminal: response.Terminal, + ExitStatus: response.ExitStatus, + ExitedAt: protobuf.FromTimestamp(response.ExitedAt), + }, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_load.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_load.go new file mode 100644 index 0000000000..4d8e6217d9 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_load.go @@ -0,0 +1,274 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/containerd/errdefs" + "github.com/containerd/log" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/timeout" + "golang.org/x/sync/errgroup" +) + +// LoadExistingShims loads existing shims from the path specified by stateDir +// rootDir is for cleaning up the unused paths of removed shims. +func (m *ShimManager) LoadExistingShims(ctx context.Context, stateDir string, rootDir string) error { + nsDirs, err := os.ReadDir(stateDir) + if err != nil { + return err + } + for _, nsd := range nsDirs { + if !nsd.IsDir() { + continue + } + ns := nsd.Name() + // skip hidden directories + if len(ns) > 0 && ns[0] == '.' { + continue + } + log.G(ctx).WithField("namespace", ns).Debug("loading tasks in namespace") + if err := m.loadShims(namespaces.WithNamespace(ctx, ns), stateDir); err != nil { + log.G(ctx).WithField("namespace", ns).WithError(err).Error("loading tasks in namespace") + continue + } + if err := m.cleanupWorkDirs(namespaces.WithNamespace(ctx, ns), rootDir); err != nil { + log.G(ctx).WithField("namespace", ns).WithError(err).Error("cleanup working directory in namespace") + continue + } + } + return nil +} + +func (m *ShimManager) loadShims(ctx context.Context, stateDir string) error { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + ctx = log.WithLogger(ctx, log.G(ctx).WithField("namespace", ns)) + + shimDirs, err := os.ReadDir(filepath.Join(stateDir, ns)) + if err != nil { + return err + } + eg, ctx2 := errgroup.WithContext(ctx) + eg.SetLimit(runtime.GOMAXPROCS(0)) + var errLoad error + for _, sd := range shimDirs { + if !sd.IsDir() { + continue + } + id := sd.Name() + // skip hidden directories + if len(id) > 0 && id[0] == '.' { + continue + } + bundle, err := LoadBundle(ctx, stateDir, id) + if err != nil { + errLoad = err + // fine to return error here, it is a programmer error if the context + // does not have a namespace + break + } + eg.Go(func() error { + // fast path + f, err := os.Open(bundle.Path) + if err != nil { + bundle.Delete() + log.G(ctx2).WithError(err).Errorf("fast path read bundle path for %s", bundle.Path) + return nil + } + + bf, err := f.Readdirnames(-1) + f.Close() + if err != nil { + bundle.Delete() + log.G(ctx2).WithError(err).Errorf("fast path read bundle path for %s", bundle.Path) + return nil + } + if len(bf) == 0 { + bundle.Delete() + return nil + } + if err := m.loadShim(ctx2, bundle); err != nil { + log.G(ctx2).WithError(err).Errorf("failed to load shim %s", bundle.Path) + bundle.Delete() + return nil + } + return nil + }) + } + _ = eg.Wait() + return errLoad +} + +func (m *ShimManager) loadShim(ctx context.Context, bundle *Bundle) error { + var ( + runtime string + id = bundle.ID + ) + + // If we're on 1.6+ and specified custom path to the runtime binary, path will be saved in 'shim-binary-path' file. + if data, err := os.ReadFile(filepath.Join(bundle.Path, "shim-binary-path")); err == nil { + runtime = string(data) + } else if err != nil && !os.IsNotExist(err) { + log.G(ctx).WithError(err).Error("failed to read `runtime` path from bundle") + } + + // Query runtime name from metadata store + if runtime == "" { + container, err := m.containers.Get(ctx, id) + if err != nil { + log.G(ctx).WithError(err).Errorf("loading container %s", id) + if err := mount.UnmountRecursive(filepath.Join(bundle.Path, "rootfs"), 0); err != nil { + log.G(ctx).WithError(err).Errorf("failed to unmount of rootfs %s", id) + } + return err + } + runtime = container.Runtime.Name + } + + runtime, err := m.resolveRuntimePath(runtime) + if err != nil { + bundle.Delete() + + return fmt.Errorf("failed to resolve runtime path: %w", err) + } + + binaryCall := shimBinary(bundle, + shimBinaryConfig{ + runtime: runtime, + address: m.containerdAddress, + ttrpcAddress: m.containerdTTRPCAddress, + socketDir: m.socketDir, + env: m.env, + }) + // TODO: It seems we can only call loadShim here if it is a sandbox shim? + shim, err := loadShimTask(ctx, bundle, func() { + log.G(ctx).WithField("id", id).Info("shim disconnected") + + cleanupAfterDeadShim(context.WithoutCancel(ctx), id, m.shims, m.events, binaryCall) + // Remove self from the runtime task list. + m.shims.Delete(ctx, id) + }) + if err != nil { + cleanupAfterDeadShim(ctx, id, m.shims, m.events, binaryCall) + return fmt.Errorf("unable to load shim %q: %w", id, err) + } + + // There are 3 possibilities for the loaded shim here: + // 1. It could be a shim that is running a task. + // 2. It could be a sandbox shim. + // 3. Or it could be a shim that was created for running a task but + // something happened (probably a containerd crash) and the task was never + // created. This shim process should be cleaned up here. Look at + // containerd/containerd#6860 for further details. + + _, sgetErr := m.sandboxStore.Get(ctx, id) + pInfo, pidErr := shim.Pids(ctx) + if sgetErr != nil && errors.Is(sgetErr, errdefs.ErrNotFound) && (len(pInfo) == 0 || errors.Is(pidErr, errdefs.ErrNotFound)) { + log.G(ctx).WithField("id", id).Info("cleaning leaked shim process") + // We are unable to get Pids from the shim and it's not a sandbox + // shim. We should clean it up her. + // No need to do anything for removeTask since we never added this shim. + shim.delete(ctx, false, func(ctx context.Context, id string) {}) + } else { + m.shims.Add(ctx, shim.ShimInstance) + } + return nil +} + +func loadShimTask(ctx context.Context, bundle *Bundle, onClose func()) (_ *shimTask, retErr error) { + shim, err := loadShim(ctx, bundle, onClose) + if err != nil { + return nil, err + } + // Check connectivity, TaskService is the only required service, so create a temp one to check connection. + s, err := newShimTask(shim) + if err != nil { + return nil, err + } + + ctx, cancel := timeout.WithContext(ctx, loadTimeout) + defer cancel() + + if _, err := s.PID(ctx); err != nil { + if !errdefs.IsNotImplemented(err) { + return nil, err + } + + downgrader, ok := shim.(clientVersionDowngrader) + if ok { + if derr := downgrader.Downgrade(); derr == nil { + log.G(ctx).WithError(err).WithField("id", shim.ID()). + Warning("failed to call task.PID, downgrading client API version to try again") + + s, err = newShimTask(shim) + if err != nil { + return nil, fmt.Errorf("failed to create shim task after downgrading: %w", err) + } + _, err = s.PID(ctx) + } + } + if err != nil { + return nil, err + } + } + return s, nil +} + +func (m *ShimManager) cleanupWorkDirs(ctx context.Context, rootDir string) error { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + + f, err := os.Open(filepath.Join(rootDir, ns)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + defer f.Close() + + dirs, err := f.Readdirnames(-1) + if err != nil { + return err + } + + for _, dir := range dirs { + // if the task was not loaded, cleanup and empty working directory + // this can happen on a reboot where /run for the bundle state is cleaned up + // but that persistent working dir is left + if _, err := m.shims.Get(ctx, dir); err != nil { + path := filepath.Join(rootDir, ns, dir) + if err := os.RemoveAll(path); err != nil { + log.G(ctx).WithError(err).Errorf("cleanup working dir %s", path) + } + } + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_manager.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_manager.go new file mode 100644 index 0000000000..7cf9efff95 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_manager.go @@ -0,0 +1,512 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + + apitypes "github.com/containerd/containerd/api/types" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/typeurl/v2" + + bootapi "github.com/containerd/containerd/api/runtime/bootstrap/v1" + "github.com/containerd/containerd/v2/core/containers" + "github.com/containerd/containerd/v2/core/events/exchange" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/core/sandbox" + "github.com/containerd/containerd/v2/pkg/namespaces" + shimbinary "github.com/containerd/containerd/v2/pkg/shim" + "github.com/containerd/containerd/v2/pkg/timeout" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/version" +) + +// ShimConfig for the shim +type ShimConfig struct { + // Env is environment variables added to shim processes + Env []string `toml:"env"` + + // SocketDir is the directory to place shim sockets. The path must be + // short enough to fit within the platform's unix socket path limit. + // Defaults: + // Linux (UID 0): /run/containerd/s + // Linux (UID >0): /run/$UID/containerd/s or /tmp/containerd-s-$(UID) + SocketDir string `toml:"socket_dir"` +} + +func init() { + // ShimManager is not only for TaskManager, + // the "shim" sandbox controller also use it to manage shims, + // so we make it an independent plugin + registry.Register(&plugin.Registration{ + Type: plugins.ShimPlugin, + ID: "manager", + Requires: []plugin.Type{ + plugins.EventPlugin, + plugins.MetadataPlugin, + }, + Config: &ShimConfig{}, + InitFn: func(ic *plugin.InitContext) (any, error) { + config := ic.Config.(*ShimConfig) + + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + ep, err := ic.GetByID(plugins.EventPlugin, "exchange") + if err != nil { + return nil, err + } + events := ep.(*exchange.Exchange) + cs := metadata.NewContainerStore(m.(*metadata.DB)) + ss := metadata.NewSandboxStore(m.(*metadata.DB)) + + // Allow configurable directory + if config.SocketDir != "" { + if !filepath.IsAbs(config.SocketDir) { + return nil, fmt.Errorf("socket_dir must be an absolute path: %q", config.SocketDir) + } + config.SocketDir = filepath.Clean(config.SocketDir) + if len(config.SocketDir) > maxSocketDirLen { + return nil, fmt.Errorf("socket_dir length must be no longer than %d characters", maxSocketDirLen) + } + } else { + config.SocketDir = defaultSocketDir() + if config.SocketDir == "" { + return nil, fmt.Errorf("failed to find a suitable socket directory for shim, please configure one") + } + } + + return NewShimManager(&ManagerConfig{ + Address: ic.Properties[plugins.PropertyGRPCAddress], + TTRPCAddress: ic.Properties[plugins.PropertyTTRPCAddress], + SocketDir: config.SocketDir, + Events: events, + Store: cs, + ShimEnv: config.Env, + SandboxStore: ss, + }) + }, + ConfigMigration: func(ctx context.Context, configVersion int, pluginConfigs map[string]any) error { + // Migrate configurations from io.containerd.runtime.v2.task + // if the configVersion >= 3 please make sure the config is under io.containerd.shim.v1.manager. + if configVersion >= version.ConfigVersion { + return nil + } + const originalPluginName = string(plugins.RuntimePluginV2) + ".task" + original, ok := pluginConfigs[originalPluginName] + if !ok { + return nil + } + src := original.(map[string]any) + dest := map[string]any{} + + if v, ok := src["sched_core"]; ok { + if schedCore, ok := v.(bool); schedCore { + dest["env"] = []string{"SCHED_CORE=1"} + } else if !ok { + log.G(ctx).Warnf("skipping migration for non-boolean 'sched_core' value %v", v) + } + + delete(src, "sched_core") + } + + const newPluginName = string(plugins.ShimPlugin) + ".manager" + pluginConfigs[originalPluginName] = src + pluginConfigs[newPluginName] = dest + return nil + }, + }) +} + +type ManagerConfig struct { + Store containers.Store + Events *exchange.Exchange + Address string + TTRPCAddress string + SocketDir string + SandboxStore sandbox.Store + ShimEnv []string +} + +// NewShimManager creates a manager for v2 shims +func NewShimManager(config *ManagerConfig) (*ShimManager, error) { + m := &ShimManager{ + containerdAddress: config.Address, + containerdTTRPCAddress: config.TTRPCAddress, + socketDir: config.SocketDir, + shims: runtime.NewNSMap[ShimInstance](), + events: config.Events, + containers: config.Store, + env: config.ShimEnv, + sandboxStore: config.SandboxStore, + } + + return m, nil +} + +// ShimManager manages currently running shim processes. +// It is mainly responsible for launching new shims and for proper shutdown and cleanup of existing instances. +// The manager is unaware of the underlying services shim provides and lets higher level services consume them, +// but don't care about lifecycle management. +type ShimManager struct { + containerdAddress string + containerdTTRPCAddress string + env []string + shims *runtime.NSMap[ShimInstance] + events *exchange.Exchange + containers containers.Store + socketDir string + // runtimePaths is a cache of `runtime names` -> `resolved fs path` + runtimePaths sync.Map + sandboxStore sandbox.Store + // shimInfos is a cache of the shim info + shimInfos sync.Map +} + +// ID of the shim manager +func (m *ShimManager) ID() string { + return plugins.ShimPlugin.String() + ".manager" +} + +// Start launches a new shim instance +func (m *ShimManager) Start(ctx context.Context, id string, bundle *Bundle, opts runtime.CreateOpts) (_ ShimInstance, retErr error) { + shouldInvokeShimBinary := false + + var params = &bootapi.BootstrapResult{} + if opts.SandboxID != "" { + _, sbErr := m.sandboxStore.Get(ctx, opts.SandboxID) + if sbErr != nil { + if !errors.Is(sbErr, errdefs.ErrNotFound) { + return nil, sbErr + } + + log.G(ctx).WithField("id", id).Warningf("sandbox (id=%s) not found, maybe created from v1.x", opts.SandboxID) + // NOTE: If sandbox container, like pause, is created by + // v1.6.x or v1.7.x, the shim may be not able to group + // multiple containers. We should invoke shim binary and + // establish new connection based on returned address. + shouldInvokeShimBinary = true + } else { + if opts.Address != "" { + // The address returned from sandbox controller should + // be in the form like ttrpc+unix:// or grpc+vsock://:, + // we should get the protocol from the url first. + protocol, address, ok := strings.Cut(opts.Address, "+") + if !ok { + return nil, fmt.Errorf("the scheme of sandbox address should be in " + + " the form of +, i.e. ttrpc+unix or grpc+vsock") + } + params = &bootapi.BootstrapResult{ + Version: int32(opts.Version), + Protocol: protocol, + Address: address, + } + } else { + process, err := m.Get(ctx, opts.SandboxID) + if err != nil { + return nil, fmt.Errorf("can't find shim for sandbox %s: %w", opts.SandboxID, err) + } + + p, err := restoreBootstrapParams(process.Bundle()) + if err != nil { + return nil, fmt.Errorf("failed to get bootstrap "+ + "params of sandbox %s: %w", opts.SandboxID, err) + } + params = p + } + } + } + // Even though one shim can be able to group multiple containers, + // it doesn't mean it supports sandbox API. The old shim implementation + // still requires containerd to invoke `shim delete` to cleanup + // container's resource when each container exits. So, if the + // shim version is not higher than 3, we should fallback to invoke + // shim binary. + // + // NOTE: The shim version indicates that the shim supports streaming I/O. + // It's rolled out together with the sandbox API and can be used + // to determine whether we should invoke the shim binary. + const supportSandboxAPIVersion = 3 + if params.Version < supportSandboxAPIVersion { + shouldInvokeShimBinary = true + } + + if !shouldInvokeShimBinary { + // Write sandbox ID this task belongs to. + if err := os.WriteFile(filepath.Join(bundle.Path, "sandbox"), []byte(opts.SandboxID), 0600); err != nil { + return nil, err + } + + if err := writeBootstrapParams(filepath.Join(bundle.Path, "bootstrap.json"), params); err != nil { + return nil, fmt.Errorf("failed to write bootstrap.json for bundle %s: %w", bundle.Path, err) + } + + shim, err := loadShim(ctx, bundle, func() {}) + if err != nil { + return nil, fmt.Errorf("failed to load sandbox task %q: %w", opts.SandboxID, err) + } + + if err := m.shims.Add(ctx, shim); err != nil { + return nil, err + } + + return shim, nil + } + + shim, err := m.startShim(ctx, bundle, id, opts) + if err != nil { + return nil, err + } + defer func() { + if retErr != nil { + m.cleanupShim(ctx, shim) + } + }() + + if err := m.shims.Add(ctx, shim); err != nil { + return nil, fmt.Errorf("failed to add task: %w", err) + } + + return shim, nil +} + +func (m *ShimManager) startShim(ctx context.Context, bundle *Bundle, id string, opts runtime.CreateOpts) (*shim, error) { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + ctx = log.WithLogger(ctx, log.G(ctx).WithField("namespace", ns)) + + topts := opts.TaskOptions + if topts == nil || topts.GetValue() == nil { + topts = opts.RuntimeOptions + } + + runtimePath, err := m.resolveRuntimePath(opts.Runtime) + if err != nil { + return nil, fmt.Errorf("failed to resolve runtime path: %w", err) + } + + b := shimBinary(bundle, shimBinaryConfig{ + runtime: runtimePath, + address: m.containerdAddress, + ttrpcAddress: m.containerdTTRPCAddress, + socketDir: m.socketDir, + env: m.env, + }) + shim, err := b.Start(ctx, typeurl.MarshalProto(topts), func() { + log.G(ctx).WithField("id", id).Info("shim disconnected") + + cleanupAfterDeadShim(context.WithoutCancel(ctx), id, m.shims, m.events, b) + // Remove self from the runtime task list. Even though the cleanupAfterDeadShim() + // would publish taskExit event, but the shim.Delete() would always failed with ttrpc + // disconnect and there is no chance to remove this dead task from runtime task lists. + // Thus it's better to delete it here. + m.shims.Delete(ctx, id) + }) + if err != nil { + return nil, fmt.Errorf("start failed: %w", err) + } + + return shim, nil +} + +// restoreBootstrapParams reads bootstrap.json to restore shim configuration. +// If its an old shim, this will perform migration - read address file and write default bootstrap +// configuration (version = 2, protocol = ttrpc, and address). +func restoreBootstrapParams(bundlePath string) (*bootapi.BootstrapResult, error) { + filePath := filepath.Join(bundlePath, "bootstrap.json") + + // Read bootstrap.json if exists + if _, err := os.Stat(filePath); err == nil { + return readBootstrapParams(filePath) + } else if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("failed to stat %s: %w", filePath, err) + } + + // File not found, likely its an older shim. Try migrate. + + address, err := shimbinary.ReadAddress(filepath.Join(bundlePath, "address")) + if err != nil { + return nil, fmt.Errorf("unable to migrate shim: failed to get socket address for bundle %s: %w", bundlePath, err) + } + + params := bootapi.BootstrapResult{ + Version: 2, + Address: address, + Protocol: "ttrpc", + } + + if err := writeBootstrapParams(filePath, ¶ms); err != nil { + return nil, fmt.Errorf("unable to migrate: failed to write bootstrap.json file: %w", err) + } + + return ¶ms, nil +} + +func (m *ShimManager) resolveRuntimePath(runtime string) (string, error) { + if runtime == "" { + return "", fmt.Errorf("no runtime name") + } + + // Custom path to runtime binary + if filepath.IsAbs(runtime) { + // Make sure it exists before returning ok + if _, err := os.Stat(runtime); err != nil { + return "", fmt.Errorf("invalid custom binary path: %w", err) + } + + return runtime, nil + } + + // Check if relative path to runtime binary provided + if strings.Contains(runtime, "/") { + return "", fmt.Errorf("invalid runtime name %s, correct runtime name should be either format like `io.containerd.runc.v2` or a full path to the binary", runtime) + } + + // Preserve existing logic and resolve runtime path from runtime name. + + name := shimbinary.BinaryName(runtime) + if name == "" { + return "", fmt.Errorf("invalid runtime name %s, correct runtime name should be either format like `io.containerd.runc.v2` or a full path to the binary", runtime) + } + + if path, ok := m.runtimePaths.Load(name); ok { + return path.(string), nil + } + + var ( + cmdPath string + lerr error + ) + + binaryPath := shimbinary.BinaryPath(runtime) + if _, serr := os.Stat(binaryPath); serr == nil { + cmdPath = binaryPath + } + + if cmdPath == "" { + if cmdPath, lerr = exec.LookPath(name); lerr != nil { + if eerr, ok := lerr.(*exec.Error); ok { + if eerr.Err == exec.ErrNotFound { + self, err := os.Executable() + if err != nil { + return "", err + } + + // Match the calling binaries (containerd) path and see + // if they are side by side. If so, execute the shim + // found there. + testPath := filepath.Join(filepath.Dir(self), name) + if _, serr := os.Stat(testPath); serr == nil { + cmdPath = testPath + } + if cmdPath == "" { + return "", fmt.Errorf("runtime %q binary not installed %q: %w", runtime, name, os.ErrNotExist) + } + } + } + } + } + + cmdPath, err := filepath.Abs(cmdPath) + if err != nil { + return "", err + } + + if path, ok := m.runtimePaths.LoadOrStore(name, cmdPath); ok { + // We didn't store cmdPath we loaded an already cached value. Use it. + cmdPath = path.(string) + } + + return cmdPath, nil +} + +// cleanupShim attempts to properly delete and cleanup shim after error +func (m *ShimManager) cleanupShim(ctx context.Context, shim *shim) { + dctx, cancel := timeout.WithContext(context.WithoutCancel(ctx), cleanupTimeout) + defer cancel() + + _ = shim.Delete(dctx) + m.shims.Delete(dctx, shim.ID()) +} + +func (m *ShimManager) Get(ctx context.Context, id string) (ShimInstance, error) { + return m.shims.Get(ctx, id) +} + +// Delete a runtime task +func (m *ShimManager) Delete(ctx context.Context, id string) error { + shim, err := m.shims.Get(ctx, id) + if err != nil { + return err + } + + err = shim.Delete(ctx) + m.shims.Delete(ctx, id) + + return err +} + +type shimInfo struct { + handledMounts []string +} + +func (m *ShimManager) loadShimInfo(ctx context.Context, shim string) (*shimInfo, error) { + if i, ok := m.shimInfos.Load(shim); ok { + return i.(*shimInfo), nil + } + // Avoid fetching info for default shims with known behavior + if shim == "io.containerd.runc.v2" || shim == "io.containerd.runhcs.v1" { + sinfo := &shimInfo{} + m.shimInfos.Store(shim, sinfo) + return sinfo, nil + } + + rinfo, err := getRuntimeInfo(ctx, m, &apitypes.RuntimeRequest{RuntimePath: shim}) + if err != nil { + return nil, err + } + sinfo := &shimInfo{} + + if rinfo.Annotations != nil { + if v, ok := rinfo.Annotations[allowedMounts]; ok { + sinfo.handledMounts = strings.Split(v, ",") + } + } + + fields := log.Fields{} + for k, v := range rinfo.Annotations { + fields[k] = v + } + log.G(ctx).WithFields(fields).WithField("shim", shim).Debug("loaded shim info") + + m.shimInfos.Store(shim, sinfo) + return sinfo, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_unix.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_unix.go new file mode 100644 index 0000000000..f383ee70a8 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_unix.go @@ -0,0 +1,125 @@ +//go:build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "syscall" + "time" + + "github.com/containerd/containerd/v2/defaults" + "github.com/containerd/fifo" + "golang.org/x/sys/unix" +) + +func openShimLog(ctx context.Context, bundle *Bundle, _ func(string, time.Duration) (net.Conn, error)) (io.ReadCloser, error) { + return fifo.OpenFifo(ctx, filepath.Join(bundle.Path, "log"), unix.O_RDWR|unix.O_CREAT|unix.O_NONBLOCK, 0700) +} + +func checkCopyShimLogError(ctx context.Context, err error) error { + select { + case <-ctx.Done(): + if err == fifo.ErrReadClosed || errors.Is(err, os.ErrClosed) { + return nil + } + default: + } + return err +} + +// defaultSocketDir returns the directory used for shim unix sockets. +// The path is intentionally kept short and hardcoded rather than derived +// from the configured state directory, because unix socket paths are +// limited to 104-108 characters. If no suitable path can be found, +// an empty string is returned. +// +// The selection order is: +// 1. The default state directory (/run/containerd/s) — try to create it, +// and if it already exists verify the current user owns it. +// 2. $XDG_RUNTIME_DIR/containerd/s — used if XDG_RUNTIME_DIR is set +// and owned by the current user. +// 3. /run//containerd/s — used if the directory /run/ +// exists and is owned by the current user. +// 4. /tmp/containerd-s- — created and ownership-verified as a last resort. +func defaultSocketDir() string { + defaultDir := filepath.Join(defaults.DefaultStateDir, "s") + uid := os.Geteuid() + if uid == 0 { + return defaultDir + } + + candidates := []string{defaultDir} + if xdgDir := os.Getenv("XDG_RUNTIME_DIR"); xdgDir != "" { + candidates = append(candidates, filepath.Join(xdgDir, "containerd", "s")) + } + candidates = append(candidates, + fmt.Sprintf("/run/%d/containerd/s", uid), + fmt.Sprintf("/tmp/containerd-s-%d", uid), + ) + + for _, dir := range candidates { + if len(dir) <= maxSocketDirLen { + if ensureSocketDir(dir, uid) { + return dir + } + } + } + + // All candidates failed, return empty string and let caller handle + return "" +} + +// ensureSocketDir attempts to create dir with mode 0700, verifies it is +// owned by uid, and corrects the permissions to 0700 if they differ. +// Returns true if the directory is ready for use. +func ensureSocketDir(dir string, uid int) bool { + if err := os.MkdirAll(dir, 0700); err != nil { + return false + } + st, err := os.Lstat(dir) + if err != nil { + return false + } + if !st.IsDir() { + return false + } + sys := st.Sys() + if sys == nil { + return false + } + stat, ok := sys.(*syscall.Stat_t) + if !ok { + return false + } + if int(stat.Uid) != uid { + return false + } + if st.Mode().Perm() != 0700 { + if err := os.Chmod(dir, 0700); err != nil { + return false + } + } + return true +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_windows.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_windows.go new file mode 100644 index 0000000000..bf7aeb510d --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/shim_windows.go @@ -0,0 +1,99 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "sync" + "time" + + "github.com/containerd/containerd/v2/defaults" + "github.com/containerd/containerd/v2/pkg/namespaces" +) + +type deferredPipeConnection struct { + ctx context.Context + + wg sync.WaitGroup + once sync.Once + + c net.Conn + conerr error +} + +func (dpc *deferredPipeConnection) Read(p []byte) (n int, err error) { + dpc.wg.Wait() + if dpc.c == nil { + return 0, dpc.conerr + } + return dpc.c.Read(p) +} +func (dpc *deferredPipeConnection) Close() error { + var err error + dpc.once.Do(func() { + dpc.wg.Wait() + if dpc.c != nil { + err = dpc.c.Close() + } else if dpc.conerr != nil { + err = dpc.conerr + } + }) + return err +} + +// openShimLog on Windows acts as the client of the log pipe. In this way the +// containerd daemon can reconnect to the shim log stream if it is restarted. +func openShimLog(ctx context.Context, bundle *Bundle, dialer func(string, time.Duration) (net.Conn, error)) (io.ReadCloser, error) { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + dpc := &deferredPipeConnection{ + ctx: ctx, + } + dpc.wg.Go(func() { + c, conerr := dialer( + fmt.Sprintf("\\\\.\\pipe\\containerd-shim-%s-%s-log", ns, bundle.ID), + time.Second*10, + ) + if conerr != nil { + dpc.conerr = fmt.Errorf("failed to connect to shim log: %w", conerr) + } + dpc.c = c + }) + return dpc, nil +} + +func checkCopyShimLogError(ctx context.Context, err error) error { + // When using a multi-container shim the 2nd to Nth container in the + // shim will not have a separate log pipe. Ignore the failure log + // message here when the shim connect times out. + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err +} + +func defaultSocketDir() string { + return filepath.Join(defaults.DefaultStateDir, "s") +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_linux.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_linux.go new file mode 100644 index 0000000000..720dd45314 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_linux.go @@ -0,0 +1,25 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +// maxSocketDirLen is the maximum length of the socket directory path. +// Unix socket paths are limited to 108 characters on Linux, minus a +// null terminator gives 107 usable characters. The socket path passed +// to the kernel is directory + "/" (1) + sha256 hash (64); the +// "unix://" scheme is stripped before net.Listen and does not count. +// So the directory can be at most 107 - 1 - 64 = 42. +const maxSocketDirLen = 42 diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_unix.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_unix.go new file mode 100644 index 0000000000..6f2f8c4c0f --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_unix.go @@ -0,0 +1,27 @@ +//go:build !windows && !linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +// maxSocketDirLen is the maximum length of the socket directory path. +// Unix socket paths are limited to 104 characters on macOS, minus a +// null terminator gives 103 usable characters. The socket path passed +// to the kernel is directory + "/" (1) + sha256 hash (64); the +// "unix://" scheme is stripped before net.Listen and does not count. +// So the directory can be at most 103 - 1 - 64 = 38. +const maxSocketDirLen = 38 diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_windows.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_windows.go new file mode 100644 index 0000000000..d9d83667bf --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/socket_windows.go @@ -0,0 +1,25 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +// maxSocketDirLen is the maximum length of the socket directory path. +// Windows uses the same limit as Linux: 108 characters minus a null +// terminator gives 107 usable characters. The socket path passed to +// the kernel is directory + "/" (1) + sha256 hash (64); the "unix://" +// scheme is stripped before net.Listen and does not count. +// So the directory can be at most 107 - 1 - 64 = 42. +const maxSocketDirLen = 42 diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager.go new file mode 100644 index 0000000000..e5cf9f7d40 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager.go @@ -0,0 +1,453 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + "os/exec" + goruntime "runtime" + "slices" + "strings" + + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/typeurl/v2" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-spec/specs-go/features" + + apitypes "github.com/containerd/containerd/api/types" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/pkg/protobuf/proto" + "github.com/containerd/containerd/v2/pkg/timeout" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services/warning" +) + +const ( + // allowedMounts are the custom mount types allowed by the runtime. These + // types should not be handled by the mount manager. + // To include prepare mount types, use "/*" suffix, such as "format/*" + allowedMounts = "containerd.io/runtime-allow-mounts" +) + +// TaskConfig for the runtime task manager +type TaskConfig struct { + // Supported platforms + Platforms []string `toml:"platforms"` +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.RuntimePluginV2, + ID: "task", + Requires: []plugin.Type{ + plugins.ShimPlugin, + plugins.MountManagerPlugin, + plugins.WarningPlugin, + }, + Config: &TaskConfig{ + Platforms: defaultPlatforms(), + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + config := ic.Config.(*TaskConfig) + + supportedPlatforms, err := platforms.ParseAll(config.Platforms) + if err != nil { + return nil, err + } + ic.Meta.Platforms = supportedPlatforms + if ic.Meta.Exports == nil { + ic.Meta.Exports = make(map[string]string, 1) + } + ic.Meta.Exports["log-uri-schemes"] = strings.Join(supportedLogURISchemes(), ",") + + shimManagerI, err := ic.GetSingle(plugins.ShimPlugin) + if err != nil { + return nil, err + } + shimManager := shimManagerI.(*ShimManager) + + var mounts mount.Manager + if mountsI, err := ic.GetSingle(plugins.MountManagerPlugin); err == nil { + mounts = mountsI.(mount.Manager) + } else if !errors.Is(err, plugin.ErrPluginNotFound) { + return nil, err + } + root, state := ic.Properties[plugins.PropertyRootDir], ic.Properties[plugins.PropertyStateDir] + for _, d := range []string{root, state} { + // root: the parent of this directory is created as 0o700, not 0o711. + // state: the parent of this directory is created as 0o711 too, so as to support userns-remapped containers. + if err := os.MkdirAll(d, 0711); err != nil { + return nil, err + } + } + + if err := shimManager.LoadExistingShims(ic.Context, state, root); err != nil { + return nil, fmt.Errorf("failed to load existing shims for task manager") + } + + warningsI, err := ic.GetSingle(plugins.WarningPlugin) + if err != nil { + return nil, err + } + warnings := warningsI.(warning.Service) + emitPlatformWarnings(ic.Context, warnings) + + return &TaskManager{ + root: root, + state: state, + manager: shimManager, + mounts: mounts, + }, nil + }, + }) +} + +// TaskManager wraps task service client on top of shim manager. +type TaskManager struct { + root string + state string + manager *ShimManager + mounts mount.Manager +} + +// NewTaskManager creates a new task manager instance. +// root is the rootDir of TaskManager plugin to store persistent data +// state is the stateDir of TaskManager plugin to store transient data +// shims is ShimManager for TaskManager to create/delete shims +func NewTaskManager(ctx context.Context, root, state string, shims *ShimManager) (*TaskManager, error) { + if err := shims.LoadExistingShims(ctx, state, root); err != nil { + return nil, fmt.Errorf("failed to load existing shims for task manager") + } + m := &TaskManager{ + root: root, + state: state, + manager: shims, + } + return m, nil +} + +// ID of the task manager +func (m *TaskManager) ID() string { + return plugins.RuntimePluginV2.String() + ".task" +} + +// Create launches new shim instance and creates new task +func (m *TaskManager) Create(ctx context.Context, taskID string, opts runtime.CreateOpts) (_ runtime.Task, retErr error) { + bundle, err := NewBundle(ctx, m.root, m.state, taskID, opts.Spec) + if err != nil { + return nil, err + } + defer func() { + if retErr != nil { + bundle.Delete() + } + }() + + log.G(ctx).WithFields(log.Fields{ + "id": taskID, + "runtime": opts.Runtime, + }).Debug("creating task") + + activateOpts := []mount.ActivateOpt{ + mount.WithLabels(map[string]string{ + "containerd.io/gc.bref.container": taskID, + }), + } + if info, err := m.manager.loadShimInfo(ctx, opts.Runtime); err == nil { + for _, t := range info.handledMounts { + activateOpts = append(activateOpts, mount.WithAllowMountType(t)) + } + } else { + log.G(ctx).WithError(err).WithField("runtime", opts.Runtime).Error("failed to load runtime info") + } + + // Add options based on runtime + if ai, err := m.mounts.Activate(ctx, taskID, opts.Rootfs, activateOpts...); err == nil { + opts.Rootfs = ai.System + defer func() { + if retErr != nil { + dctx, cancel := timeout.WithContext(context.WithoutCancel(ctx), cleanupTimeout) + defer cancel() + if err := m.mounts.Deactivate(dctx, taskID); err != nil { + log.G(ctx).WithError(err).WithField("task", taskID).Errorf("failed to deactivate mounts") + } + } + }() + } else if errdefs.IsAlreadyExists(err) { + // If creation of task with same identifier, use existing mount rather than forcing + // deactivation of the old one. The back reference will prevent racing between + // deactivation and re-use, as the container with the same ID would still exist. + ai, err = m.mounts.Info(ctx, taskID) + if err != nil { + return nil, fmt.Errorf("failed to get info on already active mount: %w", err) + } + opts.Rootfs = ai.System + } else if !errdefs.IsNotImplemented(err) { + return nil, err + } + + shim, err := m.manager.Start(ctx, taskID, bundle, opts) + if err != nil { + return nil, fmt.Errorf("failed to start shim: %w", err) + } + + // Cast to shim task and call task service to create a new container task instance. + // This will not be required once shim service / client implemented. + shimTask, err := newShimTask(shim) + if err != nil { + return nil, err + } + + // runc ignores silently features it doesn't know about, so for things that this is + // problematic let's check if this runc version supports them. + if err := m.validateRuntimeFeatures(ctx, opts); err != nil { + return nil, fmt.Errorf("failed to validate OCI runtime features: %w", err) + } + + t, err := func() (runtime.Task, error) { + t, err := shimTask.Create(ctx, opts) + if err == nil || !errdefs.IsNotImplemented(err) { + return t, err + } + + downgrader, ok := shim.(clientVersionDowngrader) + if ok { + if derr := downgrader.Downgrade(); derr == nil { + log.G(ctx).WithError(err).WithField("id", taskID). + Warning("failed to call task.Create, downgrading client API version to try again") + + shimTask, err = newShimTask(shim) + if err != nil { + return nil, fmt.Errorf("failed to create shim task after downgrading: %w", err) + } + return shimTask.Create(ctx, opts) + } + } + return t, err + }() + if err != nil { + // NOTE: ctx contains required namespace information. + m.manager.shims.Delete(ctx, taskID) + + dctx, cancel := timeout.WithContext(context.WithoutCancel(ctx), cleanupTimeout) + defer cancel() + + sandboxed := opts.SandboxID != "" + _, errShim := shimTask.delete(dctx, sandboxed, func(context.Context, string) {}) + if errShim != nil { + if errdefs.IsDeadlineExceeded(errShim) { + dctx, cancel = timeout.WithContext(context.WithoutCancel(ctx), cleanupTimeout) + defer cancel() + } + + shimTask.Shutdown(dctx) + shimTask.Close() + } + + return nil, fmt.Errorf("failed to create shim task: %w", err) + } + + return t, nil +} + +// Get a specific task +func (m *TaskManager) Get(ctx context.Context, id string) (runtime.Task, error) { + shim, err := m.manager.shims.Get(ctx, id) + if err != nil { + return nil, err + } + return newShimTask(shim) +} + +// Tasks lists all tasks +func (m *TaskManager) Tasks(ctx context.Context, all bool) ([]runtime.Task, error) { + shims, err := m.manager.shims.GetAll(ctx, all) + if err != nil { + return nil, err + } + out := make([]runtime.Task, len(shims)) + for i := range shims { + newClient, err := newShimTask(shims[i]) + if err != nil { + return nil, err + } + out[i] = newClient + } + return out, nil +} + +// Delete deletes the task and shim instance +func (m *TaskManager) Delete(ctx context.Context, taskID string) (*runtime.Exit, error) { + shim, err := m.manager.shims.Get(ctx, taskID) + if err != nil { + return nil, err + } + + container, err := m.manager.containers.Get(ctx, taskID) + if err != nil { + return nil, err + } + + shimTask, err := newShimTask(shim) + if err != nil { + return nil, err + } + + sandboxed := container.SandboxID != "" + + exit, err := shimTask.delete(ctx, sandboxed, func(ctx context.Context, id string) { + m.manager.shims.Delete(ctx, id) + }) + + if err != nil { + return nil, fmt.Errorf("failed to delete task: %w", err) + } + + if err := m.mounts.Deactivate(ctx, taskID); err != nil && !errdefs.IsNotFound(err) { + log.G(ctx).WithError(err).WithField("task", taskID).Errorf("failed to deactivate mounts") + } + + return exit, nil +} + +func supportedLogURISchemes() []string { + switch goruntime.GOOS { + case "windows": + return []string{"binary", "binary-v2", "file", "npipe"} + default: + return []string{"fifo", "binary", "binary-v2", "file"} + } +} + +func getRuntimeInfo(ctx context.Context, shims *ShimManager, req *apitypes.RuntimeRequest) (*apitypes.RuntimeInfo, error) { + runtimePath, err := shims.resolveRuntimePath(req.RuntimePath) + if err != nil { + return nil, fmt.Errorf("failed to resolve runtime path: %w", err) + } + var optsB []byte + if req.Options != nil { + optsB, err = proto.Marshal(req.Options) + if err != nil { + return nil, fmt.Errorf("failed to marshal %s: %w", req.Options.TypeUrl, err) + } + } + var stderr bytes.Buffer + cmd := exec.CommandContext(ctx, runtimePath, "-info") + cmd.Stdin = bytes.NewReader(optsB) + cmd.Stderr = &stderr + stdout, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("failed to run %v: %w (stderr: %q)", cmd.Args, err, stderr.String()) + } + var info apitypes.RuntimeInfo + if err = proto.Unmarshal(stdout, &info); err != nil { + return nil, fmt.Errorf("failed to unmarshal stdout from %v into %T: %w", cmd.Args, &info, err) + } + return &info, nil +} + +func (m *TaskManager) PluginInfo(ctx context.Context, request any) (any, error) { + req, ok := request.(*apitypes.RuntimeRequest) + if !ok { + return nil, fmt.Errorf("unknown request type %T: %w", request, errdefs.ErrNotImplemented) + } + + return getRuntimeInfo(ctx, m.manager, req) +} + +func (m *TaskManager) validateRuntimeFeatures(ctx context.Context, opts runtime.CreateOpts) error { + var spec specs.Spec + if err := typeurl.UnmarshalTo(opts.Spec, &spec); err != nil { + return fmt.Errorf("unmarshal spec: %w", err) + } + + // Only ask for the PluginInfo if idmap mounts are used. + if !usesIDMapMounts(spec) { + return nil + } + + topts := opts.TaskOptions + if topts == nil || topts.GetValue() == nil { + topts = opts.RuntimeOptions + } + + pInfo, err := m.PluginInfo(ctx, &apitypes.RuntimeRequest{RuntimePath: opts.Runtime, Options: typeurl.MarshalProto(topts)}) + if err != nil { + return fmt.Errorf("runtime info: %w", err) + } + + pluginInfo, ok := pInfo.(*apitypes.RuntimeInfo) + if !ok { + return fmt.Errorf("invalid runtime info type: %T", pInfo) + } + + feat, err := typeurl.UnmarshalAny(pluginInfo.Features) + if err != nil { + return fmt.Errorf("unmarshal runtime features: %w", err) + } + + // runc-compatible runtimes silently ignores features it doesn't know about. But ignoring + // our request to use idmap mounts can break permissions in the volume, so let's make sure + // it supports it. For more info, see: + // https://github.com/opencontainers/runtime-spec/pull/1219 + // + features, ok := feat.(*features.Features) + if !ok { + // Leave alone non runc-compatible runtimes that don't provide the features info, + // they might not be affected by this. + return nil + } + + if err := supportsIDMapMounts(features); err != nil { + return fmt.Errorf("idmap mounts not supported: %w", err) + } + + return nil +} + +func usesIDMapMounts(spec specs.Spec) bool { + for _, m := range spec.Mounts { + if m.UIDMappings != nil || m.GIDMappings != nil { + return true + } + if slices.Contains(m.Options, "idmap") || slices.Contains(m.Options, "ridmap") { + return true + } + + } + return false +} + +func supportsIDMapMounts(features *features.Features) error { + if features.Linux.MountExtensions == nil || features.Linux.MountExtensions.IDMap == nil { + return errors.New("missing `mountExtensions.idmap` entry in `features` command") + } + if enabled := features.Linux.MountExtensions.IDMap.Enabled; enabled == nil || !*enabled { + return errors.New("entry `mountExtensions.idmap.Enabled` in `features` command not present or disabled") + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager_linux.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager_linux.go new file mode 100644 index 0000000000..302d5a0f29 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager_linux.go @@ -0,0 +1,31 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + + "github.com/containerd/cgroups/v3" + "github.com/containerd/containerd/v2/pkg/deprecation" + "github.com/containerd/containerd/v2/plugins/services/warning" +) + +func emitPlatformWarnings(ctx context.Context, warnings warning.Service) { + if cgroups.Mode() != cgroups.Unified { + warnings.Emit(ctx, deprecation.CgroupV1) + } +} diff --git a/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager_others.go b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager_others.go new file mode 100644 index 0000000000..a5e3f9670a --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/runtime/v2/task_manager_others.go @@ -0,0 +1,29 @@ +//go:build !linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v2 + +import ( + "context" + + "github.com/containerd/containerd/v2/plugins/services/warning" +) + +func emitPlatformWarnings(ctx context.Context, warnings warning.Service) { + // NOP +} diff --git a/vendor/github.com/containerd/containerd/v2/core/snapshots/storage/bolt.go b/vendor/github.com/containerd/containerd/v2/core/snapshots/storage/bolt.go new file mode 100644 index 0000000000..be6010073c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/snapshots/storage/bolt.go @@ -0,0 +1,664 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package storage + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/containerd/containerd/v2/core/metadata/boltutil" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/pkg/filters" + "github.com/containerd/errdefs" + bolt "go.etcd.io/bbolt" + errbolt "go.etcd.io/bbolt/errors" +) + +var ( + bucketKeyStorageVersion = []byte("v1") + bucketKeySnapshot = []byte("snapshots") + bucketKeyParents = []byte("parents") + + bucketKeyID = []byte("id") + bucketKeyParent = []byte("parent") + bucketKeyKind = []byte("kind") + bucketKeyInodes = []byte("inodes") + bucketKeySize = []byte("size") + + // ErrNoTransaction is returned when an operation is attempted with + // a context which is not inside of a transaction. + ErrNoTransaction = errors.New("no transaction in context") +) + +// parentKey returns a composite key of the parent and child identifiers. The +// parts of the key are separated by a zero byte. +func parentKey(parent, child uint64) []byte { + b := make([]byte, binary.Size([]uint64{parent, child})+1) + i := binary.PutUvarint(b, parent) + j := binary.PutUvarint(b[i+1:], child) + return b[0 : i+j+1] +} + +// parentPrefixKey returns the parent part of the composite key with the +// zero byte separator. +func parentPrefixKey(parent uint64) []byte { + b := make([]byte, binary.Size(parent)+1) + i := binary.PutUvarint(b, parent) + return b[0 : i+1] +} + +// getParentPrefix returns the first part of the composite key which +// represents the parent identifier. +func getParentPrefix(b []byte) uint64 { + parent, _ := binary.Uvarint(b) + return parent +} + +// GetInfo returns the snapshot Info directly from the metadata. Requires a +// context with a storage transaction. +func GetInfo(ctx context.Context, key string) (string, snapshots.Info, snapshots.Usage, error) { + var ( + id uint64 + su snapshots.Usage + si = snapshots.Info{ + Name: key, + } + ) + err := withSnapshotBucket(ctx, key, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error { + getUsage(bkt, &su) + return readSnapshot(bkt, &id, &si) + }) + if err != nil { + return "", snapshots.Info{}, snapshots.Usage{}, err + } + + return strconv.FormatUint(id, 10), si, su, nil +} + +// UpdateInfo updates an existing snapshot info's data +func UpdateInfo(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { + updated := snapshots.Info{ + Name: info.Name, + } + err := withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error { + sbkt := bkt.Bucket([]byte(info.Name)) + if sbkt == nil { + return fmt.Errorf("snapshot does not exist: %w", errdefs.ErrNotFound) + } + if err := readSnapshot(sbkt, nil, &updated); err != nil { + return err + } + + if len(fieldpaths) > 0 { + for _, path := range fieldpaths { + if strings.HasPrefix(path, "labels.") { + if updated.Labels == nil { + updated.Labels = map[string]string{} + } + + key := strings.TrimPrefix(path, "labels.") + updated.Labels[key] = info.Labels[key] + continue + } + + switch path { + case "labels": + updated.Labels = info.Labels + default: + return fmt.Errorf("cannot update %q field on snapshot %q: %w", path, info.Name, errdefs.ErrInvalidArgument) + } + } + } else { + // Set mutable fields + updated.Labels = info.Labels + } + updated.Updated = time.Now().UTC() + if err := boltutil.WriteTimestamps(sbkt, updated.Created, updated.Updated); err != nil { + return err + } + + return boltutil.WriteLabels(sbkt, updated.Labels) + }) + if err != nil { + return snapshots.Info{}, err + } + return updated, nil +} + +// WalkInfo iterates through all metadata Info for the stored snapshots and +// calls the provided function for each. Requires a context with a storage +// transaction. +func WalkInfo(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { + filter, err := filters.ParseAll(fs...) + if err != nil { + return err + } + // TODO: allow indexes (name, parent, specific labels) + return withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error { + return bkt.ForEach(func(k, v []byte) error { + // skip non buckets + if v != nil { + return nil + } + var ( + sbkt = bkt.Bucket(k) + si = snapshots.Info{ + Name: string(k), + } + ) + if err := readSnapshot(sbkt, nil, &si); err != nil { + return err + } + if !filter.Match(adaptSnapshot(si)) { + return nil + } + + return fn(ctx, si) + }) + }) +} + +// GetSnapshot returns the metadata for the active or view snapshot transaction +// referenced by the given key. Requires a context with a storage transaction. +func GetSnapshot(ctx context.Context, key string) (s Snapshot, err error) { + err = withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error { + sbkt := bkt.Bucket([]byte(key)) + if sbkt == nil { + return fmt.Errorf("snapshot does not exist: %w", errdefs.ErrNotFound) + } + + s.ID = strconv.FormatUint(readID(sbkt), 10) + s.Kind = readKind(sbkt) + + if s.Kind != snapshots.KindActive && s.Kind != snapshots.KindView { + return fmt.Errorf("requested snapshot %v not active or view: %w", key, errdefs.ErrFailedPrecondition) + } + + if parentKey := sbkt.Get(bucketKeyParent); len(parentKey) > 0 { + spbkt := bkt.Bucket(parentKey) + if spbkt == nil { + return fmt.Errorf("parent does not exist: %w", errdefs.ErrNotFound) + } + + s.ParentIDs, err = parents(bkt, spbkt, readID(spbkt)) + if err != nil { + return fmt.Errorf("failed to get parent chain: %w", err) + } + } + return nil + }) + if err != nil { + return Snapshot{}, err + } + + return +} + +// CreateSnapshot inserts a record for an active or view snapshot with the provided parent. +func CreateSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts ...snapshots.Opt) (s Snapshot, err error) { + switch kind { + case snapshots.KindActive, snapshots.KindView: + default: + return Snapshot{}, fmt.Errorf("snapshot type %v invalid; only snapshots of type Active or View can be created: %w", kind, errdefs.ErrInvalidArgument) + } + var base snapshots.Info + for _, opt := range opts { + if err := opt(&base); err != nil { + return Snapshot{}, err + } + } + + err = createBucketIfNotExists(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error { + var ( + spbkt *bolt.Bucket + ) + if parent != "" { + spbkt = bkt.Bucket([]byte(parent)) + if spbkt == nil { + return fmt.Errorf("missing parent %q bucket: %w", parent, errdefs.ErrNotFound) + } + + if readKind(spbkt) != snapshots.KindCommitted { + return fmt.Errorf("parent %q is not committed snapshot: %w", parent, errdefs.ErrInvalidArgument) + } + } + sbkt, err := bkt.CreateBucket([]byte(key)) + if err != nil { + if err == errbolt.ErrBucketExists { + err = fmt.Errorf("snapshot %v: %w", key, errdefs.ErrAlreadyExists) + } + return err + } + + id, err := bkt.NextSequence() + if err != nil { + return fmt.Errorf("unable to get identifier for snapshot %q: %w", key, err) + } + + t := time.Now().UTC() + si := snapshots.Info{ + Parent: parent, + Kind: kind, + Labels: base.Labels, + Created: t, + Updated: t, + } + if err := putSnapshot(sbkt, id, si); err != nil { + return err + } + + if spbkt != nil { + pid := readID(spbkt) + + // Store a backlink from the key to the parent. Store the snapshot name + // as the value to allow following the backlink to the snapshot value. + if err := pbkt.Put(parentKey(pid, id), []byte(key)); err != nil { + return fmt.Errorf("failed to write parent link for snapshot %q: %w", key, err) + } + + s.ParentIDs, err = parents(bkt, spbkt, pid) + if err != nil { + return fmt.Errorf("failed to get parent chain for snapshot %q: %w", key, err) + } + } + + s.ID = strconv.FormatUint(id, 10) + s.Kind = kind + return nil + }) + if err != nil { + return Snapshot{}, err + } + + return +} + +// Remove removes a snapshot from the metastore. The string identifier for the +// snapshot is returned as well as the kind. The provided context must contain a +// writable transaction. +func Remove(ctx context.Context, key string) (string, snapshots.Kind, error) { + var ( + id uint64 + si snapshots.Info + ) + + if err := withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error { + sbkt := bkt.Bucket([]byte(key)) + if sbkt == nil { + return fmt.Errorf("snapshot %v: %w", key, errdefs.ErrNotFound) + } + + if err := readSnapshot(sbkt, &id, &si); err != nil { + return fmt.Errorf("failed to read snapshot %s: %w", key, err) + } + + if pbkt != nil { + k, _ := pbkt.Cursor().Seek(parentPrefixKey(id)) + if getParentPrefix(k) == id { + return fmt.Errorf("cannot remove snapshot with child: %w", errdefs.ErrFailedPrecondition) + } + + if si.Parent != "" { + spbkt := bkt.Bucket([]byte(si.Parent)) + if spbkt == nil { + return fmt.Errorf("snapshot %v: %w", key, errdefs.ErrNotFound) + } + + if err := pbkt.Delete(parentKey(readID(spbkt), id)); err != nil { + return fmt.Errorf("failed to delete parent link: %w", err) + } + } + } + + if err := bkt.DeleteBucket([]byte(key)); err != nil { + return fmt.Errorf("failed to delete snapshot: %w", err) + } + + return nil + }); err != nil { + return "", 0, err + } + + return strconv.FormatUint(id, 10), si.Kind, nil +} + +// CommitActive renames the active snapshot transaction referenced by `key` +// as a committed snapshot referenced by `Name`. The resulting snapshot will be +// committed and readonly. The `key` reference will no longer be available for +// lookup or removal. The returned string identifier for the committed snapshot +// is the same identifier of the original active snapshot. The provided context +// must contain a writable transaction. +func CommitActive(ctx context.Context, key, name string, usage snapshots.Usage, opts ...snapshots.Opt) (string, error) { + var ( + id uint64 + base snapshots.Info + ) + for _, opt := range opts { + if err := opt(&base); err != nil { + return "", err + } + } + + if err := withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error { + dbkt, err := bkt.CreateBucket([]byte(name)) + if err != nil { + if err == errbolt.ErrBucketExists { + err = errdefs.ErrAlreadyExists + } + return fmt.Errorf("committed snapshot %v: %w", name, err) + } + sbkt := bkt.Bucket([]byte(key)) + if sbkt == nil { + return fmt.Errorf("failed to get active snapshot %q: %w", key, errdefs.ErrNotFound) + } + + var si snapshots.Info + if err := readSnapshot(sbkt, &id, &si); err != nil { + return fmt.Errorf("failed to read active snapshot %q: %w", key, err) + } + + if si.Kind != snapshots.KindActive { + return fmt.Errorf("snapshot %q is not active: %w", key, errdefs.ErrFailedPrecondition) + } + si.Kind = snapshots.KindCommitted + si.Created = time.Now().UTC() + si.Updated = si.Created + + // Replace labels, do not inherit + si.Labels = base.Labels + + // If the snapshot didn't have a parent when created, allow it + // to be rebased on a parent on commit. + if si.Parent != base.Parent { + if len(si.Parent) == 0 { + si.Parent = base.Parent + } else if len(base.Parent) > 0 { + return fmt.Errorf("cannot change parent of active snapshot %q on commit: %w", key, errdefs.ErrInvalidArgument) + } + } + + if err := putSnapshot(dbkt, id, si); err != nil { + return err + } + if err := putUsage(dbkt, usage); err != nil { + return err + } + if err := bkt.DeleteBucket([]byte(key)); err != nil { + return fmt.Errorf("failed to delete active snapshot %q: %w", key, err) + } + if si.Parent != "" { + spbkt := bkt.Bucket([]byte(si.Parent)) + if spbkt == nil { + return fmt.Errorf("missing parent %q of snapshot %q: %w", si.Parent, key, errdefs.ErrNotFound) + } + pid := readID(spbkt) + + if pkind := readKind(spbkt); pkind != snapshots.KindCommitted { + return fmt.Errorf("parent %q is not committed: %w", si.Parent, errdefs.ErrFailedPrecondition) + } + + // Updates parent back link to use new key + if err := pbkt.Put(parentKey(pid, id), []byte(name)); err != nil { + return fmt.Errorf("failed to update parent link %v from %q to %q: %w", pid, key, name, err) + } + } + + return nil + }); err != nil { + return "", err + } + + return strconv.FormatUint(id, 10), nil +} + +// IDMap returns all the IDs mapped to their key +func IDMap(ctx context.Context) (map[string]string, error) { + m := map[string]string{} + if err := withBucket(ctx, func(ctx context.Context, bkt, _ *bolt.Bucket) error { + return bkt.ForEach(func(k, v []byte) error { + // skip non buckets + if v != nil { + return nil + } + id := readID(bkt.Bucket(k)) + m[strconv.FormatUint(id, 10)] = string(k) + return nil + }) + }); err != nil { + return nil, err + } + + return m, nil +} + +func withSnapshotBucket(ctx context.Context, key string, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error { + tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx) + if !ok { + return ErrNoTransaction + } + vbkt := tx.Bucket(bucketKeyStorageVersion) + if vbkt == nil { + return fmt.Errorf("bucket does not exist: %w", errdefs.ErrNotFound) + } + bkt := vbkt.Bucket(bucketKeySnapshot) + if bkt == nil { + return fmt.Errorf("snapshots bucket does not exist: %w", errdefs.ErrNotFound) + } + bkt = bkt.Bucket([]byte(key)) + if bkt == nil { + return fmt.Errorf("snapshot does not exist: %w", errdefs.ErrNotFound) + } + + return fn(ctx, bkt, vbkt.Bucket(bucketKeyParents)) +} + +func withBucket(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error { + tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx) + if !ok { + return ErrNoTransaction + } + bkt := tx.Bucket(bucketKeyStorageVersion) + if bkt == nil { + return fmt.Errorf("bucket does not exist: %w", errdefs.ErrNotFound) + } + return fn(ctx, bkt.Bucket(bucketKeySnapshot), bkt.Bucket(bucketKeyParents)) +} + +func createBucketIfNotExists(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error { + tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx) + if !ok { + return ErrNoTransaction + } + + bkt, err := tx.CreateBucketIfNotExists(bucketKeyStorageVersion) + if err != nil { + return fmt.Errorf("failed to create version bucket: %w", err) + } + sbkt, err := bkt.CreateBucketIfNotExists(bucketKeySnapshot) + if err != nil { + return fmt.Errorf("failed to create snapshots bucket: %w", err) + } + pbkt, err := bkt.CreateBucketIfNotExists(bucketKeyParents) + if err != nil { + return fmt.Errorf("failed to create parents bucket: %w", err) + } + return fn(ctx, sbkt, pbkt) +} + +func parents(bkt, pbkt *bolt.Bucket, parent uint64) (parents []string, err error) { + for { + parents = append(parents, strconv.FormatUint(parent, 10)) + + parentKey := pbkt.Get(bucketKeyParent) + if len(parentKey) == 0 { + return + } + pbkt = bkt.Bucket(parentKey) + if pbkt == nil { + return nil, fmt.Errorf("missing parent: %w", errdefs.ErrNotFound) + } + + parent = readID(pbkt) + } +} + +func readKind(bkt *bolt.Bucket) (k snapshots.Kind) { + kind := bkt.Get(bucketKeyKind) + if len(kind) == 1 { + k = snapshots.Kind(kind[0]) + } + return +} + +func readID(bkt *bolt.Bucket) uint64 { + id, _ := binary.Uvarint(bkt.Get(bucketKeyID)) + return id +} + +func readSnapshot(bkt *bolt.Bucket, id *uint64, si *snapshots.Info) error { + if id != nil { + *id = readID(bkt) + } + if si != nil { + si.Kind = readKind(bkt) + si.Parent = string(bkt.Get(bucketKeyParent)) + + if err := boltutil.ReadTimestamps(bkt, &si.Created, &si.Updated); err != nil { + return err + } + + labels, err := boltutil.ReadLabels(bkt) + if err != nil { + return err + } + si.Labels = labels + } + + return nil +} + +func putSnapshot(bkt *bolt.Bucket, id uint64, si snapshots.Info) error { + idEncoded, err := encodeID(id) + if err != nil { + return err + } + + updates := [][2][]byte{ + {bucketKeyID, idEncoded}, + {bucketKeyKind, []byte{byte(si.Kind)}}, + } + if si.Parent != "" { + updates = append(updates, [2][]byte{bucketKeyParent, []byte(si.Parent)}) + } + for _, v := range updates { + if err := bkt.Put(v[0], v[1]); err != nil { + return err + } + } + if err := boltutil.WriteTimestamps(bkt, si.Created, si.Updated); err != nil { + return err + } + return boltutil.WriteLabels(bkt, si.Labels) +} + +func getUsage(bkt *bolt.Bucket, usage *snapshots.Usage) { + usage.Inodes, _ = binary.Varint(bkt.Get(bucketKeyInodes)) + usage.Size, _ = binary.Varint(bkt.Get(bucketKeySize)) +} + +func putUsage(bkt *bolt.Bucket, usage snapshots.Usage) error { + for _, v := range []struct { + key []byte + value int64 + }{ + {bucketKeyInodes, usage.Inodes}, + {bucketKeySize, usage.Size}, + } { + e, err := encodeSize(v.value) + if err != nil { + return err + } + if err := bkt.Put(v.key, e); err != nil { + return err + } + } + return nil +} + +func encodeSize(size int64) ([]byte, error) { + var ( + buf [binary.MaxVarintLen64]byte + sizeEncoded = buf[:] + ) + sizeEncoded = sizeEncoded[:binary.PutVarint(sizeEncoded, size)] + + if len(sizeEncoded) == 0 { + return nil, fmt.Errorf("failed encoding size = %v", size) + } + return sizeEncoded, nil +} + +func encodeID(id uint64) ([]byte, error) { + var ( + buf [binary.MaxVarintLen64]byte + idEncoded = buf[:] + ) + idEncoded = idEncoded[:binary.PutUvarint(idEncoded, id)] + + if len(idEncoded) == 0 { + return nil, fmt.Errorf("failed encoding id = %v", id) + } + return idEncoded, nil +} + +func adaptSnapshot(info snapshots.Info) filters.Adaptor { + return filters.AdapterFunc(func(fieldpath []string) (string, bool) { + if len(fieldpath) == 0 { + return "", false + } + + switch fieldpath[0] { + case "kind": + switch info.Kind { + case snapshots.KindActive: + return "active", true + case snapshots.KindView: + return "view", true + case snapshots.KindCommitted: + return "committed", true + } + case "name": + return info.Name, true + case "parent": + return info.Parent, true + case "labels": + if len(info.Labels) == 0 { + return "", false + } + + v, ok := info.Labels[strings.Join(fieldpath[1:], ".")] + return v, ok + } + + return "", false + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/core/snapshots/storage/metastore.go b/vendor/github.com/containerd/containerd/v2/core/snapshots/storage/metastore.go new file mode 100644 index 0000000000..c9027a5c01 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/core/snapshots/storage/metastore.go @@ -0,0 +1,170 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package storage provides a metadata storage implementation for snapshot +// drivers. Drive implementations are responsible for starting and managing +// transactions using the defined context creator. This storage package uses +// BoltDB for storing metadata. Access to the raw boltdb transaction is not +// provided, but the stored object is provided by the proto subpackage. +package storage + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/log" + bolt "go.etcd.io/bbolt" +) + +// Transactor is used to finalize an active transaction. +type Transactor interface { + // Commit commits any changes made during the transaction. On error a + // caller is expected to clean up any resources which would have relied + // on data mutated as part of this transaction. Only writable + // transactions can commit, non-writable must call Rollback. + Commit() error + + // Rollback rolls back any changes made during the transaction. This + // must be called on all non-writable transactions and aborted writable + // transaction. + Rollback() error +} + +// Snapshot hold the metadata for an active or view snapshot transaction. The +// ParentIDs hold the snapshot identifiers for the committed snapshots this +// active or view is based on. The ParentIDs are ordered from the highest to the +// lowest base, meaning they should be applied in order from the last index to +// the first index. The first index should always be considered the active +// snapshot's immediate parent. +type Snapshot struct { + Kind snapshots.Kind + ID string + ParentIDs []string +} + +// Opt allows to customize BoltDB options. Use with care. +type Opt func(*bolt.Options) error + +// MetaStore is used to store metadata related to a snapshot driver. The +// MetaStore is intended to store metadata related to name, state and +// parentage. Using the MetaStore is not required to implement a snapshot +// driver but can be used to handle the persistence and transactional +// complexities of a driver implementation. +type MetaStore struct { + dbfile string + + dbL sync.Mutex + db *bolt.DB + opts bolt.Options +} + +// NewMetaStore returns a snapshot MetaStore for storage of metadata related to +// a snapshot driver backed by a bolt file database. This implementation is +// strongly consistent and does all metadata changes in a transaction to prevent +// against process crashes causing inconsistent metadata state. +func NewMetaStore(dbfile string, opts ...Opt) (*MetaStore, error) { + store := &MetaStore{ + dbfile: dbfile, + opts: *bolt.DefaultOptions, + } + + for _, f := range opts { + if err := f(&store.opts); err != nil { + return nil, err + } + } + + return store, nil +} + +type transactionKey struct{} + +// TransactionContext creates a new transaction context. The writable value +// should be set to true for transactions which are expected to mutate data. +func (ms *MetaStore) TransactionContext(ctx context.Context, writable bool) (context.Context, Transactor, error) { + ms.dbL.Lock() + if ms.db == nil { + db, err := bolt.Open(ms.dbfile, 0600, &ms.opts) + if err != nil { + ms.dbL.Unlock() + return ctx, nil, fmt.Errorf("failed to open database file: %w", err) + } + ms.db = db + } + ms.dbL.Unlock() + + tx, err := ms.db.Begin(writable) + if err != nil { + return ctx, nil, fmt.Errorf("failed to start transaction: %w", err) + } + + ctx = context.WithValue(ctx, transactionKey{}, tx) + + return ctx, tx, nil +} + +// TransactionCallback represents a callback to be invoked while under a metastore transaction. +type TransactionCallback func(ctx context.Context) error + +// WithTransaction is a convenience method to run a function `fn` while holding a meta store transaction. +// If the callback `fn` returns an error or the transaction is not writable, the database transaction will be discarded. +func (ms *MetaStore) WithTransaction(ctx context.Context, writable bool, fn TransactionCallback) error { + ctx, trans, err := ms.TransactionContext(ctx, writable) + if err != nil { + return err + } + + var result []error + err = fn(ctx) + if err != nil { + result = append(result, err) + } + + // Always rollback if transaction is not writable + if err != nil || !writable { + if terr := trans.Rollback(); terr != nil { + log.G(ctx).WithError(terr).Error("failed to rollback transaction") + + result = append(result, fmt.Errorf("rollback failed: %w", terr)) + } + } else { + if terr := trans.Commit(); terr != nil { + log.G(ctx).WithError(terr).Error("failed to commit transaction") + + result = append(result, fmt.Errorf("commit failed: %w", terr)) + } + } + + if err := errors.Join(result...); err != nil { + log.G(ctx).WithError(err).Debug("snapshotter error") + return err + } + + return nil +} + +// Close closes the metastore and any underlying database connections +func (ms *MetaStore) Close() error { + ms.dbL.Lock() + defer ms.dbL.Unlock() + if ms.db == nil { + return nil + } + return ms.db.Close() +} diff --git a/vendor/github.com/containerd/containerd/v2/internal/tomlext/toml_v2_util.go b/vendor/github.com/containerd/containerd/v2/internal/tomlext/toml_v2_util.go new file mode 100644 index 0000000000..e99a84e753 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/internal/tomlext/toml_v2_util.go @@ -0,0 +1,42 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package tomlext + +import "time" + +type Duration time.Duration + +func (d *Duration) UnmarshalText(b []byte) error { + x, err := time.ParseDuration(string(b)) + if err != nil { + return err + } + *d = Duration(x) + return nil +} + +func (d Duration) MarshalText() (text []byte, err error) { + return []byte(time.Duration(d).String()), nil +} + +func ToStdTime(d Duration) time.Duration { + return time.Duration(d) +} + +func FromStdTime(duration time.Duration) Duration { + return Duration(duration) +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/atomicfile/file.go b/vendor/github.com/containerd/containerd/v2/pkg/atomicfile/file.go new file mode 100644 index 0000000000..7b870f7a78 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/atomicfile/file.go @@ -0,0 +1,148 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* +Package atomicfile provides a mechanism (on Unix-like platforms) to present a consistent view of a file to separate +processes even while the file is being written. This is accomplished by writing a temporary file, syncing to disk, and +renaming over the destination file name. + +Partial/inconsistent reads can occur due to: + 1. A process attempting to read the file while it is being written to (both in the case of a new file with a + short/incomplete write or in the case of an existing, updated file where new bytes may be written at the beginning + but old bytes may still be present after). + 2. Concurrent goroutines leading to multiple active writers of the same file. + +The above mechanism explicitly protects against (1) as all writes are to a file with a temporary name. + +There is no explicit protection against multiple, concurrent goroutines attempting to write the same file. However, +atomically writing the file should mean only one writer will "win" and a consistent file will be visible. + +Note: atomicfile is partially implemented for Windows. The Windows codepath performs the same operations, however +Windows does not guarantee that a rename operation is atomic; a crash in the middle may leave the destination file +truncated rather than with the expected content. +*/ +package atomicfile + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sync" +) + +// File is an io.ReadWriteCloser that can also be Canceled if a change needs to be abandoned. +type File interface { + io.ReadWriteCloser + // Cancel abandons a change to a file. This can be called if a write fails or another error occurs. + Cancel() error +} + +// ErrClosed is returned if Read or Write are called on a closed File. +var ErrClosed = errors.New("file is closed") + +// New returns a new atomic file. On Unix-like platforms, the writer (an io.ReadWriteCloser) is backed by a temporary +// file placed into the same directory as the destination file (using filepath.Dir to split the directory from the +// name). On a call to Close the temporary file is synced to disk and renamed to its final name, hiding any previous +// file by the same name. +// +// Note: Take care to call Close and handle any errors that are returned. Errors returned from Close may indicate that +// the file was not written with its final name. +func New(name string, mode os.FileMode) (File, error) { + return newFile(name, mode) +} + +type atomicFile struct { + name string + f *os.File + closed bool + closedMu sync.RWMutex +} + +func newFile(name string, mode os.FileMode) (File, error) { + dir := filepath.Dir(name) + f, err := os.CreateTemp(dir, "") + if err != nil { + return nil, fmt.Errorf("failed to create temp file: %w", err) + } + if err := f.Chmod(mode); err != nil { + return nil, fmt.Errorf("failed to change temp file permissions: %w", err) + } + return &atomicFile{name: name, f: f}, nil +} + +func (a *atomicFile) Close() (err error) { + a.closedMu.Lock() + defer a.closedMu.Unlock() + + if a.closed { + return nil + } + a.closed = true + + defer func() { + if err != nil { + _ = os.Remove(a.f.Name()) // ignore errors + } + }() + // The order of operations here is: + // 1. sync + // 2. close + // 3. rename + // While the ordering of 2 and 3 is not important on Unix-like operating systems, Windows cannot rename an open + // file. By closing first, we allow the rename operation to succeed. + if err = a.f.Sync(); err != nil { + return fmt.Errorf("failed to sync temp file %q: %w", a.f.Name(), err) + } + if err = a.f.Close(); err != nil { + return fmt.Errorf("failed to close temp file %q: %w", a.f.Name(), err) + } + if err = os.Rename(a.f.Name(), a.name); err != nil { + return fmt.Errorf("failed to rename %q to %q: %w", a.f.Name(), a.name, err) + } + return nil +} + +func (a *atomicFile) Cancel() error { + a.closedMu.Lock() + defer a.closedMu.Unlock() + + if a.closed { + return nil + } + a.closed = true + _ = a.f.Close() // ignore error + return os.Remove(a.f.Name()) +} + +func (a *atomicFile) Read(p []byte) (n int, err error) { + a.closedMu.RLock() + defer a.closedMu.RUnlock() + if a.closed { + return 0, ErrClosed + } + return a.f.Read(p) +} + +func (a *atomicFile) Write(p []byte) (n int, err error) { + a.closedMu.RLock() + defer a.closedMu.RUnlock() + if a.closed { + return 0, ErrClosed + } + return a.f.Write(p) +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/blockio/blockio_linux.go b/vendor/github.com/containerd/containerd/v2/pkg/blockio/blockio_linux.go new file mode 100644 index 0000000000..4fca593db3 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/blockio/blockio_linux.go @@ -0,0 +1,72 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package blockio + +import ( + "fmt" + "sync" + + "github.com/intel/goresctrl/pkg/blockio" + runtimespec "github.com/opencontainers/runtime-spec/specs-go" + + "github.com/containerd/log" +) + +var ( + enabled bool + enabledMu sync.RWMutex +) + +// IsEnabled checks whether blockio is enabled. +func IsEnabled() bool { + enabledMu.RLock() + defer enabledMu.RUnlock() + + return enabled +} + +// SetConfig updates blockio config with a given config path. +func SetConfig(configFilePath string) error { + enabledMu.Lock() + defer enabledMu.Unlock() + + enabled = false + if configFilePath == "" { + log.L.Debug("No blockio config file specified, blockio not configured") + return nil + } + + if err := blockio.SetConfigFromFile(configFilePath, true); err != nil { + return fmt.Errorf("blockio not enabled: %w", err) + } + enabled = true + return nil +} + +// ClassNameToLinuxOCI converts blockio class name into the LinuxBlockIO +// structure in the OCI runtime spec. +func ClassNameToLinuxOCI(className string) (*runtimespec.LinuxBlockIO, error) { + return blockio.OciLinuxBlockIO(className) +} + +// ContainerClassFromAnnotations examines container and pod annotations of a +// container and returns its blockio class. +func ContainerClassFromAnnotations(containerName string, containerAnnotations, podAnnotations map[string]string) (string, error) { + return blockio.ContainerClassFromAnnotations(containerName, containerAnnotations, podAnnotations) +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/blockio/blockio_nonlinux.go b/vendor/github.com/containerd/containerd/v2/pkg/blockio/blockio_nonlinux.go new file mode 100644 index 0000000000..f06a409787 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/blockio/blockio_nonlinux.go @@ -0,0 +1,39 @@ +//go:build !linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package blockio + +import runtimespec "github.com/opencontainers/runtime-spec/specs-go" + +// IsEnabled always returns false in non-linux platforms. +func IsEnabled() bool { return false } + +// SetConfig always is no-op in non-linux platforms. +func SetConfig(configFilePath string) error { + return nil +} + +// ClassNameToLinuxOCI always is no-op in non-linux platforms. +func ClassNameToLinuxOCI(className string) (*runtimespec.LinuxBlockIO, error) { + return nil, nil +} + +// ContainerClassFromAnnotations always is no-op in non-linux platforms. +func ContainerClassFromAnnotations(containerName string, containerAnnotations, podAnnotations map[string]string) (string, error) { + return "", nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/deprecation/deprecation.go b/vendor/github.com/containerd/containerd/v2/pkg/deprecation/deprecation.go new file mode 100644 index 0000000000..45a07f4198 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/deprecation/deprecation.go @@ -0,0 +1,84 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package deprecation + +type Warning string + +const ( + // Prefix is a standard prefix for all Warnings, used for filtering plugin Exports + Prefix = "io.containerd.deprecation/" + // CRIRegistryMirrors is a warning for the use of the `mirrors` property + CRIRegistryMirrors Warning = Prefix + "cri-registry-mirrors" + // CRIRegistryAuths is a warning for the use of the `auths` property + CRIRegistryAuths Warning = Prefix + "cri-registry-auths" + // CRIRegistryConfigs is a warning for the use of the `configs` property + CRIRegistryConfigs Warning = Prefix + "cri-registry-configs" + // CRICNIBinDir is a warning for the use of the `bin_dir` property + CRICNIBinDir = Prefix + "cri-cni-bin-dir" + // OTLPTracingConfig is a warning for the use of the `otlp` property + TracingOTLPConfig Warning = Prefix + "tracing-processor-config" + // TracingServiceConfig is a warning for the use of the `tracing` property + TracingServiceConfig Warning = Prefix + "tracing-service-config" + // NRIV010Plugin is a warning for the use of NRI 0.1.0-style plugins + NRIV010Plugin Warning = Prefix + "nri-v010-plugin" + // CgroupV1 is a warning for the use of cgroup v1 + CgroupV1 Warning = Prefix + "cgroup-v1" + // CRIEnableCDI is a warning for the use of the `enable_cdi` property + CRIEnableCDI Warning = Prefix + "enable-cdi" + // RuncOptionsTaskAPIAddress is a warning for the use of `task_api_address` in runc options + RuncOptionsTaskAPIAddress Warning = Prefix + "runc-options-task-api-address" + // RuncOptionsTaskAPIVersion is a warning for the use of `task_api_version` in runc options + RuncOptionsTaskAPIVersion Warning = Prefix + "runc-options-task-api-version" +) + +const ( + EnvPrefix = "CONTAINERD_ENABLE_DEPRECATED_" +) + +var messages = map[Warning]string{ + CRIRegistryMirrors: "The `mirrors` property of `[plugins.\"io.containerd.grpc.v1.cri\".registry]` is deprecated since containerd v1.5 and will be removed in containerd v2.4. " + + "Use `config_path` instead.", + CRIRegistryAuths: "The `auths` property of `[plugins.\"io.containerd.grpc.v1.cri\".registry]` is deprecated since containerd v1.3 and will be removed in containerd v2.4. " + + "Use `ImagePullSecrets` instead.", + CRIRegistryConfigs: "The `configs` property of `[plugins.\"io.containerd.grpc.v1.cri\".registry]` is deprecated since containerd v1.5 and will be removed in containerd v2.4. " + + "Use `config_path` instead.", + CRICNIBinDir: "The `bin_dir` property of `[plugins.\"io.containerd.cri.v1.runtime\".cni`] is deprecated since containerd v2.1 and will be removed in containerd v2.4. " + + "Use `bin_dirs` in the same section instead.", + + TracingOTLPConfig: "The `otlp` property of `[plugins.\"io.containerd.tracing.processor.v1\".otlp]` is deprecated since containerd v1.6 and will be removed in containerd v2.4. " + + "Use OTLP environment variables instead: https://opentelemetry.io/docs/specs/otel/protocol/exporter/", + TracingServiceConfig: "The `tracing` property of `[plugins.\"io.containerd.internal.v1\".tracing]` is deprecated since containerd v1.6 and will be removed in containerd v2.4. " + + "Use OTEL environment variables instead: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/", + NRIV010Plugin: "NRI 0.1.0-style plugins are deprecated since containerd 2.2 and should only be used through the v010-adapter plugin.", + CgroupV1: "The support for cgroup v1 is deprecated since containerd v2.2 and will be removed by no later than May 2029. Upgrade the host to use cgroup v2.", + CRIEnableCDI: "The `enable_cdi` property of `[plugins.\"io.containerd.cri.v1.runtime\"]` is deprecated, will be removed in containerd v2.3, and CDI support will always be enabled.", + + RuncOptionsTaskAPIAddress: "The `task_api_address` field in runc options is deprecated since containerd v2.3. Set `task_api_address` on CreateTaskRequest instead.", + RuncOptionsTaskAPIVersion: "The `task_api_version` field in runc options is deprecated since containerd v2.3. Set `task_api_version` on CreateTaskRequest instead.", +} + +// Valid checks whether a given Warning is valid +func Valid(id Warning) bool { + _, ok := messages[id] + return ok +} + +// Message returns the human-readable message for a given Warning +func Message(id Warning) (string, bool) { + msg, ok := messages[id] + return msg, ok +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/rdt/rdt_linux.go b/vendor/github.com/containerd/containerd/v2/pkg/rdt/rdt_linux.go new file mode 100644 index 0000000000..520ca35093 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/rdt/rdt_linux.go @@ -0,0 +1,85 @@ +//go:build linux && !no_rdt + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package rdt + +import ( + "fmt" + "sync" + + "github.com/containerd/log" + + "github.com/intel/goresctrl/pkg/rdt" +) + +const ( + // ResctrlPrefix is the prefix used for class/closid directories under the resctrl filesystem + ResctrlPrefix = "" +) + +var ( + enabled bool + enabledMu sync.RWMutex +) + +// IsEnabled checks whether rdt is enabled. +func IsEnabled() bool { + enabledMu.RLock() + defer enabledMu.RUnlock() + + return enabled +} + +var ( + initOnce sync.Once + initErr error +) + +// SetConfig updates rdt config with a given config path. +func SetConfig(configFilePath string) error { + enabledMu.Lock() + defer enabledMu.Unlock() + + enabled = false + if configFilePath == "" { + log.L.Debug("No RDT config file specified, RDT not configured") + return nil + } + + initOnce.Do(func() { + err := rdt.Initialize(ResctrlPrefix) + if err != nil { + initErr = fmt.Errorf("RDT not enabled: %w", err) + } + }) + if initErr != nil { + return initErr + } + + if err := rdt.SetConfigFromFile(configFilePath, true); err != nil { + return err + } + enabled = true + return nil +} + +// ContainerClassFromAnnotations examines container and pod annotations of a +// container and returns its RDT class. +func ContainerClassFromAnnotations(containerName string, containerAnnotations, podAnnotations map[string]string) (string, error) { + return rdt.ContainerClassFromAnnotations(containerName, containerAnnotations, podAnnotations) +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/rdt/rdt_nonlinux.go b/vendor/github.com/containerd/containerd/v2/pkg/rdt/rdt_nonlinux.go new file mode 100644 index 0000000000..9a3571fd3b --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/rdt/rdt_nonlinux.go @@ -0,0 +1,30 @@ +//go:build !linux || no_rdt + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package rdt + +// IsEnabled always returns false in non-linux platforms. +func IsEnabled() bool { return false } + +// SetConfig always is no-op in non-linux platforms. +func SetConfig(configFilePath string) error { return nil } + +// ContainerClassFromAnnotations always is no-op in non-linux platforms. +func ContainerClassFromAnnotations(containerName string, containerAnnotations, podAnnotations map[string]string) (string, error) { + return "", nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/compat.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/compat.go new file mode 100644 index 0000000000..c41b2edebd --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/compat.go @@ -0,0 +1,53 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +// This file contains the compatibility layer between the new shim bootstrap +// protocol (see https://github.com/containerd/containerd/pull/12786) and the +// old shim APIs (prior containerd 2.3), which mainly relies on CLI, env vars, stdin, and spec.json annotations. +// Once settled, this file should be removed. + +import ( + "bytes" + "fmt" + "os" + + bootapi "github.com/containerd/containerd/api/runtime/bootstrap/v1" + "github.com/containerd/containerd/api/types/runc/options" +) + +func readBootstrapParamsFromDeprecatedFields(input []byte, params *bootapi.BootstrapParams, parsedID string, parsedNamespace string, parsedBinary string, parsedDebug bool) error { + params.InstanceID = parsedID + params.Namespace = parsedNamespace + params.ContainerdTtrpcAddress = os.Getenv(ttrpcAddressEnv) + params.ContainerdGrpcAddress = os.Getenv(grpcAddressEnv) + params.ContainerdBinary = parsedBinary + + if parsedDebug { + params.LogLevel = bootapi.LogLevel_LOG_LEVEL_DEBUG + } + + // Task options + + if opts, err := ReadRuntimeOptions[*options.Options](bytes.NewBuffer(input)); err == nil { + if err := params.AddExtension(opts); err != nil { + return fmt.Errorf("unable to add runc options: %w", err) + } + } + + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/deprecated.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/deprecated.go new file mode 100644 index 0000000000..80251a3232 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/deprecated.go @@ -0,0 +1,113 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "context" + "fmt" + "io" + "os" + + bootapi "github.com/containerd/containerd/api/runtime/bootstrap/v1" + "github.com/containerd/containerd/api/types" + "github.com/containerd/log" +) + +// StartOpts describes shim start configuration received from containerd. +// +// Deprecated: Use [bootapi.BootstrapParams] instead. +type StartOpts struct { + Address string + TTRPCAddress string + Debug bool +} + +// BootstrapParams is a JSON payload returned in stdout from shim.Start call. +// +// Deprecated: Use [bootapi.BootstrapResult] instead. +type BootstrapParams struct { + // Version is the version of shim parameters (expected 2 for shim v2) + Version int `json:"version"` + // Address is a address containerd should use to connect to shim. + Address string `json:"address"` + // Protocol is either TTRPC or GRPC. + Protocol string `json:"protocol"` +} + +// Manager is the interface which manages the shim process. +// +// Deprecated: Use [Shim] instead. +type Manager interface { + Name() string + Start(ctx context.Context, id string, opts StartOpts) (BootstrapParams, error) + Stop(ctx context.Context, id string) (StopStatus, error) + Info(ctx context.Context, optionsR io.Reader) (*types.RuntimeInfo, error) +} + +// managerShim wraps a deprecated Manager to implement the Shim interface. +type managerShim struct { + manager Manager +} + +func (m *managerShim) Name() string { + return m.manager.Name() +} + +func (m *managerShim) Start(ctx context.Context, params *bootapi.BootstrapParams) (*bootapi.BootstrapResult, error) { + opts := StartOpts{ + Address: params.ContainerdGrpcAddress, + TTRPCAddress: params.ContainerdTtrpcAddress, + Debug: params.LogLevel <= bootapi.LogLevel_LOG_LEVEL_DEBUG, + } + + bp, err := m.manager.Start(ctx, params.InstanceID, opts) + if err != nil { + return nil, err + } + + return &bootapi.BootstrapResult{ + Version: int32(bp.Version), + Address: bp.Address, + Protocol: bp.Protocol, + }, nil +} + +func (m *managerShim) Stop(ctx context.Context, id string) (StopStatus, error) { + return m.manager.Stop(ctx, id) +} + +func (m *managerShim) Info(ctx context.Context, optionsR io.Reader) (*types.RuntimeInfo, error) { + return m.manager.Info(ctx, optionsR) +} + +// Run initializes and runs a shim server. +// +// Deprecated: Use [RunShim] instead. +func Run(ctx context.Context, manager Manager, opts ...BinaryOpts) { + var config Config + for _, o := range opts { + o(&config) + } + + shim := &managerShim{manager: manager} + ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", shim.Name())) + + if err := run(ctx, shim, config); err != nil { + fmt.Fprintf(os.Stderr, "%s: %s", shim.Name(), err) + os.Exit(1) + } +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/publisher.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/publisher.go new file mode 100644 index 0000000000..269f771931 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/publisher.go @@ -0,0 +1,183 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "context" + "sync" + "time" + + v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1" + "github.com/containerd/containerd/api/types" + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/protobuf" + "github.com/containerd/containerd/v2/pkg/ttrpcutil" + "github.com/containerd/log" + "github.com/containerd/ttrpc" + "github.com/containerd/typeurl/v2" +) + +const ( + queueSize = 2048 + maxRequeue = 5 +) + +type item struct { + ev *types.Envelope + ctx context.Context + count int +} + +type publisherConfig struct { + ttrpcOpts []ttrpc.ClientOpts +} + +type PublisherOpts func(*publisherConfig) + +func WithPublishTTRPCOpts(opts ...ttrpc.ClientOpts) PublisherOpts { + return func(cfg *publisherConfig) { + cfg.ttrpcOpts = append(cfg.ttrpcOpts, opts...) + } +} + +// NewPublisher creates a new remote events publisher +func NewPublisher(address string, opts ...PublisherOpts) (*RemoteEventsPublisher, error) { + client, err := ttrpcutil.NewClient(address) + if err != nil { + return nil, err + } + + l := &RemoteEventsPublisher{ + client: client, + closed: make(chan struct{}), + requeue: make(chan *item, queueSize), + } + + go l.processQueue() + return l, nil +} + +// RemoteEventsPublisher forwards events to a ttrpc server +type RemoteEventsPublisher struct { + client *ttrpcutil.Client + closed chan struct{} + closer sync.Once + requeue chan *item +} + +// Done returns a channel which closes when done +func (l *RemoteEventsPublisher) Done() <-chan struct{} { + return l.closed +} + +// Close closes the remote connection and closes the done channel +func (l *RemoteEventsPublisher) Close() (err error) { + err = l.client.Close() + l.closer.Do(func() { + close(l.closed) + }) + return err +} + +func (l *RemoteEventsPublisher) processQueue() { + for i := range l.requeue { + if i.count > maxRequeue { + log.L.Errorf("evicting %s from queue because of retry count", i.ev.Topic) + // drop the event + continue + } + + if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); err != nil { + log.L.WithError(err).Error("forward event") + l.queue(i) + } + } +} + +func (l *RemoteEventsPublisher) queue(i *item) { + go func() { + i.count++ + // re-queue after a short delay + time.Sleep(time.Duration(1*i.count) * time.Second) + l.requeue <- i + }() +} + +// Publish publishes the event by forwarding it to the configured ttrpc server +func (l *RemoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + evt, err := typeurl.MarshalAnyToProto(event) + if err != nil { + return err + } + i := &item{ + ev: &types.Envelope{ + Timestamp: protobuf.ToTimestamp(time.Now()), + Namespace: ns, + Topic: topic, + Event: evt, + }, + ctx: ctx, + } + + if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); err != nil { + l.queue(i) + return err + } + + return nil +} + +func (l *RemoteEventsPublisher) forwardRequest(ctx context.Context, req *v1.ForwardRequest) error { + service, err := l.client.EventsService() + if err == nil { + fCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + _, err = service.Forward(fCtx, req) + cancel() + if err == nil { + return nil + } + } + + if err != ttrpc.ErrClosed { + return err + } + + // Reconnect and retry request + if err = l.client.Reconnect(); err != nil { + return err + } + + service, err = l.client.EventsService() + if err != nil { + return err + } + + // try again with a fresh context, otherwise we may get a context timeout unexpectedly. + fCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + _, err = service.Forward(fCtx, req) + cancel() + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/shim.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim.go new file mode 100644 index 0000000000..19fce05f3d --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim.go @@ -0,0 +1,533 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "context" + "errors" + "flag" + "fmt" + "io" + "net" + "os" + "path/filepath" + "runtime" + "runtime/debug" + "time" + + bootapi "github.com/containerd/containerd/api/runtime/bootstrap/v1" + shimapi "github.com/containerd/containerd/api/runtime/task/v3" + "github.com/containerd/containerd/api/types" + + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/protobuf" + "github.com/containerd/containerd/v2/pkg/protobuf/proto" + "github.com/containerd/containerd/v2/pkg/shutdown" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/version" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/ttrpc" +) + +// Publisher for events +type Publisher interface { + events.Publisher + io.Closer +} + +type StopStatus struct { + Pid int + ExitStatus int + ExitedAt time.Time +} + +// Shim is the interface which manages the shim process lifecycle using the +// new bootstrap protocol. +type Shim interface { + Name() string + Start(ctx context.Context, params *bootapi.BootstrapParams) (*bootapi.BootstrapResult, error) + Stop(ctx context.Context, id string) (StopStatus, error) + Info(ctx context.Context, optionsR io.Reader) (*types.RuntimeInfo, error) +} + +// OptsKey is the context key for the Opts value. +type OptsKey struct{} + +// Opts are context options associated with the shim invocation. +type Opts struct { + BundlePath string + Debug bool +} + +// BinaryOpts allows the configuration of a shims binary setup +type BinaryOpts func(*Config) + +// Config of shim binary options provided by shim implementations +type Config struct { + // NoSubreaper disables setting the shim as a child subreaper + NoSubreaper bool + // NoReaper disables the shim binary from reaping any child process implicitly + NoReaper bool + // NoSetupLogger disables automatic configuration of logrus to use the shim FIFO + NoSetupLogger bool +} + +type TTRPCService interface { + RegisterTTRPC(*ttrpc.Server) error +} + +type TTRPCServerUnaryOptioner interface { + UnaryServerInterceptor() ttrpc.UnaryServerInterceptor +} + +type TTRPCClientUnaryOptioner interface { + UnaryClientInterceptor() ttrpc.UnaryClientInterceptor +} + +var ( + debugFlag bool + versionFlag bool + infoFlag bool + id string + namespaceFlag string + socketFlag string + debugSocketFlag string + bundlePath string + addressFlag string + containerdBinaryFlag string + action string +) + +const ( + ttrpcAddressEnv = "TTRPC_ADDRESS" + grpcAddressEnv = "GRPC_ADDRESS" + namespaceEnv = "NAMESPACE" + maxVersionEnv = "MAX_SHIM_VERSION" +) + +func parseFlags() { + flag.BoolVar(&debugFlag, "debug", false, "enable debug output in logs") + + // short + long form. omitting the usage (description) of the short-form + // to group it with the long form. + flag.BoolVar(&versionFlag, "v", false, "") + flag.BoolVar(&versionFlag, "version", false, "show the shim version and exit") + + // "info" is not a subcommand, because old shims produce very confusing errors for unknown subcommands + // https://github.com/containerd/containerd/pull/8509#discussion_r1210021403 + flag.BoolVar(&infoFlag, "info", false, "get the option protobuf from stdin, print the shim info protobuf to stdout, and exit") + flag.StringVar(&namespaceFlag, "namespace", "", "namespace that owns the shim") + flag.StringVar(&id, "id", "", "id of the task") + flag.StringVar(&socketFlag, "socket", "", "socket path to serve") + flag.StringVar(&debugSocketFlag, "debug-socket", "", "debug socket path to serve") + flag.StringVar(&bundlePath, "bundle", "", "path to the bundle if not workdir") // Provided only during -delete action + + flag.StringVar(&addressFlag, "address", "", "grpc address back to main containerd") + flag.StringVar(&containerdBinaryFlag, "publish-binary", "", + fmt.Sprintf("path to publish binary (used for publishing events), but %s will ignore this flag, please use the %s env", os.Args[0], ttrpcAddressEnv), + ) + + flag.Parse() + action = flag.Arg(0) +} + +func setRuntime() { + debug.SetGCPercent(40) + go func() { + for range time.Tick(30 * time.Second) { + debug.FreeOSMemory() + } + }() + if os.Getenv("GOMAXPROCS") == "" { + // If GOMAXPROCS hasn't been set, we default to a value of 2 to reduce + // the number of Go stacks present in the shim. + runtime.GOMAXPROCS(2) + } +} + +func setLogger(ctx context.Context, id string) (context.Context, error) { + l := log.G(ctx) + _ = log.SetFormat(log.TextFormat) + if debugFlag { + _ = log.SetLevel("debug") + } + f, err := openLog(ctx, id) + if err != nil { + return ctx, err + } + l.Logger.SetOutput(f) + return log.WithLogger(ctx, l), nil +} + +// RunShim initializes and runs a shim server using the new bootstrap protocol. +func RunShim(ctx context.Context, shim Shim, opts ...BinaryOpts) { + var config Config + for _, o := range opts { + o(&config) + } + + ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", shim.Name())) + + if err := run(ctx, shim, config); err != nil { + fmt.Fprintf(os.Stderr, "%s: %s", shim.Name(), err) + os.Exit(1) + } +} + +func runInfo(ctx context.Context, manager Shim) error { + info, err := manager.Info(ctx, os.Stdin) + if err != nil { + return err + } + infoB, err := proto.Marshal(info) + if err != nil { + return err + } + _, err = os.Stdout.Write(infoB) + return err +} + +func run(ctx context.Context, manager Shim, config Config) error { + parseFlags() + if versionFlag { + fmt.Printf("%s:\n", filepath.Base(os.Args[0])) + fmt.Println(" Version: ", version.Version) + fmt.Println(" Revision:", version.Revision) + fmt.Println(" Go version:", version.GoVersion) + fmt.Println("") + return nil + } + + if infoFlag { + return runInfo(ctx, manager) + } + + if namespaceFlag == "" { + return fmt.Errorf("shim namespace cannot be empty") + } + + setRuntime() + + signals, err := setupSignals(config) + if err != nil { + return err + } + + if !config.NoSubreaper { + if err := subreaper(); err != nil { + return err + } + } + + ttrpcAddress := os.Getenv(ttrpcAddressEnv) + + ctx = namespaces.WithNamespace(ctx, namespaceFlag) + ctx = context.WithValue(ctx, OptsKey{}, Opts{BundlePath: bundlePath, Debug: debugFlag}) + ctx, sd := shutdown.WithShutdown(ctx) + defer sd.Shutdown() + + // Handle explicit actions + switch action { + case "delete": + logger := log.G(ctx).WithFields(log.Fields{ + "pid": os.Getpid(), + "namespace": namespaceFlag, + }) + if debugFlag { + logger.Logger.SetLevel(log.DebugLevel) + } + go reap(ctx, logger, signals) + ss, err := manager.Stop(ctx, id) + if err != nil { + return err + } + data, err := proto.Marshal(&shimapi.DeleteResponse{ + Pid: uint32(ss.Pid), + ExitStatus: uint32(ss.ExitStatus), + ExitedAt: protobuf.ToTimestamp(ss.ExitedAt), + }) + if err != nil { + return err + } + if _, err := os.Stdout.Write(data); err != nil { + return err + } + return nil + case "start": + // We try reading stdin twice: first for the new boot API, then runc Options. + // The stdin pipe is not seekable, so this should be read into memory first. + // Protect against unbounded memory consumption with a limit (e.g., 10MB). + input, err := io.ReadAll(io.LimitReader(os.Stdin, 10<<20)) + if err != nil { + return fmt.Errorf("failed to read stdin: %w", err) + } + + var params bootapi.BootstrapParams + if len(input) == 0 || proto.Unmarshal(input, ¶ms) != nil { + // TODO: Return error once the new API is stable + if err := readBootstrapParamsFromDeprecatedFields(input, ¶ms, id, namespaceFlag, containerdBinaryFlag, debugFlag); err != nil { + return err + } + } + + // Persist the socket directory so the long-running server process + // (which doesn't receive bootstrap params) can read it for cleanup. + if dir := params.GetSocketDir(); dir != "" { + if err := writeSocketDir(dir); err != nil { + return fmt.Errorf("failed to write socket-dir: %w", err) + } + } + + result, err := manager.Start(ctx, ¶ms) + if err != nil { + return err + } + + // On Windows, the shim daemon may not have created its named pipe + // yet when this start helper returns. Wait for it to be connectable + // before writing the bootstrap result to stdout. + // Similar to hcsshim's readiness pattern (microsoft/hcsshim notifyReady). + // On Unix this is a no-op: domain sockets appear atomically. + if err := awaitPipeReady(result.Address); err != nil { + return fmt.Errorf("shim pipe not ready: %w", err) + } + + data, err := proto.Marshal(result) + if err != nil { + return fmt.Errorf("failed to marshal bootstrap params: %w", err) + } + + if _, err := os.Stdout.Write(data); err != nil { + return err + } + + return nil + } + + if !config.NoSetupLogger { + ctx, err = setLogger(ctx, id) + if err != nil { + return err + } + } + + registry.Register(&plugin.Registration{ + Type: plugins.InternalPlugin, + ID: "shutdown", + InitFn: func(ic *plugin.InitContext) (any, error) { + return sd, nil + }, + }) + + // Register event plugin + registry.Register(&plugin.Registration{ + Type: plugins.EventPlugin, + ID: "publisher", + InitFn: func(ic *plugin.InitContext) (any, error) { + return NewPublisher(ttrpcAddress, func(cfg *publisherConfig) { + p, _ := ic.GetByID(plugins.TTRPCPlugin, "otelttrpc") + if p == nil { + return + } + + opts := ttrpc.WithUnaryClientInterceptor(p.(TTRPCClientUnaryOptioner).UnaryClientInterceptor()) + WithPublishTTRPCOpts(opts)(cfg) + }) + }, + }) + + var ( + initialized = plugin.NewPluginSet() + ttrpcServices = []TTRPCService{} + + ttrpcUnaryInterceptors = []ttrpc.UnaryServerInterceptor{} + + pprofHandler server + ) + + for _, p := range registry.Graph(func(*plugin.Registration) bool { return false }) { + pID := p.URI() + log.G(ctx).WithFields(log.Fields{"id": pID, "type": p.Type}).Debug("loading plugin") + + initContext := plugin.NewContext( + ctx, + initialized, + map[string]string{ + // NOTE: Root is empty since the shim does not support persistent storage, + // shim plugins should make use state directory for writing files to disk. + // The state directory will be destroyed when the shim if cleaned up or + // on reboot + plugins.PropertyStateDir: filepath.Join(bundlePath, p.URI()), + plugins.PropertyGRPCAddress: addressFlag, + plugins.PropertyTTRPCAddress: ttrpcAddress, + }, + ) + + // load the plugin specific configuration if it is provided + // TODO: Read configuration passed into shim, or from state directory? + // if p.Config != nil { + // pc, err := config.Decode(p) + // if err != nil { + // return nil, err + // } + // initContext.Config = pc + // } + + result := p.Init(initContext) + if err := initialized.Add(result); err != nil { + return fmt.Errorf("could not add plugin result to plugin set: %w", err) + } + + instance, err := result.Instance() + if err != nil { + if plugin.IsSkipPlugin(err) { + log.G(ctx).WithFields(log.Fields{"id": pID, "type": p.Type, "error": err}).Info("skip loading plugin") + continue + } + return fmt.Errorf("failed to load plugin %s: %w", pID, err) + } + + if src, ok := instance.(TTRPCService); ok { + log.G(ctx).WithField("id", pID).Debug("registering ttrpc service") + ttrpcServices = append(ttrpcServices, src) + } + + if src, ok := instance.(TTRPCServerUnaryOptioner); ok { + ttrpcUnaryInterceptors = append(ttrpcUnaryInterceptors, src.UnaryServerInterceptor()) + } + + if result.Registration.ID == "pprof" { + if src, ok := instance.(server); ok { + pprofHandler = src + } + } + } + + if len(ttrpcServices) == 0 { + return fmt.Errorf("required that ttrpc service") + } + + unaryInterceptor := chainUnaryServerInterceptors(ttrpcUnaryInterceptors...) + server, err := newServer(ttrpc.WithUnaryServerInterceptor(unaryInterceptor)) + if err != nil { + return fmt.Errorf("failed creating server: %w", err) + } + + for _, srv := range ttrpcServices { + if err := srv.RegisterTTRPC(server); err != nil { + return fmt.Errorf("failed to register service: %w", err) + } + } + + if err := serve(ctx, server, signals, sd.Shutdown, pprofHandler); err != nil { + if !errors.Is(err, shutdown.ErrShutdown) { + cleanupSockets(ctx) + return err + } + } + + // NOTE: If the shim server is down(like oom killer), the address + // socket might be leaking. + cleanupSockets(ctx) + + select { + case <-sd.Done(): + return nil + case <-time.After(5 * time.Second): + return errors.New("shim shutdown timeout") + } +} + +// serve serves the ttrpc API over a unix socket in the current working directory +// and blocks until the context is canceled +func serve(ctx context.Context, server *ttrpc.Server, signals chan os.Signal, shutdown func(), pprof server) error { + dump := make(chan os.Signal, 32) + setupDumpStacks(dump) + + path, err := os.Getwd() + if err != nil { + return err + } + + l, err := serveListener(socketFlag, 3) + if err != nil { + return err + } + go func() { + defer l.Close() + if err := server.Serve(ctx, l); err != nil && !errors.Is(err, net.ErrClosed) { + log.G(ctx).WithError(err).Fatal("containerd-shim: ttrpc server failure") + } + }() + + if debugFlag && pprof != nil { + if err := setupPprof(ctx, pprof); err != nil { + log.G(ctx).WithError(err).Warn("Could not setup pprof") + } + } + + logger := log.G(ctx).WithFields(log.Fields{ + "pid": os.Getpid(), + "path": path, + "namespace": namespaceFlag, + }) + go func() { + for range dump { + dumpStacks(logger) + } + }() + + go handleExitSignals(ctx, logger, shutdown) + return reap(ctx, logger, signals) +} + +func dumpStacks(logger *log.Entry) { + var ( + buf []byte + stackSize int + ) + bufferLen := 16384 + for stackSize == len(buf) { + buf = make([]byte, bufferLen) + stackSize = runtime.Stack(buf, true) + bufferLen *= 2 + } + buf = buf[:stackSize] + logger.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) +} + +type server interface { + Serve(net.Listener) error +} + +func setupPprof(ctx context.Context, srv server) error { + l, err := serveListener(debugSocketFlag, 4) + if err != nil { + return fmt.Errorf("could not setup pprof listener: %w", err) + } + + go func() { + if err := srv.Serve(l); err != nil && !errors.Is(err, net.ErrClosed) { + log.G(ctx).WithError(err).Fatal("containerd-shim: pprof endpoint failure") + } + }() + + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_darwin.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_darwin.go new file mode 100644 index 0000000000..0bdf289bbe --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_darwin.go @@ -0,0 +1,27 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import "github.com/containerd/ttrpc" + +func newServer(opts ...ttrpc.ServerOpt) (*ttrpc.Server, error) { + return ttrpc.NewServer(opts...) +} + +func subreaper() error { + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_freebsd.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_freebsd.go new file mode 100644 index 0000000000..0bdf289bbe --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_freebsd.go @@ -0,0 +1,27 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import "github.com/containerd/ttrpc" + +func newServer(opts ...ttrpc.ServerOpt) (*ttrpc.Server, error) { + return ttrpc.NewServer(opts...) +} + +func subreaper() error { + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_linux.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_linux.go new file mode 100644 index 0000000000..f1bf2eb581 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_linux.go @@ -0,0 +1,31 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "github.com/containerd/containerd/v2/pkg/sys/reaper" + "github.com/containerd/ttrpc" +) + +func newServer(opts ...ttrpc.ServerOpt) (*ttrpc.Server, error) { + opts = append(opts, ttrpc.WithServerHandshaker(ttrpc.UnixSocketRequireSameUser())) + return ttrpc.NewServer(opts...) +} + +func subreaper() error { + return reaper.SetSubreaper(1) +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_unix.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_unix.go new file mode 100644 index 0000000000..36e4797073 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_unix.go @@ -0,0 +1,119 @@ +//go:build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "context" + "fmt" + "io" + "net" + "os" + "os/signal" + "syscall" + + "github.com/containerd/containerd/v2/pkg/sys/reaper" + "github.com/containerd/fifo" + "github.com/containerd/log" + "golang.org/x/sys/unix" +) + +// setupSignals creates a new signal handler for all signals and sets the shim as a +// sub-reaper so that the container processes are reparented +func setupSignals(config Config) (chan os.Signal, error) { + signals := make(chan os.Signal, 32) + smp := []os.Signal{unix.SIGTERM, unix.SIGINT, unix.SIGPIPE} + if !config.NoReaper { + smp = append(smp, unix.SIGCHLD) + } + signal.Notify(signals, smp...) + return signals, nil +} + +func setupDumpStacks(dump chan<- os.Signal) { + signal.Notify(dump, syscall.SIGUSR1) +} + +func serveListener(path string, fd uintptr) (net.Listener, error) { + var ( + l net.Listener + err error + ) + if path == "" { + l, err = net.FileListener(os.NewFile(fd, "socket")) + path = "[inherited from parent]" + } else { + if len(path) > socketPathLimit { + return nil, fmt.Errorf("%q: unix socket path too long (> %d)", path, socketPathLimit) + } + l, err = net.Listen("unix", path) + } + if err != nil { + return nil, err + } + log.L.WithField("socket", path).Debug("serving api on socket") + return l, nil +} + +func reap(ctx context.Context, logger *log.Entry, signals chan os.Signal) error { + logger.Debug("starting signal loop") + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case s := <-signals: + // Exit signals are handled separately from this loop + // They get registered with this channel so that we can ignore such signals for short-running actions (e.g. `delete`) + switch s { + case unix.SIGCHLD: + if err := reaper.Reap(); err != nil { + logger.WithError(err).Error("reap exit status") + } + case unix.SIGPIPE: + } + } + } +} + +func handleExitSignals(ctx context.Context, logger *log.Entry, cancel context.CancelFunc) { + ch := make(chan os.Signal, 32) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + + for { + select { + case s := <-ch: + logger.WithField("signal", s).Debugf("Caught exit signal") + cancel() + return + case <-ctx.Done(): + return + } + } +} + +func openLog(ctx context.Context, _ string) (io.Writer, error) { + return fifo.OpenFifoDup2(ctx, "log", unix.O_WRONLY, 0700, int(os.Stderr.Fd())) +} + +// awaitPipeReady is a no-op on Unix. Unix domain sockets appear atomically +// when net.Listen("unix", path) is called, so there is no race between the +// shim creating its endpoint and the parent connecting to it. +func awaitPipeReady(_ string) error { + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_windows.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_windows.go new file mode 100644 index 0000000000..b0b03c5f07 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/shim_windows.go @@ -0,0 +1,100 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "context" + "fmt" + "io" + "net" + "os" + "time" + + winio "github.com/Microsoft/go-winio" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/ttrpc" +) + +func setupSignals(config Config) (chan os.Signal, error) { + return nil, errdefs.ErrNotImplemented +} + +func newServer(opts ...ttrpc.ServerOpt) (*ttrpc.Server, error) { + return nil, errdefs.ErrNotImplemented +} + +func subreaper() error { + return errdefs.ErrNotImplemented +} + +func setupDumpStacks(dump chan<- os.Signal) { +} + +func serveListener(path string, fd uintptr) (net.Listener, error) { + return nil, errdefs.ErrNotImplemented +} + +func reap(ctx context.Context, logger *log.Entry, signals chan os.Signal) error { + return errdefs.ErrNotImplemented +} + +func handleExitSignals(ctx context.Context, logger *log.Entry, cancel context.CancelFunc) { +} + +func openLog(ctx context.Context, _ string) (io.Writer, error) { + return nil, errdefs.ErrNotImplemented +} + +// awaitPipeReady polls a named pipe address until it is connectable, +// retrying for up to 5 seconds with 10ms intervals. +// +// The shim "start" helper returns the pipe address before the long-lived +// daemon has called winio.ListenPipe(). Unlike Unix domain sockets (which +// appear atomically on Listen), Windows named pipes may take measurable +// time to appear — especially under load. See #3659, microsoft/hcsshim. +func awaitPipeReady(address string) error { + if address == "" { + return nil + } + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + + var lastErr error + for { + // Use a 1s per-attempt timeout to avoid blocking indefinitely if + // the pipe exists but all instances are busy. + dialTimeout := time.Second + conn, err := winio.DialPipe(address, &dialTimeout) + if err == nil { + conn.Close() + return nil + } + lastErr = err + // Retry on both "pipe not found" and "pipe busy / deadline exceeded" + // — the pipe may still be starting up or temporarily at capacity. + if !os.IsNotExist(err) && err != context.DeadlineExceeded { + return err + } + select { + case <-timer.C: + return fmt.Errorf("pipe %s not ready after 5s: %w", address, lastErr) + default: + time.Sleep(10 * time.Millisecond) + } + } +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/util.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/util.go new file mode 100644 index 0000000000..1b7f0bddf4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/util.go @@ -0,0 +1,287 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/containerd/ttrpc" + "github.com/containerd/typeurl/v2" + + bootapi "github.com/containerd/containerd/api/runtime/bootstrap/v1" + "github.com/containerd/containerd/v2/pkg/atomicfile" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/protobuf/proto" + "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/version" + "github.com/containerd/errdefs" + "github.com/containerd/log" +) + +type CommandConfig struct { + ID string + RuntimePath string + BundlePath string + GRPCAddress string + TTRPCAddress string + WorkDir string + Args []string + Opts *types.Any + Env []string + LogLevel log.Level + Action string // Either "start" or "delete" + SocketDir string +} + +// Command returns the shim command with the provided args and configuration. +// +// Deprecated: this function is internal to the containerd daemon, which uses it to +// invoke the shim binary for "start" and "delete" actions during the shim lifecycle. +// It encodes daemon-specific launch internals — backwards compatibility with older shim +// models and the new Bootstrap protocol used by 2.3+ shims — and is not intended for use +// by external callers. It will be moved into the core containerd runtime package in the future. +func Command(ctx context.Context, config *CommandConfig) (*exec.Cmd, error) { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + self, err := os.Executable() + if err != nil { + return nil, err + } + + // TODO: Remove in a future release in favor of Bootstrap protocol. + args := []string{ + "-namespace", ns, + "-address", config.GRPCAddress, + "-publish-binary", self, + "-id", config.ID, + } + if config.BundlePath != "" { + args = append(args, "-bundle", config.BundlePath) + } + switch config.LogLevel { + case log.DebugLevel, log.TraceLevel: + args = append(args, "-debug") + } + if config.Action == "" { + return nil, errors.New("action must be specified in CommandConfig") + } + + args = append(args, config.Action) + + if len(config.Args) > 0 { + args = append(args, config.Args...) + } + + cmd := exec.CommandContext(ctx, config.RuntimePath, args...) + cmd.Dir = config.WorkDir + cmd.Env = append( + os.Environ(), + "GOMAXPROCS=2", + fmt.Sprintf("%s=2", maxVersionEnv), + // TODO: Remove in a future release in favor of Bootstrap protocol. + fmt.Sprintf("%s=%s", ttrpcAddressEnv, config.TTRPCAddress), + fmt.Sprintf("%s=%s", grpcAddressEnv, config.GRPCAddress), + fmt.Sprintf("%s=%s", namespaceEnv, ns), + ) + if len(config.Env) > 0 { + cmd.Env = append(cmd.Env, config.Env...) + } + cmd.SysProcAttr = getSysProcAttr() + + // Special path when upgrading from 1.7 shims to 2.x containerd. + // v1 shims would fail if passed wrong stdin data. + // TODO: Remove in a future release in favor of Bootstrap protocol. + execName := filepath.Base(config.RuntimePath) + if strings.Contains(execName, "shim-runc-v1") || strings.Contains(execName, "shim-runhcs-v1") { + if config.Opts != nil { + d, err := proto.Marshal(config.Opts) + if err != nil { + return nil, err + } + cmd.Stdin = bytes.NewReader(d) + } + } else if config.Action == "start" { + // Use the new Bootstrap protocol for all newer shims. + params := bootapi.BootstrapParams{ + InstanceID: config.ID, + Namespace: ns, + LogLevel: bootapi.LogLevelFromString(config.LogLevel.String()), + ContainerdVersion: version.Version, + ContainerdGrpcAddress: config.GRPCAddress, + ContainerdTtrpcAddress: config.TTRPCAddress, + ContainerdBinary: self, + } + if config.SocketDir != "" { + params.SocketDir = &config.SocketDir + } + + if config.Opts != nil { + if err := params.AddExtension(config.Opts); err != nil { + return nil, fmt.Errorf("unable to add runtime options extensions: %w", err) + } + } + + data, err := proto.Marshal(¶ms) + if err != nil { + return nil, fmt.Errorf("unable to marshal bootstrap params: %w", err) + } + + cmd.Stdin = bytes.NewReader(data) + } + + return cmd, nil +} + +// BinaryName returns the shim binary name from the runtime name, +// empty string returns means runtime name is invalid +func BinaryName(runtime string) string { + // runtime name should format like $prefix.name.version + parts := strings.Split(runtime, ".") + if len(parts) < 2 || parts[0] == "" { + return "" + } + + return fmt.Sprintf(shimBinaryFormat, parts[len(parts)-2], parts[len(parts)-1]) +} + +// BinaryPath returns the full path for the shim binary from the runtime name, +// empty string returns means runtime name is invalid +func BinaryPath(runtime string) string { + dir := filepath.Dir(runtime) + binary := BinaryName(runtime) + + path, err := filepath.Abs(filepath.Join(dir, binary)) + if err != nil { + return "" + } + + return path +} + +// Connect to the provided address +func Connect(address string, d func(string, time.Duration) (net.Conn, error)) (net.Conn, error) { + return d(address, 100*time.Second) +} + +// WritePidFile writes a pid file atomically +func WritePidFile(path string, pid int) error { + path, err := filepath.Abs(path) + if err != nil { + return err + } + f, err := atomicfile.New(path, 0o644) + if err != nil { + return err + } + _, err = fmt.Fprintf(f, "%d", pid) + if err != nil { + f.Cancel() + return err + } + return f.Close() +} + +// ErrNoAddress is returned when the address file has no content +var ErrNoAddress = errors.New("no shim address") + +// ReadAddress returns the shim's socket address from the path +func ReadAddress(path string) (string, error) { + path, err := filepath.Abs(path) + if err != nil { + return "", err + } + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + if len(data) == 0 { + return "", ErrNoAddress + } + return string(data), nil +} + +// ReadRuntimeOptions reads config bytes from io.Reader and unmarshals it into the provided type. +// The type must be registered with typeurl. +// +// The function will return ErrNotFound, if the config is not provided. +// And ErrInvalidArgument, if unable to cast the config to the provided type T. +func ReadRuntimeOptions[T any](reader io.Reader) (T, error) { + var config T + + data, err := io.ReadAll(reader) + if err != nil { + return config, fmt.Errorf("failed to read config bytes from stdin: %w", err) + } + + if len(data) == 0 { + return config, errdefs.ErrNotFound + } + + var any types.Any + if err := proto.Unmarshal(data, &any); err != nil { + return config, err + } + + v, err := typeurl.UnmarshalAny(&any) + if err != nil { + return config, err + } + + config, ok := v.(T) + if !ok { + return config, fmt.Errorf("invalid type %T: %w", v, errdefs.ErrInvalidArgument) + } + + return config, nil +} + +// chainUnaryServerInterceptors creates a single ttrpc server interceptor from +// a chain of many interceptors executed from first to last. +func chainUnaryServerInterceptors(interceptors ...ttrpc.UnaryServerInterceptor) ttrpc.UnaryServerInterceptor { + n := len(interceptors) + + // force to use default interceptor in ttrpc + if n == 0 { + return nil + } + + return func(ctx context.Context, unmarshal ttrpc.Unmarshaler, info *ttrpc.UnaryServerInfo, method ttrpc.Method) (any, error) { + currentMethod := method + + for i := n - 1; i > 0; i-- { + interceptor := interceptors[i] + innerMethod := currentMethod + + currentMethod = func(currentCtx context.Context, currentUnmarshal func(any) error) (any, error) { + return interceptor(currentCtx, currentUnmarshal, info, innerMethod) + } + } + return interceptors[0](ctx, unmarshal, info, currentMethod) + } +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/util_unix.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/util_unix.go new file mode 100644 index 0000000000..d8779c51c1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/util_unix.go @@ -0,0 +1,321 @@ +//go:build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "bufio" + "context" + "crypto/sha256" + "errors" + "fmt" + "io" + "math" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/containerd/log" + "github.com/mdlayher/vsock" + + "github.com/containerd/containerd/v2/defaults" + "github.com/containerd/containerd/v2/pkg/namespaces" + "github.com/containerd/containerd/v2/pkg/sys" +) + +const ( + shimBinaryFormat = "containerd-shim-%s-%s" + socketPathLimit = 106 + protoVsock = "vsock" + protoHybridVsock = "hvsock" + protoUnix = "unix" +) + +func getSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{ + Setpgid: true, + } +} + +// AdjustOOMScore sets the OOM score for the process to the parents OOM score +1 +// to ensure that they parent has a lower* score than the shim +// if not already at the maximum OOM Score +func AdjustOOMScore(pid int) error { + parent := os.Getppid() + score, err := sys.GetOOMScoreAdj(parent) + if err != nil { + return fmt.Errorf("get parent OOM score: %w", err) + } + shimScore := score + 1 + if err := sys.AdjustOOMScore(pid, shimScore); err != nil { + return fmt.Errorf("set shim OOM score: %w", err) + } + return nil +} + +// SocketAddress returns a socket address in the default state directory +// +// TODO: Consider deprecating for CreateSocketAddress +func SocketAddress(ctx context.Context, socketPath, id string, debug bool) (string, error) { + return CreateSocketAddress(ctx, filepath.Join(defaults.DefaultStateDir, "s"), socketPath, id, debug) +} + +// CreateSocketAddress returns a shim socket address under socketRoot. +func CreateSocketAddress(ctx context.Context, socketRoot, socketPath, id string, debug bool) (string, error) { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return "", err + } + path := filepath.Join(socketPath, ns, id) + if debug { + path = filepath.Join(path, "debug") + } + d := sha256.Sum256([]byte(path)) + return fmt.Sprintf("unix://%s/%x", socketRoot, d), nil +} + +// AnonDialer returns a dialer for a socket +func AnonDialer(address string, timeout time.Duration) (net.Conn, error) { + proto, addr, ok := strings.Cut(address, "://") + if !ok { + return net.DialTimeout("unix", socket(address).path(), timeout) + } + switch proto { + case protoVsock: + // vsock dialer can not set timeout + return dialVsock(addr) + case protoHybridVsock: + return dialHybridVsock(addr, timeout) + case protoUnix: + return net.DialTimeout("unix", socket(address).path(), timeout) + default: + return nil, fmt.Errorf("unsupported protocol: %s", proto) + } +} + +// AnonReconnectDialer returns a dialer for an existing socket on reconnection +func AnonReconnectDialer(address string, timeout time.Duration) (net.Conn, error) { + return AnonDialer(address, timeout) +} + +// NewSocket returns a new socket +func NewSocket(address string) (*net.UnixListener, error) { + var ( + sock = socket(address) + path = sock.path() + isAbstract = sock.isAbstract() + // Socket file permissions: read/write for owner only + sockPerm = os.FileMode(0600) + // Directory permissions: need execute bit for traversal + dirPerm = os.FileMode(0700) + ) + + if !isAbstract { + if err := os.MkdirAll(filepath.Dir(path), dirPerm); err != nil { + return nil, fmt.Errorf("mkdir failed for %s: %w", path, err) + } + } + l, err := net.Listen("unix", path) + if err != nil { + return nil, err + } + + if !isAbstract { + if err := os.Chmod(path, sockPerm); err != nil { + os.Remove(sock.path()) + l.Close() + return nil, fmt.Errorf("chmod failed for %s: %w", path, err) + } + } + + return l.(*net.UnixListener), nil +} + +const abstractSocketPrefix = "\x00" + +type socket string + +func (s socket) isAbstract() bool { + return !strings.HasPrefix(string(s), "unix://") +} + +func (s socket) path() string { + path := strings.TrimPrefix(string(s), "unix://") + // if there was no trim performed, we assume an abstract socket + if len(path) == len(s) { + path = abstractSocketPrefix + path + } + return path +} + +// RemoveSocket removes the socket at the specified address if +// it exists on the filesystem +func RemoveSocket(address string) error { + sock := socket(address) + if !sock.isAbstract() { + return os.Remove(sock.path()) + } + return nil +} + +// SocketEaddrinuse returns true if the provided error is caused by the +// EADDRINUSE error number +func SocketEaddrinuse(err error) bool { + var netErr *net.OpError + if errors.As(err, &netErr) { + if netErr.Op != "listen" { + return false + } + return errors.Is(err, syscall.EADDRINUSE) + } + return false +} + +// CanConnect returns true if the socket provided at the address +// is accepting new connections +func CanConnect(address string) bool { + conn, err := AnonDialer(address, 100*time.Millisecond) + if err != nil { + return false + } + conn.Close() + return true +} + +func hybridVsockDialer(addr string, port uint64, timeout time.Duration) (net.Conn, error) { + timeoutCh := time.After(timeout) + // Do 10 retries before timeout + retryInterval := timeout / 10 + for { + conn, err := net.DialTimeout("unix", addr, timeout) + if err != nil { + return nil, err + } + if _, err = fmt.Fprintln(conn, "CONNECT", port); err != nil { + conn.Close() + return nil, err + } + errChan := make(chan error, 1) + go func() { + reader := bufio.NewReader(conn) + response, err := reader.ReadString('\n') + if err != nil { + errChan <- err + return + } + if strings.Contains(response, "OK") { + errChan <- nil + } else { + errChan <- fmt.Errorf("hybrid vsock handshake response error: %s", response) + } + }() + select { + case err = <-errChan: + if err != nil { + conn.Close() + // When it is EOF, maybe the server side is not ready. + if err == io.EOF { + log.G(context.Background()).Warnf("Read hybrid vsock got EOF, server may not ready") + time.Sleep(retryInterval) + continue + } + return nil, err + } + return conn, nil + case <-timeoutCh: + conn.Close() + return nil, fmt.Errorf("timeout waiting for hybrid vsocket handshake of %s:%d", addr, port) + } + } + +} + +func dialVsock(address string) (net.Conn, error) { + contextIDString, portString, ok := strings.Cut(address, ":") + if !ok { + return nil, fmt.Errorf("invalid vsock address %s", address) + } + contextID, err := strconv.ParseUint(contextIDString, 10, 0) + if err != nil { + return nil, fmt.Errorf("failed to parse vsock context id %s, %v", contextIDString, err) + } + if contextID > math.MaxUint32 { + return nil, fmt.Errorf("vsock context id %d is invalid", contextID) + } + port, err := strconv.ParseUint(portString, 10, 0) + if err != nil { + return nil, fmt.Errorf("failed to parse vsock port %s, %v", portString, err) + } + if port > math.MaxUint32 { + return nil, fmt.Errorf("vsock port %d is invalid", port) + } + return vsock.Dial(uint32(contextID), uint32(port), &vsock.Config{}) +} + +func dialHybridVsock(address string, timeout time.Duration) (net.Conn, error) { + addr, portString, ok := strings.Cut(address, ":") + if !ok { + return nil, fmt.Errorf("invalid hybrid vsock address %s", address) + } + port, err := strconv.ParseUint(portString, 10, 0) + if err != nil { + return nil, fmt.Errorf("failed to parse hybrid vsock port %s, %v", portString, err) + } + if port > math.MaxUint32 { + return nil, fmt.Errorf("hybrid vsock port %d is invalid", port) + } + return hybridVsockDialer(addr, port, timeout) +} + +const socketDirLink = "s" + +// writeSocketDir creates a symlink in the bundle working directory pointing +// to the socket directory so the long-running server process can resolve it +// for cleanup. +func writeSocketDir(dir string) error { + if _, err := os.Lstat(socketDirLink); err == nil { + if err := os.Remove(socketDirLink); err != nil { + return fmt.Errorf("remove existing socket dir link: %w", err) + } + } + return os.Symlink(dir, socketDirLink) +} + +func cleanupSockets(ctx context.Context) { + if address, err := ReadAddress("address"); err == nil { + _ = RemoveSocket(address) + } + socketDir, _ := os.Readlink(socketDirLink) + if socketDir == "" { + socketDir = filepath.Join(defaults.DefaultStateDir, "s") + } + if len(socketFlag) > 0 { + _ = RemoveSocket("unix://" + socketFlag) + } else if address, err := CreateSocketAddress(ctx, socketDir, addressFlag, id, false); err == nil { + _ = RemoveSocket(address) + } + if len(debugSocketFlag) > 0 { + _ = RemoveSocket("unix://" + debugSocketFlag) + } else if address, err := CreateSocketAddress(ctx, socketDir, addressFlag, id, true); err == nil { + _ = RemoveSocket(address) + } +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shim/util_windows.go b/vendor/github.com/containerd/containerd/v2/pkg/shim/util_windows.go new file mode 100644 index 0000000000..b3feedc2fb --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shim/util_windows.go @@ -0,0 +1,120 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shim + +import ( + "context" + "fmt" + "net" + "os" + "syscall" + "time" + + winio "github.com/Microsoft/go-winio" +) + +const shimBinaryFormat = "containerd-shim-%s-%s.exe" + +func getSysProcAttr() *syscall.SysProcAttr { + return nil +} + +// AnonReconnectDialer connects to a named pipe that should already exist. +// It fails immediately if the pipe is not found, rather than retrying. +// +// Use this when reconnecting to a shim that is expected to be running +// (e.g. after a containerd restart). If the pipe doesn't exist, the shim +// is dead and there's no point waiting. +// +// This fail-fast behavior is critical on Windows: the Service Control Manager +// enforces a ~30s startup deadline on containerd. If reconnecting to many dead +// shims, a 5s retry per shim (as in AnonDialer) could exceed that budget and +// cause the SCM to kill containerd. See #3659. +// +// On Unix, this function simply calls AnonDialer since Unix domain sockets +// appear atomically and the distinction is unnecessary. +func AnonReconnectDialer(address string, timeout time.Duration) (net.Conn, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + c, err := winio.DialPipeContext(ctx, address) + if os.IsNotExist(err) { + return nil, fmt.Errorf("npipe not found on reconnect: %w", os.ErrNotExist) + } else if err == context.DeadlineExceeded { + return nil, fmt.Errorf("timed out waiting for npipe %s: %w", address, err) + } else if err != nil { + return nil, err + } + return c, nil +} + +// AnonDialer connects to a named pipe, retrying for up to 5 seconds if the +// pipe does not yet exist. +// +// Use this when connecting to a newly started shim. The shim's "start" helper +// returns the pipe address before the long-lived shim daemon has created the +// pipe, so a brief retry window is expected. 5 seconds is generous enough for +// any healthy shim to begin serving. +// +// Unlike Unix domain sockets (which appear atomically on Listen), Windows named +// pipes may take measurable time to appear — especially under load on CI +// runners. See #2519, #2692. +func AnonDialer(address string, timeout time.Duration) (net.Conn, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + // If there is nobody serving the pipe we limit the timeout for this case to + // 5 seconds because any shim that would serve this endpoint should serve it + // within 5 seconds. + serveTimer := time.NewTimer(5 * time.Second) + defer serveTimer.Stop() + for { + c, err := winio.DialPipeContext(ctx, address) + if err != nil { + if os.IsNotExist(err) { + select { + case <-serveTimer.C: + return nil, fmt.Errorf("pipe not found before timeout: %w", os.ErrNotExist) + default: + // Wait 10ms for the shim to serve and try again. + time.Sleep(10 * time.Millisecond) + continue + } + } else if err == context.DeadlineExceeded { + return nil, fmt.Errorf("timed out waiting for npipe %s: %w", address, err) + } + return nil, err + } + return c, nil + } +} + +// RemoveSocket removes the socket at the specified address if +// it exists on the filesystem +func RemoveSocket(address string) error { + return nil +} + +func writeSocketDir(string) error { + return nil +} + +func cleanupSockets(context.Context) { + if address, err := ReadAddress("address"); err == nil { + _ = RemoveSocket(address) + } +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/shutdown/shutdown.go b/vendor/github.com/containerd/containerd/v2/pkg/shutdown/shutdown.go new file mode 100644 index 0000000000..12ffac1457 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/shutdown/shutdown.go @@ -0,0 +1,115 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package shutdown + +import ( + "context" + "errors" + "sync" + "time" + + "golang.org/x/sync/errgroup" +) + +// ErrShutdown is the error condition when a context has been fully shutdown +var ErrShutdown = errors.New("shutdown") + +// Service is used to facilitate shutdown by through callback +// registration and shutdown initiation +type Service interface { + // Shutdown initiates shutdown + Shutdown() + // RegisterCallback registers functions to be called on shutdown and before + // the shutdown channel is closed. A callback error will propagate to the + // context error + RegisterCallback(func(context.Context) error) + // Done returns a channel that's closed when all shutdown callbacks are invoked. + Done() <-chan struct{} + // Err returns nil if Done is not yet closed. + // If Done is closed, Err returns first failed callback error or ErrShutdown. + Err() error +} + +// WithShutdown returns a context which is similar to a cancel context, but +// with callbacks which can propagate to the context error. Unlike a cancel +// context, the shutdown context cannot be canceled from the parent context. +// However, future child contexes will be canceled upon shutdown. +func WithShutdown(ctx context.Context) (context.Context, Service) { + ss := &shutdownService{ + Context: ctx, + doneC: make(chan struct{}), + timeout: 30 * time.Second, + } + return ss, ss +} + +type shutdownService struct { + context.Context + + mu sync.Mutex + isShutdown bool + callbacks []func(context.Context) error + doneC chan struct{} + err error + timeout time.Duration +} + +func (s *shutdownService) Shutdown() { + s.mu.Lock() + defer s.mu.Unlock() + if s.isShutdown { + return + } + s.isShutdown = true + + go func(callbacks []func(context.Context) error) { + ctx, cancel := context.WithTimeout(context.Background(), s.timeout) + defer cancel() + grp, ctx := errgroup.WithContext(ctx) + for i := range callbacks { + fn := callbacks[i] + grp.Go(func() error { return fn(ctx) }) + } + err := grp.Wait() + if err == nil { + err = ErrShutdown + } + s.mu.Lock() + s.err = err + close(s.doneC) + s.mu.Unlock() + }(s.callbacks) +} + +func (s *shutdownService) Done() <-chan struct{} { + return s.doneC +} + +func (s *shutdownService) Err() error { + s.mu.Lock() + defer s.mu.Unlock() + return s.err +} + +func (s *shutdownService) RegisterCallback(fn func(context.Context) error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.callbacks == nil { + s.callbacks = []func(context.Context) error{} + } + s.callbacks = append(s.callbacks, fn) +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/sys/reaper/reaper_unix.go b/vendor/github.com/containerd/containerd/v2/pkg/sys/reaper/reaper_unix.go new file mode 100644 index 0000000000..0a7abf4f9b --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/sys/reaper/reaper_unix.go @@ -0,0 +1,289 @@ +//go:build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package reaper + +import ( + "errors" + "fmt" + "maps" + "os/exec" + "runtime" + "sync" + "syscall" + "time" + + runc "github.com/containerd/go-runc" + "golang.org/x/sys/unix" +) + +// ErrNoSuchProcess is returned when the process no longer exists +var ErrNoSuchProcess = errors.New("no such process") + +const bufferSize = 32 + +type subscriber struct { + sync.Mutex + c chan runc.Exit + closed bool +} + +func (s *subscriber) close() { + s.Lock() + if s.closed { + s.Unlock() + return + } + close(s.c) + s.closed = true + s.Unlock() +} + +func (s *subscriber) do(fn func()) { + s.Lock() + fn() + s.Unlock() +} + +// Reap should be called when the process receives an SIGCHLD. Reap will reap +// all exited processes and close their wait channels +func Reap() error { + now := time.Now() + exits, err := reap(false) + for _, e := range exits { + done := Default.notify(runc.Exit{ + Timestamp: now, + Pid: e.Pid, + Status: e.Status, + }) + + select { + case <-done: + case <-time.After(1 * time.Second): + } + } + return err +} + +// Default is the default monitor initialized for the package +var Default = &Monitor{ + subscribers: make(map[chan runc.Exit]*subscriber), +} + +// Monitor monitors the underlying system for process status changes +type Monitor struct { + sync.Mutex + + subscribers map[chan runc.Exit]*subscriber +} + +// Start starts the command and registers the process with the reaper +func (m *Monitor) Start(c *exec.Cmd) (chan runc.Exit, error) { + ec := m.Subscribe() + if err := c.Start(); err != nil { + m.Unsubscribe(ec) + return nil, err + } + return ec, nil +} + +// StartLocked starts the command and registers the process with the reaper +func (m *Monitor) StartLocked(c *exec.Cmd) (chan runc.Exit, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + return m.Start(c) +} + +// Wait blocks until a process is signal as dead. +// User should rely on the value of the exit status to determine if the +// command was successful or not. +func (m *Monitor) Wait(c *exec.Cmd, ec chan runc.Exit) (int, error) { + for e := range ec { + if e.Pid == c.Process.Pid { + // make sure we flush all IO + c.Wait() + m.Unsubscribe(ec) + return e.Status, nil + } + } + // return no such process if the ec channel is closed and no more exit + // events will be sent + return -1, ErrNoSuchProcess +} + +// WaitTimeout is used to skip the blocked command and kill the left process. +func (m *Monitor) WaitTimeout(c *exec.Cmd, ec chan runc.Exit, timeout time.Duration) (int, error) { + type exitStatusWrapper struct { + status int + err error + } + + // capacity can make sure that the following goroutine will not be + // blocked if there is no receiver when timeout. + waitCh := make(chan *exitStatusWrapper, 1) + go func() { + defer close(waitCh) + + status, err := m.Wait(c, ec) + waitCh <- &exitStatusWrapper{ + status: status, + err: err, + } + }() + + timer := time.NewTimer(timeout) + defer timer.Stop() + + select { + case <-timer.C: + syscall.Kill(c.Process.Pid, syscall.SIGKILL) + return 0, fmt.Errorf("timeout %v for cmd(pid=%d): %s, %s", timeout, c.Process.Pid, c.Path, c.Args) + case res := <-waitCh: + return res.status, res.err + } +} + +// Subscribe to process exit changes +func (m *Monitor) Subscribe() chan runc.Exit { + c := make(chan runc.Exit, bufferSize) + m.Lock() + m.subscribers[c] = &subscriber{ + c: c, + } + m.Unlock() + return c +} + +// Unsubscribe to process exit changes +func (m *Monitor) Unsubscribe(c chan runc.Exit) { + m.Lock() + s, ok := m.subscribers[c] + if !ok { + m.Unlock() + return + } + s.close() + delete(m.subscribers, c) + m.Unlock() +} + +func (m *Monitor) getSubscribers() map[chan runc.Exit]*subscriber { + out := make(map[chan runc.Exit]*subscriber) + m.Lock() + maps.Copy(out, m.subscribers) + m.Unlock() + return out +} + +func (m *Monitor) notify(e runc.Exit) chan struct{} { + const timeout = 1 * time.Millisecond + var ( + done = make(chan struct{}, 1) + timer = time.NewTimer(timeout) + success = make(map[chan runc.Exit]struct{}) + ) + stop(timer, true) + + go func() { + defer close(done) + + for { + var ( + failed int + subscribers = m.getSubscribers() + ) + for _, s := range subscribers { + s.do(func() { + if s.closed { + return + } + if _, ok := success[s.c]; ok { + return + } + timer.Reset(timeout) + recv := true + select { + case s.c <- e: + success[s.c] = struct{}{} + case <-timer.C: + recv = false + failed++ + } + stop(timer, recv) + }) + } + // all subscribers received the message + if failed == 0 { + return + } + } + }() + return done +} + +func stop(timer *time.Timer, recv bool) { + if !timer.Stop() && recv { + <-timer.C + } +} + +// exit is the wait4 information from an exited process +type exit struct { + Pid int + Status int +} + +// reap reaps all child processes for the calling process and returns their +// exit information +func reap(wait bool) (exits []exit, err error) { + var ( + ws unix.WaitStatus + rus unix.Rusage + ) + flag := unix.WNOHANG + if wait { + flag = 0 + } + for { + pid, err := unix.Wait4(-1, &ws, flag, &rus) + if err != nil { + if err == unix.ECHILD { + return exits, nil + } + return exits, err + } + if pid <= 0 { + return exits, nil + } + exits = append(exits, exit{ + Pid: pid, + Status: exitStatus(ws), + }) + } +} + +const exitSignalOffset = 128 + +// exitStatus returns the correct exit status for a process based on if it +// was signaled or exited cleanly +func exitStatus(status unix.WaitStatus) int { + if status.Signaled() { + return exitSignalOffset + int(status.Signal()) + } + return status.ExitStatus() +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/sys/reaper/reaper_utils_linux.go b/vendor/github.com/containerd/containerd/v2/pkg/sys/reaper/reaper_utils_linux.go new file mode 100644 index 0000000000..cadcdc42c3 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/sys/reaper/reaper_utils_linux.go @@ -0,0 +1,39 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package reaper + +import ( + "unsafe" + + "golang.org/x/sys/unix" +) + +// SetSubreaper sets the value i as the subreaper setting for the calling process +func SetSubreaper(i int) error { + return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0) +} + +// GetSubreaper returns the subreaper setting for the calling process +func GetSubreaper() (int, error) { + var i uintptr + + if err := unix.Prctl(unix.PR_GET_CHILD_SUBREAPER, uintptr(unsafe.Pointer(&i)), 0, 0, 0); err != nil { + return -1, err + } + + return int(i), nil +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/timeout/timeout.go b/vendor/github.com/containerd/containerd/v2/pkg/timeout/timeout.go new file mode 100644 index 0000000000..22cd238a4f --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/timeout/timeout.go @@ -0,0 +1,65 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package timeout + +import ( + "context" + "maps" + "sync" + "time" +) + +var ( + mu sync.RWMutex + timeouts = make(map[string]time.Duration) + + // DefaultTimeout of the timeout package + DefaultTimeout = 1 * time.Second +) + +// Set the timeout for the key +func Set(key string, t time.Duration) { + mu.Lock() + timeouts[key] = t + mu.Unlock() +} + +// Get returns the timeout for the provided key +func Get(key string) time.Duration { + mu.RLock() + t, ok := timeouts[key] + mu.RUnlock() + if !ok { + t = DefaultTimeout + } + return t +} + +// WithContext returns a context with the specified timeout for the provided key +func WithContext(ctx context.Context, key string) (context.Context, func()) { + t := Get(key) + return context.WithTimeout(ctx, t) +} + +// All returns all keys and their timeouts +func All() map[string]time.Duration { + out := make(map[string]time.Duration) + mu.RLock() + defer mu.RUnlock() + maps.Copy(out, timeouts) + return out +} diff --git a/vendor/github.com/containerd/containerd/v2/pkg/ttrpcutil/client.go b/vendor/github.com/containerd/containerd/v2/pkg/ttrpcutil/client.go new file mode 100644 index 0000000000..0a54ceff53 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/pkg/ttrpcutil/client.go @@ -0,0 +1,123 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpcutil + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1" + "github.com/containerd/containerd/v2/pkg/dialer" + "github.com/containerd/ttrpc" +) + +const ttrpcDialTimeout = 5 * time.Second + +type ttrpcConnector func() (*ttrpc.Client, error) + +// Client is the client to interact with TTRPC part of containerd server (plugins, events) +type Client struct { + mu sync.Mutex + connector ttrpcConnector + client *ttrpc.Client + closed bool +} + +// NewClient returns a new containerd TTRPC client that is connected to the containerd instance provided by address +func NewClient(address string, opts ...ttrpc.ClientOpts) (*Client, error) { + connector := func() (*ttrpc.Client, error) { + ctx, cancel := context.WithTimeout(context.Background(), ttrpcDialTimeout) + defer cancel() + conn, err := dialer.ContextDialer(ctx, address) + if err != nil { + return nil, fmt.Errorf("failed to connect: %w", err) + } + + client := ttrpc.NewClient(conn, opts...) + return client, nil + } + + return &Client{ + connector: connector, + }, nil +} + +// Reconnect re-establishes the TTRPC connection to the containerd daemon +func (c *Client) Reconnect() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.connector == nil { + return errors.New("unable to reconnect to containerd, no connector available") + } + + if c.closed { + return errors.New("client is closed") + } + + if c.client != nil { + if err := c.client.Close(); err != nil { + return err + } + } + + client, err := c.connector() + if err != nil { + return err + } + + c.client = client + return nil +} + +// EventsService creates an EventsService client +func (c *Client) EventsService() (v1.TTRPCEventsService, error) { + client, err := c.Client() + if err != nil { + return nil, err + } + return v1.NewTTRPCEventsClient(client), nil +} + +// Client returns the underlying TTRPC client object +func (c *Client) Client() (*ttrpc.Client, error) { + c.mu.Lock() + defer c.mu.Unlock() + if c.client == nil { + client, err := c.connector() + if err != nil { + return nil, err + } + c.client = client + } + return c.client, nil +} + +// Close closes the clients TTRPC connection to containerd +func (c *Client) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + c.closed = true + if c.client != nil { + return c.client.Close() + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/content/local/plugin/plugin.go b/vendor/github.com/containerd/containerd/v2/plugins/content/local/plugin/plugin.go new file mode 100644 index 0000000000..51d6d4726d --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/content/local/plugin/plugin.go @@ -0,0 +1,37 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package plugin + +import ( + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/content/local" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ContentPlugin, + ID: "content", + InitFn: func(ic *plugin.InitContext) (any, error) { + root := ic.Properties[plugins.PropertyRootDir] + ic.Meta.Exports["root"] = root + return local.NewStore(root) + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/diff/lcow/lcow.go b/vendor/github.com/containerd/containerd/v2/plugins/diff/lcow/lcow.go new file mode 100644 index 0000000000..7ddbd0b404 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/diff/lcow/lcow.go @@ -0,0 +1,217 @@ +//go:build windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package lcow + +import ( + "context" + "fmt" + "io" + "os" + "path" + "runtime" + "time" + + "github.com/Microsoft/go-winio/pkg/security" + "github.com/Microsoft/hcsshim/ext4/tar2ext4" + "github.com/containerd/containerd/v2/core/content" + "github.com/containerd/containerd/v2/core/diff" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + digest "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +const ( + // maxLcowVhdSizeGB is the max size in GB of any layer + maxLcowVhdSizeGB = 128 * 1024 * 1024 * 1024 +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.DiffPlugin, + ID: "windows-lcow", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + md, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + ic.Meta.Platforms = append(ic.Meta.Platforms, ocispec.Platform{ + OS: "linux", + Architecture: runtime.GOARCH, + }) + return NewWindowsLcowDiff(md.(*metadata.DB).ContentStore()) + }, + }) +} + +// CompareApplier handles both comparison and +// application of layer diffs. +type CompareApplier interface { + diff.Applier + diff.Comparer +} + +// windowsLcowDiff does filesystem comparison and application +// for Windows specific Linux layer diffs. +type windowsLcowDiff struct { + store content.Store +} + +var emptyDesc = ocispec.Descriptor{} + +// NewWindowsLcowDiff is the Windows LCOW container layer implementation +// for comparing and applying Linux filesystem layers on Windows +func NewWindowsLcowDiff(store content.Store) (CompareApplier, error) { + return windowsLcowDiff{ + store: store, + }, nil +} + +// Apply applies the content associated with the provided digests onto the +// provided mounts. Archive content will be extracted and decompressed if +// necessary. +func (s windowsLcowDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) { + t1 := time.Now() + defer func() { + if err == nil { + log.G(ctx).WithFields(log.Fields{ + "d": time.Since(t1), + "digest": desc.Digest, + "size": desc.Size, + "media": desc.MediaType, + }).Debugf("diff applied") + } + }() + + var config diff.ApplyConfig + for _, o := range opts { + if err := o(ctx, desc, &config); err != nil { + return emptyDesc, fmt.Errorf("failed to apply config opt: %w", err) + } + } + + layer, _, err := mountsToLayerAndParents(mounts) + if err != nil { + return emptyDesc, err + } + + ra, err := s.store.ReaderAt(ctx, desc) + if err != nil { + return emptyDesc, fmt.Errorf("failed to get reader from content store: %w", err) + } + defer ra.Close() + + processor := diff.NewProcessorChain(desc.MediaType, content.NewReader(ra)) + for { + if processor, err = diff.GetProcessor(ctx, processor, config.ProcessorPayloads); err != nil { + return emptyDesc, fmt.Errorf("failed to get stream processor for %s: %w", desc.MediaType, err) + } + if processor.MediaType() == ocispec.MediaTypeImageLayer { + break + } + } + defer processor.Close() + + // Calculate the Digest as we go + digester := digest.Canonical.Digester() + rc := &readCounter{ + r: io.TeeReader(processor, digester.Hash()), + } + + layerPath := path.Join(layer, "layer.vhd") + outFile, err := os.Create(layerPath) + if err != nil { + return emptyDesc, err + } + defer func() { + if err != nil { + outFile.Close() + os.Remove(layerPath) + } + }() + + err = tar2ext4.Convert(rc, outFile, tar2ext4.ConvertWhiteout, tar2ext4.AppendVhdFooter, tar2ext4.MaximumDiskSize(maxLcowVhdSizeGB)) + if err != nil { + return emptyDesc, fmt.Errorf("failed to convert tar2ext4 vhd: %w", err) + } + err = outFile.Sync() + if err != nil { + return emptyDesc, fmt.Errorf("failed to sync tar2ext4 vhd to disk: %w", err) + } + outFile.Close() + + // Read any trailing data + if _, err := io.Copy(io.Discard, rc); err != nil { + return emptyDesc, err + } + + err = security.GrantVmGroupAccess(layerPath) + if err != nil { + return emptyDesc, fmt.Errorf("failed GrantVmGroupAccess on layer vhd: %v: %w", layerPath, err) + } + + return ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageLayer, + Size: rc.c, + Digest: digester.Digest(), + }, nil +} + +// Compare creates a diff between the given mounts and uploads the result +// to the content store. +func (s windowsLcowDiff) Compare(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) { + return emptyDesc, fmt.Errorf("windowsLcowDiff does not implement Compare method: %w", errdefs.ErrNotImplemented) +} + +type readCounter struct { + r io.Reader + c int64 +} + +func (rc *readCounter) Read(p []byte) (n int, err error) { + n, err = rc.r.Read(p) + rc.c += int64(n) + return +} + +func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) { + if len(mounts) != 1 { + return "", nil, fmt.Errorf("number of mounts should always be 1 for Windows lcow-layers: %w", errdefs.ErrInvalidArgument) + } + mnt := mounts[0] + if mnt.Type != "lcow-layer" { + return "", nil, fmt.Errorf("mount layer type must be lcow-layer: %w", errdefs.ErrNotImplemented) + } + + parentLayerPaths, err := mnt.GetParentPaths() + if err != nil { + return "", nil, err + } + + return mnt.Source, parentLayerPaths, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/diff/walking/plugin/plugin.go b/vendor/github.com/containerd/containerd/v2/plugins/diff/walking/plugin/plugin.go new file mode 100644 index 0000000000..8cf666b891 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/diff/walking/plugin/plugin.go @@ -0,0 +1,68 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package plugin + +import ( + "errors" + + "github.com/containerd/containerd/v2/core/diff" + "github.com/containerd/containerd/v2/core/diff/apply" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/diff/walking" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.DiffPlugin, + ID: "walking", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + plugins.MountManagerPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + md, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + var mm mount.Manager + if mountsI, err := ic.GetSingle(plugins.MountManagerPlugin); err == nil { + mm = mountsI.(mount.Manager) + } else if !errors.Is(err, plugin.ErrPluginNotFound) { + return nil, err + } + + ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) + cs := md.(*metadata.DB).ContentStore() + + return diffPlugin{ + Comparer: walking.NewWalkingDiff(cs), + Applier: apply.NewFileSystemApplierWithMountManager(cs, mm), + }, nil + }, + }) +} + +type diffPlugin struct { + diff.Comparer + diff.Applier +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/diff/windows/cimfs.go b/vendor/github.com/containerd/containerd/v2/plugins/diff/windows/cimfs.go new file mode 100644 index 0000000000..321273a70f --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/diff/windows/cimfs.go @@ -0,0 +1,311 @@ +//go:build windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package windows + +import ( + "context" + "encoding/json" + "fmt" + "io" + "path/filepath" + "strings" + "time" + + "github.com/Microsoft/hcsshim/pkg/cimfs" + ocicimlayer "github.com/Microsoft/hcsshim/pkg/ociwclayer/cim" + "github.com/containerd/containerd/v2/core/content" + "github.com/containerd/containerd/v2/core/diff" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.DiffPlugin, + ID: "cimfs", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + md, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + if !cimfs.IsCimFSSupported() { + return nil, fmt.Errorf("host windows version doesn't support CimFS: %w", plugin.ErrSkipPlugin) + } + ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) + return NewCimDiff(md.(*metadata.DB).ContentStore()) + }, + }) + + registry.Register(&plugin.Registration{ + Type: plugins.DiffPlugin, + ID: "blockcim", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + if !cimfs.IsBlockCimSupported() { + return nil, fmt.Errorf("host OS version doesn't support block CIMs: %w", plugin.ErrSkipPlugin) + } + + md, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) + return NewBlockCimDiff(md.(*metadata.DB).ContentStore()) + }, + }) +} + +// cimApplyFunc is an applier function used when extracting layers into the CimFS format. +// Using the archive.applyFunc is very limiting for CimFS use cases as it forces you to +// represent layers as a single string. So for CimFS we skip the archive.Apply call and +// directly call into the layer writer +type cimApplyFunc func(context.Context, io.Reader) (int64, error) + +// cimDiff does filesystem comparison and application +// for CimFS specific layer diffs. +type cimDiff struct { + store content.Store +} + +// NewCimDiff is the Windows cim container layer implementation +// for comparing and applying filesystem layers +func NewCimDiff(store content.Store) (CompareApplier, error) { + return cimDiff{ + store: store, + }, nil +} + +// Apply applies the content associated with the provided digests onto the +// provided mounts. Archive content will be extracted and decompressed if +// necessary. +func (c cimDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) { + if len(mounts) != 1 { + return emptyDesc, fmt.Errorf("number of mounts should always be 1 for CimFS layers: %w", errdefs.ErrInvalidArgument) + } else if mounts[0].Type != mount.CimFSMountType { + return emptyDesc, fmt.Errorf("cimDiff does not support layer type %s: %w", mounts[0].Type, errdefs.ErrNotImplemented) + } + + m := mounts[0] + parentLayerPaths, err := m.GetParentPaths() + if err != nil { + return emptyDesc, err + } + parentLayerCimPaths, err := mount.GetParentCimPaths(&m) + if err != nil { + return emptyDesc, err + } + cimPath, err := mount.GetCimPath(&m) + if err != nil { + return emptyDesc, err + } + + applyFunc := func(fCtx context.Context, r io.Reader) (int64, error) { + return ocicimlayer.ImportCimLayerFromTar(fCtx, r, m.Source, cimPath, parentLayerPaths, parentLayerCimPaths) + } + + return applyCIMLayerCommon(ctx, desc, c.store, applyFunc, opts...) +} + +// Compare creates a diff between the given mounts and uploads the result +// to the content store. +func (c cimDiff) Compare(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) { + // support for generating layer diff of cimfs layers will be added later. + return emptyDesc, errdefs.ErrNotImplemented +} + +// blockCIMDiff does filesystem comparison and application +// for blocked CIMs. +type blockCIMDiff struct { + store content.Store +} + +// NewBlockCimDiff is the Windows blocked cim container layer implementation for comparing +// and applying filesystem layers +func NewBlockCimDiff(store content.Store) (CompareApplier, error) { + return blockCIMDiff{ + store: store, + }, nil +} + +// parseBlockCIMMount parses the mount returned by the BlockCIM snapshotter and returns +func parseBlockCIMMount(m *mount.Mount) (*cimfs.BlockCIM, []*cimfs.BlockCIM, error) { + var ( + parentPaths []string + ) + + for _, option := range m.Options { + if val, ok := strings.CutPrefix(option, mount.ParentLayerCimPathsFlag); ok { + err := json.Unmarshal([]byte(val), &parentPaths) + if err != nil { + return nil, nil, err + } + } else if val, ok = strings.CutPrefix(option, mount.BlockCIMTypeFlag); ok { + // only support single file for extraction for now + if val != mount.BlockCIMTypeFile { + return nil, nil, fmt.Errorf("extraction doesn't support layer type `%s`", val) + } + } + } + + var ( + parentLayers []*cimfs.BlockCIM + extractionLayer *cimfs.BlockCIM + ) + + extractionLayer = &cimfs.BlockCIM{ + Type: cimfs.BlockCIMTypeSingleFile, + BlockPath: filepath.Dir(m.Source), + CimName: filepath.Base(m.Source), + } + for _, p := range parentPaths { + parentLayers = append(parentLayers, &cimfs.BlockCIM{ + Type: cimfs.BlockCIMTypeSingleFile, + BlockPath: filepath.Dir(p), + CimName: filepath.Base(p), + }) + } + return extractionLayer, parentLayers, nil +} + +// Apply applies the content associated with the provided digests onto the +// provided mounts. Archive content will be extracted and decompressed if +// necessary. +func (c blockCIMDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) { + if len(mounts) != 1 { + return emptyDesc, fmt.Errorf("number of mounts should always be 1 for CimFS layers: %w", errdefs.ErrInvalidArgument) + } else if mounts[0].Type != mount.BlockCIMMountType { + return emptyDesc, fmt.Errorf("blockCIMDiff does not support layer type %s: %w", mounts[0].Type, errdefs.ErrNotImplemented) + } + + m := mounts[0] + + log.G(ctx).WithFields(logrus.Fields{ + "mount": m, + }).Info("applying blockCIM diff") + + layer, parentLayers, err := parseBlockCIMMount(&m) + if err != nil { + return emptyDesc, err + } + + enableLayerIntegrity := mount.GetEnableLayerIntegrity(&m) + appendVHDFooter := mount.GetAppendVHDFooter(&m) + + // Build import options based on mount configuration + var importOpts []ocicimlayer.BlockCIMLayerImportOpt + importOpts = append(importOpts, ocicimlayer.WithParentLayers(parentLayers)) + + if appendVHDFooter { + importOpts = append(importOpts, ocicimlayer.WithVHDFooter()) + } + + if enableLayerIntegrity { + importOpts = append(importOpts, ocicimlayer.WithLayerIntegrity()) + } + + applyFunc := func(ctx context.Context, r io.Reader) (int64, error) { + return ocicimlayer.ImportBlockCIMLayerWithOpts(ctx, r, layer, importOpts...) + } + + return applyCIMLayerCommon(ctx, desc, c.store, applyFunc, opts...) + +} + +// Compare creates a diff between the given mounts and uploads the result +// to the content store. +func (c blockCIMDiff) Compare(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) { + // support for generating layer diff of cimfs layers will be added later. + return emptyDesc, errdefs.ErrNotImplemented +} + +// applyCimFSCommon is a common function used for applying all diffs to a cim layer. +func applyCIMLayerCommon(ctx context.Context, desc ocispec.Descriptor, store content.Store, applyFunc cimApplyFunc, opts ...diff.ApplyOpt) (_ ocispec.Descriptor, err error) { + var config diff.ApplyConfig + for _, o := range opts { + if err := o(ctx, desc, &config); err != nil { + return emptyDesc, fmt.Errorf("failed to apply config opt: %w", err) + } + } + + t1 := time.Now() + defer func() { + if err == nil { + log.G(ctx).WithFields(log.Fields{ + "d": time.Since(t1), + "digest": desc.Digest, + "size": desc.Size, + "media": desc.MediaType, + }).Debug("diff applied") + } + }() + + ra, err := store.ReaderAt(ctx, desc) + if err != nil { + return emptyDesc, fmt.Errorf("failed to get reader from content store: %w", err) + } + defer ra.Close() + + processor := diff.NewProcessorChain(desc.MediaType, content.NewReader(ra)) + for { + if processor, err = diff.GetProcessor(ctx, processor, config.ProcessorPayloads); err != nil { + return emptyDesc, fmt.Errorf("failed to get stream processor for %s: %w", desc.MediaType, err) + } + if processor.MediaType() == ocispec.MediaTypeImageLayer { + break + } + } + defer processor.Close() + + digester := digest.Canonical.Digester() + rc := &readCounter{ + r: io.TeeReader(processor, digester.Hash()), + } + + if _, err = applyFunc(ctx, rc); err != nil { + return emptyDesc, err + } + + // Read any trailing data + if _, err := io.Copy(io.Discard, rc); err != nil { + return emptyDesc, err + } + + return ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageLayer, + Size: rc.c, + Digest: digester.Digest(), + }, nil + +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/diff/windows/windows.go b/vendor/github.com/containerd/containerd/v2/plugins/diff/windows/windows.go new file mode 100644 index 0000000000..287fac3d0e --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/diff/windows/windows.go @@ -0,0 +1,408 @@ +//go:build windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package windows + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "time" + + "github.com/Microsoft/go-winio" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/containerd/containerd/v2/core/content" + "github.com/containerd/containerd/v2/core/diff" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/pkg/archive" + "github.com/containerd/containerd/v2/pkg/archive/compression" + "github.com/containerd/containerd/v2/pkg/epoch" + "github.com/containerd/containerd/v2/pkg/labels" + "github.com/containerd/containerd/v2/plugins" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.DiffPlugin, + ID: "windows", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + md, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) + return NewWindowsDiff(md.(*metadata.DB).ContentStore()) + }, + }) +} + +// CompareApplier handles both comparison and +// application of layer diffs. +type CompareApplier interface { + diff.Applier + diff.Comparer +} + +// windowsDiff does filesystem comparison and application +// for Windows specific layer diffs. +type windowsDiff struct { + store content.Store +} + +var emptyDesc = ocispec.Descriptor{} + +// NewWindowsDiff is the Windows container layer implementation +// for comparing and applying filesystem layers +func NewWindowsDiff(store content.Store) (CompareApplier, error) { + return windowsDiff{ + store: store, + }, nil +} + +// Apply applies the content associated with the provided digests onto the +// provided mounts. Archive content will be extracted and decompressed if +// necessary. +func (s windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) { + layerPath, parentLayerPaths, err := mountsToLayerAndParents(mounts) + if err != nil { + return emptyDesc, err + } + + // TODO darrenstahlmsft: When this is done isolated, we should disable these. + // it currently cannot be disabled, unless we add ref counting. Since this is + // temporary, leaving it enabled is OK for now. + // https://github.com/containerd/containerd/issues/1681 + if err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege}); err != nil { + return emptyDesc, err + } + + t1 := time.Now() + defer func() { + if err == nil { + log.G(ctx).WithFields(log.Fields{ + "d": time.Since(t1), + "digest": desc.Digest, + "size": desc.Size, + "media": desc.MediaType, + }).Debug("diff applied") + } + }() + + var config diff.ApplyConfig + for _, o := range opts { + if err := o(ctx, desc, &config); err != nil { + return emptyDesc, fmt.Errorf("failed to apply config opt: %w", err) + } + } + + ra, err := s.store.ReaderAt(ctx, desc) + if err != nil { + return emptyDesc, fmt.Errorf("failed to get reader from content store: %w", err) + } + defer ra.Close() + + processor := diff.NewProcessorChain(desc.MediaType, content.NewReader(ra)) + for { + if processor, err = diff.GetProcessor(ctx, processor, config.ProcessorPayloads); err != nil { + return emptyDesc, fmt.Errorf("failed to get stream processor for %s: %w", desc.MediaType, err) + } + if processor.MediaType() == ocispec.MediaTypeImageLayer { + break + } + } + defer processor.Close() + + digester := digest.Canonical.Digester() + rc := &readCounter{ + r: io.TeeReader(processor, digester.Hash()), + } + + archiveOpts := []archive.ApplyOpt{ + archive.WithParents(parentLayerPaths), + archive.WithNoSameOwner(), // Lchown is not supported on Windows + archive.AsWindowsContainerLayer(), + } + + if _, err := archive.Apply(ctx, layerPath, rc, archiveOpts...); err != nil { + return emptyDesc, err + } + + // Read any trailing data + if _, err := io.Copy(io.Discard, rc); err != nil { + return emptyDesc, err + } + + return ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageLayer, + Size: rc.c, + Digest: digester.Digest(), + }, nil + +} + +// Compare creates a diff between the given mounts and uploads the result +// to the content store. +func (s windowsDiff) Compare(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) { + t1 := time.Now() + + var config diff.Config + for _, opt := range opts { + if err := opt(&config); err != nil { + return emptyDesc, err + } + } + if tm := epoch.FromContext(ctx); tm != nil && config.SourceDateEpoch == nil { + config.SourceDateEpoch = tm + } + + layers, err := mountPairToLayerStack(lower, upper) + if err != nil { + return emptyDesc, err + } + + if config.MediaType == "" { + config.MediaType = ocispec.MediaTypeImageLayerGzip + } + + compressionType := compression.Uncompressed + switch config.MediaType { + case ocispec.MediaTypeImageLayer: + case ocispec.MediaTypeImageLayerGzip: + compressionType = compression.Gzip + case ocispec.MediaTypeImageLayerZstd: + compressionType = compression.Zstd + default: + return emptyDesc, fmt.Errorf("unsupported diff media type: %v: %w", config.MediaType, errdefs.ErrNotImplemented) + } + + newReference := false + if config.Reference == "" { + newReference = true + config.Reference = uniqueRef() + } + + cw, err := s.store.Writer(ctx, content.WithRef(config.Reference), content.WithDescriptor(ocispec.Descriptor{ + MediaType: config.MediaType, + })) + + if err != nil { + return emptyDesc, fmt.Errorf("failed to open writer: %w", err) + } + + defer func() { + if err != nil { + cw.Close() + if newReference { + if abortErr := s.store.Abort(ctx, config.Reference); abortErr != nil { + log.G(ctx).WithError(abortErr).WithField("ref", config.Reference).Warnf("failed to delete diff upload") + } + } + } + }() + + if !newReference { + if err = cw.Truncate(0); err != nil { + return emptyDesc, err + } + } + + // TODO darrenstahlmsft: When this is done isolated, we should disable this. + // it currently cannot be disabled, unless we add ref counting. Since this is + // temporary, leaving it enabled is OK for now. + // https://github.com/containerd/containerd/issues/1681 + if err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege}); err != nil { + return emptyDesc, err + } + + if compressionType != compression.Uncompressed { + dgstr := digest.SHA256.Digester() + var compressed io.WriteCloser + compressed, err = compression.CompressStream(cw, compressionType) + if err != nil { + return emptyDesc, fmt.Errorf("failed to get compressed stream: %w", err) + } + err = archive.WriteDiff(ctx, io.MultiWriter(compressed, dgstr.Hash()), "", layers[0], archive.AsWindowsContainerLayerPair(), archive.WithParentLayers(layers[1:])) + compressed.Close() + if err != nil { + return emptyDesc, fmt.Errorf("failed to write compressed diff: %w", err) + } + + if config.Labels == nil { + config.Labels = map[string]string{} + } + config.Labels[labels.LabelUncompressed] = dgstr.Digest().String() + } else { + if err = archive.WriteDiff(ctx, cw, "", layers[0], archive.AsWindowsContainerLayerPair(), archive.WithParentLayers(layers[1:])); err != nil { + return emptyDesc, fmt.Errorf("failed to write diff: %w", err) + } + } + + var commitopts []content.Opt + if config.Labels != nil { + commitopts = append(commitopts, content.WithLabels(config.Labels)) + } + + dgst := cw.Digest() + if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil { + if !errdefs.IsAlreadyExists(err) { + return emptyDesc, fmt.Errorf("failed to commit: %w", err) + } + } + + info, err := s.store.Info(ctx, dgst) + if err != nil { + return emptyDesc, fmt.Errorf("failed to get info from content store: %w", err) + } + if info.Labels == nil { + info.Labels = make(map[string]string) + } + // Set "containerd.io/uncompressed" label if digest already existed without label + if _, ok := info.Labels[labels.LabelUncompressed]; !ok { + info.Labels[labels.LabelUncompressed] = config.Labels[labels.LabelUncompressed] + if _, err := s.store.Update(ctx, info, "labels."+labels.LabelUncompressed); err != nil { + return emptyDesc, fmt.Errorf("error setting uncompressed label: %w", err) + } + } + + desc := ocispec.Descriptor{ + MediaType: config.MediaType, + Size: info.Size, + Digest: info.Digest, + } + + log.G(ctx).WithFields(log.Fields{ + "d": time.Since(t1), + "dgst": desc.Digest, + "size": desc.Size, + "media": desc.MediaType, + }).Debug("diff created") + + return desc, nil +} + +type readCounter struct { + r io.Reader + c int64 +} + +func (rc *readCounter) Read(p []byte) (n int, err error) { + n, err = rc.r.Read(p) + rc.c += int64(n) + return +} + +func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) { + if len(mounts) != 1 { + return "", nil, fmt.Errorf("number of mounts should always be 1 for Windows layers: %w", errdefs.ErrInvalidArgument) + } + mnt := mounts[0] + + if mnt.Type != "windows-layer" { + // This is a special case error. When this is received the diff service + // will attempt the next differ in the chain which for Windows is the + // lcow differ that we want. + return "", nil, fmt.Errorf("windowsDiff does not support layer type %s: %w", mnt.Type, errdefs.ErrNotImplemented) + } + + parentLayerPaths, err := mnt.GetParentPaths() + if err != nil { + return "", nil, err + } + + if mnt.ReadOnly() { + if len(parentLayerPaths) == 0 { + // rootfs.CreateDiff creates a new, empty View to diff against, + // when diffing something with no parent. + // This makes perfect sense for a walking Diff, but for WCOW, + // we have to recognise this as "diff against nothing" + return "", nil, nil + } + // Ignore the dummy sandbox. + return parentLayerPaths[0], parentLayerPaths[1:], nil + } + return mnt.Source, parentLayerPaths, nil +} + +// mountPairToLayerStack ensures that the two sets of mount-lists are actually a correct +// parent-and-child, or orphan-and-empty-list, and return the full list of layers, starting +// with the upper-most (most childish?) +func mountPairToLayerStack(lower, upper []mount.Mount) ([]string, error) { + + // May return an ErrNotImplemented, which will fall back to LCOW + upperLayer, upperParentLayerPaths, err := mountsToLayerAndParents(upper) + if err != nil { + return nil, fmt.Errorf("upper mount invalid: %w", err) + } + + lowerLayer, lowerParentLayerPaths, err := mountsToLayerAndParents(lower) + if errdefs.IsNotImplemented(err) { + // Upper was a windows-layer, lower is not. We can't handle that. + return nil, fmt.Errorf("windowsDiff cannot diff a windows-layer against a non-windows-layer: %w", errdefs.ErrInvalidArgument) + } else if err != nil { + return nil, fmt.Errorf("lower mount invalid: %w", err) + } + + // Trivial case, diff-against-nothing + if lowerLayer == "" { + if len(upperParentLayerPaths) != 0 { + return nil, fmt.Errorf("windowsDiff cannot diff a layer with parents against a null layer: %w", errdefs.ErrInvalidArgument) + } + return []string{upperLayer}, nil + } + + if len(upperParentLayerPaths) < 1 { + return nil, fmt.Errorf("windowsDiff cannot diff a layer with no parents against another layer: %w", errdefs.ErrInvalidArgument) + } + + if upperParentLayerPaths[0] != lowerLayer { + return nil, fmt.Errorf("windowsDiff cannot diff a layer against a layer other than its own parent: %w", errdefs.ErrInvalidArgument) + } + + if len(upperParentLayerPaths) != len(lowerParentLayerPaths)+1 { + return nil, fmt.Errorf("windowsDiff cannot diff a layer against a layer with different parents: %w", errdefs.ErrInvalidArgument) + } + for i, upperParent := range upperParentLayerPaths[1:] { + if upperParent != lowerParentLayerPaths[i] { + return nil, fmt.Errorf("windowsDiff cannot diff a layer against a layer with different parents: %w", errdefs.ErrInvalidArgument) + } + } + + return append([]string{upperLayer}, upperParentLayerPaths...), nil +} + +func uniqueRef() string { + t := time.Now() + var b [3]byte + // Ignore read failures, just decreases uniqueness + rand.Read(b[:]) + return fmt.Sprintf("%d-%s", t.UnixNano(), base64.URLEncoding.EncodeToString(b[:])) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/events/plugin.go b/vendor/github.com/containerd/containerd/v2/plugins/events/plugin.go new file mode 100644 index 0000000000..39c7eca3c3 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/events/plugin.go @@ -0,0 +1,34 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package events + +import ( + "github.com/containerd/containerd/v2/core/events/exchange" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.EventPlugin, + ID: "exchange", + InitFn: func(ic *plugin.InitContext) (any, error) { + return exchange.NewExchange(), nil + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/gc/metrics.go b/vendor/github.com/containerd/containerd/v2/plugins/gc/metrics.go new file mode 100644 index 0000000000..3caa8cbac9 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/gc/metrics.go @@ -0,0 +1,34 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package scheduler + +import "github.com/docker/go-metrics" + +var ( + // collectionCounter metrics for counter of gc scheduler collections. + collectionCounter metrics.LabeledCounter + + // gcTimeHist histogram metrics for duration of gc scheduler collections. + gcTimeHist metrics.Timer +) + +func init() { + ns := metrics.NewNamespace("containerd", "gc", nil) + collectionCounter = ns.NewLabeledCounter("collections", "counter of gc scheduler collections", "status") + gcTimeHist = ns.NewTimer("gc", "duration of gc scheduler collections") + metrics.Register(ns) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/gc/scheduler.go b/vendor/github.com/containerd/containerd/v2/plugins/gc/scheduler.go new file mode 100644 index 0000000000..42bf406ffa --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/gc/scheduler.go @@ -0,0 +1,358 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package scheduler + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "github.com/containerd/containerd/v2/internal/tomlext" + "github.com/containerd/containerd/v2/pkg/gc" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +// config configures the garbage collection policies. +type config struct { + // PauseThreshold represents the maximum amount of time garbage + // collection should be scheduled based on the average pause time. + // For example, a value of 0.02 means that scheduled garbage collection + // pauses should present at most 2% of real time, + // or 20ms of every second. + // + // A maximum value of .5 is enforced to prevent over scheduling of the + // garbage collector, trigger options are available to run in a more + // predictable time frame after mutation. + // + // Default is 0.02 + PauseThreshold float64 `toml:"pause_threshold"` + + // DeletionThreshold is used to guarantee that a garbage collection is + // scheduled after configured number of deletions have occurred + // since the previous garbage collection. A value of 0 indicates that + // garbage collection will not be triggered by deletion count. + // + // Default 0 + DeletionThreshold int `toml:"deletion_threshold"` + + // MutationThreshold is used to guarantee that a garbage collection is + // run after a configured number of database mutations have occurred + // since the previous garbage collection. A value of 0 indicates that + // garbage collection will only be run after a manual trigger or + // deletion. Unlike the deletion threshold, the mutation threshold does + // not cause scheduling of a garbage collection, but ensures GC is run + // at the next scheduled GC. + // + // Default 100 + MutationThreshold int `toml:"mutation_threshold"` + + // ScheduleDelay is the duration in the future to schedule a garbage + // collection triggered manually or by exceeding the configured + // threshold for deletion or mutation. A zero value will immediately + // schedule. Use suffix "ms" for millisecond and "s" for second. + // + // Default is "0ms" + ScheduleDelay tomlext.Duration `toml:"schedule_delay"` + + // StartupDelay is the delay duration to do an initial garbage + // collection after startup. The initial garbage collection is used to + // set the base for pause threshold and should be scheduled in the + // future to avoid slowing down other startup processes. Use suffix + // "ms" for millisecond and "s" for second. + // + // Default is "100ms" + StartupDelay tomlext.Duration `toml:"startup_delay"` +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GCPlugin, + ID: "scheduler", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + }, + Config: &config{ + PauseThreshold: 0.02, + DeletionThreshold: 0, + MutationThreshold: 100, + ScheduleDelay: tomlext.FromStdTime(0), + StartupDelay: tomlext.FromStdTime(100 * time.Millisecond), + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + md, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + mdCollector, ok := md.(collector) + if !ok { + return nil, fmt.Errorf("%s %T must implement collector", plugins.MetadataPlugin, md) + } + + m := newScheduler(mdCollector, ic.Config.(*config)) + + ic.Meta.Exports = map[string]string{ + "PauseThreshold": fmt.Sprint(m.pauseThreshold), + "DeletionThreshold": fmt.Sprint(m.deletionThreshold), + "MutationThreshold": fmt.Sprint(m.mutationThreshold), + "ScheduleDelay": fmt.Sprint(m.scheduleDelay), + } + + go m.run(ic.Context) + + return m, nil + }, + }) +} + +type mutationEvent struct { + ts time.Time + mutation bool + dirty bool +} + +type collector interface { + RegisterMutationCallback(func(bool)) + GarbageCollect(context.Context) (gc.Stats, error) +} + +type gcScheduler struct { + c collector + + eventC chan mutationEvent + + waiterL sync.Mutex + waiters []chan gc.Stats + + pauseThreshold float64 + deletionThreshold int + mutationThreshold int + scheduleDelay time.Duration + startupDelay time.Duration +} + +func newScheduler(c collector, cfg *config) *gcScheduler { + eventC := make(chan mutationEvent) + + s := &gcScheduler{ + c: c, + eventC: eventC, + pauseThreshold: cfg.PauseThreshold, + deletionThreshold: cfg.DeletionThreshold, + mutationThreshold: cfg.MutationThreshold, + scheduleDelay: time.Duration(cfg.ScheduleDelay), + startupDelay: time.Duration(cfg.StartupDelay), + } + + if s.pauseThreshold < 0.0 { + s.pauseThreshold = 0.0 + } + if s.pauseThreshold > 0.5 { + s.pauseThreshold = 0.5 + } + if s.mutationThreshold < 0 { + s.mutationThreshold = 0 + } + if s.scheduleDelay < 0 { + s.scheduleDelay = 0 + } + if s.startupDelay < 0 { + s.startupDelay = 0 + } + + c.RegisterMutationCallback(s.mutationCallback) + + return s +} + +func (s *gcScheduler) ScheduleAndWait(ctx context.Context) (gc.Stats, error) { + return s.wait(ctx, true) +} + +func (s *gcScheduler) wait(ctx context.Context, trigger bool) (gc.Stats, error) { + wc := make(chan gc.Stats, 1) + s.waiterL.Lock() + s.waiters = append(s.waiters, wc) + s.waiterL.Unlock() + + if trigger { + e := mutationEvent{ + ts: time.Now(), + } + go func() { + s.eventC <- e + }() + } + + var gcStats gc.Stats + select { + case stats, ok := <-wc: + if !ok { + return gcStats, errors.New("gc failed") + } + gcStats = stats + case <-ctx.Done(): + return gcStats, ctx.Err() + } + + return gcStats, nil +} + +func (s *gcScheduler) mutationCallback(dirty bool) { + e := mutationEvent{ + ts: time.Now(), + mutation: true, + dirty: dirty, + } + go func() { + s.eventC <- e + }() +} + +func schedule(d time.Duration) (<-chan time.Time, *time.Time) { + next := time.Now().Add(d) + return time.After(d), &next +} + +func (s *gcScheduler) run(ctx context.Context) { + const minimumGCTime = float64(5 * time.Millisecond) + var ( + schedC <-chan time.Time + + lastCollection *time.Time + nextCollection *time.Time + + interval = time.Second + gcTimeSum time.Duration + collections int + + triggered bool + deletions int + mutations int + ) + if s.startupDelay > 0 { + schedC, nextCollection = schedule(s.startupDelay) + } + for { + select { + case <-schedC: + // Check if garbage collection can be skipped because + // it is not needed or was not requested and reschedule + // it to attempt again after another time interval. + if !triggered && lastCollection != nil && deletions == 0 && + (s.mutationThreshold == 0 || mutations < s.mutationThreshold) { + schedC, nextCollection = schedule(interval) + continue + } + case e := <-s.eventC: + if lastCollection != nil && lastCollection.After(e.ts) { + continue + } + if e.dirty { + deletions++ + } + if e.mutation { + mutations++ + } else { + triggered = true + } + + // Check if condition should cause immediate collection. + if triggered || + (s.deletionThreshold > 0 && deletions >= s.deletionThreshold) || + (nextCollection == nil && ((s.deletionThreshold == 0 && deletions > 0) || + (s.mutationThreshold > 0 && mutations >= s.mutationThreshold))) { + // Check if not already scheduled before delay threshold + if nextCollection == nil || nextCollection.After(time.Now().Add(s.scheduleDelay)) { + // TODO(dmcg): track re-schedules for tuning schedule config + schedC, nextCollection = schedule(s.scheduleDelay) + } + } + + continue + case <-ctx.Done(): + return + } + + s.waiterL.Lock() + + stats, err := s.c.GarbageCollect(ctx) + last := time.Now() + if err != nil { + log.G(ctx).WithError(err).Error("garbage collection failed") + collectionCounter.WithValues("fail").Inc() + var retryDelay time.Duration + if lastCollection != nil { + // If we have a previous collection time, reschedule based on that interval. + retryDelay = nextCollection.Sub(*lastCollection) + time.Second + } else { + // If this is the first collection and it failed, use the default schedule delay. + retryDelay = s.scheduleDelay + } + schedC, nextCollection = schedule(retryDelay) + + // Update last collection time even though failure occurred + lastCollection = &last + + for _, w := range s.waiters { + close(w) + } + s.waiters = nil + s.waiterL.Unlock() + continue + } + + gcTime := stats.Elapsed() + gcTimeHist.Update(gcTime) + log.G(ctx).WithField("d", gcTime).Trace("garbage collected") + gcTimeSum += gcTime + collections++ + collectionCounter.WithValues("success").Inc() + triggered = false + deletions = 0 + mutations = 0 + + // Calculate new interval with updated times + if s.pauseThreshold > 0.0 { + // Set interval to average gc time divided by the pause threshold + // This algorithm ensures that a gc is scheduled to allow enough + // runtime in between gc to reach the pause threshold. + // Pause threshold is always 0.0 < threshold <= 0.5 + avg := float64(gcTimeSum) / float64(collections) + // Enforce that avg is no less than minimumGCTime + // to prevent immediate rescheduling + if avg < minimumGCTime { + avg = minimumGCTime + } + interval = time.Duration(avg/s.pauseThreshold - avg) + } + + lastCollection = &last + schedC, nextCollection = schedule(interval) + + for _, w := range s.waiters { + w <- stats + } + s.waiters = nil + s.waiterL.Unlock() + } +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/leases/local.go b/vendor/github.com/containerd/containerd/v2/plugins/leases/local.go new file mode 100644 index 0000000000..7503e6cc58 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/leases/local.go @@ -0,0 +1,84 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package plugin + +import ( + "context" + + "github.com/containerd/containerd/v2/core/leases" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/pkg/gc" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.LeasePlugin, + ID: "manager", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + plugins.GCPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + g, err := ic.GetSingle(plugins.GCPlugin) + if err != nil { + return nil, err + } + return &local{ + Manager: metadata.NewLeaseManager(m.(*metadata.DB)), + gc: g.(gcScheduler), + }, nil + }, + }) +} + +type gcScheduler interface { + ScheduleAndWait(context.Context) (gc.Stats, error) +} + +type local struct { + leases.Manager + gc gcScheduler +} + +func (l *local) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error { + var do leases.DeleteOptions + for _, opt := range opts { + if err := opt(ctx, &do); err != nil { + return err + } + } + + if err := l.Manager.Delete(ctx, lease); err != nil { + return err + } + + if do.Synchronous { + if _, err := l.gc.ScheduleAndWait(ctx); err != nil { + return err + } + } + + return nil + +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/metadata/plugin.go b/vendor/github.com/containerd/containerd/v2/plugins/metadata/plugin.go new file mode 100644 index 0000000000..aeb92ee9f0 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/metadata/plugin.go @@ -0,0 +1,200 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package plugin + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/containerd/containerd/v2/core/content" + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/pkg/timeout" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + + bolt "go.etcd.io/bbolt" +) + +const ( + boltOpenTimeout = "io.containerd.timeout.bolt.open" +) + +func init() { + timeout.Set(boltOpenTimeout, 0) // set to 0 means to wait indefinitely for bolt.Open +} + +// BoltConfig defines the configuration values for the bolt plugin, which is +// loaded here, rather than back registered in the metadata package. +type BoltConfig struct { + // ContentSharingPolicy sets the sharing policy for content between + // namespaces. + // + // The default mode "shared" will make blobs available in all + // namespaces once it is pulled into any namespace. The blob will be pulled + // into the namespace if a writer is opened with the "Expected" digest that + // is already present in the backend. + // + // The alternative mode, "isolated" requires that clients prove they have + // access to the content by providing all of the content to the ingest + // before the blob is added to the namespace. + // + // Both modes share backing data, while "shared" will reduce total + // bandwidth across namespaces, at the cost of allowing access to any blob + // just by knowing its digest. + ContentSharingPolicy string `toml:"content_sharing_policy"` + + // NoSync enables optimizations that improve database write performance by: + // 1. Disabling fsync calls after every write, which prevents ensuring that data is immediately flushed + // to disk but significantly improves write throughput (NoSync). + // 2. Preventing automatic growth of the memory-mapped file during writes, further improving performance + // in environments where the database size is stable (NoGrowSync). + // + // These settings can improve performance, but introduce a risk of data loss during crashes. Use with care! + NoSync bool `toml:"no_sync"` +} + +const ( + // SharingPolicyShared represents the "shared" sharing policy + SharingPolicyShared = "shared" + // SharingPolicyIsolated represents the "isolated" sharing policy + SharingPolicyIsolated = "isolated" +) + +// Validate validates if BoltConfig is valid +func (bc *BoltConfig) Validate() error { + switch bc.ContentSharingPolicy { + case SharingPolicyShared, SharingPolicyIsolated: + return nil + default: + return fmt.Errorf("unknown policy: %s: %w", bc.ContentSharingPolicy, errdefs.ErrInvalidArgument) + } +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.MetadataPlugin, + ID: "bolt", + Requires: []plugin.Type{ + plugins.ContentPlugin, + plugins.EventPlugin, + plugins.SnapshotPlugin, + }, + Config: &BoltConfig{ + ContentSharingPolicy: SharingPolicyShared, + NoSync: false, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + root := ic.Properties[plugins.PropertyRootDir] + if err := os.MkdirAll(root, 0711); err != nil { + return nil, err + } + cs, err := ic.GetSingle(plugins.ContentPlugin) + if err != nil { + return nil, err + } + + snapshottersRaw, err := ic.GetByType(plugins.SnapshotPlugin) + if err != nil { + return nil, err + } + + snapshotters := make(map[string]snapshots.Snapshotter) + for name, sn := range snapshottersRaw { + snapshotters[name] = sn.(snapshots.Snapshotter) + } + + ep, err := ic.GetSingle(plugins.EventPlugin) + if err != nil { + return nil, err + } + + options := *bolt.DefaultOptions + // Reading bbolt's freelist sometimes fails when the file has a data corruption. + // Disabling freelist sync reduces the chance of the breakage. + // https://github.com/etcd-io/bbolt/pull/1 + // https://github.com/etcd-io/bbolt/pull/6 + options.NoFreelistSync = true + // Without the timeout, bbolt.Open would block indefinitely due to flock(2). + options.Timeout = timeout.Get(boltOpenTimeout) + + shared := true + ic.Meta.Exports["policy"] = SharingPolicyShared + if cfg, ok := ic.Config.(*BoltConfig); ok { + if cfg.ContentSharingPolicy != "" { + if err := cfg.Validate(); err != nil { + return nil, err + } + if cfg.ContentSharingPolicy == SharingPolicyIsolated { + ic.Meta.Exports["policy"] = SharingPolicyIsolated + shared = false + } + + log.G(ic.Context).WithField("policy", cfg.ContentSharingPolicy).Info("metadata content store policy set") + + if cfg.NoSync { + options.NoSync = true + options.NoGrowSync = true + + log.G(ic.Context).Warn("using async mode for boltdb") + } + } + } + + path := filepath.Join(root, "meta.db") + ic.Meta.Exports["path"] = path + + doneCh := make(chan struct{}) + go func() { + t := time.NewTimer(10 * time.Second) + defer t.Stop() + select { + case <-t.C: + log.G(ic.Context).WithField("plugin", "bolt").Warn("waiting for response from boltdb open") + case <-doneCh: + return + } + }() + + db, err := bolt.Open(path, 0644, &options) + close(doneCh) + if err != nil { + return nil, err + } + + dbopts := []metadata.DBOpt{ + metadata.WithEventsPublisher(ep.(events.Publisher)), + } + + if !shared { + dbopts = append(dbopts, metadata.WithPolicyIsolated) + } + + mdb := metadata.NewDB(db, cs.(content.Store), snapshotters, dbopts...) + if err := mdb.Init(ic.Context); err != nil { + return nil, err + } + return mdb, nil + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/mount/manager.go b/vendor/github.com/containerd/containerd/v2/plugins/mount/manager.go new file mode 100644 index 0000000000..5f1dca7d0c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/mount/manager.go @@ -0,0 +1,125 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package mount + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + + bolt "go.etcd.io/bbolt" + + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/metadata/boltutil" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/mount/manager" + "github.com/containerd/containerd/v2/plugins" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.MountManagerPlugin, + ID: "bolt", + Requires: []plugin.Type{ + plugins.MetadataPlugin, + plugins.MountHandlerPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + md, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + hp, err := ic.GetByType(plugins.MountHandlerPlugin) + if err != nil && !errors.Is(err, plugin.ErrPluginNotFound) { + return nil, err + } + var opts []manager.Opt + for k, v := range hp { + opts = append(opts, manager.WithMountHandler(k, v.(mount.Handler))) + } + + root := ic.Properties[plugins.PropertyStateDir] + + // TODO: Allow overriding root and target directory from config + + targets := filepath.Join(root, "t") + if merr := os.MkdirAll(targets, 0700); merr != nil { + return nil, merr + } + + // roots are the directories that mount handlers can operate in + // TODO: support additional roots from config + opts = append(opts, manager.WithAllowedRoot(filepath.Dir(ic.Properties[plugins.PropertyRootDir]))) + + //if _, ok := mhandlers["mkdir"]; !ok { + // mkdir, err := handlers.MkdirHandler(roots...) + // if err != nil { + // return nil, fmt.Errorf("failed to create mkdir handler: %w", err) + // } + // mhandlers["mkdir"] = mkdir + //} + + metadb := filepath.Join(root, "mounts.db") + + db, err := bolt.Open(metadb, 0600, nil) + if err != nil { + return nil, fmt.Errorf("failed to open database file: %w", err) + } + + mm, err := manager.NewManager(db, targets, opts...) + if err != nil { + db.Close() + return nil, fmt.Errorf("failed to create mount manager: %w", err) + } + + //TODO: IF has sync + if sync, ok := mm.(interface{ Sync(context.Context) error }); ok { + + // Start transaction and then background sync with mount state, + // ensure startup waits until ready to continue + tx, err := db.Begin(true) + if err != nil { + db.Close() + return nil, fmt.Errorf("failed to open database for write: %w", err) + } + ctx := boltutil.WithTransaction(ic.Context, tx) + + ready := ic.RegisterReadiness() + go func() { + defer ready() + if err := sync.Sync(ctx); err == nil { + tx.Commit() + } else { + log.G(ctx).WithError(err).Errorf("failed to sync mounts") + tx.Rollback() + } + }() + } + + if collector, ok := mm.(metadata.Collector); ok { + md.(*metadata.DB).RegisterCollectibleResource(metadata.ResourceMount, collector) + } + return mm, nil + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/containers/helpers.go b/vendor/github.com/containerd/containerd/v2/plugins/services/containers/helpers.go new file mode 100644 index 0000000000..b28aff73bd --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/containers/helpers.go @@ -0,0 +1,83 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containers + +import ( + api "github.com/containerd/containerd/api/services/containers/v1" + "github.com/containerd/containerd/v2/core/containers" + "github.com/containerd/containerd/v2/pkg/protobuf" + "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/typeurl/v2" +) + +func containersToProto(containers []containers.Container) []*api.Container { + var containerspb []*api.Container + + for _, image := range containers { + containerspb = append(containerspb, containerToProto(&image)) + } + + return containerspb +} + +func containerToProto(container *containers.Container) *api.Container { + extensions := make(map[string]*types.Any) + for k, v := range container.Extensions { + extensions[k] = typeurl.MarshalProto(v) + } + return &api.Container{ + ID: container.ID, + Labels: container.Labels, + Image: container.Image, + Runtime: &api.Container_Runtime{ + Name: container.Runtime.Name, + Options: typeurl.MarshalProto(container.Runtime.Options), + }, + Spec: typeurl.MarshalProto(container.Spec), + Snapshotter: container.Snapshotter, + SnapshotKey: container.SnapshotKey, + CreatedAt: protobuf.ToTimestamp(container.CreatedAt), + UpdatedAt: protobuf.ToTimestamp(container.UpdatedAt), + Extensions: extensions, + Sandbox: container.SandboxID, + } +} + +func containerFromProto(containerpb *api.Container) containers.Container { + var runtime containers.RuntimeInfo + if containerpb.Runtime != nil { + runtime = containers.RuntimeInfo{ + Name: containerpb.Runtime.Name, + Options: containerpb.Runtime.Options, + } + } + extensions := make(map[string]typeurl.Any) + for k, v := range containerpb.Extensions { + extensions[k] = v + } + return containers.Container{ + ID: containerpb.ID, + Labels: containerpb.Labels, + Image: containerpb.Image, + Runtime: runtime, + Spec: containerpb.Spec, + Snapshotter: containerpb.Snapshotter, + SnapshotKey: containerpb.SnapshotKey, + Extensions: extensions, + SandboxID: containerpb.Sandbox, + } +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/containers/local.go b/vendor/github.com/containerd/containerd/v2/plugins/services/containers/local.go new file mode 100644 index 0000000000..e6cb36dd99 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/containers/local.go @@ -0,0 +1,260 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containers + +import ( + "context" + "io" + + eventstypes "github.com/containerd/containerd/api/events" + api "github.com/containerd/containerd/api/services/containers/v1" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + bolt "go.etcd.io/bbolt" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + grpcm "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/containerd/containerd/v2/core/containers" + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/metadata/boltutil" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" +) + +var empty = &ptypes.Empty{} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.ContainersService, + Requires: []plugin.Type{ + plugins.EventPlugin, + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + ep, err := ic.GetSingle(plugins.EventPlugin) + if err != nil { + return nil, err + } + + db := m.(*metadata.DB) + return &local{ + Store: metadata.NewContainerStore(db), + db: db, + publisher: ep.(events.Publisher), + }, nil + }, + }) +} + +type local struct { + containers.Store + db *metadata.DB + publisher events.Publisher +} + +var _ api.ContainersClient = &local{} + +func (l *local) Get(ctx context.Context, req *api.GetContainerRequest, _ ...grpc.CallOption) (*api.GetContainerResponse, error) { + var resp api.GetContainerResponse + + return &resp, errgrpc.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error { + container, err := l.Store.Get(ctx, req.ID) + if err != nil { + return err + } + containerpb := containerToProto(&container) + resp.Container = containerpb + + return nil + })) +} + +func (l *local) List(ctx context.Context, req *api.ListContainersRequest, _ ...grpc.CallOption) (*api.ListContainersResponse, error) { + var resp api.ListContainersResponse + return &resp, errgrpc.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error { + containers, err := l.Store.List(ctx, req.Filters...) + if err != nil { + return err + } + resp.Containers = containersToProto(containers) + return nil + })) +} + +func (l *local) ListStream(ctx context.Context, req *api.ListContainersRequest, _ ...grpc.CallOption) (api.Containers_ListStreamClient, error) { + stream := &localStream{ + ctx: ctx, + } + return stream, errgrpc.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error { + containers, err := l.Store.List(ctx, req.Filters...) + if err != nil { + return err + } + stream.containers = containersToProto(containers) + return nil + })) +} + +func (l *local) Create(ctx context.Context, req *api.CreateContainerRequest, _ ...grpc.CallOption) (*api.CreateContainerResponse, error) { + var resp api.CreateContainerResponse + + if err := l.withStoreUpdate(ctx, func(ctx context.Context) error { + container := containerFromProto(req.Container) + + created, err := l.Store.Create(ctx, container) + if err != nil { + return err + } + + resp.Container = containerToProto(&created) + + return nil + }); err != nil { + return &resp, errgrpc.ToGRPC(err) + } + if err := l.publisher.Publish(ctx, "/containers/create", &eventstypes.ContainerCreate{ + ID: resp.Container.ID, + Image: resp.Container.Image, + Runtime: &eventstypes.ContainerCreate_Runtime{ + Name: resp.Container.Runtime.Name, + Options: resp.Container.Runtime.Options, + }, + }); err != nil { + return &resp, err + } + + return &resp, nil +} + +func (l *local) Update(ctx context.Context, req *api.UpdateContainerRequest, _ ...grpc.CallOption) (*api.UpdateContainerResponse, error) { + if req.Container.ID == "" { + return nil, status.Errorf(codes.InvalidArgument, "Container.ID required") + } + var ( + resp api.UpdateContainerResponse + container = containerFromProto(req.Container) + ) + + if err := l.withStoreUpdate(ctx, func(ctx context.Context) error { + var fieldpaths []string + if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 { + fieldpaths = append(fieldpaths, req.UpdateMask.Paths...) + } + + updated, err := l.Store.Update(ctx, container, fieldpaths...) + if err != nil { + return err + } + + resp.Container = containerToProto(&updated) + return nil + }); err != nil { + return &resp, errgrpc.ToGRPC(err) + } + + if err := l.publisher.Publish(ctx, "/containers/update", &eventstypes.ContainerUpdate{ + ID: resp.Container.ID, + Image: resp.Container.Image, + Labels: resp.Container.Labels, + SnapshotKey: resp.Container.SnapshotKey, + }); err != nil { + return &resp, err + } + + return &resp, nil +} + +func (l *local) Delete(ctx context.Context, req *api.DeleteContainerRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + if err := l.withStoreUpdate(ctx, func(ctx context.Context) error { + return l.Store.Delete(ctx, req.ID) + }); err != nil { + return empty, errgrpc.ToGRPC(err) + } + + if err := l.publisher.Publish(ctx, "/containers/delete", &eventstypes.ContainerDelete{ + ID: req.ID, + }); err != nil { + return empty, err + } + + return empty, nil +} + +func (l *local) withStore(ctx context.Context, fn func(ctx context.Context) error) func(tx *bolt.Tx) error { + return func(tx *bolt.Tx) error { + return fn(boltutil.WithTransaction(ctx, tx)) + } +} + +func (l *local) withStoreView(ctx context.Context, fn func(ctx context.Context) error) error { + return l.db.View(l.withStore(ctx, fn)) +} + +func (l *local) withStoreUpdate(ctx context.Context, fn func(ctx context.Context) error) error { + return l.db.Update(l.withStore(ctx, fn)) +} + +type localStream struct { + ctx context.Context + containers []*api.Container + i int +} + +func (s *localStream) Recv() (*api.ListContainerMessage, error) { + if s.i >= len(s.containers) { + return nil, io.EOF + } + c := s.containers[s.i] + s.i++ + return &api.ListContainerMessage{ + Container: c, + }, nil +} + +func (s *localStream) Context() context.Context { + return s.ctx +} + +func (s *localStream) CloseSend() error { + return nil +} + +func (s *localStream) Header() (grpcm.MD, error) { + return nil, nil +} + +func (s *localStream) Trailer() grpcm.MD { + return nil +} + +func (s *localStream) SendMsg(m any) error { + return nil +} + +func (s *localStream) RecvMsg(m any) error { + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/containers/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/containers/service.go new file mode 100644 index 0000000000..28cf52761d --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/containers/service.go @@ -0,0 +1,103 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containers + +import ( + "context" + "io" + + api "github.com/containerd/containerd/api/services/containers/v1" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "containers", + Requires: []plugin.Type{ + plugins.ServicePlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.ServicePlugin, services.ContainersService) + if err != nil { + return nil, err + } + return &service{local: i.(api.ContainersClient)}, nil + }, + }) +} + +type service struct { + local api.ContainersClient + api.UnimplementedContainersServer +} + +var _ api.ContainersServer = &service{} + +func (s *service) Register(server *grpc.Server) error { + api.RegisterContainersServer(server, s) + return nil +} + +func (s *service) Get(ctx context.Context, req *api.GetContainerRequest) (*api.GetContainerResponse, error) { + return s.local.Get(ctx, req) +} + +func (s *service) List(ctx context.Context, req *api.ListContainersRequest) (*api.ListContainersResponse, error) { + return s.local.List(ctx, req) +} + +func (s *service) ListStream(req *api.ListContainersRequest, stream api.Containers_ListStreamServer) error { + containers, err := s.local.ListStream(stream.Context(), req) + if err != nil { + return err + } + for { + select { + case <-stream.Context().Done(): + return nil + default: + c, err := containers.Recv() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + if err := stream.Send(c); err != nil { + return err + } + } + } +} + +func (s *service) Create(ctx context.Context, req *api.CreateContainerRequest) (*api.CreateContainerResponse, error) { + return s.local.Create(ctx, req) +} + +func (s *service) Update(ctx context.Context, req *api.UpdateContainerRequest) (*api.UpdateContainerResponse, error) { + return s.local.Update(ctx, req) +} + +func (s *service) Delete(ctx context.Context, req *api.DeleteContainerRequest) (*ptypes.Empty, error) { + return s.local.Delete(ctx, req) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/content/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/content/service.go new file mode 100644 index 0000000000..6e3902a496 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/content/service.go @@ -0,0 +1,43 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package content + +import ( + "github.com/containerd/containerd/v2/core/content" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/containerd/v2/plugins/services/content/contentserver" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "content", + Requires: []plugin.Type{ + plugins.ServicePlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + cs, err := ic.GetByID(plugins.ServicePlugin, services.ContentService) + if err != nil { + return nil, err + } + return contentserver.New(cs.(content.Store)), nil + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/content/store.go b/vendor/github.com/containerd/containerd/v2/plugins/services/content/store.go new file mode 100644 index 0000000000..3c86b84b85 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/content/store.go @@ -0,0 +1,44 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package content + +import ( + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.ContentService, + Requires: []plugin.Type{ + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + return m.(*metadata.DB).ContentStore(), nil + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/diff/local.go b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/local.go new file mode 100644 index 0000000000..6149f460f6 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/local.go @@ -0,0 +1,175 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package diff + +import ( + "context" + "fmt" + + diffapi "github.com/containerd/containerd/api/services/diff/v1" + "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/typeurl/v2" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "google.golang.org/grpc" + + "github.com/containerd/containerd/v2/core/diff" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/pkg/oci" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" +) + +type config struct { + // Order is the order of preference in which to try diff algorithms, the + // first differ which is supported is used. + // Note when multiple differs may be supported, this order will be + // respected for which is chosen. Each differ should return the same + // correct output, allowing any ordering to be used to prefer + // more optimimal implementations. + Order []string `toml:"default"` + // sync_fs is an experimental setting. It's to force sync + // filesystem during unpacking to ensure that data integrity. + // It is effective for all containerd client. + SyncFs bool `toml:"sync_fs"` +} + +type differ interface { + diff.Comparer + diff.Applier +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.DiffService, + Requires: []plugin.Type{ + plugins.DiffPlugin, + }, + Config: defaultDifferConfig, + InitFn: func(ic *plugin.InitContext) (any, error) { + differs, err := ic.GetByType(plugins.DiffPlugin) + if err != nil { + return nil, err + } + syncFs := ic.Config.(*config).SyncFs + orderedNames := ic.Config.(*config).Order + ordered := make([]differ, len(orderedNames)) + for i, n := range orderedNames { + d, ok := differs[n] + if !ok { + return nil, fmt.Errorf("needed differ not loaded: %s", n) + } + + ordered[i], ok = d.(differ) + if !ok { + return nil, fmt.Errorf("differ does not implement Comparer and Applier interface: %s", n) + } + } + + return &local{ + differs: ordered, + syncfs: syncFs, + }, nil + }, + }) +} + +type local struct { + differs []differ + syncfs bool +} + +var _ diffapi.DiffClient = &local{} + +func (l *local) Apply(ctx context.Context, er *diffapi.ApplyRequest, _ ...grpc.CallOption) (*diffapi.ApplyResponse, error) { + var ( + ocidesc ocispec.Descriptor + err error + desc = oci.DescriptorFromProto(er.Diff) + mounts = mount.FromProto(er.Mounts) + ) + + var opts []diff.ApplyOpt + if er.Payloads != nil { + payloads := make(map[string]typeurl.Any) + for k, v := range er.Payloads { + payloads[k] = v + } + opts = append(opts, diff.WithPayloads(payloads)) + } + if l.syncfs { + er.SyncFs = true + } + opts = append(opts, diff.WithSyncFs(er.SyncFs)) + + for _, differ := range l.differs { + ocidesc, err = differ.Apply(ctx, desc, mounts, opts...) + if !errdefs.IsNotImplemented(err) { + break + } + } + + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &diffapi.ApplyResponse{ + Applied: oci.DescriptorToProto(ocidesc), + }, nil + +} + +func (l *local) Diff(ctx context.Context, dr *diffapi.DiffRequest, _ ...grpc.CallOption) (*diffapi.DiffResponse, error) { + var ( + ocidesc ocispec.Descriptor + err error + aMounts = mount.FromProto(dr.Left) + bMounts = mount.FromProto(dr.Right) + ) + + var opts []diff.Opt + if dr.MediaType != "" { + opts = append(opts, diff.WithMediaType(dr.MediaType)) + } + if dr.Ref != "" { + opts = append(opts, diff.WithReference(dr.Ref)) + } + if dr.Labels != nil { + opts = append(opts, diff.WithLabels(dr.Labels)) + } + if dr.SourceDateEpoch != nil { + tm := dr.SourceDateEpoch.AsTime() + opts = append(opts, diff.WithSourceDateEpoch(&tm)) + } + + for _, d := range l.differs { + ocidesc, err = d.Compare(ctx, aMounts, bMounts, opts...) + if !errdefs.IsNotImplemented(err) { + break + } + } + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &diffapi.DiffResponse{ + Diff: oci.DescriptorToProto(ocidesc), + }, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service.go new file mode 100644 index 0000000000..ad6f43ed38 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service.go @@ -0,0 +1,65 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package diff + +import ( + "context" + + diffapi "github.com/containerd/containerd/api/services/diff/v1" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "diff", + Requires: []plugin.Type{ + plugins.ServicePlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.ServicePlugin, services.DiffService) + if err != nil { + return nil, err + } + return &service{local: i.(diffapi.DiffClient)}, nil + }, + }) +} + +type service struct { + local diffapi.DiffClient + diffapi.UnimplementedDiffServer +} + +var _ diffapi.DiffServer = &service{} + +func (s *service) Register(gs *grpc.Server) error { + diffapi.RegisterDiffServer(gs, s) + return nil +} + +func (s *service) Apply(ctx context.Context, er *diffapi.ApplyRequest) (*diffapi.ApplyResponse, error) { + return s.local.Apply(ctx, er) +} + +func (s *service) Diff(ctx context.Context, dr *diffapi.DiffRequest) (*diffapi.DiffResponse, error) { + return s.local.Diff(ctx, dr) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_darwin.go b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_darwin.go new file mode 100644 index 0000000000..30437c1e7c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_darwin.go @@ -0,0 +1,22 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package diff + +var defaultDifferConfig = &config{ + Order: []string{"erofs", "walking"}, + SyncFs: false, +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_unix.go b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_unix.go new file mode 100644 index 0000000000..415c66a79c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_unix.go @@ -0,0 +1,24 @@ +//go:build !windows && !darwin + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package diff + +var defaultDifferConfig = &config{ + Order: []string{"walking"}, + SyncFs: false, +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_windows.go b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_windows.go new file mode 100644 index 0000000000..d6f1e5a1a1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/diff/service_windows.go @@ -0,0 +1,22 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package diff + +var defaultDifferConfig = &config{ + Order: []string{"windows", "windows-lcow"}, + SyncFs: false, +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/events/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/events/service.go new file mode 100644 index 0000000000..4cf22ec4fa --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/events/service.go @@ -0,0 +1,138 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package events + +import ( + "context" + "fmt" + + api "github.com/containerd/containerd/api/services/events/v1" + apittrpc "github.com/containerd/containerd/api/services/ttrpc/events/v1" + "github.com/containerd/containerd/api/types" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/ttrpc" + "github.com/containerd/typeurl/v2" + "google.golang.org/grpc" + + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/events/exchange" + "github.com/containerd/containerd/v2/pkg/protobuf" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" +) + +var empty = &ptypes.Empty{} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "events", + Requires: []plugin.Type{ + plugins.EventPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + ep, err := ic.GetByID(plugins.EventPlugin, "exchange") + if err != nil { + return nil, err + } + return NewService(ep.(*exchange.Exchange)), nil + }, + }) +} + +type service struct { + ttService *ttrpcService + events *exchange.Exchange + api.UnimplementedEventsServer +} + +// NewService returns the GRPC events server +func NewService(events *exchange.Exchange) api.EventsServer { + return &service{ + ttService: &ttrpcService{ + events: events, + }, + events: events, + } +} + +func (s *service) Register(server *grpc.Server) error { + api.RegisterEventsServer(server, s) + return nil +} + +func (s *service) RegisterTTRPC(server *ttrpc.Server) error { + apittrpc.RegisterTTRPCEventsService(server, s.ttService) + return nil +} + +func (s *service) Publish(ctx context.Context, r *api.PublishRequest) (*ptypes.Empty, error) { + if err := s.events.Publish(ctx, r.Topic, r.Event); err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return empty, nil +} + +func (s *service) Forward(ctx context.Context, r *api.ForwardRequest) (*ptypes.Empty, error) { + if err := s.events.Forward(ctx, fromProto(r.Envelope)); err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return empty, nil +} + +func (s *service) Subscribe(req *api.SubscribeRequest, srv api.Events_SubscribeServer) error { + ctx, cancel := context.WithCancel(srv.Context()) + defer cancel() + + eventq, errq := s.events.Subscribe(ctx, req.Filters...) + for { + select { + case ev := <-eventq: + if err := srv.Send(toProto(ev)); err != nil { + return fmt.Errorf("failed sending event to subscriber: %w", err) + } + case err := <-errq: + if err != nil { + return fmt.Errorf("subscription error: %w", err) + } + + return nil + } + } +} + +func toProto(env *events.Envelope) *types.Envelope { + return &types.Envelope{ + Timestamp: protobuf.ToTimestamp(env.Timestamp), + Namespace: env.Namespace, + Topic: env.Topic, + Event: typeurl.MarshalProto(env.Event), + } +} + +func fromProto(env *types.Envelope) *events.Envelope { + return &events.Envelope{ + Timestamp: protobuf.FromTimestamp(env.Timestamp), + Namespace: env.Namespace, + Topic: env.Topic, + Event: env.Event, + } +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/events/ttrpc.go b/vendor/github.com/containerd/containerd/v2/plugins/services/events/ttrpc.go new file mode 100644 index 0000000000..cfcca189db --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/events/ttrpc.go @@ -0,0 +1,51 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package events + +import ( + "context" + + api "github.com/containerd/containerd/api/services/ttrpc/events/v1" + "github.com/containerd/containerd/api/types" + "github.com/containerd/errdefs/pkg/errgrpc" + + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/events/exchange" + "github.com/containerd/containerd/v2/pkg/protobuf" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" +) + +type ttrpcService struct { + events *exchange.Exchange +} + +func (s *ttrpcService) Forward(ctx context.Context, r *api.ForwardRequest) (*ptypes.Empty, error) { + if err := s.events.Forward(ctx, fromTProto(r.Envelope)); err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return empty, nil +} + +func fromTProto(env *types.Envelope) *events.Envelope { + return &events.Envelope{ + Timestamp: protobuf.FromTimestamp(env.Timestamp), + Namespace: env.Namespace, + Topic: env.Topic, + Event: env.Event, + } +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/healthcheck/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/healthcheck/service.go new file mode 100644 index 0000000000..78ffbdee0c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/healthcheck/service.go @@ -0,0 +1,52 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package healthcheck + +import ( + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + + "google.golang.org/grpc" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" +) + +type service struct { + serve *health.Server +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "healthcheck", + InitFn: func(*plugin.InitContext) (any, error) { + return newService() + }, + }) +} + +func newService() (*service, error) { + return &service{ + health.NewServer(), + }, nil +} + +func (s *service) Register(server *grpc.Server) error { + grpc_health_v1.RegisterHealthServer(server, s.serve) + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/images/helpers.go b/vendor/github.com/containerd/containerd/v2/plugins/services/images/helpers.go new file mode 100644 index 0000000000..3e9b92f8b9 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/images/helpers.go @@ -0,0 +1,54 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package images + +import ( + imagesapi "github.com/containerd/containerd/api/services/images/v1" + "github.com/containerd/containerd/v2/core/images" + "github.com/containerd/containerd/v2/pkg/oci" + "github.com/containerd/containerd/v2/pkg/protobuf" +) + +func imagesToProto(images []images.Image) []*imagesapi.Image { + var imagespb []*imagesapi.Image + + for _, image := range images { + imagespb = append(imagespb, imageToProto(&image)) + } + + return imagespb +} + +func imageToProto(image *images.Image) *imagesapi.Image { + return &imagesapi.Image{ + Name: image.Name, + Labels: image.Labels, + Target: oci.DescriptorToProto(image.Target), + CreatedAt: protobuf.ToTimestamp(image.CreatedAt), + UpdatedAt: protobuf.ToTimestamp(image.UpdatedAt), + } +} + +func imageFromProto(imagepb *imagesapi.Image) images.Image { + return images.Image{ + Name: imagepb.Name, + Labels: imagepb.Labels, + Target: oci.DescriptorFromProto(imagepb.Target), + CreatedAt: protobuf.FromTimestamp(imagepb.CreatedAt), + UpdatedAt: protobuf.FromTimestamp(imagepb.UpdatedAt), + } +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/images/local.go b/vendor/github.com/containerd/containerd/v2/plugins/services/images/local.go new file mode 100644 index 0000000000..50f2835e82 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/images/local.go @@ -0,0 +1,187 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package images + +import ( + "context" + + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/log" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + imagesapi "github.com/containerd/containerd/api/services/images/v1" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + + "github.com/containerd/containerd/v2/core/images" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/pkg/epoch" + "github.com/containerd/containerd/v2/pkg/gc" + "github.com/containerd/containerd/v2/pkg/oci" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/containerd/v2/plugins/services/warning" +) + +var empty = &ptypes.Empty{} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.ImagesService, + Requires: []plugin.Type{ + plugins.MetadataPlugin, + plugins.GCPlugin, + plugins.WarningPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + g, err := ic.GetSingle(plugins.GCPlugin) + if err != nil { + return nil, err + } + w, err := ic.GetSingle(plugins.WarningPlugin) + if err != nil { + return nil, err + } + + return &local{ + store: metadata.NewImageStore(m.(*metadata.DB)), + gc: g.(gcScheduler), + warnings: w.(warning.Service), + }, nil + }, + }) +} + +type gcScheduler interface { + ScheduleAndWait(context.Context) (gc.Stats, error) +} + +type local struct { + store images.Store + gc gcScheduler + warnings warning.Service +} + +var _ imagesapi.ImagesClient = &local{} + +func (l *local) Get(ctx context.Context, req *imagesapi.GetImageRequest, _ ...grpc.CallOption) (*imagesapi.GetImageResponse, error) { + image, err := l.store.Get(ctx, req.Name) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + imagepb := imageToProto(&image) + return &imagesapi.GetImageResponse{ + Image: imagepb, + }, nil +} + +func (l *local) List(ctx context.Context, req *imagesapi.ListImagesRequest, _ ...grpc.CallOption) (*imagesapi.ListImagesResponse, error) { + images, err := l.store.List(ctx, req.Filters...) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &imagesapi.ListImagesResponse{ + Images: imagesToProto(images), + }, nil +} + +func (l *local) Create(ctx context.Context, req *imagesapi.CreateImageRequest, _ ...grpc.CallOption) (*imagesapi.CreateImageResponse, error) { + log.G(ctx).WithField("name", req.Image.Name).WithField("target", req.Image.Target.Digest).Debugf("create image") + if req.Image.Name == "" { + return nil, status.Errorf(codes.InvalidArgument, "Image.Name required") + } + + var ( + image = imageFromProto(req.Image) + resp imagesapi.CreateImageResponse + ) + if req.SourceDateEpoch != nil { + tm := req.SourceDateEpoch.AsTime() + ctx = epoch.WithSourceDateEpoch(ctx, &tm) + } + created, err := l.store.Create(ctx, image) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + resp.Image = imageToProto(&created) + + return &resp, nil +} + +func (l *local) Update(ctx context.Context, req *imagesapi.UpdateImageRequest, _ ...grpc.CallOption) (*imagesapi.UpdateImageResponse, error) { + if req.Image.Name == "" { + return nil, status.Errorf(codes.InvalidArgument, "Image.Name required") + } + + var ( + image = imageFromProto(req.Image) + resp imagesapi.UpdateImageResponse + fieldpaths []string + ) + + if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 { + fieldpaths = append(fieldpaths, req.UpdateMask.Paths...) + } + + if req.SourceDateEpoch != nil { + tm := req.SourceDateEpoch.AsTime() + ctx = epoch.WithSourceDateEpoch(ctx, &tm) + } + + updated, err := l.store.Update(ctx, image, fieldpaths...) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + resp.Image = imageToProto(&updated) + + return &resp, nil +} + +func (l *local) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + log.G(ctx).WithField("name", req.Name).Debugf("delete image") + + var opts []images.DeleteOpt + if req.Target != nil { + desc := oci.DescriptorFromProto(req.Target) + opts = append(opts, images.DeleteTarget(&desc)) + } + + // Sync option handled here after event is published + if err := l.store.Delete(ctx, req.Name, opts...); err != nil { + return nil, errgrpc.ToGRPC(err) + } + + if req.Sync { + if _, err := l.gc.ScheduleAndWait(ctx); err != nil { + return nil, err + } + } + + return empty, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/images/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/images/service.go new file mode 100644 index 0000000000..0d13b06bb5 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/images/service.go @@ -0,0 +1,78 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package images + +import ( + "context" + + imagesapi "github.com/containerd/containerd/api/services/images/v1" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "images", + Requires: []plugin.Type{ + plugins.ServicePlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.ServicePlugin, services.ImagesService) + if err != nil { + return nil, err + } + return &service{local: i.(imagesapi.ImagesClient)}, nil + }, + }) +} + +type service struct { + local imagesapi.ImagesClient + imagesapi.UnimplementedImagesServer +} + +var _ imagesapi.ImagesServer = &service{} + +func (s *service) Register(server *grpc.Server) error { + imagesapi.RegisterImagesServer(server, s) + return nil +} + +func (s *service) Get(ctx context.Context, req *imagesapi.GetImageRequest) (*imagesapi.GetImageResponse, error) { + return s.local.Get(ctx, req) +} + +func (s *service) List(ctx context.Context, req *imagesapi.ListImagesRequest) (*imagesapi.ListImagesResponse, error) { + return s.local.List(ctx, req) +} + +func (s *service) Create(ctx context.Context, req *imagesapi.CreateImageRequest) (*imagesapi.CreateImageResponse, error) { + return s.local.Create(ctx, req) +} + +func (s *service) Update(ctx context.Context, req *imagesapi.UpdateImageRequest) (*imagesapi.UpdateImageResponse, error) { + return s.local.Update(ctx, req) +} + +func (s *service) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest) (*ptypes.Empty, error) { + return s.local.Delete(ctx, req) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/local.go b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/local.go new file mode 100644 index 0000000000..d57a9cd795 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/local.go @@ -0,0 +1,323 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package introspection + +import ( + context "context" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + + "github.com/google/uuid" + "google.golang.org/genproto/googleapis/rpc/code" + rpc "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc/status" + + api "github.com/containerd/containerd/api/services/introspection/v1" + "github.com/containerd/containerd/api/types" + "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/typeurl/v2" + + "github.com/containerd/containerd/v2/core/introspection" + "github.com/containerd/containerd/v2/pkg/filters" + "github.com/containerd/containerd/v2/pkg/protobuf" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/containerd/v2/plugins/services/warning" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.IntrospectionService, + Requires: []plugin.Type{plugins.WarningPlugin}, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.WarningPlugin, plugins.DeprecationsPlugin) + if err != nil { + return nil, err + } + + warningClient, ok := i.(warning.Service) + if !ok { + return nil, errors.New("could not create a local client for warning service") + } + + // this service fetches all plugins through the plugin set of the plugin context + return &Local{ + plugins: ic.Plugins(), + root: ic.Properties[plugins.PropertyRootDir], + warningClient: warningClient, + }, nil + }, + }) +} + +// Local is a local implementation of the introspection service +type Local struct { + mu sync.Mutex + root string + plugins *plugin.Set + pluginCache []*api.Plugin + warningClient warning.Service +} + +var _ = (introspection.Service)(&Local{}) + +// UpdateLocal updates the local introspection service +func (l *Local) UpdateLocal(root string) { + l.mu.Lock() + defer l.mu.Unlock() + l.root = root +} + +// Plugins returns the locally defined plugins +func (l *Local) Plugins(ctx context.Context, fs ...string) (*api.PluginsResponse, error) { + filter, err := filters.ParseAll(fs...) + if err != nil { + return nil, fmt.Errorf("%w: %w", errdefs.ErrInvalidArgument, err) + } + + var plugins []*api.Plugin + allPlugins := l.getPlugins() + for _, p := range allPlugins { + if filter.Match(adaptPlugin(p)) { + plugins = append(plugins, p) + } + } + + return &api.PluginsResponse{ + Plugins: plugins, + }, nil +} + +func (l *Local) getPlugins() []*api.Plugin { + l.mu.Lock() + defer l.mu.Unlock() + plugins := l.plugins.GetAll() + if l.pluginCache == nil || len(plugins) != len(l.pluginCache) { + l.pluginCache = pluginsToPB(plugins) + } + return l.pluginCache +} + +// Server returns the local server information +func (l *Local) Server(ctx context.Context) (*api.ServerResponse, error) { + u, err := l.getUUID() + if err != nil { + return nil, err + } + pid := os.Getpid() + var pidns uint64 + if runtime.GOOS == "linux" { + pidns, err = statPIDNS(pid) + if err != nil { + return nil, err + } + } + return &api.ServerResponse{ + UUID: u, + Pid: uint64(pid), + Pidns: pidns, + Deprecations: l.getWarnings(ctx), + }, nil +} + +func (l *Local) getUUID() (string, error) { + l.mu.Lock() + defer l.mu.Unlock() + + data, err := os.ReadFile(l.uuidPath()) + if err != nil { + if os.IsNotExist(err) { + return l.generateUUID() + } + return "", err + } + if len(data) == 0 { + return l.generateUUID() + } + u := string(data) + if _, err := uuid.Parse(u); err != nil { + return "", err + } + return u, nil +} + +func (l *Local) generateUUID() (string, error) { + u, err := uuid.NewRandom() + if err != nil { + return "", err + } + path := l.uuidPath() + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return "", err + } + uu := u.String() + if err := os.WriteFile(path, []byte(uu), 0666); err != nil { + return "", err + } + return uu, nil +} + +func (l *Local) uuidPath() string { + return filepath.Join(l.root, "uuid") +} + +func (l *Local) getWarnings(ctx context.Context) []*api.DeprecationWarning { + return warningsPB(ctx, l.warningClient.Warnings()) +} + +func adaptPlugin(o any) filters.Adaptor { + obj := o.(*api.Plugin) + return filters.AdapterFunc(func(fieldpath []string) (string, bool) { + if len(fieldpath) == 0 { + return "", false + } + + switch fieldpath[0] { + case "type": + return obj.Type, len(obj.Type) > 0 + case "id": + return obj.ID, len(obj.ID) > 0 + case "platforms": + // TODO(stevvooe): Another case here where have multiple values. + // May need to refactor the filter system to allow filtering by + // platform, if this is required. + case "capabilities": + // TODO(stevvooe): Need a better way to match against + // collections. We can only return "the value" but really it + // would be best if we could return a set of values for the + // path, any of which could match. + } + + return "", false + }) +} + +func pluginToPB(p *plugin.Plugin) *api.Plugin { + var requires []string + for _, r := range p.Registration.Requires { + requires = append(requires, r.String()) + } + + var initErr *rpc.Status + if err := p.Err(); err != nil { + st, ok := status.FromError(errgrpc.ToGRPC(err)) + if ok { + var details []*ptypes.Any + for _, d := range st.Proto().Details { + details = append(details, &ptypes.Any{ + TypeUrl: d.TypeUrl, + Value: d.Value, + }) + } + initErr = &rpc.Status{ + Code: int32(st.Code()), + Message: st.Message(), + Details: details, + } + } else { + initErr = &rpc.Status{ + Code: int32(code.Code_UNKNOWN), + Message: err.Error(), + } + } + } + + return &api.Plugin{ + Type: p.Registration.Type.String(), + ID: p.Registration.ID, + Requires: requires, + Platforms: types.OCIPlatformToProto(p.Meta.Platforms), + Capabilities: p.Meta.Capabilities, + Exports: p.Meta.Exports, + InitErr: initErr, + } +} + +func pluginsToPB(plugins []*plugin.Plugin) []*api.Plugin { + pluginsPB := make([]*api.Plugin, 0, len(plugins)) + for _, p := range plugins { + pluginsPB = append(pluginsPB, pluginToPB(p)) + } + + return pluginsPB +} + +func warningsPB(ctx context.Context, warnings []warning.Warning) []*api.DeprecationWarning { + var pb []*api.DeprecationWarning + + for _, w := range warnings { + pb = append(pb, &api.DeprecationWarning{ + ID: string(w.ID), + Message: w.Message, + LastOccurrence: protobuf.ToTimestamp(w.LastOccurrence), + }) + } + return pb +} + +type pluginInfoProvider interface { + PluginInfo(context.Context, any) (any, error) +} + +func (l *Local) PluginInfo(ctx context.Context, pluginType, id string, options any) (*api.PluginInfoResponse, error) { + p := l.plugins.Get(plugin.Type(pluginType), id) + if p == nil { + return nil, fmt.Errorf("plugin %s.%s not found: %w", pluginType, id, errdefs.ErrNotFound) + } + + resp := &api.PluginInfoResponse{ + Plugin: pluginToPB(p), + } + + // Request additional info from plugin instance + if options != nil { + if p.Err() != nil { + return resp, fmt.Errorf("cannot get extra info, plugin not successfully loaded: %w", errdefs.ErrFailedPrecondition) + } + inst, err := p.Instance() + if err != nil { + return resp, fmt.Errorf("failed to get plugin instance: %w", errdefs.ErrFailedPrecondition) + } + pi, ok := inst.(pluginInfoProvider) + if !ok { + return resp, fmt.Errorf("plugin does not provided extra information: %w", errdefs.ErrNotImplemented) + } + + info, err := pi.PluginInfo(ctx, options) + if err != nil { + return resp, errgrpc.ToGRPC(err) + } + ai, err := typeurl.MarshalAny(info) + if err != nil { + return resp, fmt.Errorf("failed to marshal plugin info: %w", err) + } + resp.Extra = &ptypes.Any{ + TypeUrl: ai.GetTypeUrl(), + Value: ai.GetValue(), + } + } + return resp, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/pidns_linux.go b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/pidns_linux.go new file mode 100644 index 0000000000..4dce1a5e7b --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/pidns_linux.go @@ -0,0 +1,36 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package introspection + +import ( + "fmt" + "os" + "syscall" +) + +func statPIDNS(pid int) (uint64, error) { + f := fmt.Sprintf("/proc/%d/ns/pid", pid) + st, err := os.Stat(f) + if err != nil { + return 0, err + } + stSys, ok := st.Sys().(*syscall.Stat_t) + if !ok { + return 0, fmt.Errorf("%T is not *syscall.Stat_t", st.Sys()) + } + return stSys.Ino, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/pidns_others.go b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/pidns_others.go new file mode 100644 index 0000000000..c2cc475af3 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/pidns_others.go @@ -0,0 +1,23 @@ +//go:build !linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package introspection + +func statPIDNS(pid int) (uint64, error) { + return 0, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/service.go new file mode 100644 index 0000000000..75470724e4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/introspection/service.go @@ -0,0 +1,94 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package introspection + +import ( + context "context" + "errors" + "fmt" + + api "github.com/containerd/containerd/api/services/introspection/v1" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/typeurl/v2" + "google.golang.org/grpc" + + "github.com/containerd/containerd/v2/core/introspection" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "introspection", + Requires: []plugin.Type{plugins.ServicePlugin}, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.ServicePlugin, services.IntrospectionService) + if err != nil { + return nil, err + } + + localClient, ok := i.(*Local) + if !ok { + return nil, errors.New("could not create a local client for introspection service") + } + localClient.UpdateLocal(ic.Properties[plugins.PropertyRootDir]) + + return &server{ + local: localClient, + }, nil + }, + }) +} + +type server struct { + local introspection.Service + api.UnimplementedIntrospectionServer +} + +var _ = (api.IntrospectionServer)(&server{}) + +func (s *server) Register(server *grpc.Server) error { + api.RegisterIntrospectionServer(server, s) + return nil +} + +func (s *server) Plugins(ctx context.Context, req *api.PluginsRequest) (resp *api.PluginsResponse, err error) { + resp, err = s.local.Plugins(ctx, req.Filters...) + return resp, errgrpc.ToGRPC(err) +} + +func (s *server) Server(ctx context.Context, _ *ptypes.Empty) (resp *api.ServerResponse, err error) { + resp, err = s.local.Server(ctx) + return resp, errgrpc.ToGRPC(err) +} + +func (s *server) PluginInfo(ctx context.Context, req *api.PluginInfoRequest) (resp *api.PluginInfoResponse, err error) { + var options any + if req.Options != nil { + options, err = typeurl.UnmarshalAny(req.Options) + if err != nil { + return resp, errgrpc.ToGRPC(fmt.Errorf("failed to unmarshal plugin info Options: %w", err)) + } + } + + resp, err = s.local.PluginInfo(ctx, req.Type, req.ID, options) + return resp, errgrpc.ToGRPC(err) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/leases/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/leases/service.go new file mode 100644 index 0000000000..b43038fe87 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/leases/service.go @@ -0,0 +1,168 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package leases + +import ( + "context" + + api "github.com/containerd/containerd/api/services/leases/v1" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" + + "github.com/containerd/containerd/v2/core/leases" + "github.com/containerd/containerd/v2/pkg/protobuf" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" +) + +var empty = &ptypes.Empty{} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "leases", + Requires: []plugin.Type{ + plugins.LeasePlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.LeasePlugin, "manager") + if err != nil { + return nil, err + } + return &service{lm: i.(leases.Manager)}, nil + }, + }) +} + +type service struct { + lm leases.Manager + api.UnimplementedLeasesServer +} + +func (s *service) Register(server *grpc.Server) error { + api.RegisterLeasesServer(server, s) + return nil +} + +func (s *service) Create(ctx context.Context, r *api.CreateRequest) (*api.CreateResponse, error) { + opts := []leases.Opt{ + leases.WithLabels(r.Labels), + } + if r.ID == "" { + opts = append(opts, leases.WithRandomID()) + } else { + opts = append(opts, leases.WithID(r.ID)) + } + + l, err := s.lm.Create(ctx, opts...) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &api.CreateResponse{ + Lease: leaseToGRPC(l), + }, nil +} + +func (s *service) Delete(ctx context.Context, r *api.DeleteRequest) (*ptypes.Empty, error) { + var opts []leases.DeleteOpt + if r.Sync { + opts = append(opts, leases.SynchronousDelete) + } + if err := s.lm.Delete(ctx, leases.Lease{ + ID: r.ID, + }, opts...); err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (s *service) List(ctx context.Context, r *api.ListRequest) (*api.ListResponse, error) { + l, err := s.lm.List(ctx, r.Filters...) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + apileases := make([]*api.Lease, len(l)) + for i := range l { + apileases[i] = leaseToGRPC(l[i]) + } + + return &api.ListResponse{ + Leases: apileases, + }, nil +} + +func (s *service) AddResource(ctx context.Context, r *api.AddResourceRequest) (*ptypes.Empty, error) { + lease := leases.Lease{ + ID: r.ID, + } + + if err := s.lm.AddResource(ctx, lease, leases.Resource{ + ID: r.Resource.ID, + Type: r.Resource.Type, + }); err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (s *service) DeleteResource(ctx context.Context, r *api.DeleteResourceRequest) (*ptypes.Empty, error) { + lease := leases.Lease{ + ID: r.ID, + } + + if err := s.lm.DeleteResource(ctx, lease, leases.Resource{ + ID: r.Resource.ID, + Type: r.Resource.Type, + }); err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (s *service) ListResources(ctx context.Context, r *api.ListResourcesRequest) (*api.ListResourcesResponse, error) { + lease := leases.Lease{ + ID: r.ID, + } + + rs, err := s.lm.ListResources(ctx, lease) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + apiResources := make([]*api.Resource, 0, len(rs)) + for _, i := range rs { + apiResources = append(apiResources, &api.Resource{ + ID: i.ID, + Type: i.Type, + }) + } + return &api.ListResourcesResponse{ + Resources: apiResources, + }, nil +} + +func leaseToGRPC(l leases.Lease) *api.Lease { + return &api.Lease{ + ID: l.ID, + Labels: l.Labels, + CreatedAt: protobuf.ToTimestamp(l.CreatedAt), + } +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/namespaces/local.go b/vendor/github.com/containerd/containerd/v2/plugins/services/namespaces/local.go new file mode 100644 index 0000000000..0fdc2609db --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/namespaces/local.go @@ -0,0 +1,235 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package namespaces + +import ( + "context" + "strings" + + eventstypes "github.com/containerd/containerd/api/events" + api "github.com/containerd/containerd/api/services/namespaces/v1" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + bolt "go.etcd.io/bbolt" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/pkg/namespaces" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" +) + +var empty = &ptypes.Empty{} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.NamespacesService, + Requires: []plugin.Type{ + plugins.EventPlugin, + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + ep, err := ic.GetSingle(plugins.EventPlugin) + if err != nil { + return nil, err + } + return &local{ + db: m.(*metadata.DB), + publisher: ep.(events.Publisher), + }, nil + }, + }) +} + +// Provide local namespaces service instead of local namespace store, +// because namespace store interface doesn't provide enough functionality +// for namespaces service. +type local struct { + db *metadata.DB + publisher events.Publisher +} + +var _ api.NamespacesClient = &local{} + +func (l *local) Get(ctx context.Context, req *api.GetNamespaceRequest, _ ...grpc.CallOption) (*api.GetNamespaceResponse, error) { + var resp api.GetNamespaceResponse + + return &resp, l.withStoreView(ctx, func(ctx context.Context, store namespaces.Store) error { + labels, err := store.Labels(ctx, req.Name) + if err != nil { + return errgrpc.ToGRPC(err) + } + + resp.Namespace = &api.Namespace{ + Name: req.Name, + Labels: labels, + } + + return nil + }) +} + +func (l *local) List(ctx context.Context, req *api.ListNamespacesRequest, _ ...grpc.CallOption) (*api.ListNamespacesResponse, error) { + var resp api.ListNamespacesResponse + + return &resp, l.withStoreView(ctx, func(ctx context.Context, store namespaces.Store) error { + namespaces, err := store.List(ctx) + if err != nil { + return err + } + + for _, namespace := range namespaces { + labels, err := store.Labels(ctx, namespace) + if err != nil { + // In general, this should be unlikely, since we are holding a + // transaction to service this request. + return errgrpc.ToGRPC(err) + } + + resp.Namespaces = append(resp.Namespaces, &api.Namespace{ + Name: namespace, + Labels: labels, + }) + } + + return nil + }) +} + +func (l *local) Create(ctx context.Context, req *api.CreateNamespaceRequest, _ ...grpc.CallOption) (*api.CreateNamespaceResponse, error) { + var resp api.CreateNamespaceResponse + + if err := l.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error { + if err := store.Create(ctx, req.Namespace.Name, req.Namespace.Labels); err != nil { + return errgrpc.ToGRPC(err) + } + + for k, v := range req.Namespace.Labels { + if err := store.SetLabel(ctx, req.Namespace.Name, k, v); err != nil { + return err + } + } + + resp.Namespace = req.Namespace + return nil + }); err != nil { + return &resp, err + } + + ctx = namespaces.WithNamespace(ctx, req.Namespace.Name) + if err := l.publisher.Publish(ctx, "/namespaces/create", &eventstypes.NamespaceCreate{ + Name: req.Namespace.Name, + Labels: req.Namespace.Labels, + }); err != nil { + return &resp, err + } + + return &resp, nil + +} + +func (l *local) Update(ctx context.Context, req *api.UpdateNamespaceRequest, _ ...grpc.CallOption) (*api.UpdateNamespaceResponse, error) { + var resp api.UpdateNamespaceResponse + if err := l.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error { + if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 { + for _, path := range req.UpdateMask.Paths { + switch { + case strings.HasPrefix(path, "labels."): + key := strings.TrimPrefix(path, "labels.") + if err := store.SetLabel(ctx, req.Namespace.Name, key, req.Namespace.Labels[key]); err != nil { + return err + } + default: + return status.Errorf(codes.InvalidArgument, "cannot update %q field", path) + } + } + } else { + // clear out the existing labels and then set them to the incoming request. + // get current set of labels + labels, err := store.Labels(ctx, req.Namespace.Name) + if err != nil { + return errgrpc.ToGRPC(err) + } + + for k := range labels { + if err := store.SetLabel(ctx, req.Namespace.Name, k, ""); err != nil { + return err + } + } + + for k, v := range req.Namespace.Labels { + if err := store.SetLabel(ctx, req.Namespace.Name, k, v); err != nil { + return err + } + + } + } + + return nil + }); err != nil { + return &resp, err + } + + ctx = namespaces.WithNamespace(ctx, req.Namespace.Name) + if err := l.publisher.Publish(ctx, "/namespaces/update", &eventstypes.NamespaceUpdate{ + Name: req.Namespace.Name, + Labels: req.Namespace.Labels, + }); err != nil { + return &resp, err + } + + return &resp, nil +} + +func (l *local) Delete(ctx context.Context, req *api.DeleteNamespaceRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + if err := l.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error { + return errgrpc.ToGRPC(store.Delete(ctx, req.Name)) + }); err != nil { + return empty, err + } + // set the namespace in the context before publishing the event + ctx = namespaces.WithNamespace(ctx, req.Name) + if err := l.publisher.Publish(ctx, "/namespaces/delete", &eventstypes.NamespaceDelete{ + Name: req.Name, + }); err != nil { + return empty, err + } + + return empty, nil +} + +func (l *local) withStore(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) func(tx *bolt.Tx) error { + return func(tx *bolt.Tx) error { return fn(ctx, metadata.NewNamespaceStore(tx)) } +} + +func (l *local) withStoreView(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) error { + return l.db.View(l.withStore(ctx, fn)) +} + +func (l *local) withStoreUpdate(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) error { + return l.db.Update(l.withStore(ctx, fn)) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/namespaces/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/namespaces/service.go new file mode 100644 index 0000000000..ee14bbedd9 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/namespaces/service.go @@ -0,0 +1,78 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package namespaces + +import ( + "context" + + api "github.com/containerd/containerd/api/services/namespaces/v1" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "namespaces", + Requires: []plugin.Type{ + plugins.ServicePlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.ServicePlugin, services.NamespacesService) + if err != nil { + return nil, err + } + return &service{local: i.(api.NamespacesClient)}, nil + }, + }) +} + +type service struct { + local api.NamespacesClient + api.UnimplementedNamespacesServer +} + +var _ api.NamespacesServer = &service{} + +func (s *service) Register(server *grpc.Server) error { + api.RegisterNamespacesServer(server, s) + return nil +} + +func (s *service) Get(ctx context.Context, req *api.GetNamespaceRequest) (*api.GetNamespaceResponse, error) { + return s.local.Get(ctx, req) +} + +func (s *service) List(ctx context.Context, req *api.ListNamespacesRequest) (*api.ListNamespacesResponse, error) { + return s.local.List(ctx, req) +} + +func (s *service) Create(ctx context.Context, req *api.CreateNamespaceRequest) (*api.CreateNamespaceResponse, error) { + return s.local.Create(ctx, req) +} + +func (s *service) Update(ctx context.Context, req *api.UpdateNamespaceRequest) (*api.UpdateNamespaceResponse, error) { + return s.local.Update(ctx, req) +} + +func (s *service) Delete(ctx context.Context, req *api.DeleteNamespaceRequest) (*ptypes.Empty, error) { + return s.local.Delete(ctx, req) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/snapshots/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/snapshots/service.go new file mode 100644 index 0000000000..980eae22ff --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/snapshots/service.go @@ -0,0 +1,273 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package snapshots + +import ( + "context" + + snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1" + "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/core/snapshots/proxy" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "snapshots", + Requires: []plugin.Type{ + plugins.ServicePlugin, + }, + InitFn: newService, + }) +} + +var empty = &ptypes.Empty{} + +type service struct { + ss map[string]snapshots.Snapshotter + snapshotsapi.UnimplementedSnapshotsServer +} + +func newService(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.ServicePlugin, services.SnapshotsService) + if err != nil { + return nil, err + } + return &service{ss: i.(map[string]snapshots.Snapshotter)}, nil +} + +func (s *service) getSnapshotter(name string) (snapshots.Snapshotter, error) { + if name == "" { + return nil, errgrpc.ToGRPCf(errdefs.ErrInvalidArgument, "snapshotter argument missing") + } + + sn := s.ss[name] + if sn == nil { + return nil, errgrpc.ToGRPCf(errdefs.ErrInvalidArgument, "snapshotter not loaded: %s", name) + } + return sn, nil +} + +func (s *service) Register(gs *grpc.Server) error { + snapshotsapi.RegisterSnapshotsServer(gs, s) + return nil +} + +func (s *service) Prepare(ctx context.Context, pr *snapshotsapi.PrepareSnapshotRequest) (*snapshotsapi.PrepareSnapshotResponse, error) { + log.G(ctx).WithFields(log.Fields{"parent": pr.Parent, "key": pr.Key, "snapshotter": pr.Snapshotter}).Debugf("prepare snapshot") + sn, err := s.getSnapshotter(pr.Snapshotter) + if err != nil { + return nil, err + } + + var opts []snapshots.Opt + if pr.Labels != nil { + opts = append(opts, snapshots.WithLabels(pr.Labels)) + } + mounts, err := sn.Prepare(ctx, pr.Key, pr.Parent, opts...) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &snapshotsapi.PrepareSnapshotResponse{ + Mounts: mount.ToProto(mounts), + }, nil +} + +func (s *service) View(ctx context.Context, pr *snapshotsapi.ViewSnapshotRequest) (*snapshotsapi.ViewSnapshotResponse, error) { + log.G(ctx).WithFields(log.Fields{"parent": pr.Parent, "key": pr.Key, "snapshotter": pr.Snapshotter}).Debugf("prepare view snapshot") + sn, err := s.getSnapshotter(pr.Snapshotter) + if err != nil { + return nil, err + } + var opts []snapshots.Opt + if pr.Labels != nil { + opts = append(opts, snapshots.WithLabels(pr.Labels)) + } + mounts, err := sn.View(ctx, pr.Key, pr.Parent, opts...) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &snapshotsapi.ViewSnapshotResponse{ + Mounts: mount.ToProto(mounts), + }, nil +} + +func (s *service) Mounts(ctx context.Context, mr *snapshotsapi.MountsRequest) (*snapshotsapi.MountsResponse, error) { + log.G(ctx).WithFields(log.Fields{"key": mr.Key, "snapshotter": mr.Snapshotter}).Debugf("get snapshot mounts") + sn, err := s.getSnapshotter(mr.Snapshotter) + if err != nil { + return nil, err + } + + mounts, err := sn.Mounts(ctx, mr.Key) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &snapshotsapi.MountsResponse{ + Mounts: mount.ToProto(mounts), + }, nil +} + +func (s *service) Commit(ctx context.Context, cr *snapshotsapi.CommitSnapshotRequest) (*ptypes.Empty, error) { + log.G(ctx).WithFields(log.Fields{"key": cr.Key, "snapshotter": cr.Snapshotter, "name": cr.Name}).Debugf("commit snapshot") + sn, err := s.getSnapshotter(cr.Snapshotter) + if err != nil { + return nil, err + } + + var opts []snapshots.Opt + if cr.Labels != nil { + opts = append(opts, snapshots.WithLabels(cr.Labels)) + } + if cr.Parent != "" { + opts = append(opts, snapshots.WithParent(cr.Parent)) + } + if err := sn.Commit(ctx, cr.Name, cr.Key, opts...); err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return empty, nil +} + +func (s *service) Remove(ctx context.Context, rr *snapshotsapi.RemoveSnapshotRequest) (*ptypes.Empty, error) { + log.G(ctx).WithFields(log.Fields{"key": rr.Key, "snapshotter": rr.Snapshotter}).Debugf("remove snapshot") + sn, err := s.getSnapshotter(rr.Snapshotter) + if err != nil { + return nil, err + } + + if err := sn.Remove(ctx, rr.Key); err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return empty, nil +} + +func (s *service) Stat(ctx context.Context, sr *snapshotsapi.StatSnapshotRequest) (*snapshotsapi.StatSnapshotResponse, error) { + log.G(ctx).WithFields(log.Fields{"key": sr.Key, "snapshotter": sr.Snapshotter}).Debugf("stat snapshot") + sn, err := s.getSnapshotter(sr.Snapshotter) + if err != nil { + return nil, err + } + + info, err := sn.Stat(ctx, sr.Key) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &snapshotsapi.StatSnapshotResponse{Info: proxy.InfoToProto(info)}, nil +} + +func (s *service) Update(ctx context.Context, sr *snapshotsapi.UpdateSnapshotRequest) (*snapshotsapi.UpdateSnapshotResponse, error) { + log.G(ctx).WithFields(log.Fields{"key": sr.Info.Name, "snapshotter": sr.Snapshotter}).Debugf("update snapshot") + sn, err := s.getSnapshotter(sr.Snapshotter) + if err != nil { + return nil, err + } + + info, err := sn.Update(ctx, proxy.InfoFromProto(sr.Info), sr.UpdateMask.GetPaths()...) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &snapshotsapi.UpdateSnapshotResponse{Info: proxy.InfoToProto(info)}, nil +} + +func (s *service) List(sr *snapshotsapi.ListSnapshotsRequest, ss snapshotsapi.Snapshots_ListServer) error { + sn, err := s.getSnapshotter(sr.Snapshotter) + if err != nil { + return err + } + + var ( + buffer []*snapshotsapi.Info + sendBlock = func(block []*snapshotsapi.Info) error { + return ss.Send(&snapshotsapi.ListSnapshotsResponse{ + Info: block, + }) + } + ) + err = sn.Walk(ss.Context(), func(ctx context.Context, info snapshots.Info) error { + buffer = append(buffer, proxy.InfoToProto(info)) + + if len(buffer) >= 100 { + if err := sendBlock(buffer); err != nil { + return err + } + + buffer = buffer[:0] + } + + return nil + }, sr.Filters...) + if err != nil { + return err + } + if len(buffer) > 0 { + // Send remaining infos + if err := sendBlock(buffer); err != nil { + return err + } + } + + return nil +} + +func (s *service) Usage(ctx context.Context, ur *snapshotsapi.UsageRequest) (*snapshotsapi.UsageResponse, error) { + sn, err := s.getSnapshotter(ur.Snapshotter) + if err != nil { + return nil, err + } + + usage, err := sn.Usage(ctx, ur.Key) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return proxy.UsageToProto(usage), nil +} + +func (s *service) Cleanup(ctx context.Context, cr *snapshotsapi.CleanupRequest) (*ptypes.Empty, error) { + sn, err := s.getSnapshotter(cr.Snapshotter) + if err != nil { + return nil, err + } + + c, ok := sn.(snapshots.Cleaner) + if !ok { + return nil, errgrpc.ToGRPCf(errdefs.ErrNotImplemented, "snapshotter does not implement Cleanup method") + } + + err = c.Cleanup(ctx) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return empty, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/snapshots/snapshotters.go b/vendor/github.com/containerd/containerd/v2/plugins/services/snapshots/snapshotters.go new file mode 100644 index 0000000000..7af289c250 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/snapshots/snapshotters.go @@ -0,0 +1,43 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package snapshots + +import ( + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.SnapshotsService, + Requires: []plugin.Type{ + plugins.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + return m.(*metadata.DB).Snapshotters(), nil + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/tasks/local.go b/vendor/github.com/containerd/containerd/v2/plugins/services/tasks/local.go new file mode 100644 index 0000000000..857ebc6232 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/tasks/local.go @@ -0,0 +1,792 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package tasks + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "time" + + api "github.com/containerd/containerd/api/services/tasks/v1" + "github.com/containerd/containerd/api/types" + "github.com/containerd/containerd/api/types/runc/options" + "github.com/containerd/containerd/api/types/task" + "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/errgrpc" + "github.com/containerd/log" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "github.com/containerd/typeurl/v2" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/containerd/containerd/v2/core/containers" + "github.com/containerd/containerd/v2/core/content" + "github.com/containerd/containerd/v2/core/events" + "github.com/containerd/containerd/v2/core/images" + "github.com/containerd/containerd/v2/core/metadata" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/runtime" + "github.com/containerd/containerd/v2/pkg/archive" + "github.com/containerd/containerd/v2/pkg/blockio" + "github.com/containerd/containerd/v2/pkg/deprecation" + "github.com/containerd/containerd/v2/pkg/filters" + "github.com/containerd/containerd/v2/pkg/protobuf" + "github.com/containerd/containerd/v2/pkg/protobuf/proto" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/pkg/rdt" + "github.com/containerd/containerd/v2/pkg/timeout" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/containerd/v2/plugins/services/warning" +) + +var ( + _ = (api.TasksClient)(&local{}) + empty = &ptypes.Empty{} + tasksServiceRequires = []plugin.Type{ + plugins.EventPlugin, + plugins.RuntimePluginV2, + plugins.MetadataPlugin, + plugins.TaskMonitorPlugin, + plugins.WarningPlugin, + } +) + +const ( + stateTimeout = "io.containerd.timeout.task.state" +) + +// Config for the tasks service plugin +type Config struct { + // BlockIOConfigFile specifies the path to blockio configuration file + BlockIOConfigFile string `toml:"blockio_config_file" json:"blockioConfigFile"` + // RdtConfigFile specifies the path to RDT configuration file + RdtConfigFile string `toml:"rdt_config_file" json:"rdtConfigFile"` +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.ServicePlugin, + ID: services.TasksService, + Requires: tasksServiceRequires, + Config: &Config{}, + InitFn: initFunc, + }) + + timeout.Set(stateTimeout, 2*time.Second) +} + +func initFunc(ic *plugin.InitContext) (any, error) { + config := ic.Config.(*Config) + + v2r, err := ic.GetByID(plugins.RuntimePluginV2, "task") + if err != nil { + return nil, err + } + + m, err := ic.GetSingle(plugins.MetadataPlugin) + if err != nil { + return nil, err + } + + ep, err := ic.GetSingle(plugins.EventPlugin) + if err != nil { + return nil, err + } + + monitor, err := ic.GetSingle(plugins.TaskMonitorPlugin) + if err != nil { + if !errors.Is(err, plugin.ErrPluginNotFound) { + return nil, err + } + monitor = runtime.NewNoopMonitor() + } + + warnings, err := ic.GetSingle(plugins.WarningPlugin) + if err != nil { + return nil, err + } + + db := m.(*metadata.DB) + l := &local{ + containers: metadata.NewContainerStore(db), + store: db.ContentStore(), + publisher: ep.(events.Publisher), + monitor: monitor.(runtime.TaskMonitor), + v2Runtime: v2r.(runtime.PlatformRuntime), + warnings: warnings.(warning.Service), + } + + v2Tasks, err := l.v2Runtime.Tasks(ic.Context, true) + if err != nil { + return nil, err + } + for _, t := range v2Tasks { + l.monitor.Monitor(t, nil) + } + + if err := blockio.SetConfig(config.BlockIOConfigFile); err != nil { + log.G(ic.Context).WithError(err).Errorf("blockio initialization failed") + } + if err := rdt.SetConfig(config.RdtConfigFile); err != nil { + log.G(ic.Context).WithError(err).Errorf("RDT initialization failed") + } + + return l, nil +} + +type local struct { + containers containers.Store + store content.Store + publisher events.Publisher + + monitor runtime.TaskMonitor + v2Runtime runtime.PlatformRuntime + warnings warning.Service +} + +func (l *local) Create(ctx context.Context, r *api.CreateTaskRequest, _ ...grpc.CallOption) (*api.CreateTaskResponse, error) { + container, err := l.getContainer(ctx, r.ContainerID) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + var ( + checkpointPath string + taskAPIAddress = r.TaskApiAddress + taskAPIVersion = r.TaskApiVersion + ) + + if r.Options != nil { + taskOptions, err := formatOptions(container.Runtime.Name, r.Options) + if err != nil { + return nil, err + } + checkpointPath = taskOptions.CriuImagePath + + // This path is deprecated and here for backward compatibility, + // task address and version should not be passed via runc options, + // this is sandbox API specific and has nothing to do with runc. + deprecatedAddr, deprecatedVer := taskOptions.GetTaskApiAddress(), taskOptions.GetTaskApiVersion() //nolint:staticcheck // deprecated, kept for backward compatibility + if taskAPIAddress == "" && deprecatedAddr != "" { + l.warnings.Emit(ctx, deprecation.RuncOptionsTaskAPIAddress) + taskAPIAddress = deprecatedAddr + } + if taskAPIVersion == 0 && deprecatedVer != 0 { + l.warnings.Emit(ctx, deprecation.RuncOptionsTaskAPIVersion) + taskAPIVersion = deprecatedVer + } + } + + restoreFromPath := false + // For a restore via CRI. + if r.Checkpoint != nil && r.Checkpoint.Annotations != nil { + ann, ok := r.Checkpoint.Annotations["RestoreFromPath"] + if ok { + checkpointPath = ann + restoreFromPath = true + } + } + + // jump get checkpointPath from checkpoint image + if checkpointPath == "" && r.Checkpoint != nil { + checkpointPath, err = os.MkdirTemp(os.Getenv("XDG_RUNTIME_DIR"), "ctrd-checkpoint") + if err != nil { + return nil, err + } + if r.Checkpoint.MediaType != images.MediaTypeContainerd1Checkpoint { + return nil, fmt.Errorf("unsupported checkpoint type %q", r.Checkpoint.MediaType) + } + reader, err := l.store.ReaderAt(ctx, ocispec.Descriptor{ + MediaType: r.Checkpoint.MediaType, + Digest: digest.Digest(r.Checkpoint.Digest), + Size: r.Checkpoint.Size, + Annotations: r.Checkpoint.Annotations, + }) + if err != nil { + return nil, err + } + _, err = archive.Apply(ctx, checkpointPath, content.NewReader(reader)) + reader.Close() + if err != nil { + return nil, err + } + } + + opts := runtime.CreateOpts{ + Spec: container.Spec, + IO: runtime.IO{ + Stdin: r.Stdin, + Stdout: r.Stdout, + Stderr: r.Stderr, + Terminal: r.Terminal, + }, + Checkpoint: checkpointPath, + RestoreFromPath: restoreFromPath, + Runtime: container.Runtime.Name, + RuntimeOptions: container.Runtime.Options, + TaskOptions: r.Options, + SandboxID: container.SandboxID, + Address: taskAPIAddress, + Version: taskAPIVersion, + } + if r.RuntimePath != "" { + opts.Runtime = r.RuntimePath + } + for _, m := range r.Rootfs { + opts.Rootfs = append(opts.Rootfs, mount.Mount{ + Type: m.Type, + Source: m.Source, + Target: m.Target, + Options: m.Options, + }) + } + + rtime := l.v2Runtime + + _, err = rtime.Get(ctx, r.ContainerID) + if err != nil && !errdefs.IsNotFound(err) { + return nil, errgrpc.ToGRPC(err) + } + if err == nil { + return nil, errgrpc.ToGRPC(fmt.Errorf("task %s: %w", r.ContainerID, errdefs.ErrAlreadyExists)) + } + c, err := rtime.Create(ctx, r.ContainerID, opts) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + labels := map[string]string{"runtime": container.Runtime.Name} + if err := l.monitor.Monitor(c, labels); err != nil { + return nil, fmt.Errorf("monitor task: %w", err) + } + pid, err := c.PID(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get task pid: %w", err) + } + return &api.CreateTaskResponse{ + ContainerID: r.ContainerID, + Pid: pid, + }, nil +} + +func (l *local) Start(ctx context.Context, r *api.StartRequest, _ ...grpc.CallOption) (*api.StartResponse, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + p := runtime.Process(t) + if r.ExecID != "" { + if p, err = t.Process(ctx, r.ExecID); err != nil { + return nil, errgrpc.ToGRPC(err) + } + } + if err := p.Start(ctx); err != nil { + return nil, errgrpc.ToGRPC(err) + } + state, err := p.State(ctx) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &api.StartResponse{ + Pid: state.Pid, + }, nil +} + +func (l *local) Delete(ctx context.Context, r *api.DeleteTaskRequest, _ ...grpc.CallOption) (*api.DeleteResponse, error) { + container, err := l.getContainer(ctx, r.ContainerID) + if err != nil { + return nil, err + } + + // Get task object + t, err := l.v2Runtime.Get(ctx, container.ID) + if err != nil { + return nil, status.Errorf(codes.NotFound, "task %v not found", container.ID) + } + + if err := l.monitor.Stop(t); err != nil { + return nil, err + } + + exit, err := l.v2Runtime.Delete(ctx, r.ContainerID) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + + return &api.DeleteResponse{ + ExitStatus: exit.Status, + ExitedAt: protobuf.ToTimestamp(exit.Timestamp), + Pid: exit.Pid, + }, nil +} + +func (l *local) DeleteProcess(ctx context.Context, r *api.DeleteProcessRequest, _ ...grpc.CallOption) (*api.DeleteResponse, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + process, err := t.Process(ctx, r.ExecID) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + exit, err := process.Delete(ctx) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &api.DeleteResponse{ + ID: r.ExecID, + ExitStatus: exit.Status, + ExitedAt: protobuf.ToTimestamp(exit.Timestamp), + Pid: exit.Pid, + }, nil +} + +func getProcessState(ctx context.Context, p runtime.Process) (*task.Process, error) { + ctx, cancel := timeout.WithContext(ctx, stateTimeout) + defer cancel() + + state, err := p.State(ctx) + if err != nil { + if errdefs.IsNotFound(err) || errdefs.IsUnavailable(err) || errdefs.IsDeadlineExceeded(err) { + return nil, err + } + log.G(ctx).WithError(err).Errorf("get state for %s", p.ID()) + } + status := task.Status_UNKNOWN + switch state.Status { + case runtime.CreatedStatus: + status = task.Status_CREATED + case runtime.RunningStatus: + status = task.Status_RUNNING + case runtime.StoppedStatus: + status = task.Status_STOPPED + case runtime.PausedStatus: + status = task.Status_PAUSED + case runtime.PausingStatus: + status = task.Status_PAUSING + default: + log.G(ctx).WithField("status", state.Status).Warn("unknown status") + } + return &task.Process{ + ID: p.ID(), + Pid: state.Pid, + Status: status, + Stdin: state.Stdin, + Stdout: state.Stdout, + Stderr: state.Stderr, + Terminal: state.Terminal, + ExitStatus: state.ExitStatus, + ExitedAt: protobuf.ToTimestamp(state.ExitedAt), + }, nil +} + +func (l *local) Get(ctx context.Context, r *api.GetRequest, _ ...grpc.CallOption) (*api.GetResponse, error) { + task, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + p := runtime.Process(task) + if r.ExecID != "" { + if p, err = task.Process(ctx, r.ExecID); err != nil { + return nil, errgrpc.ToGRPC(err) + } + } + t, err := getProcessState(ctx, p) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &api.GetResponse{ + Process: t, + }, nil +} + +func (l *local) List(ctx context.Context, r *api.ListTasksRequest, _ ...grpc.CallOption) (*api.ListTasksResponse, error) { + resp := &api.ListTasksResponse{} + tasks, err := l.v2Runtime.Tasks(ctx, false) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + addTasks(ctx, resp, tasks) + return resp, nil +} + +func addTasks(ctx context.Context, r *api.ListTasksResponse, tasks []runtime.Task) { + for _, t := range tasks { + tt, err := getProcessState(ctx, t) + if err != nil { + if !errdefs.IsNotFound(err) { // handle race with deletion + log.G(ctx).WithError(err).WithField("id", t.ID()).Error("converting task to protobuf") + } + continue + } + r.Tasks = append(r.Tasks, tt) + } +} + +func (l *local) Pause(ctx context.Context, r *api.PauseTaskRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + err = t.Pause(ctx) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (l *local) Resume(ctx context.Context, r *api.ResumeTaskRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + err = t.Resume(ctx) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (l *local) Kill(ctx context.Context, r *api.KillRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + p := runtime.Process(t) + if r.ExecID != "" { + if p, err = t.Process(ctx, r.ExecID); err != nil { + return nil, errgrpc.ToGRPC(err) + } + } + if err := p.Kill(ctx, r.Signal, r.All); err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (l *local) ListPids(ctx context.Context, r *api.ListPidsRequest, _ ...grpc.CallOption) (*api.ListPidsResponse, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + processList, err := t.Pids(ctx) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + var processes []*task.ProcessInfo + for _, p := range processList { + pInfo := task.ProcessInfo{ + Pid: p.Pid, + } + if p.Info != nil { + a, err := typeurl.MarshalAnyToProto(p.Info) + if err != nil { + return nil, fmt.Errorf("failed to marshal process %d info: %w", p.Pid, err) + } + pInfo.Info = a + } + processes = append(processes, &pInfo) + } + return &api.ListPidsResponse{ + Processes: processes, + }, nil +} + +func (l *local) Exec(ctx context.Context, r *api.ExecProcessRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + if r.ExecID == "" { + return nil, status.Errorf(codes.InvalidArgument, "exec id cannot be empty") + } + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + if _, err := t.Exec(ctx, r.ExecID, runtime.ExecOpts{ + Spec: r.Spec, + IO: runtime.IO{ + Stdin: r.Stdin, + Stdout: r.Stdout, + Stderr: r.Stderr, + Terminal: r.Terminal, + }, + }); err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (l *local) ResizePty(ctx context.Context, r *api.ResizePtyRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + p := runtime.Process(t) + if r.ExecID != "" { + if p, err = t.Process(ctx, r.ExecID); err != nil { + return nil, errgrpc.ToGRPC(err) + } + } + if err := p.ResizePty(ctx, runtime.ConsoleSize{ + Width: r.Width, + Height: r.Height, + }); err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (l *local) CloseIO(ctx context.Context, r *api.CloseIORequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + p := runtime.Process(t) + if r.ExecID != "" { + if p, err = t.Process(ctx, r.ExecID); err != nil { + return nil, errgrpc.ToGRPC(err) + } + } + if r.Stdin { + if err := p.CloseIO(ctx); err != nil { + return nil, errgrpc.ToGRPC(err) + } + } + return empty, nil +} + +func (l *local) Checkpoint(ctx context.Context, r *api.CheckpointTaskRequest, _ ...grpc.CallOption) (*api.CheckpointTaskResponse, error) { + container, err := l.getContainer(ctx, r.ContainerID) + if err != nil { + return nil, err + } + t, err := l.getTaskFromContainer(ctx, container) + if err != nil { + return nil, err + } + image, err := getCheckpointPath(container.Runtime.Name, r.Options) + if err != nil { + return nil, err + } + checkpointImageExists := false + if image == "" { + checkpointImageExists = true + image, err = os.MkdirTemp(os.Getenv("XDG_RUNTIME_DIR"), "ctrd-checkpoint") + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + defer os.RemoveAll(image) + } + if err := t.Checkpoint(ctx, image, r.Options); err != nil { + return nil, errgrpc.ToGRPC(err) + } + // do not commit checkpoint image if checkpoint ImagePath is passed, + // return if checkpointImageExists is false + if !checkpointImageExists { + return &api.CheckpointTaskResponse{}, nil + } + // write checkpoint to the content store + tar := archive.Diff(ctx, "", image) + cp, err := l.writeContent(ctx, images.MediaTypeContainerd1Checkpoint, image, tar) + // close tar first after write + if err := tar.Close(); err != nil { + return nil, err + } + if err != nil { + return nil, err + } + // write the config to the content store + pbany := typeurl.MarshalProto(container.Spec) + data, err := proto.Marshal(pbany) + if err != nil { + return nil, err + } + spec := bytes.NewReader(data) + specD, err := l.writeContent(ctx, images.MediaTypeContainerd1CheckpointConfig, filepath.Join(image, "spec"), spec) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &api.CheckpointTaskResponse{ + Descriptors: []*types.Descriptor{ + cp, + specD, + }, + }, nil +} + +func (l *local) Update(ctx context.Context, r *api.UpdateTaskRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + if err := t.Update(ctx, r.Resources, r.Annotations); err != nil { + return nil, errgrpc.ToGRPC(err) + } + return empty, nil +} + +func (l *local) Metrics(ctx context.Context, r *api.MetricsRequest, _ ...grpc.CallOption) (*api.MetricsResponse, error) { + filter, err := filters.ParseAll(r.Filters...) + if err != nil { + return nil, err + } + var resp api.MetricsResponse + tasks, err := l.v2Runtime.Tasks(ctx, false) + if err != nil { + return nil, err + } + getTasksMetrics(ctx, filter, tasks, &resp) + return &resp, nil +} + +func (l *local) Wait(ctx context.Context, r *api.WaitRequest, _ ...grpc.CallOption) (*api.WaitResponse, error) { + t, err := l.getTask(ctx, r.ContainerID) + if err != nil { + return nil, err + } + p := runtime.Process(t) + if r.ExecID != "" { + if p, err = t.Process(ctx, r.ExecID); err != nil { + return nil, errgrpc.ToGRPC(err) + } + } + exit, err := p.Wait(ctx) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &api.WaitResponse{ + ExitStatus: exit.Status, + ExitedAt: protobuf.ToTimestamp(exit.Timestamp), + }, nil +} + +func getTasksMetrics(ctx context.Context, filter filters.Filter, tasks []runtime.Task, r *api.MetricsResponse) { + for _, tk := range tasks { + if !filter.Match(filters.AdapterFunc(func(fieldpath []string) (string, bool) { + t := tk + switch fieldpath[0] { + case "id": + return t.ID(), true + case "namespace": + return t.Namespace(), true + case "runtime": + // return t.Info().Runtime, true + } + return "", false + })) { + continue + } + collected := time.Now() + stats, err := tk.Stats(ctx) + if err != nil { + if !errdefs.IsNotFound(err) { + log.G(ctx).WithError(err).Errorf("collecting metrics for %s", tk.ID()) + } + continue + } + r.Metrics = append(r.Metrics, &types.Metric{ + Timestamp: protobuf.ToTimestamp(collected), + ID: tk.ID(), + Data: stats, + }) + } +} + +func (l *local) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) { + writer, err := l.store.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{MediaType: mediaType})) + if err != nil { + return nil, err + } + defer writer.Close() + size, err := io.Copy(writer, r) + if err != nil { + return nil, err + } + if err := writer.Commit(ctx, 0, ""); err != nil && !errdefs.IsAlreadyExists(err) { + return nil, err + } + return &types.Descriptor{ + MediaType: mediaType, + Digest: writer.Digest().String(), + Size: size, + Annotations: make(map[string]string), + }, nil +} + +func (l *local) getContainer(ctx context.Context, id string) (*containers.Container, error) { + var container containers.Container + container, err := l.containers.Get(ctx, id) + if err != nil { + return nil, errgrpc.ToGRPC(err) + } + return &container, nil +} + +func (l *local) getTask(ctx context.Context, id string) (runtime.Task, error) { + container, err := l.getContainer(ctx, id) + if err != nil { + return nil, err + } + return l.getTaskFromContainer(ctx, container) +} + +func (l *local) getTaskFromContainer(ctx context.Context, container *containers.Container) (runtime.Task, error) { + t, err := l.v2Runtime.Get(ctx, container.ID) + if err != nil { + return nil, status.Errorf(codes.NotFound, "task %v not found", container.ID) + } + return t, nil +} + +// getCheckpointPath only suitable for runc runtime now +func getCheckpointPath(runtime string, option *ptypes.Any) (string, error) { + if option == nil { + return "", nil + } + + var checkpointPath string + v, err := typeurl.UnmarshalAny(option) + if err != nil { + return "", err + } + opts, ok := v.(*options.CheckpointOptions) + if !ok { + return "", fmt.Errorf("invalid task checkpoint option for %s", runtime) + } + checkpointPath = opts.ImagePath + + return checkpointPath, nil +} + +func formatOptions(runtime string, option *ptypes.Any) (*options.Options, error) { + v, err := typeurl.UnmarshalAny(option) + if err != nil { + return nil, err + } + opts, ok := v.(*options.Options) + if !ok { + return nil, fmt.Errorf("invalid task create option for %s", runtime) + } + return opts, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/tasks/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/tasks/service.go new file mode 100644 index 0000000000..022ae9fd3c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/tasks/service.go @@ -0,0 +1,128 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package tasks + +import ( + "context" + + api "github.com/containerd/containerd/api/services/tasks/v1" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" +) + +var ( + _ = (api.TasksServer)(&service{}) +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "tasks", + Requires: []plugin.Type{ + plugins.ServicePlugin, + }, + InitFn: func(ic *plugin.InitContext) (any, error) { + i, err := ic.GetByID(plugins.ServicePlugin, services.TasksService) + if err != nil { + return nil, err + } + return &service{local: i.(api.TasksClient)}, nil + }, + }) +} + +type service struct { + local api.TasksClient + api.UnimplementedTasksServer +} + +func (s *service) Register(server *grpc.Server) error { + api.RegisterTasksServer(server, s) + return nil +} + +func (s *service) Create(ctx context.Context, r *api.CreateTaskRequest) (*api.CreateTaskResponse, error) { + return s.local.Create(ctx, r) +} + +func (s *service) Start(ctx context.Context, r *api.StartRequest) (*api.StartResponse, error) { + return s.local.Start(ctx, r) +} + +func (s *service) Delete(ctx context.Context, r *api.DeleteTaskRequest) (*api.DeleteResponse, error) { + return s.local.Delete(ctx, r) +} + +func (s *service) DeleteProcess(ctx context.Context, r *api.DeleteProcessRequest) (*api.DeleteResponse, error) { + return s.local.DeleteProcess(ctx, r) +} + +func (s *service) Get(ctx context.Context, r *api.GetRequest) (*api.GetResponse, error) { + return s.local.Get(ctx, r) +} + +func (s *service) List(ctx context.Context, r *api.ListTasksRequest) (*api.ListTasksResponse, error) { + return s.local.List(ctx, r) +} + +func (s *service) Pause(ctx context.Context, r *api.PauseTaskRequest) (*ptypes.Empty, error) { + return s.local.Pause(ctx, r) +} + +func (s *service) Resume(ctx context.Context, r *api.ResumeTaskRequest) (*ptypes.Empty, error) { + return s.local.Resume(ctx, r) +} + +func (s *service) Kill(ctx context.Context, r *api.KillRequest) (*ptypes.Empty, error) { + return s.local.Kill(ctx, r) +} + +func (s *service) ListPids(ctx context.Context, r *api.ListPidsRequest) (*api.ListPidsResponse, error) { + return s.local.ListPids(ctx, r) +} + +func (s *service) Exec(ctx context.Context, r *api.ExecProcessRequest) (*ptypes.Empty, error) { + return s.local.Exec(ctx, r) +} + +func (s *service) ResizePty(ctx context.Context, r *api.ResizePtyRequest) (*ptypes.Empty, error) { + return s.local.ResizePty(ctx, r) +} + +func (s *service) CloseIO(ctx context.Context, r *api.CloseIORequest) (*ptypes.Empty, error) { + return s.local.CloseIO(ctx, r) +} + +func (s *service) Checkpoint(ctx context.Context, r *api.CheckpointTaskRequest) (*api.CheckpointTaskResponse, error) { + return s.local.Checkpoint(ctx, r) +} + +func (s *service) Update(ctx context.Context, r *api.UpdateTaskRequest) (*ptypes.Empty, error) { + return s.local.Update(ctx, r) +} + +func (s *service) Metrics(ctx context.Context, r *api.MetricsRequest) (*api.MetricsResponse, error) { + return s.local.Metrics(ctx, r) +} + +func (s *service) Wait(ctx context.Context, r *api.WaitRequest) (*api.WaitResponse, error) { + return s.local.Wait(ctx, r) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/version/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/version/service.go new file mode 100644 index 0000000000..86c03a89a4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/version/service.go @@ -0,0 +1,59 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package version + +import ( + "context" + + api "github.com/containerd/containerd/api/services/version/v1" + ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" + "github.com/containerd/containerd/v2/plugins" + ctrdversion "github.com/containerd/containerd/v2/version" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + "google.golang.org/grpc" +) + +var _ api.VersionServer = &service{} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.GRPCPlugin, + ID: "version", + InitFn: initFunc, + }) +} + +func initFunc(ic *plugin.InitContext) (any, error) { + return &service{}, nil +} + +type service struct { + api.UnimplementedVersionServer +} + +func (s *service) Register(server *grpc.Server) error { + api.RegisterVersionServer(server, s) + return nil +} + +func (s *service) Version(ctx context.Context, _ *ptypes.Empty) (*api.VersionResponse, error) { + return &api.VersionResponse{ + Version: ctrdversion.Version, + Revision: ctrdversion.Revision, + }, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/services/warning/service.go b/vendor/github.com/containerd/containerd/v2/plugins/services/warning/service.go new file mode 100644 index 0000000000..91a90a98cc --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/services/warning/service.go @@ -0,0 +1,85 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package warning + +import ( + "context" + "sync" + "time" + + "github.com/containerd/log" + + deprecation "github.com/containerd/containerd/v2/pkg/deprecation" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +type Service interface { + Emit(context.Context, deprecation.Warning) + Warnings() []Warning +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.WarningPlugin, + ID: plugins.DeprecationsPlugin, + InitFn: func(ic *plugin.InitContext) (any, error) { + return &service{warnings: make(map[deprecation.Warning]time.Time)}, nil + }, + }) +} + +type Warning struct { + ID deprecation.Warning + LastOccurrence time.Time + Message string +} + +var _ Service = (*service)(nil) + +type service struct { + warnings map[deprecation.Warning]time.Time + m sync.RWMutex +} + +func (s *service) Emit(ctx context.Context, warning deprecation.Warning) { + if !deprecation.Valid(warning) { + log.G(ctx).WithField("warningID", string(warning)).Warn("invalid deprecation warning") + return + } + s.m.Lock() + defer s.m.Unlock() + s.warnings[warning] = time.Now() +} +func (s *service) Warnings() []Warning { + s.m.RLock() + defer s.m.RUnlock() + var warnings []Warning + for k, v := range s.warnings { + msg, ok := deprecation.Message(k) + if !ok { + continue + } + warnings = append(warnings, Warning{ + ID: k, + LastOccurrence: v, + Message: msg, + }) + } + return warnings +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native.go new file mode 100644 index 0000000000..2291339c9a --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native.go @@ -0,0 +1,316 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package native + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/core/snapshots/storage" + "github.com/containerd/log" + + "github.com/containerd/continuity/fs" +) + +type snapshotter struct { + root string + ms *storage.MetaStore +} + +// NewSnapshotter returns a Snapshotter which copies layers on the underlying +// file system. A metadata file is stored under the root. +func NewSnapshotter(root string) (snapshots.Snapshotter, error) { + if err := os.MkdirAll(root, 0700); err != nil { + return nil, err + } + ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) + if err != nil { + return nil, err + } + + if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { + return nil, err + } + + return &snapshotter{ + root: root, + ms: ms, + }, nil +} + +// Stat returns the info for an active or committed snapshot by name or +// key. +// +// Should be used for parent resolution, existence checks and to discern +// the kind of snapshot. +func (o *snapshotter) Stat(ctx context.Context, key string) (info snapshots.Info, err error) { + err = o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + _, info, _, err = storage.GetInfo(ctx, key) + return err + }) + if err != nil { + return snapshots.Info{}, err + } + + return info, nil +} + +func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (_ snapshots.Info, err error) { + err = o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + info, err = storage.UpdateInfo(ctx, info, fieldpaths...) + return err + }) + if err != nil { + return snapshots.Info{}, err + } + + return info, nil +} + +func (o *snapshotter) Usage(ctx context.Context, key string) (usage snapshots.Usage, err error) { + var ( + id string + info snapshots.Info + ) + + err = o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + id, info, usage, err = storage.GetInfo(ctx, key) + return err + }) + if err != nil { + return snapshots.Usage{}, err + } + + if info.Kind == snapshots.KindActive { + du, err := fs.DiskUsage(ctx, o.getSnapshotDir(id)) + if err != nil { + return snapshots.Usage{}, err + } + usage = snapshots.Usage(du) + } + + return usage, nil +} + +func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) +} + +func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return o.createSnapshot(ctx, snapshots.KindView, key, parent, opts) +} + +// Mounts returns the mounts for the transaction identified by key. Can be +// called on an read-write or readonly transaction. +// +// This can be used to recover mounts after calling View or Prepare. +func (o *snapshotter) Mounts(ctx context.Context, key string) (_ []mount.Mount, err error) { + var s storage.Snapshot + err = o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + s, err = storage.GetSnapshot(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot mount: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + return o.mounts(s), nil +} + +func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { + return o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + id, _, _, err := storage.GetInfo(ctx, key) + if err != nil { + return err + } + + usage, err := fs.DiskUsage(ctx, o.getSnapshotDir(id)) + if err != nil { + return err + } + + if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { + return fmt.Errorf("failed to commit snapshot: %w", err) + } + return nil + }) +} + +// Remove abandons the transaction identified by key. All resources +// associated with the key will be removed. +func (o *snapshotter) Remove(ctx context.Context, key string) (err error) { + var ( + renamed, path string + restore bool + ) + + err = o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + id, _, err := storage.Remove(ctx, key) + if err != nil { + return fmt.Errorf("failed to remove: %w", err) + } + + path = o.getSnapshotDir(id) + renamed = filepath.Join(o.root, "snapshots", "rm-"+id) + if err = os.Rename(path, renamed); err != nil { + if !os.IsNotExist(err) { + return fmt.Errorf("failed to rename: %w", err) + } + renamed = "" + } + + restore = true + return nil + }) + + if err != nil { + if renamed != "" && restore { + if err1 := os.Rename(renamed, path); err1 != nil { + // May cause inconsistent data on disk + log.G(ctx).WithError(err1).WithField("path", renamed).Error("failed to rename after failed commit") + } + } + return err + } + if renamed != "" { + if err := os.RemoveAll(renamed); err != nil { + // Must be cleaned up, any "rm-*" could be removed if no active transactions + log.G(ctx).WithError(err).WithField("path", renamed).Warnf("failed to remove root filesystem") + } + } + + return nil +} + +// Walk the committed snapshots. +func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { + return o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + return storage.WalkInfo(ctx, fn, fs...) + }) +} + +func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { + var ( + path, td string + s storage.Snapshot + ) + + if kind == snapshots.KindActive || parent == "" { + td, err = os.MkdirTemp(filepath.Join(o.root, "snapshots"), "new-") + if err != nil { + return nil, fmt.Errorf("failed to create temp dir: %w", err) + } + if err := os.Chmod(td, 0755); err != nil { + return nil, fmt.Errorf("failed to chmod %s to 0755: %w", td, err) + } + defer func() { + if err != nil { + if td != "" { + if err1 := os.RemoveAll(td); err1 != nil { + err = fmt.Errorf("remove failed: %v: %w", err1, err) + } + } + if path != "" { + if err1 := os.RemoveAll(path); err1 != nil { + err = fmt.Errorf("failed to remove path: %v: %w", err1, err) + } + } + } + }() + } + + err = o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + s, err = storage.CreateSnapshot(ctx, kind, key, parent, opts...) + if err != nil { + return fmt.Errorf("failed to create snapshot: %w", err) + } + + if td != "" { + if len(s.ParentIDs) > 0 { + parent := o.getSnapshotDir(s.ParentIDs[0]) + xattrErrorHandler := func(dst, src, xattrKey string, copyErr error) error { + // security.* xattr cannot be copied in most cases (moby/buildkit#1189) + log.G(ctx).WithError(copyErr).Debugf("failed to copy xattr %q", xattrKey) + return nil + } + copyDirOpts := []fs.CopyDirOpt{ + fs.WithXAttrErrorHandler(xattrErrorHandler), + } + if err = fs.CopyDir(td, parent, copyDirOpts...); err != nil { + return fmt.Errorf("copying of parent failed: %w", err) + } + } + + path = o.getSnapshotDir(s.ID) + if err = os.Rename(td, path); err != nil { + return fmt.Errorf("failed to rename: %w", err) + } + td = "" + } + + return nil + }) + if err != nil { + return nil, err + } + + return o.mounts(s), nil +} + +func (o *snapshotter) getSnapshotDir(id string) string { + return filepath.Join(o.root, "snapshots", id) +} + +func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { + var ( + roFlag string + source string + ) + + if s.Kind == snapshots.KindView { + roFlag = "ro" + } else { + roFlag = "rw" + } + + if len(s.ParentIDs) == 0 || s.Kind == snapshots.KindActive { + source = o.getSnapshotDir(s.ID) + } else { + source = o.getSnapshotDir(s.ParentIDs[0]) + } + + return []mount.Mount{ + { + Source: source, + Type: mountType, + Options: append(defaultMountOptions, roFlag), + }, + } +} + +// Close closes the snapshotter +func (o *snapshotter) Close() error { + return o.ms.Close() +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native_default.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native_default.go new file mode 100644 index 0000000000..d2d99bbe20 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native_default.go @@ -0,0 +1,23 @@ +//go:build !freebsd + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package native + +const mountType = "bind" + +var defaultMountOptions = []string{"rbind"} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native_freebsd.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native_freebsd.go new file mode 100644 index 0000000000..4647785869 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/native_freebsd.go @@ -0,0 +1,21 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package native + +const mountType = "nullfs" + +var defaultMountOptions = []string{} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/plugin/plugin.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/plugin/plugin.go new file mode 100644 index 0000000000..bf0dfb78a6 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/native/plugin/plugin.go @@ -0,0 +1,57 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package plugin + +import ( + "errors" + + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/snapshots/native" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +// Config represents configuration for the native plugin. +type Config struct { + // Root directory for the plugin + RootPath string `toml:"root_path"` +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.SnapshotPlugin, + ID: "native", + Config: &Config{}, + InitFn: func(ic *plugin.InitContext) (any, error) { + ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) + + config, ok := ic.Config.(*Config) + if !ok { + return nil, errors.New("invalid native configuration") + } + + root := ic.Properties[plugins.PropertyRootDir] + if len(config.RootPath) != 0 { + root = config.RootPath + } + + ic.Meta.Exports[plugins.SnapshotterRootDir] = root + return native.NewSnapshotter(root) + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/overlay/overlay.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/overlay/overlay.go new file mode 100644 index 0000000000..80535ba823 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/overlay/overlay.go @@ -0,0 +1,636 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package overlay + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/core/snapshots/storage" + "github.com/containerd/containerd/v2/internal/userns" + "github.com/containerd/containerd/v2/plugins/snapshots/overlay/overlayutils" + "github.com/containerd/continuity/fs" + "github.com/containerd/log" +) + +// upperdirKey is a key of an optional label to each snapshot. +// This optional label of a snapshot contains the location of "upperdir" where +// the change set between this snapshot and its parent is stored. +const upperdirKey = "containerd.io/snapshot/overlay.upperdir" + +// SnapshotterConfig is used to configure the overlay snapshotter instance +type SnapshotterConfig struct { + asyncRemove bool + upperdirLabel bool + ms MetaStore + mountOptions []string + remapIDs bool + slowChown bool +} + +// Opt is an option to configure the overlay snapshotter +type Opt func(config *SnapshotterConfig) error + +// AsynchronousRemove defers removal of filesystem content until +// the Cleanup method is called. Removals will make the snapshot +// referred to by the key unavailable and make the key immediately +// available for re-use. +func AsynchronousRemove(config *SnapshotterConfig) error { + config.asyncRemove = true + return nil +} + +// WithUpperdirLabel adds as an optional label +// "containerd.io/snapshot/overlay.upperdir". This stores the location +// of the upperdir that contains the changeset between the labelled +// snapshot and its parent. +func WithUpperdirLabel(config *SnapshotterConfig) error { + config.upperdirLabel = true + return nil +} + +// WithMountOptions defines the default mount options used for the overlay mount. +// NOTE: Options are not applied to bind mounts. +func WithMountOptions(options []string) Opt { + return func(config *SnapshotterConfig) error { + config.mountOptions = append(config.mountOptions, options...) + return nil + } +} + +type MetaStore interface { + TransactionContext(ctx context.Context, writable bool) (context.Context, storage.Transactor, error) + WithTransaction(ctx context.Context, writable bool, fn storage.TransactionCallback) error + Close() error +} + +// WithMetaStore allows the MetaStore to be created outside the snapshotter +// and passed in. +func WithMetaStore(ms MetaStore) Opt { + return func(config *SnapshotterConfig) error { + config.ms = ms + return nil + } +} + +func WithRemapIDs(config *SnapshotterConfig) error { + config.remapIDs = true + return nil +} + +func WithSlowChown(config *SnapshotterConfig) error { + config.slowChown = true + return nil +} + +type snapshotter struct { + root string + ms MetaStore + asyncRemove bool + upperdirLabel bool + options []string + remapIDs bool + slowChown bool +} + +// NewSnapshotter returns a Snapshotter which uses overlayfs. The overlayfs +// diffs are stored under the provided root. A metadata file is stored under +// the root. +func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) { + var config SnapshotterConfig + for _, opt := range opts { + if err := opt(&config); err != nil { + return nil, err + } + } + + if err := os.MkdirAll(root, 0700); err != nil { + return nil, err + } + supportsDType, err := fs.SupportsDType(root) + if err != nil { + return nil, err + } + if !supportsDType { + return nil, fmt.Errorf("%s does not support d_type. If the backing filesystem is xfs, please reformat with ftype=1 to enable d_type support", root) + } + if config.ms == nil { + config.ms, err = storage.NewMetaStore(filepath.Join(root, "metadata.db")) + if err != nil { + return nil, err + } + } + + if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { + return nil, err + } + + if !hasOption(config.mountOptions, "userxattr", false) { + // figure out whether "userxattr" option is recognized by the kernel && needed + userxattr, err := overlayutils.NeedsUserXAttr(root) + if err != nil { + log.L.WithError(err).Warnf("cannot detect whether \"userxattr\" option needs to be used, assuming to be %v", userxattr) + } + if userxattr { + config.mountOptions = append(config.mountOptions, "userxattr") + } + } + + if !hasOption(config.mountOptions, "index", false) && supportsIndex() { + config.mountOptions = append(config.mountOptions, "index=off") + } + + return &snapshotter{ + root: root, + ms: config.ms, + asyncRemove: config.asyncRemove, + upperdirLabel: config.upperdirLabel, + options: config.mountOptions, + remapIDs: config.remapIDs, + slowChown: config.slowChown, + }, nil +} + +func hasOption(options []string, key string, hasValue bool) bool { + for _, option := range options { + if hasValue { + if strings.HasPrefix(option, key) && len(option) > len(key) && option[len(key)] == '=' { + return true + } + } else if option == key { + return true + } + } + return false +} + +// Stat returns the info for an active or committed snapshot by name or +// key. +// +// Should be used for parent resolution, existence checks and to discern +// the kind of snapshot. +func (o *snapshotter) Stat(ctx context.Context, key string) (info snapshots.Info, err error) { + var id string + if err := o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + id, info, _, err = storage.GetInfo(ctx, key) + return err + }); err != nil { + return info, err + } + + if o.upperdirLabel { + if info.Labels == nil { + info.Labels = make(map[string]string) + } + info.Labels[upperdirKey] = o.upperPath(id) + } + return info, nil +} + +func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (newInfo snapshots.Info, err error) { + err = o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + newInfo, err = storage.UpdateInfo(ctx, info, fieldpaths...) + if err != nil { + return err + } + + if o.upperdirLabel { + id, _, _, err := storage.GetInfo(ctx, newInfo.Name) + if err != nil { + return err + } + if newInfo.Labels == nil { + newInfo.Labels = make(map[string]string) + } + newInfo.Labels[upperdirKey] = o.upperPath(id) + } + return nil + }) + return newInfo, err +} + +// Usage returns the resources taken by the snapshot identified by key. +// +// For active snapshots, this will scan the usage of the overlay "diff" (aka +// "upper") directory and may take some time. +// +// For committed snapshots, the value is returned from the metadata database. +func (o *snapshotter) Usage(ctx context.Context, key string) (_ snapshots.Usage, err error) { + var ( + usage snapshots.Usage + info snapshots.Info + id string + ) + if err := o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + id, info, usage, err = storage.GetInfo(ctx, key) + return err + }); err != nil { + return usage, err + } + + if info.Kind == snapshots.KindActive { + upperPath := o.upperPath(id) + du, err := fs.DiskUsage(ctx, upperPath) + if err != nil { + // TODO(stevvooe): Consider not reporting an error in this case. + return snapshots.Usage{}, err + } + usage = snapshots.Usage(du) + } + return usage, nil +} + +func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) +} + +func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return o.createSnapshot(ctx, snapshots.KindView, key, parent, opts) +} + +// Mounts returns the mounts for the transaction identified by key. Can be +// called on an read-write or readonly transaction. +// +// This can be used to recover mounts after calling View or Prepare. +func (o *snapshotter) Mounts(ctx context.Context, key string) (_ []mount.Mount, err error) { + var s storage.Snapshot + var info snapshots.Info + if err := o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + s, err = storage.GetSnapshot(ctx, key) + if err != nil { + return fmt.Errorf("failed to get active mount: %w", err) + } + + _, info, _, err = storage.GetInfo(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot info: %w", err) + } + return nil + }); err != nil { + return nil, err + } + return o.mounts(s, info), nil +} + +func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { + return o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + // grab the existing id + id, _, _, err := storage.GetInfo(ctx, key) + if err != nil { + return err + } + + usage, err := fs.DiskUsage(ctx, o.upperPath(id)) + if err != nil { + return err + } + + if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { + return fmt.Errorf("failed to commit snapshot %s: %w", key, err) + } + return nil + }) +} + +// Remove abandons the snapshot identified by key. The snapshot will +// immediately become unavailable and unrecoverable. Disk space will +// be freed up on the next call to `Cleanup`. +func (o *snapshotter) Remove(ctx context.Context, key string) (err error) { + var removals []string + // Remove directories after the transaction is closed, failures must not + // return error since the transaction is committed with the removal + // key no longer available. + defer func() { + if err == nil { + for _, dir := range removals { + if err := os.RemoveAll(dir); err != nil { + log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") + } + } + } + }() + return o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + _, _, err = storage.Remove(ctx, key) + if err != nil { + return fmt.Errorf("failed to remove snapshot %s: %w", key, err) + } + + if !o.asyncRemove { + removals, err = o.getCleanupDirectories(ctx) + if err != nil { + return fmt.Errorf("unable to get directories for removal: %w", err) + } + } + return nil + }) +} + +// Walk the snapshots. +func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { + return o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + if o.upperdirLabel { + return storage.WalkInfo(ctx, func(ctx context.Context, info snapshots.Info) error { + id, _, _, err := storage.GetInfo(ctx, info.Name) + if err != nil { + return err + } + if info.Labels == nil { + info.Labels = make(map[string]string) + } + info.Labels[upperdirKey] = o.upperPath(id) + return fn(ctx, info) + }, fs...) + } + return storage.WalkInfo(ctx, fn, fs...) + }) +} + +// Cleanup cleans up disk resources from removed or abandoned snapshots +func (o *snapshotter) Cleanup(ctx context.Context) error { + cleanup, err := o.cleanupDirectories(ctx) + if err != nil { + return err + } + + for _, dir := range cleanup { + if err := os.RemoveAll(dir); err != nil { + log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") + } + } + + return nil +} + +func (o *snapshotter) cleanupDirectories(ctx context.Context) (_ []string, err error) { + var cleanupDirs []string + // Get a write transaction to ensure no other write transaction can be entered + // while the cleanup is scanning. + if err := o.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + cleanupDirs, err = o.getCleanupDirectories(ctx) + return err + }); err != nil { + return nil, err + } + return cleanupDirs, nil +} + +func (o *snapshotter) getCleanupDirectories(ctx context.Context) ([]string, error) { + ids, err := storage.IDMap(ctx) + if err != nil { + return nil, err + } + + snapshotDir := filepath.Join(o.root, "snapshots") + fd, err := os.Open(snapshotDir) + if err != nil { + return nil, err + } + defer fd.Close() + + dirs, err := fd.Readdirnames(0) + if err != nil { + return nil, err + } + + cleanup := []string{} + for _, d := range dirs { + if _, ok := ids[d]; ok { + continue + } + cleanup = append(cleanup, filepath.Join(snapshotDir, d)) + } + + return cleanup, nil +} + +func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { + var ( + s storage.Snapshot + td, path string + info snapshots.Info + ) + + defer func() { + if err != nil { + if td != "" { + if err1 := os.RemoveAll(td); err1 != nil { + log.G(ctx).WithError(err1).Warn("failed to cleanup temp snapshot directory") + } + } + if path != "" { + if err1 := os.RemoveAll(path); err1 != nil { + log.G(ctx).WithError(err1).WithField("path", path).Error("failed to reclaim snapshot directory, directory may need removal") + err = fmt.Errorf("failed to remove path: %v: %w", err1, err) + } + } + } + }() + + if err := o.ms.WithTransaction(ctx, true, func(ctx context.Context) (err error) { + snapshotDir := filepath.Join(o.root, "snapshots") + td, err = o.prepareDirectory(ctx, snapshotDir, kind) + if err != nil { + return fmt.Errorf("failed to create prepare snapshot dir: %w", err) + } + + s, err = storage.CreateSnapshot(ctx, kind, key, parent, opts...) + if err != nil { + return fmt.Errorf("failed to create snapshot: %w", err) + } + + _, info, _, err = storage.GetInfo(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot info: %w", err) + } + + var ( + mappedUID, mappedGID = -1, -1 + uidmapLabel, gidmapLabel string + needsRemap = false + ) + // NOTE: if idmapped mounts' supported by hosted kernel there may be + // no parents at all, so overlayfs will not work and snapshotter + // will use bind mount. To be able to create file objects inside the + // rootfs -- just chown this only bound directory according to provided + // {uid,gid}map. In case of one/multiple parents -- chown upperdir. + if v, ok := info.Labels[snapshots.LabelSnapshotUIDMapping]; ok { + uidmapLabel = v + needsRemap = true + } + if v, ok := info.Labels[snapshots.LabelSnapshotGIDMapping]; ok { + gidmapLabel = v + needsRemap = true + } + + if needsRemap { + var idMap userns.IDMap + if err = idMap.Unmarshal(uidmapLabel, gidmapLabel); err != nil { + return fmt.Errorf("failed to unmarshal snapshot ID mapped labels: %w", err) + } + root, err := idMap.RootPair() + if err != nil { + return fmt.Errorf("failed to find root pair: %w", err) + } + mappedUID, mappedGID = int(root.Uid), int(root.Gid) + } + + if mappedUID == -1 || mappedGID == -1 { + if len(s.ParentIDs) > 0 { + st, err := os.Stat(o.upperPath(s.ParentIDs[0])) + if err != nil { + return fmt.Errorf("failed to stat parent: %w", err) + } + stat, ok := st.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("incompatible types after stat call: *syscall.Stat_t expected") + } + mappedUID = int(stat.Uid) + mappedGID = int(stat.Gid) + } + } + + if mappedUID != -1 && mappedGID != -1 { + if err := os.Lchown(filepath.Join(td, "fs"), mappedUID, mappedGID); err != nil { + return fmt.Errorf("failed to chown: %w", err) + } + } + + path = filepath.Join(snapshotDir, s.ID) + if err = os.Rename(td, path); err != nil { + return fmt.Errorf("failed to rename: %w", err) + } + td = "" + + return nil + }); err != nil { + return nil, err + } + return o.mounts(s, info), nil +} + +func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) { + td, err := os.MkdirTemp(snapshotDir, "new-") + if err != nil { + return "", fmt.Errorf("failed to create temp dir: %w", err) + } + + if err := os.Mkdir(filepath.Join(td, "fs"), 0755); err != nil { + return td, err + } + + if kind == snapshots.KindActive { + if err := os.Mkdir(filepath.Join(td, "work"), 0711); err != nil { + return td, err + } + } + + return td, nil +} + +func (o *snapshotter) mounts(s storage.Snapshot, info snapshots.Info) []mount.Mount { + var options []string + + if o.remapIDs { + if v, ok := info.Labels[snapshots.LabelSnapshotUIDMapping]; ok { + options = append(options, fmt.Sprintf("uidmap=%s", v)) + } + if v, ok := info.Labels[snapshots.LabelSnapshotGIDMapping]; ok { + options = append(options, fmt.Sprintf("gidmap=%s", v)) + } + } + + if len(s.ParentIDs) == 0 { + // if we only have one layer/no parents then just return a bind mount as overlay + // will not work + roFlag := "rw" + if s.Kind == snapshots.KindView { + roFlag = "ro" + } + return []mount.Mount{ + { + Source: o.upperPath(s.ID), + Type: "bind", + Options: append(options, + roFlag, + "rbind", + ), + }, + } + } + + if s.Kind == snapshots.KindActive { + options = append(options, + fmt.Sprintf("workdir=%s", o.workPath(s.ID)), + fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)), + ) + } else if len(s.ParentIDs) == 1 { + return []mount.Mount{ + { + Source: o.upperPath(s.ParentIDs[0]), + Type: "bind", + Options: append(options, + "ro", + "rbind", + ), + }, + } + } + + parentPaths := make([]string, len(s.ParentIDs)) + for i := range s.ParentIDs { + parentPaths[i] = o.upperPath(s.ParentIDs[i]) + } + options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":"))) + options = append(options, o.options...) + + return []mount.Mount{ + { + Type: "overlay", + Source: "overlay", + Options: options, + }, + } +} + +func (o *snapshotter) upperPath(id string) string { + return filepath.Join(o.root, "snapshots", id, "fs") +} + +func (o *snapshotter) workPath(id string) string { + return filepath.Join(o.root, "snapshots", id, "work") +} + +// Close closes the snapshotter +func (o *snapshotter) Close() error { + return o.ms.Close() +} + +// supportsIndex checks whether the "index=off" option is supported by the kernel. +func supportsIndex() bool { + if _, err := os.Stat("/sys/module/overlay/parameters/index"); err == nil { + return true + } + return false +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/overlay/plugin/plugin.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/overlay/plugin/plugin.go new file mode 100644 index 0000000000..5b9ae9480c --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/overlay/plugin/plugin.go @@ -0,0 +1,109 @@ +//go:build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package overlay + +import ( + "errors" + + "github.com/moby/sys/userns" + + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/snapshots/overlay" + "github.com/containerd/containerd/v2/plugins/snapshots/overlay/overlayutils" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" +) + +const ( + capaRemapIDs = "remap-ids" + capaOnlyRemapIDs = "only-remap-ids" + capaRebase = "rebase" +) + +// Config represents configuration for the overlay plugin. +type Config struct { + // Root directory for the plugin + RootPath string `toml:"root_path"` + UpperdirLabel bool `toml:"upperdir_label"` + SyncRemove bool `toml:"sync_remove"` + + // slowChown allows the plugin to fallback to a recursive chown if fast options (like + // idmap mounts) are not available. See more info about the overhead this can have in + // github.com/containerd/containerd/docs/user-namespaces/. + SlowChown bool `toml:"slow_chown"` + + // MountOptions are options used for the overlay mount (not used on bind mounts) + MountOptions []string `toml:"mount_options"` +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.SnapshotPlugin, + ID: "overlayfs", + Config: &Config{}, + InitFn: func(ic *plugin.InitContext) (any, error) { + ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) + + config, ok := ic.Config.(*Config) + if !ok { + return nil, errors.New("invalid overlay configuration") + } + + root := ic.Properties[plugins.PropertyRootDir] + if config.RootPath != "" { + root = config.RootPath + } + + var oOpts []overlay.Opt + if config.UpperdirLabel { + oOpts = append(oOpts, overlay.WithUpperdirLabel) + } + if !config.SyncRemove { + oOpts = append(oOpts, overlay.AsynchronousRemove) + } + + if len(config.MountOptions) > 0 { + oOpts = append(oOpts, overlay.WithMountOptions(config.MountOptions)) + } + if ok, err := overlayutils.SupportsIDMappedMounts(); err == nil && ok { + oOpts = append(oOpts, overlay.WithRemapIDs) + ic.Meta.Capabilities = append(ic.Meta.Capabilities, capaRemapIDs) + } + + if config.SlowChown { + oOpts = append(oOpts, overlay.WithSlowChown) + } else { + // If slowChown is false, we use capaOnlyRemapIDs to signal we only + // allow idmap mounts. + ic.Meta.Capabilities = append(ic.Meta.Capabilities, capaOnlyRemapIDs) + } + + if !userns.RunningInUserNS() { + // "rebase" capability depends on `mknod c 0 0` via OverlayConvertWhiteout, + // so it does not work when running in UserNS. + // https://github.com/containerd/containerd/issues/13388 + ic.Meta.Capabilities = append(ic.Meta.Capabilities, capaRebase) + } + + ic.Meta.Exports[plugins.SnapshotterRootDir] = root + return overlay.NewSnapshotter(root, oOpts...) + }, + }) +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/block_cimfs.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/block_cimfs.go new file mode 100644 index 0000000000..c249642fb4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/block_cimfs.go @@ -0,0 +1,554 @@ +//go:build windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package windows + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Microsoft/hcsshim" + "github.com/Microsoft/hcsshim/pkg/cimfs" + cimlayer "github.com/Microsoft/hcsshim/pkg/ociwclayer/cim" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/core/snapshots/storage" + "github.com/containerd/containerd/v2/internal/kmutex" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/continuity/fs" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" +) + +const ( + // An unformatted scratch is used with ReFS. This scratch gets formatted with ReFS inside the guest before we run any + // containers. ReFS requires the minimum size of 40GB. + minimumUnformattedScratchSizeInBytes uint64 = 40 * 1024 * 1024 * 1024 +) + +// BlockCIMSnapshotterConfig contains configuration options for the block CIM snapshotter. +type BlockCIMSnapshotterConfig struct { + // EnableLayerIntegrity enables data integrity checking for CIM layers. + // When enabled, CIMs will be verified and sealed on close to ensure tamper-proofing. + EnableLayerIntegrity bool `toml:"enable_layer_integrity"` + // AppendVHDFooter controls whether VHD footers are appended to layer CIMs and merged CIMs. + AppendVHDFooter bool `toml:"append_vhd_footer"` + // UnformattedScratch will prepare the scratch VHDs without formatting them with + // NTFS. This is mainly useful in case when we want to format the scratch on + // inside the guest (hyperv isolation) before starting containers. Usually, this + // config should be used along with the EnableLayerIntegrity config. As of now, + // you can't run process isolated windows containers if you enable this config + // since there is nothing on the host that will format this scratch. + UnformattedScratch bool `toml:"unformatted_scratch"` +} + +// blockCIMSnapshotter is a snapshotter that uses block CIMs for storing windows container image layers. +type blockCIMSnapshotter struct { + *windowsBaseSnapshotter + mergeLock kmutex.KeyedLocker + config *BlockCIMSnapshotterConfig +} + +var _ snapshots.Snapshotter = &blockCIMSnapshotter{} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.SnapshotPlugin, + ID: "blockcim", + Config: &BlockCIMSnapshotterConfig{}, + InitFn: func(ic *plugin.InitContext) (any, error) { + ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()} + + config, ok := ic.Config.(*BlockCIMSnapshotterConfig) + if !ok { + return nil, fmt.Errorf("invalid block CIM snapshotter configuration") + } + + log.G(ic.Context).WithField("config", config).Trace("initializing blockcim snapshotter") + + return NewBlockCIMSnapshotter(ic.Properties[plugins.PropertyRootDir], config) + }, + }) +} + +func NewBlockCIMSnapshotter(root string, config *BlockCIMSnapshotterConfig) (snapshots.Snapshotter, error) { + if !cimfs.IsBlockCimSupported() { + return nil, fmt.Errorf("host windows version doesn't support block CIMs: %w", plugin.ErrSkipPlugin) + } + + baseSn, err := newBaseSnapshotter(root) + if err != nil { + return nil, err + } + + if config.UnformattedScratch { + // If UnformattedScratch is enabled, we don't need to create the differing VHD as + // the scratch isn't even formatted. We just create 1 VHD and copy it for every + // snapshot. In cases where snapshot creation specifies a different size of the + // VHD, then we create a new one because we can't expand unformatted VHD. Also + // note that unformatted VHDs are mostly used with ReFS, and ReFS requires a + // minimum of 40GB space, so use that as the default. + // create this VHD with `templateVHDName` as that gets copied in snapshot creation + err = createScratchVHD(context.Background(), filepath.Join(root, templateVHDName), WithSize(minimumUnformattedScratchSizeInBytes)) + } else { + // If UnformattedScratch is disabled, we make the base VHD and differing VHD and + // copy the differing VHD for every new scratch snapshot. If a different size is + // specified, we use ExpandVHD to change the size. + err = createDifferencingScratchVHDs(context.Background(), root) + + } + if err != nil { + return nil, fmt.Errorf("failed to prepare scratch VHDs: %w", err) + } + + return &blockCIMSnapshotter{ + windowsBaseSnapshotter: baseSn, + mergeLock: kmutex.New(), + config: config, + }, nil +} + +func (s *blockCIMSnapshotter) getSnapshotDir(id string) string { + return filepath.Join(s.root, "snapshots", id) +} + +func (s *blockCIMSnapshotter) getSingleFileCIMBlockPath(id string) string { + return filepath.Join(s.root, "snapshots", id, layerBlockName) +} + +func (s *blockCIMSnapshotter) getLayerCIMPathFromCIMBlock(cimBlockPath string) string { + return filepath.Join(cimBlockPath, layerCIMName) +} + +// context MUST be a transaction context +func (s *blockCIMSnapshotter) snapshotInfoFromID(ctx context.Context, snIDs []string) (snInfos []snapshots.Info, err error) { + idToKey, err := storage.IDMap(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get snapshot ID to Key map: %w", err) + } + + for _, sid := range snIDs { + _, info, _, err := storage.GetInfo(ctx, idToKey[sid]) + if err != nil { + return nil, fmt.Errorf("failed to get info for snapshot %s: %w", sid, err) + } + snInfos = append(snInfos, info) + } + return snInfos, nil +} + +// returns a BlockCIM representing the snapshot +func (s *blockCIMSnapshotter) getSnapshotBlockCIM(ctx context.Context, snID string, snInfo snapshots.Info) (*cimfs.BlockCIM, error) { + if snInfo.Kind != snapshots.KindCommitted { + return nil, fmt.Errorf("requested BlockCIM of uncommitted snapshot `%s` ", snInfo.Name) + } + + return &cimfs.BlockCIM{ + CimName: layerCIMName, + Type: cimfs.BlockCIMTypeSingleFile, + BlockPath: s.getSingleFileCIMBlockPath(snID), + }, nil + +} + +func (s *blockCIMSnapshotter) Usage(ctx context.Context, key string) (usage snapshots.Usage, err error) { + var ( + id string + info snapshots.Info + ) + + err = s.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + id, info, usage, err = storage.GetInfo(ctx, key) + return err + }) + if err != nil { + return snapshots.Usage{}, err + } + + if info.Kind == snapshots.KindActive { + path := s.getSnapshotDir(id) + du, err := fs.DiskUsage(ctx, path) + if err != nil { + return snapshots.Usage{}, err + } + usage = snapshots.Usage(du) + } + + return usage, nil +} + +func (s *blockCIMSnapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) +} + +func (s *blockCIMSnapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts) +} + +func (s *blockCIMSnapshotter) Mounts(ctx context.Context, key string) (_ []mount.Mount, err error) { + var snapshot storage.Snapshot + err = s.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + snapshot, err = storage.GetSnapshot(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot mount: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + return s.mounts(ctx, snapshot, key) +} + +func (s *blockCIMSnapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { + if !strings.Contains(key, snapshots.UnpackKeyPrefix) { + return fmt.Errorf("committing a scratch snapshot to read-only cim layer isn't supported yet") + } + + return s.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + usage, err := s.Usage(ctx, key) + if err != nil { + return fmt.Errorf("failed to get usage during commit: %w", err) + } + if _, err := storage.CommitActive(ctx, key, name, usage, opts...); err != nil { + return fmt.Errorf("failed to commit snapshot: %w", err) + } + + return nil + }) +} + +// Remove abandons the transaction identified by key. All resources +// associated with the key will be removed. +func (s *blockCIMSnapshotter) Remove(ctx context.Context, key string) error { + renamedID, err := s.preRemove(ctx, key) + if err != nil { + // wrap as ErrFailedPrecondition so that cleanup of other snapshots can continue + return fmt.Errorf("%w: %s", errdefs.ErrFailedPrecondition, err) + } + + // It is a either a scratch or layer snapshot. for scratch, the VHD must have been + // unmounted so can be deleted and for layer CIM must have been unmounted so it + // can be deleted. + if err = os.RemoveAll(s.getSnapshotDir(renamedID)); err != nil { + log.G(ctx).WithError(err).WithField("renamedID", renamedID).Warnf("failed to remove snapshot") + } + return nil +} + +func (s *blockCIMSnapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { + var newSnapshot storage.Snapshot + err = s.ms.WithTransaction(ctx, true, func(ctx context.Context) (retErr error) { + newSnapshot, err = storage.CreateSnapshot(ctx, kind, key, parent, opts...) + if err != nil { + return fmt.Errorf("failed to create snapshot: %w", err) + } + + log.G(ctx).WithFields(logrus.Fields{ + "key": key, + "parent": parent, + "ID": newSnapshot.ID, + }).Debugf("createSnapshot") + + // Create the new snapshot dir + snDir := s.getSnapshotDir(newSnapshot.ID) + if err = os.MkdirAll(snDir, 0700); err != nil { + return fmt.Errorf("failed to create snapshot dir %s: %w", snDir, err) + } + defer func() { + if retErr != nil { + os.RemoveAll(snDir) + } + }() + + if strings.Contains(key, snapshots.UnpackKeyPrefix) { + // IO/disk space optimization: Do nothing + // + // We only need one sandbox.vhdx for the container. Skip making one for this + // snapshot if this isn't the snapshot that just houses the final sandbox.vhd + // that will be mounted as the containers scratch. Currently the key for a snapshot + // where a layer will be extracted to will have the string `extract-` in it. + return nil + } + + if len(newSnapshot.ParentIDs) == 0 { + return fmt.Errorf("scratch snapshot without any parents isn't supported") + } + + parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs) + var snapshotInfo snapshots.Info + for _, o := range opts { + o(&snapshotInfo) + } + + sizeInBytes, err := getRequestedScratchSize(ctx, snapshotInfo) + if err != nil { + return err + } + + var makeUVMScratch bool + if _, ok := snapshotInfo.Labels[uvmScratchLabel]; ok { + makeUVMScratch = true + } + + // This has to be run first to avoid clashing with the containers sandbox.vhdx. + if makeUVMScratch { + if err = s.createUVMScratchLayer(ctx, snDir, parentLayerPaths); err != nil { + return fmt.Errorf("failed to make UVM's scratch layer: %w", err) + } + } + if err = s.createScratchLayer(ctx, snDir, sizeInBytes); err != nil { + return fmt.Errorf("failed to create scratch layer: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + if !strings.Contains(key, snapshots.UnpackKeyPrefix) && len(newSnapshot.ParentIDs) > 1 { + // We are creating a scratch snapshot, that means we will start a + // container with this scratch snapshot and its parent layers. So we + // should merge the parent layer CIMs. That operation can take long time. + // All concurrent requests to create the merge should first acquire a lock + // for that operation. Each merge operation can be uniquely identified by + // the last layer in that merge, which in case of the scratch snapshot is + // a direct parent of that scratch snapshot. + + prepareMergedCIMLocked := func() error { + // function created to limit the scope of locked code and ensure + // that the lock is always released via defer + s.mergeLock.Lock(ctx, parent) + defer s.mergeLock.Unlock(parent) + return s.prepareMergedCIM(ctx, newSnapshot.ParentIDs) + } + if err = prepareMergedCIMLocked(); err != nil { + return nil, fmt.Errorf("failed to prepare merged CIM: %w", err) + } + } + + return s.mounts(ctx, newSnapshot, key) +} + +// In case of CimFS layers, the scratch VHDs are fully empty (WCIFS layers have reparse points in scratch VHDs, hence those VHDs are unique per image), +// For regular cimfs based layers we create only one scratch VHD and then copy & expand it if required. +// For block CIM based layers, if the scratch is formatted with NTFS we can expand it, +// however, if the scratch is unformatted we have to create a new VHD with the requested +// size, ExpandSandboxSize expects formatted VHD. +func (s *blockCIMSnapshotter) createScratchLayer(ctx context.Context, snDir string, sizeInBytes uint64) error { + if s.config.UnformattedScratch { + if sizeInBytes == 0 { + return copyScratchDisk(filepath.Join(s.root, templateVHDName), filepath.Join(snDir, "sandbox.vhdx")) + } else { + if sizeInBytes < minimumUnformattedScratchSizeInBytes { + return fmt.Errorf("scratch size MUST be at least %d GB, requested size: %d bytes", minimumUnformattedScratchSizeInBytes/(1024*1024*1024), sizeInBytes) + } + if err := createScratchVHD(ctx, filepath.Join(snDir, "sandbox.vhdx"), WithSize(sizeInBytes)); err != nil { + return fmt.Errorf("failed to create scratch VHD of requested size: %w", err) + } + } + } else { + dest := filepath.Join(snDir, "sandbox.vhdx") + if err := copyScratchDisk(filepath.Join(s.root, templateVHDName), dest); err != nil { + return err + } + + if sizeInBytes != 0 { + if err := hcsshim.ExpandSandboxSize(s.info, filepath.Base(snDir), sizeInBytes); err != nil { + return fmt.Errorf("failed to expand sandbox vhdx size to %d bytes: %w", sizeInBytes, err) + } + } + } + return nil +} + +func (s *blockCIMSnapshotter) mounts(ctx context.Context, sn storage.Snapshot, key string) ([]mount.Mount, error) { + var ( + m mount.Mount + blockType string + parentSnInfo []snapshots.Info + parentLayers []*cimfs.BlockCIM + ) + m.Type = "BlockCIM" + blockType = "file" + + err := s.ms.WithTransaction(ctx, false, func(tCtx context.Context) error { + var tErr error + parentSnInfo, tErr = s.snapshotInfoFromID(tCtx, sn.ParentIDs) + if tErr != nil { + return fmt.Errorf("failed to get info for snapshots: %w", tErr) + } + + for i := 0; i < len(parentSnInfo); i++ { + sCIM, tErr := s.getSnapshotBlockCIM(tCtx, sn.ParentIDs[i], parentSnInfo[i]) + if tErr != nil { + return tErr + } + parentLayers = append(parentLayers, sCIM) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to get parent snapshot CIMs: %w", err) + } + + parentLayerPaths := make([]string, 0, len(parentLayers)) + for _, pl := range parentLayers { + parentLayerPaths = append(parentLayerPaths, filepath.Join(pl.BlockPath, pl.CimName)) + } + parentLayersJSON, _ := json.Marshal(parentLayerPaths) + m.Options = append(m.Options, mount.ParentLayerCimPathsFlag+string(parentLayersJSON)) + m.Options = append(m.Options, mount.BlockCIMTypeFlag+blockType) + + // Add configuration options as mount options + if s.config.EnableLayerIntegrity { + m.Options = append(m.Options, mount.EnableLayerIntegrityFlag) + } + + if s.config.AppendVHDFooter { + m.Options = append(m.Options, mount.AppendVHDFooterFlag) + } + + isScratch, err := s.isScratchSnapshot(sn.ID) + if err != nil { + return nil, err + } + + if isScratch { + if len(sn.ParentIDs) > 1 { + mergeBlockCIMPath := filepath.Join(s.getSnapshotDir(sn.ParentIDs[0]), mergedBlockName, mergedCIMName) + m.Options = append(m.Options, mount.MergedCIMPathFlag+mergeBlockCIMPath) + } + m.Source = s.getSnapshotDir(sn.ID) + } else { + m.Source = s.getLayerCIMPathFromCIMBlock(s.getSingleFileCIMBlockPath(sn.ID)) + } + + log.G(ctx).WithFields(logrus.Fields{ + "snapshot ID": sn.ID, + "snapshot name": key, + "parent IDs": sn.ParentIDs, + "mount": m, + }).Debugf("snapshot mounts") + + return []mount.Mount{m}, nil +} + +const ( + layerCIMName = "layer.cim" + layerBlockName = "layer.vhd" + mergedCIMName = "merged.cim" + mergedBlockName = "merged.vhd" +) + +// prepareMergedCIM creates a new merged block CIM from the given snapshots. The order of +// `snapshotIDs` is important. It is expected that the snapshot ID at the last index is +// the base layer and the snapshot ID at the 0th index is the immediate parent layer. +// Newly created merged CIM is stored in the same directory as that of the snapshot at 0th +// index. already exists in the 0th snapshot's directory, nothing is done. +func (s *blockCIMSnapshotter) prepareMergedCIM(ctx context.Context, snapshotIDs []string) (rErr error) { + if len(snapshotIDs) < 2 { + return fmt.Errorf("merging CIM requires at least 2 snapshots") + } + log.G(ctx).WithFields(logrus.Fields{ + "source snapshots": snapshotIDs, + }).Debugf("preparing merged CIM") + + var ( + // directory in which the merged CIM is stored + mergeDir = s.getSnapshotDir(snapshotIDs[0]) + mergeBlockCIMPath = filepath.Join(mergeDir, mergedBlockName) + snInfos []snapshots.Info + ) + + // check if a merged CIM already exists + if _, err := os.Stat(mergeBlockCIMPath); err == nil { + log.G(ctx).Debugf("merged CIM already exists.") + return nil + } + + err := s.ms.WithTransaction(ctx, false, func(tctx context.Context) error { + var terr error + snInfos, terr = s.snapshotInfoFromID(tctx, snapshotIDs) + if terr != nil { + return fmt.Errorf("failed to get info for snapshots: %w", terr) + } + return nil + }) + if err != nil { + return err + } + + sourceCIMs := make([]*cimfs.BlockCIM, 0, len(snapshotIDs)) + for i := range snapshotIDs { + sCIM, err := s.getSnapshotBlockCIM(ctx, snapshotIDs[i], snInfos[i]) + if err != nil { + return err + } + sourceCIMs = append(sourceCIMs, sCIM) + } + + mergedCIM := &cimfs.BlockCIM{ + Type: cimfs.BlockCIMTypeSingleFile, + BlockPath: mergeBlockCIMPath, + CimName: "merged.cim", + } + + // Build merge options based on snapshotter configuration + var mergeOpts []cimlayer.BlockCIMLayerImportOpt + if s.config.EnableLayerIntegrity { + mergeOpts = append(mergeOpts, cimlayer.WithLayerIntegrity()) + } + if s.config.AppendVHDFooter { + mergeOpts = append(mergeOpts, cimlayer.WithVHDFooter()) + } + + err = cimlayer.MergeBlockCIMLayersWithOpts(ctx, sourceCIMs, mergedCIM, mergeOpts...) + if err != nil { + return fmt.Errorf("failed to merge CIMs: %w", err) + } + + log.G(ctx).WithFields(logrus.Fields{ + "merged CIM": mergedCIM, + }).Debugf("merged CIM created") + + return nil +} + +func (s *blockCIMSnapshotter) isScratchSnapshot(snID string) (bool, error) { + scratchDiskPath := filepath.Join(s.getSnapshotDir(snID), "sandbox.vhdx") + if _, err := os.Stat(scratchDiskPath); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/cimfs.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/cimfs.go new file mode 100644 index 0000000000..436048eff7 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/cimfs.go @@ -0,0 +1,501 @@ +//go:build windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package windows + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/Microsoft/go-winio/pkg/security" + "github.com/Microsoft/go-winio/vhd" + "github.com/Microsoft/hcsshim" + "github.com/Microsoft/hcsshim/computestorage" + "github.com/Microsoft/hcsshim/pkg/cimfs" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/core/snapshots/storage" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/sys/windows" +) + +const ( + baseVHDName = "blank-base.vhdx" + templateVHDName = "blank.vhdx" + defaultScratchVHDSizeInBytes uint64 = 10 * 1024 * 1024 * 1024 // 10 GB + vhdBlockSizeInBytes uint32 = 1 * 1024 * 1024 // 1 MB +) + +// scratchCreationOpt is a functional option for configuring scratch VHD creation +type scratchCreationOpt func(*scratchCreationOptions) error + +// scratchCreationOptions holds configuration for scratch VHD creation +type scratchCreationOptions struct { + ntfsFormat bool + sizeInBytes uint64 +} + +// WithNTFSFormat specifies whether the scratch VHD should be formatted with NTFS +func WithNTFSFormat() scratchCreationOpt { + return func(opts *scratchCreationOptions) error { + opts.ntfsFormat = true + return nil + } +} + +// WithSize specifies the size of the scratch VHD in bytes +func WithSize(size uint64) scratchCreationOpt { + return func(opts *scratchCreationOptions) error { + if size == 0 { + return fmt.Errorf("VHD size cannot be zero") + } + opts.sizeInBytes = size + return nil + } +} + +// defaultScratchCreationOptions returns the default options for scratch VHD creation +func defaultScratchCreationOptions() *scratchCreationOptions { + return &scratchCreationOptions{ + ntfsFormat: true, + sizeInBytes: defaultScratchVHDSizeInBytes, + } +} + +// Composite image FileSystem (CimFS) is a new read-only filesystem (similar to overlayFS on Linux) created +// specifically for storing container image layers on windows. cimFSSnapshotter is a snapshotter that uses +// CimFS to create read-only parent layer snapshots. Each snapshot is represented by a `.cim` +// file and some other files (region & objectid files) which hold contents of that snapshot. Once a cim file for a layer is created it +// can only be used as a read-only layer by mounting it to a volume. Hence, CimFs will not be used when we are +// creating writable layers for container scratch and such. (However, in the future scratch layer of a container can be +// exported to a cim layer and then be used as a parent layer for another container). +type cimFSSnapshotter struct { + *windowsBaseSnapshotter + // cimDir is the path to the directory which holds all of the layer cim files. CimFS needs all the + // layer cim files to be present in the same directory. Hence, cim files of all the snapshots (even if + // they are of different images) will be kept in the same directory. + cimDir string +} + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.SnapshotPlugin, + ID: "cimfs", + InitFn: func(ic *plugin.InitContext) (any, error) { + ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()} + return NewCimFSSnapshotter(ic.Properties[plugins.PropertyRootDir]) + }, + }) +} + +// NewCimFSSnapshotter returns a new CimFS based windows snapshotter +func NewCimFSSnapshotter(root string) (snapshots.Snapshotter, error) { + if !cimfs.IsCimFSSupported() { + return nil, fmt.Errorf("host windows version doesn't support CimFS: %w", plugin.ErrSkipPlugin) + } + + baseSn, err := newBaseSnapshotter(root) + if err != nil { + return nil, err + } + + if err = createDifferencingScratchVHDs(context.Background(), baseSn.root); err != nil { + return nil, fmt.Errorf("failed to init base scratch VHD: %w", err) + } + + if err = os.MkdirAll(filepath.Join(baseSn.info.HomeDir, "cim-layers"), 0755); err != nil { + return nil, err + } + + return &cimFSSnapshotter{ + windowsBaseSnapshotter: baseSn, + cimDir: filepath.Join(baseSn.info.HomeDir, "cim-layers"), + }, nil +} + +// getLayerCimPath returns the path of the cim file for the given snapshot. Note that this function doesn't +// actually check if the cim layer exists it simply does string manipulation to generate the path isCimLayer +// can be used to verify if it is actually a cim layer. +func (s *cimFSSnapshotter) getLayerCimPath(snID string) string { + return filepath.Join(s.cimDir, (snID + ".cim")) +} + +func (s *cimFSSnapshotter) parentIDsToCimPaths(parentIDs []string) []string { + cimPaths := make([]string, 0, len(parentIDs)) + for _, id := range parentIDs { + cimPaths = append(cimPaths, s.getLayerCimPath(id)) + } + return cimPaths +} + +func (s *cimFSSnapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { + baseUsage, err := s.windowsBaseSnapshotter.Usage(ctx, key) + if err != nil { + return snapshots.Usage{}, err + } + + ctx, t, err := s.ms.TransactionContext(ctx, false) + if err != nil { + return snapshots.Usage{}, err + } + defer t.Rollback() + + id, info, _, err := storage.GetInfo(ctx, key) + if err != nil { + return snapshots.Usage{}, fmt.Errorf("failed to get snapshot info: %w", err) + } + + if info.Kind == snapshots.KindCommitted { + // Committed MUST be a cimfs layer + cimUsage, err := cimfs.GetCimUsage(ctx, s.getLayerCimPath(id)) + if err != nil { + return snapshots.Usage{}, err + } + baseUsage.Size += int64(cimUsage) + } + return baseUsage, nil +} + +func (s *cimFSSnapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) +} + +func (s *cimFSSnapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts) +} + +func (s *cimFSSnapshotter) Mounts(ctx context.Context, key string) (_ []mount.Mount, err error) { + var snapshot storage.Snapshot + err = s.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + snapshot, err = storage.GetSnapshot(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot mount: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + return s.mounts(snapshot, key), nil +} + +func (s *cimFSSnapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { + if !strings.Contains(key, snapshots.UnpackKeyPrefix) { + return fmt.Errorf("committing a scratch snapshot to read-only cim layer isn't supported yet") + } + + return s.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + usage, err := s.Usage(ctx, key) + if err != nil { + return fmt.Errorf("failed to get usage during commit: %w", err) + } + if _, err := storage.CommitActive(ctx, key, name, usage, opts...); err != nil { + return fmt.Errorf("failed to commit snapshot: %w", err) + } + + return nil + }) +} + +// Remove abandons the transaction identified by key. All resources +// associated with the key will be removed. +func (s *cimFSSnapshotter) Remove(ctx context.Context, key string) error { + var ID, renamedID string + + // collect original ID before preRemove + err := s.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + var infoErr error + ID, _, _, infoErr = storage.GetInfo(ctx, key) + return infoErr + }) + if err != nil { + return fmt.Errorf("%w: failed to get snapshot info: %s", errdefs.ErrFailedPrecondition, err) + } + + renamedID, err = s.preRemove(ctx, key) + if err != nil { + // wrap as ErrFailedPrecondition so that cleanup of other snapshots can continue + return fmt.Errorf("%w: %s", errdefs.ErrFailedPrecondition, err) + } + + if err := cimfs.DestroyCim(ctx, s.getLayerCimPath(ID)); err != nil { + // Must be cleaned up, any "rm-*" could be removed if no active transactions + log.G(ctx).WithError(err).WithField("ID", ID).Warnf("failed to cleanup cim files") + } + + if err = hcsshim.DestroyLayer(s.info, renamedID); err != nil { + // Must be cleaned up, any "rm-*" could be removed if no active transactions + log.G(ctx).WithError(err).WithField("renamedID", renamedID).Warnf("failed to remove root filesystem") + } + return nil +} + +func (s *cimFSSnapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { + var newSnapshot storage.Snapshot + err = s.ms.WithTransaction(ctx, true, func(ctx context.Context) (retErr error) { + newSnapshot, err = storage.CreateSnapshot(ctx, kind, key, parent, opts...) + if err != nil { + return fmt.Errorf("failed to create snapshot: %w", err) + } + + log.G(ctx).Debug("createSnapshot") + // Create the new snapshot dir + snDir := s.getSnapshotDir(newSnapshot.ID) + if err = os.MkdirAll(snDir, 0700); err != nil { + return fmt.Errorf("failed to create snapshot dir %s: %w", snDir, err) + } + defer func() { + if retErr != nil { + os.RemoveAll(snDir) + } + }() + + if strings.Contains(key, snapshots.UnpackKeyPrefix) { + // IO/disk space optimization: Do nothing + // + // We only need one sandbox.vhdx for the container. Skip making one for this + // snapshot if this isn't the snapshot that just houses the final sandbox.vhd + // that will be mounted as the containers scratch. Currently the key for a snapshot + // where a layer will be extracted to will have the string `extract-` in it. + return nil + } + + if len(newSnapshot.ParentIDs) == 0 { + return fmt.Errorf("scratch snapshot without any parents isn't supported") + } + + parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs) + var snapshotInfo snapshots.Info + for _, o := range opts { + o(&snapshotInfo) + } + + sizeInBytes, err := getRequestedScratchSize(ctx, snapshotInfo) + if err != nil { + return err + } + + var makeUVMScratch bool + if _, ok := snapshotInfo.Labels[uvmScratchLabel]; ok { + makeUVMScratch = true + } + + // This has to be run first to avoid clashing with the containers sandbox.vhdx. + if makeUVMScratch { + if err = s.createUVMScratchLayer(ctx, snDir, parentLayerPaths); err != nil { + return fmt.Errorf("failed to make UVM's scratch layer: %w", err) + } + } + if err = s.createScratchLayer(ctx, snDir, sizeInBytes); err != nil { + return fmt.Errorf("failed to create scratch layer: %w", err) + } + return nil + }) + if err != nil { + return nil, err + } + + return s.mounts(newSnapshot, key), nil +} + +// In case of CimFS layers, the scratch VHDs are fully empty (WCIFS layers have reparse points in scratch VHDs, hence those VHDs are unique per image), so we create only one scratch VHD and then copy & expand it for every scratch layer creation. +func (s *cimFSSnapshotter) createScratchLayer(ctx context.Context, snDir string, sizeInBytes uint64) error { + dest := filepath.Join(snDir, "sandbox.vhdx") + if err := copyScratchDisk(filepath.Join(s.root, templateVHDName), dest); err != nil { + return err + } + + if sizeInBytes != 0 { + if err := hcsshim.ExpandSandboxSize(s.info, filepath.Base(snDir), sizeInBytes); err != nil { + return fmt.Errorf("failed to expand sandbox vhdx size to %d bytes: %w", sizeInBytes, err) + } + } + return nil +} + +func (s *cimFSSnapshotter) mounts(sn storage.Snapshot, key string) []mount.Mount { + var ( + roFlag string + ) + + if sn.Kind == snapshots.KindView { + roFlag = "ro" + } else { + roFlag = "rw" + } + + options := []string{ + roFlag, + } + + // add the layer CIM path - this path will only be used if we are extracting an + // image layer, in case of scratch snapshots this path MUST be ignored. + layerCimPath := s.getLayerCimPath(sn.ID) + options = append(options, mount.LayerCimPathFlag+layerCimPath) + + if len(sn.ParentIDs) != 0 { + parentLayerPaths := s.parentIDsToParentPaths(sn.ParentIDs) + parentLayerCimPaths := s.parentIDsToCimPaths(sn.ParentIDs) + // error is not checked here, as a string array will never fail to Marshal + parentLayersJSON, _ := json.Marshal(parentLayerPaths) + parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON) + parentCimLayersJSON, _ := json.Marshal(parentLayerCimPaths) + parentCimLayersOption := mount.ParentLayerCimPathsFlag + string(parentCimLayersJSON) + options = append(options, parentLayersOption) + options = append(options, parentCimLayersOption) + } + + mounts := []mount.Mount{ + { + Source: s.getSnapshotDir(sn.ID), + Type: mount.CimFSMountType, + Options: options, + }, + } + + return mounts +} + +// creates a base scratch VHD and a differencing VHD from that base VHD inside the given +// `path` directory. Once these VHDs are created, every scratch snapshot will make a copy +// of the differencing VHD to be used as the scratch for that snapshot. We could ideally +// just have a base VHD and no differencing VHD and copy the base VHD for every scratch +// snapshot. However, base VHDs are slightly bigger in size and so take longer to copy so +// we keep a differencing VHD and copy that. +func createDifferencingScratchVHDs(ctx context.Context, path string) (err error) { + baseVHDPath := filepath.Join(path, baseVHDName) + diffVHDPath := filepath.Join(path, templateVHDName) + baseVHDExists := false + diffVHDExists := false + + if _, err = os.Stat(baseVHDPath); err == nil { + baseVHDExists = true + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to stat base VHD: %w", err) + } + + _, err = os.Stat(diffVHDPath) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to stat diff VHD: %w", err) + } else if baseVHDExists && err == nil { + diffVHDExists = true + } else { + // remove this diff VHD, it must be recreated with the new base VHD. + os.RemoveAll(diffVHDPath) + } + + defer func() { + if err != nil { + os.RemoveAll(baseVHDPath) + os.RemoveAll(diffVHDPath) + } + }() + + if !baseVHDExists { + if err = createScratchVHD(ctx, baseVHDPath); err != nil { + return fmt.Errorf("failed to create base vhd: %w", err) + } + } + + if !diffVHDExists { + // Create the differencing disk that will be what's copied for the final rw layer + // for a container. + if err = vhd.CreateDiffVhd(diffVHDPath, baseVHDPath, vhdBlockSizeInBytes); err != nil { + return fmt.Errorf("failed to create differencing disk: %w", err) + } + } + + // Grant VM group access to the differencing VHD (base VHD access is already granted by createSingleVHD) + if err = security.GrantVmGroupAccess(diffVHDPath); err != nil { + return fmt.Errorf("failed to grant vm group access to %s: %w", diffVHDPath, err) + } + return nil +} + +// createScratchVHD creates a new scratch VHD at the specified path with the given options +func createScratchVHD(ctx context.Context, vhdPath string, opts ...scratchCreationOpt) (retErr error) { + // Apply default options + options := defaultScratchCreationOptions() + for _, opt := range opts { + if err := opt(options); err != nil { + return fmt.Errorf("failed to apply scratch creation option: %w", err) + } + } + + // Check if VHD already exists + if _, err := os.Stat(vhdPath); err == nil { + return nil + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to stat VHD path: %w", err) + } + + createParams := &vhd.CreateVirtualDiskParameters{ + Version: 2, + Version2: vhd.CreateVersion2{ + MaximumSize: options.sizeInBytes, + BlockSizeInBytes: vhdBlockSizeInBytes, + }, + } + + vhdHandle, err := vhd.CreateVirtualDisk(vhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams) + if err != nil { + return fmt.Errorf("failed to create VHD: %w", err) + } + defer func() { + if retErr != nil { + if rmErr := os.RemoveAll(vhdPath); rmErr != nil { + log.G(ctx).WithError(err).Warnf("on error cleanup failed: %s", rmErr) + } + } + }() + + // Format the VHD if requested + if options.ntfsFormat { + err = computestorage.FormatWritableLayerVhd(ctx, windows.Handle(vhdHandle)) + } + + // Always close the handle + closeErr := syscall.CloseHandle(vhdHandle) + + // Handle errors from formatting and closing + if err != nil { + return fmt.Errorf("failed to format VHD: %w", err) + } + if closeErr != nil { + return fmt.Errorf("failed to close VHD handle: %w", closeErr) + } + + // Grant VM group access to the VHD + if err = security.GrantVmGroupAccess(vhdPath); err != nil { + return fmt.Errorf("failed to grant vm group access to %s: %w", vhdPath, err) + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/common.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/common.go new file mode 100644 index 0000000000..314e544fd2 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/common.go @@ -0,0 +1,273 @@ +//go:build windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package windows + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + + "github.com/Microsoft/hcsshim" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/core/snapshots/storage" + "github.com/containerd/continuity/fs" + "github.com/containerd/log" +) + +// windowsBaseSnapshotter is a type that implements common functionality required by both windows & cimfs +// snapshotters (sort of a base type that windows & cimfs snapshotter types derive from - however, windowsBaseSnapshotter does NOT impelement the full Snapshotter interface). Some functions +// (like Stat, Update) that are identical for both snapshotters are directly implemented in this base +// snapshotter and such functions handle database transaction creation etc. However, the functions that are +// not common don't create a transaction to allow the caller the flexibility of deciding whether to commit or +// abort the transaction. +type windowsBaseSnapshotter struct { + root string + ms *storage.MetaStore + info hcsshim.DriverInfo +} + +func newBaseSnapshotter(root string) (*windowsBaseSnapshotter, error) { + if err := os.MkdirAll(root, 0700); err != nil { + return nil, err + } + + ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) + if err != nil { + return nil, err + } + + if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { + return nil, err + } + + return &windowsBaseSnapshotter{ + root: root, + ms: ms, + info: hcsshim.DriverInfo{HomeDir: filepath.Join(root, "snapshots")}, + }, nil +} + +func (w *windowsBaseSnapshotter) getSnapshotDir(id string) string { + return filepath.Join(w.root, "snapshots", id) +} + +func (w *windowsBaseSnapshotter) parentIDsToParentPaths(parentIDs []string) []string { + parentLayerPaths := make([]string, 0, len(parentIDs)) + for _, ID := range parentIDs { + parentLayerPaths = append(parentLayerPaths, w.getSnapshotDir(ID)) + } + return parentLayerPaths +} + +func (w *windowsBaseSnapshotter) Stat(ctx context.Context, key string) (info snapshots.Info, err error) { + err = w.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + _, info, _, err = storage.GetInfo(ctx, key) + return err + }) + if err != nil { + return snapshots.Info{}, err + } + + return info, nil +} + +func (w *windowsBaseSnapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (_ snapshots.Info, err error) { + err = w.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + info, err = storage.UpdateInfo(ctx, info, fieldpaths...) + return err + }) + if err != nil { + return snapshots.Info{}, err + } + + return info, nil +} + +func (w *windowsBaseSnapshotter) Usage(ctx context.Context, key string) (usage snapshots.Usage, err error) { + var ( + id string + info snapshots.Info + ) + + err = w.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + id, info, usage, err = storage.GetInfo(ctx, key) + return err + }) + if err != nil { + return snapshots.Usage{}, err + } + + if info.Kind == snapshots.KindActive { + path := w.getSnapshotDir(id) + du, err := fs.DiskUsage(ctx, path) + if err != nil { + return snapshots.Usage{}, err + } + + usage = snapshots.Usage(du) + } + + return usage, nil +} + +// Walk the committed snapshots. +func (w *windowsBaseSnapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { + return w.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + return storage.WalkInfo(ctx, fn, fs...) + }) +} + +// preRemove prepares for removal of a snapshot by first renaming the snapshot directory and if that succeeds +// removing the snapshot info from the database. Then the caller can decide how to remove the actual renamed +// snapshot directory. Returns the new 'ID' (i.e the directory name after rename). +func (w *windowsBaseSnapshotter) preRemove(ctx context.Context, key string) (string, error) { + var ( + renamed, path, renamedID string + restore bool + ) + + err := w.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + id, _, err := storage.Remove(ctx, key) + if err != nil { + return fmt.Errorf("failed to remove: %w", err) + } + + path = w.getSnapshotDir(id) + renamedID = "rm-" + id + renamed = w.getSnapshotDir(renamedID) + if err = os.Rename(path, renamed); err != nil && !os.IsNotExist(err) { + if !os.IsPermission(err) { + return err + } + // If permission denied, it's possible that the scratch is still mounted, an + // artifact after a hard daemon crash for example. Worth a shot to try deactivating it + // before retrying the rename. + var ( + home, layerID = filepath.Split(path) + di = hcsshim.DriverInfo{ + HomeDir: home, + } + ) + + if deactivateErr := hcsshim.DeactivateLayer(di, layerID); deactivateErr != nil && !os.IsNotExist(deactivateErr) { + return fmt.Errorf("failed to deactivate layer following failed rename: %s: %w", deactivateErr, err) + } + + if renameErr := os.Rename(path, renamed); renameErr != nil && !os.IsNotExist(renameErr) { + return fmt.Errorf("second rename attempt following detach failed: %s: %w", renameErr, err) + } + } + + restore = true + return nil + }) + if err != nil { + if restore { // failed to commit + if err1 := os.Rename(renamed, path); err1 != nil { + // May cause inconsistent data on disk + log.G(ctx).WithError(err1).WithField("path", renamed).Error("Failed to rename after failed commit") + } + } + return "", err + } + return renamedID, nil +} + +// Close closes the snapshotter +func (w *windowsBaseSnapshotter) Close() error { + return w.ms.Close() +} + +// This handles creating the UVMs scratch layer. +func (w *windowsBaseSnapshotter) createUVMScratchLayer(ctx context.Context, snDir string, parentLayers []string) error { + parentLen := len(parentLayers) + if parentLen == 0 { + return errors.New("no parent layers present") + } + baseLayer := parentLayers[parentLen-1] + + // Make sure base layer has a UtilityVM folder. + uvmPath := filepath.Join(baseLayer, "UtilityVM") + if _, err := os.Stat(uvmPath); os.IsNotExist(err) { + return fmt.Errorf("failed to find UtilityVM directory in base layer %q: %w", baseLayer, err) + } + + templateDiffDisk := filepath.Join(uvmPath, "SystemTemplate.vhdx") + + // Check if SystemTemplate disk doesn't exist for some reason (this should be made during the unpacking + // of the base layer). + if _, err := os.Stat(templateDiffDisk); os.IsNotExist(err) { + return fmt.Errorf("%q does not exist in Utility VM image", templateDiffDisk) + } + + // Move the sandbox.vhdx into a nested vm folder to avoid clashing with a containers sandbox.vhdx. + vmScratchDir := filepath.Join(snDir, "vm") + if err := os.MkdirAll(vmScratchDir, 0777); err != nil { + return fmt.Errorf("failed to make `vm` directory for vm's scratch space: %w", err) + } + + return copyScratchDisk(templateDiffDisk, filepath.Join(vmScratchDir, "sandbox.vhdx")) +} + +func copyScratchDisk(source, dest string) error { + scratchSource, err := os.OpenFile(source, os.O_RDWR, 0700) + if err != nil { + return fmt.Errorf("failed to open %s: %w", source, err) + } + defer scratchSource.Close() + + f, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0700) + if err != nil { + return fmt.Errorf("failed to create sandbox.vhdx in snapshot: %w", err) + } + defer f.Close() + + if _, err := io.Copy(f, scratchSource); err != nil { + os.Remove(dest) + return fmt.Errorf("failed to copy cached %q to %q in snapshot: %w", source, dest, err) + } + return nil +} + +func getRequestedScratchSize(ctx context.Context, snapshotInfo snapshots.Info) (uint64, error) { + var sizeInBytes uint64 + var err error + if sizeGBstr, ok := snapshotInfo.Labels[rootfsSizeInGBLabel]; ok { + log.G(ctx).Warnf("%q label is deprecated, please use %q instead.", rootfsSizeInGBLabel, rootfsSizeInBytesLabel) + + sizeInGB, err := strconv.ParseUint(sizeGBstr, 10, 32) + if err != nil { + return 0, fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInGBLabel, sizeGBstr, err) + } + sizeInBytes = sizeInGB * 1024 * 1024 * 1024 + } + + // Prefer the newer label in bytes over the deprecated Windows specific GB variant. + if sizeBytesStr, ok := snapshotInfo.Labels[rootfsSizeInBytesLabel]; ok { + sizeInBytes, err = strconv.ParseUint(sizeBytesStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInBytesLabel, sizeBytesStr, err) + } + } + return sizeInBytes, nil +} diff --git a/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/windows.go b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/windows.go new file mode 100644 index 0000000000..475b710ca1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/v2/plugins/snapshots/windows/windows.go @@ -0,0 +1,357 @@ +//go:build windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package windows + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/Microsoft/go-winio" + winfs "github.com/Microsoft/go-winio/pkg/fs" + "github.com/Microsoft/hcsshim" + "github.com/Microsoft/hcsshim/pkg/ociwclayer" + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/core/snapshots" + "github.com/containerd/containerd/v2/core/snapshots/storage" + "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/continuity/fs" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/containerd/platforms" + "github.com/containerd/plugin" + "github.com/containerd/plugin/registry" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +func init() { + registry.Register(&plugin.Registration{ + Type: plugins.SnapshotPlugin, + ID: "windows", + InitFn: func(ic *plugin.InitContext) (any, error) { + ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()} + return NewWindowsSnapshotter(ic.Properties[plugins.PropertyRootDir]) + }, + }) +} + +const ( + // Label to specify that we should make a scratch space for a UtilityVM. + uvmScratchLabel = "containerd.io/snapshot/io.microsoft.vm.storage.scratch" + // Label to control a containers scratch space size (sandbox.vhdx). + // + // Deprecated: use rootfsSizeInBytesLabel + rootfsSizeInGBLabel = "containerd.io/snapshot/io.microsoft.container.storage.rootfs.size-gb" + // rootfsSizeInBytesLabel is a label to control a Windows containers scratch space + // size in bytes. + rootfsSizeInBytesLabel = "containerd.io/snapshot/windows/rootfs.sizebytes" +) + +// snapshotter for legacy windows layers +type wcowSnapshotter struct { + *windowsBaseSnapshotter +} + +// NewWindowsSnapshotter returns a new windows snapshotter +func NewWindowsSnapshotter(root string) (snapshots.Snapshotter, error) { + fsType, err := winfs.GetFileSystemType(root) + if err != nil { + return nil, err + } + if strings.ToLower(fsType) != "ntfs" { + return nil, fmt.Errorf("%s is not on an NTFS volume - only NTFS volumes are supported: %w", root, errdefs.ErrInvalidArgument) + } + + baseSn, err := newBaseSnapshotter(root) + if err != nil { + return nil, err + } + + return &wcowSnapshotter{ + windowsBaseSnapshotter: baseSn, + }, nil +} + +func (s *wcowSnapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) +} + +func (s *wcowSnapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts) +} + +func (s *wcowSnapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (retErr error) { + return s.ms.WithTransaction(ctx, true, func(ctx context.Context) error { + // grab the existing id + id, _, _, err := storage.GetInfo(ctx, key) + if err != nil { + return fmt.Errorf("failed to get storage info for %s: %w", key, err) + } + + snapshot, err := storage.GetSnapshot(ctx, key) + if err != nil { + return err + } + + path := s.getSnapshotDir(id) + + // If (windowsDiff).Apply was used to populate this layer, then it's already in the 'committed' state. + // See createSnapshot below for more details + if !strings.Contains(key, snapshots.UnpackKeyPrefix) { + if len(snapshot.ParentIDs) == 0 { + if err = hcsshim.ConvertToBaseLayer(path); err != nil { + return err + } + } else if err := s.convertScratchToReadOnlyLayer(ctx, snapshot, path); err != nil { + return err + } + } + + usage, err := fs.DiskUsage(ctx, path) + if err != nil { + return fmt.Errorf("failed to collect disk usage of snapshot storage: %s: %w", path, err) + } + + if _, err := storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { + return fmt.Errorf("failed to commit snapshot: %w", err) + } + + return nil + }) +} + +func (s *wcowSnapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { + var newSnapshot storage.Snapshot + err = s.ms.WithTransaction(ctx, true, func(ctx context.Context) (retErr error) { + newSnapshot, err = storage.CreateSnapshot(ctx, kind, key, parent, opts...) + if err != nil { + return fmt.Errorf("failed to create snapshot: %w", err) + } + + log.G(ctx).Debug("createSnapshot") + // Create the new snapshot dir + snDir := s.getSnapshotDir(newSnapshot.ID) + if err = os.MkdirAll(snDir, 0700); err != nil { + return fmt.Errorf("failed to create snapshot dir %s: %w", snDir, err) + } + defer func() { + if retErr != nil { + os.RemoveAll(snDir) + } + }() + + if strings.Contains(key, snapshots.UnpackKeyPrefix) { + // IO/disk space optimization: Do nothing + // + // We only need one sandbox.vhdx for the container. Skip making one for this + // snapshot if this isn't the snapshot that just houses the final sandbox.vhd + // that will be mounted as the containers scratch. Currently the key for a snapshot + // where a layer will be extracted to will have the string `extract-` in it. + return nil + } + + if len(newSnapshot.ParentIDs) == 0 { + // A parentless snapshot a new base layer. Valid base layers must have a "Files" folder. + // When committed, there'll be some post-processing to fill in the rest + // of the metadata. + filesDir := filepath.Join(snDir, "Files") + if err := os.MkdirAll(filesDir, 0700); err != nil { + return fmt.Errorf("creating Files dir: %w", err) + } + return nil + } + + parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs) + var snapshotInfo snapshots.Info + for _, o := range opts { + o(&snapshotInfo) + } + + sizeInBytes, err := getRequestedScratchSize(ctx, snapshotInfo) + if err != nil { + return err + } + + var makeUVMScratch bool + if _, ok := snapshotInfo.Labels[uvmScratchLabel]; ok { + makeUVMScratch = true + } + + // This has to be run first to avoid clashing with the containers sandbox.vhdx. + if makeUVMScratch { + if err = s.createUVMScratchLayer(ctx, snDir, parentLayerPaths); err != nil { + return fmt.Errorf("failed to make UVM's scratch layer: %w", err) + } + } + if err = s.createScratchLayer(ctx, snDir, parentLayerPaths, sizeInBytes); err != nil { + return fmt.Errorf("failed to create scratch layer: %w", err) + } + return nil + }) + if err != nil { + return nil, err + } + + return s.mounts(newSnapshot, key), nil +} + +// Remove abandons the transaction identified by key. All resources +// associated with the key will be removed. +func (s *wcowSnapshotter) Remove(ctx context.Context, key string) error { + renamedID, err := s.preRemove(ctx, key) + if err != nil { + // wrap as ErrFailedPrecondition so that cleanup of other snapshots can continue + return fmt.Errorf("%w: %s", errdefs.ErrFailedPrecondition, err) + } + + if err = hcsshim.DestroyLayer(s.info, renamedID); err != nil { + // Must be cleaned up, any "rm-*" could be removed if no active transactions + log.G(ctx).WithError(err).WithField("renamedID", renamedID).Warnf("Failed to remove root filesystem") + } + + return nil +} + +// Mounts returns the mounts for the transaction identified by key. Can be +// called on an read-write or readonly transaction. +// +// This can be used to recover mounts after calling View or Prepare. +func (s *wcowSnapshotter) Mounts(ctx context.Context, key string) (_ []mount.Mount, err error) { + var snapshot storage.Snapshot + err = s.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + snapshot, err = storage.GetSnapshot(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot mount: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + return s.mounts(snapshot, key), nil +} + +// This is essentially a recreation of what HCS' CreateSandboxLayer does with some extra bells and +// whistles like expanding the volume if a size is specified. +func (s *wcowSnapshotter) createScratchLayer(ctx context.Context, snDir string, parentLayers []string, sizeInBytes uint64) error { + parentLen := len(parentLayers) + if parentLen == 0 { + return fmt.Errorf("no parent layers present") + } + + baseLayer := parentLayers[parentLen-1] + templateDiffDisk := filepath.Join(baseLayer, "blank.vhdx") + dest := filepath.Join(snDir, "sandbox.vhdx") + if err := copyScratchDisk(templateDiffDisk, dest); err != nil { + return err + } + + if sizeInBytes != 0 { + if err := hcsshim.ExpandSandboxSize(s.info, filepath.Base(snDir), sizeInBytes); err != nil { + return fmt.Errorf("failed to expand sandbox vhdx size to %d bytes: %w", sizeInBytes, err) + } + } + return nil +} + +// convertScratchToReadOnlyLayer reimports the layer over itself, to transfer the files from the sandbox.vhdx to the on-disk storage. +func (s *wcowSnapshotter) convertScratchToReadOnlyLayer(ctx context.Context, snapshot storage.Snapshot, path string) (retErr error) { + + // TODO darrenstahlmsft: When this is done isolated, we should disable these. + // it currently cannot be disabled, unless we add ref counting. Since this is + // temporary, leaving it enabled is OK for now. + // https://github.com/containerd/containerd/issues/1681 + if err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege}); err != nil { + return fmt.Errorf("failed to enable necessary privileges: %w", err) + } + + parentLayerPaths := s.parentIDsToParentPaths(snapshot.ParentIDs) + reader, writer := io.Pipe() + + go func() { + err := ociwclayer.ExportLayerToTar(ctx, writer, path, parentLayerPaths) + writer.CloseWithError(err) + }() + + // It seems that in certain situations, like having the containerd root and state on a file system hosted on a + // mounted VHDX, we need SeSecurityPrivilege when opening a file with winio.ACCESS_SYSTEM_SECURITY. This happens + // in the base layer writer in hcsshim when adding a new file. + if err := winio.RunWithPrivileges([]string{winio.SeSecurityPrivilege}, func() error { + _, err := ociwclayer.ImportLayerFromTar(ctx, reader, path, parentLayerPaths) + return err + }); err != nil { + return fmt.Errorf("failed to reimport snapshot: %w", err) + } + + if _, err := io.Copy(io.Discard, reader); err != nil { + return fmt.Errorf("failed discarding extra data in import stream: %w", err) + } + + // NOTE: We do not delete the sandbox.vhdx here, as that will break later calls to + // ociwclayer.ExportLayerToTar for this snapshot. + // As a consequence, the data for this layer is held twice, once on-disk and once + // in the sandbox.vhdx. + // TODO: This is either a bug or misfeature in hcsshim, so will need to be resolved + // there first. + + return nil +} + +func (s *wcowSnapshotter) mounts(sn storage.Snapshot, key string) []mount.Mount { + var ( + roFlag string + ) + + if sn.Kind == snapshots.KindView { + roFlag = "ro" + } else { + roFlag = "rw" + } + + source := s.getSnapshotDir(sn.ID) + parentLayerPaths := s.parentIDsToParentPaths(sn.ParentIDs) + + mountType := "windows-layer" + + // error is not checked here, as a string array will never fail to Marshal + parentLayersJSON, _ := json.Marshal(parentLayerPaths) + parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON) + + options := []string{ + roFlag, + } + if len(sn.ParentIDs) != 0 { + options = append(options, parentLayersOption) + } + mounts := []mount.Mount{ + { + Source: source, + Type: mountType, + Options: options, + }, + } + + return mounts +} diff --git a/vendor/github.com/containerd/nri/pkg/adaptation/adaptation.go b/vendor/github.com/containerd/nri/pkg/adaptation/adaptation.go index fd3707c5d1..bf0ca88a1f 100644 --- a/vendor/github.com/containerd/nri/pkg/adaptation/adaptation.go +++ b/vendor/github.com/containerd/nri/pkg/adaptation/adaptation.go @@ -479,6 +479,9 @@ func (r *Adaptation) RemoveContainer(ctx context.Context, req *RemoveContainerRe // Perform a set of unsolicited container updates requested by a plugin. func (r *Adaptation) updateContainers(ctx context.Context, req []*ContainerUpdate) ([]*ContainerUpdate, error) { + r.Lock() + defer r.Unlock() + return r.updateFn(ctx, req) } diff --git a/vendor/github.com/containerd/otelttrpc/.gitignore b/vendor/github.com/containerd/otelttrpc/.gitignore new file mode 100644 index 0000000000..88ceb2764b --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/.gitignore @@ -0,0 +1,13 @@ +# Binaries for programs and plugins +/bin/ +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +coverage.txt diff --git a/vendor/github.com/containerd/otelttrpc/LICENSE b/vendor/github.com/containerd/otelttrpc/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/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/vendor/github.com/containerd/otelttrpc/Makefile b/vendor/github.com/containerd/otelttrpc/Makefile new file mode 100644 index 0000000000..c8a2b4f540 --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/Makefile @@ -0,0 +1,126 @@ +# Copyright The containerd Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default commands and binaries used for builds, testing, etc. +GO ?= go +GOTEST ?= $(GO) test +GOBUILD ?= $(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} +GOINSTALL ?= $(GO) install +INSTALL ?= install + +# Go build tags. +ifdef BUILDTAGS + GO_BUILDTAGS = ${BUILDTAGS} +endif + +GO_BUILDTAGS ?= +GO_TAGS = $(if $(GO_BUILDTAGS),-tags "$(strip $(GO_BUILDTAGS))",) + +# Go build and test flags. +GO_BUILD_FLAGS = +TESTFLAGS_RACE = +TESTFLAGS ?= $(TESTFLAGS_RACE) $(EXTRA_TESTFLAGS) +TESTFLAGS_PARALLEL ?= 8 + +# See Golang issue re: '-trimpath': https://github.com/golang/go/issues/13809 +GOPATHS = $(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n") +GO_GCFLAGS= $(shell \ + set -- ${GOPATHS}; \ + echo "-gcflags=-trimpath=$${1}/src";) + +# Project packages. +PACKAGES ?= $(shell \ + $(GO) list ${GO_TAGS} ./... | \ + grep -v /example) + +# Packages to $(GOTEST). +TESTPACKAGES ?= $(shell \ + $(GO) list ${GO_TAGS} ./... | \ + grep -v /cmd | grep -v /integration | grep -v /example) + +define BUILD_BINARY +$(call WHALE_TARGET); \ +$(GOBUILD) -o $@ ${GO_TAGS} ./$< +endef + +SUBPACKAGES ?= $(shell \ + find . -name go.mod | tr -s ' ' '\n' | \ + grep -v '\./go.mod' | grep -v /example | \ + sed 's:/go.mod::g') + +define WHALE_TARGET +$(if $(SUBPKG),echo "$(WHALE) $@ $(SUBPKG)",echo "$(WHALE) $@") +endef + +WHALE := "🇩" +ONI := "👹" + +# Do quiet builds by default. Override with V=1 or Q= +ifeq ($(V),1) +Q = +else +Q = @ +endif + +all: build + +showvar: + $(Q)echo $(VAR)=$($(VAR)) + +lint: ## run all linters + $(Q)echo "$(WHALE) $@"; \ + GOGC=75 golangci-lint run; + +build: ## build the go packages + $(Q)echo "$(WHALE) $@"; \ + $(GOBUILD) -v ${PACKAGES}; + +test: ## run tests + $(Q)echo "$(WHALE) $@"; \ + $(GOTEST) ${TESTFLAGS} ${TESTPACKAGES}; + +coverage: ## generate coverprofiles from the unit tests, except tests that require root + $(Q)echo "$(WHALE) $@"; \ + rm -f coverage.txt; \ + $(GOTEST) ${TESTFLAGS} ${TESTPACKAGES} 2> /dev/null; \ + for pkg in ${PACKAGES}; do \ + $(GOTEST) ${TESTFLAGS} \ + -cover \ + -coverprofile=profile.out \ + -covermode=atomic $$pkg || exit; \ + if [ -f profile.out ]; then \ + cat profile.out >> coverage.txt.raw; \ + rm profile.out; \ + fi; \ + done; \ + sort -u coverage.txt.raw > coverage.txt; \ + rm coverage.txt.raw; + +vendor: ## ensure that all the go.mod/go.sum files are up-to-date + $(Q)echo "$(WHALE) $@"; \ + $(GO) mod tidy && \ + $(GO) mod verify + +verify-vendor: vendor ## verify if all the go.mod/go.sum files are up-to-date + $(Q)echo "$(WHALE) $@"; \ + test -z "$$(git status --short | grep "go.sum" | tee /dev/stderr)" || \ + ((git diff | cat) && \ + (echo "$(ONI) make sure to checkin changes after go mod tidy" && false)) + +help: ## this help + $(Q)awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort + +.PHONY: lint build test coverage vendor verify-vendor help + +.DEFAULT: default diff --git a/vendor/github.com/containerd/otelttrpc/README.md b/vendor/github.com/containerd/otelttrpc/README.md new file mode 100644 index 0000000000..91a3ebc54a --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/README.md @@ -0,0 +1,61 @@ +# ttrpc OpenTelemetry Instrumentation + +This golang package implements OpenTelemetry instrumentation support for +ttrpc. It can be used to automatically generate OpenTelemetry trace spans +for RPC methods called on the ttrpc client side and served on the ttrpc +server side. + +# Usage + +Instrumentation is provided by two interceptors, one to enable instrumentation +for unary clients and another for enabling instrumentation for unary servers. +These interceptors can be passed as ttrpc.ClientOpts and ttrpc.ServerOpt to +ttrpc during client and server creation with code like this: + +```golang + + import ( + "github.com/containerd/ttrpc" + "github.com/containerd/otelttrpc" + ) + + // on the client side + ... + client := ttrpc.NewClient( + conn, + ttrpc.UnaryClientInterceptor( + otelttrpc.UnaryClientInterceptor(), + ), + ) + + // and on the server side + ... + server, err := ttrpc.NewServer( + ttrpc.WithUnaryServerInterceptor( + otelttrpc.UnaryServerInterceptor(), + ), + ) +``` + +Once enabled, the interceptors generate trace Spans for all called and served +unary method calls. If the rest of the code is properly set up to collect and +export tracing data to opentelemetry, these spans should show up as part of +the collected traces. + +For a more complete example see the [sample client](example/client/main.go) +and the [sample server](example/server/main.go) code. + +# Limitations + +Currently only unary client and unary server methods can be instrumented. +Support for streaming interfaces is yet to be implemented. + +# Project details + +otelttrpc is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd sub-project, you will find the: + * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), + * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), + * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) + +information in our [`containerd/project`](https://github.com/containerd/project) repository. diff --git a/vendor/github.com/containerd/otelttrpc/config.go b/vendor/github.com/containerd/otelttrpc/config.go new file mode 100644 index 0000000000..186308a1ee --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/config.go @@ -0,0 +1,175 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package otelttrpc + +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" + "go.opentelemetry.io/otel/trace" +) + +const ( + // instrumentationName is the name of this instrumentation package. + instrumentationName = "github.com/containerd/otelttrpc" + + // TTRPCStatusCodeKey is convention for numeric status code of a ttRPC request. + TTRPCStatusCodeKey = attribute.Key("rpc.ttrpc.status_code") +) + +// config is a group of options for this instrumentation. +type config struct { + Propagators propagation.TextMapPropagator + TracerProvider trace.TracerProvider + MeterProvider metric.MeterProvider + + ReceivedEvent bool + SentEvent bool + + meter metric.Meter + rpcServerDuration metric.Int64Histogram +} + +// Option applies an option value for a config. +type Option interface { + apply(*config) +} + +// newConfig returns a config configured with all the passed Options. +func newConfig(opts []Option) *config { + c := &config{ + Propagators: otel.GetTextMapPropagator(), + TracerProvider: otel.GetTracerProvider(), + MeterProvider: otel.GetMeterProvider(), + } + for _, o := range opts { + o.apply(c) + } + + var err error + c.meter = c.MeterProvider.Meter( + instrumentationName, + metric.WithInstrumentationVersion(Version()), + metric.WithSchemaURL(semconv.SchemaURL), + ) + if c.rpcServerDuration, err = c.meter.Int64Histogram( + "rpc.server.duration", + metric.WithUnit("ms"), + ); err != nil { + otel.Handle(err) + } + + return c +} + +type propagatorsOption struct{ p propagation.TextMapPropagator } + +func (o propagatorsOption) apply(c *config) { + if o.p != nil { + c.Propagators = o.p + } +} + +// WithPropagators returns an Option for setting the Propagators used +// to inject and extract trace context from requests. If this option +// is not provided the global TextMapPropagator will be used. +func WithPropagators(p propagation.TextMapPropagator) Option { + return propagatorsOption{p: p} +} + +type tracerProviderOption struct{ tp trace.TracerProvider } + +func (o tracerProviderOption) apply(c *config) { + if o.tp != nil { + c.TracerProvider = o.tp + } +} + +// WithTracerProvider returns an Option for setting the TracerProvider +// for creating a Tracer. If this option is not provided the global +// TracerProvider will be used. +func WithTracerProvider(tp trace.TracerProvider) Option { + return tracerProviderOption{tp: tp} +} + +type meterProviderOption struct{ mp metric.MeterProvider } + +func (o meterProviderOption) apply(c *config) { + if o.mp != nil { + c.MeterProvider = o.mp + } +} + +// WithMeterProvider returns an Option for setting the MeterProvider +// when creating a Meter. If this option is not provided the global +// MeterProvider will be used. +func WithMeterProvider(mp metric.MeterProvider) Option { + return meterProviderOption{mp: mp} +} + +// Event type that can be recorded, see WithMessageEvents. +type Event int + +// Different types of events that can be recorded, see WithMessageEvents. +const ( + ReceivedEvents Event = iota + SentEvents +) + +type messageEventsProviderOption struct { + events []Event +} + +func (m messageEventsProviderOption) apply(c *config) { + for _, e := range m.events { + switch e { + case ReceivedEvents: + c.ReceivedEvent = true + case SentEvents: + c.SentEvent = true + } + } +} + +// WithMessageEvents configures the interceptors to record the specified +// events (span.AddEvent) on spans. By default only summary attributes +// are added at the end of the request. +// +// Valid events are: +// - ReceivedEvents: Record an event for every message received. +// - SentEvents: Record an event for every message sent. +func WithMessageEvents(events ...Event) Option { + return messageEventsProviderOption{events: events} +} diff --git a/vendor/github.com/containerd/otelttrpc/doc.go b/vendor/github.com/containerd/otelttrpc/doc.go new file mode 100644 index 0000000000..b64eb62ed9 --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/doc.go @@ -0,0 +1,27 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* +package otelttrpc implements Opentelemetry instrumentation support for ttRPC. +The package implements unary client and server interceptors for opentelemetry +tracing instrumentation. The interceptors can be passed as ttrpc.ClientOpts +and ttrpc.ServerOpt to ttRPC during client and server creation. The interceptors +then automatically handle generating trace spans for all called and served +unary method calls. If the rest of the code is properly set up to collect and +export tracing data to opentelemetry, these spans should show up as part of the +collected traces. +*/ +package otelttrpc diff --git a/vendor/github.com/containerd/otelttrpc/interceptor.go b/vendor/github.com/containerd/otelttrpc/interceptor.go new file mode 100644 index 0000000000..e0c05c07c5 --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/interceptor.go @@ -0,0 +1,255 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package otelttrpc + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/containerd/ttrpc" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" + "go.opentelemetry.io/otel/trace" + grpc_codes "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/containerd/otelttrpc/internal" +) + +type messageType attribute.KeyValue + +// Event adds an event of the messageType to the span associated with the +// passed context with a message id. +func (m messageType) Event(ctx context.Context, id int, _ interface{}) { + span := trace.SpanFromContext(ctx) + if !span.IsRecording() { + return + } + span.AddEvent("message", trace.WithAttributes( + attribute.KeyValue(m), + RPCMessageIDKey.Int(id), + )) +} + +var ( + messageSent = messageType(RPCMessageTypeSent) + messageReceived = messageType(RPCMessageTypeReceived) +) + +// UnaryClientInterceptor returns a ttrpc.UnaryClientInterceptor suitable +// for use in a ttrpc.NewClient call. +func UnaryClientInterceptor(opts ...Option) ttrpc.UnaryClientInterceptor { + cfg := newConfig(opts) + tracer := cfg.TracerProvider.Tracer( + instrumentationName, + trace.WithInstrumentationVersion(Version()), + ) + + return func( + ctx context.Context, + req *ttrpc.Request, reply *ttrpc.Response, + info *ttrpc.UnaryClientInfo, + invoker ttrpc.Invoker, + ) error { + name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx)) + var span trace.Span + ctx, span = tracer.Start( + ctx, + name, + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(attr...), + ) + defer span.End() + + ctx = inject(ctx, cfg.Propagators, req) + + if cfg.SentEvent { + messageSent.Event(ctx, 1, req) + } + + err := invoker(ctx, req, reply) + + if cfg.ReceivedEvent { + messageReceived.Event(ctx, 1, reply) + } + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(codes.Error, s.Message()) + span.SetAttributes(statusCodeAttr(s.Code())) + } else { + span.SetAttributes(statusCodeAttr(grpc_codes.OK)) + } + + return err + } +} + +// UnaryServerInterceptor returns ttrpc.UnaryServerInterceptor suitable +// for use in a ttrpc.NewServer call. +func UnaryServerInterceptor(opts ...Option) ttrpc.UnaryServerInterceptor { + cfg := newConfig(opts) + tracer := cfg.TracerProvider.Tracer( + instrumentationName, + trace.WithInstrumentationVersion(Version()), + ) + + return func( + ctx context.Context, + unmarshal ttrpc.Unmarshaler, info *ttrpc.UnaryServerInfo, method ttrpc.Method) (interface{}, error) { + + ctx = extract(ctx, cfg.Propagators) + + name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx)) + ctx, span := tracer.Start( + trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)), + name, + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes(attr...), + ) + defer span.End() + + if cfg.ReceivedEvent { + messageReceived.Event(ctx, 1, nil) + } + + var statusCode grpc_codes.Code + defer func(t time.Time) { + elapsedTime := time.Since(t) / time.Millisecond + attr = append(attr, TTRPCStatusCodeKey.Int64(int64(statusCode))) + o := metric.WithAttributes(attr...) + cfg.rpcServerDuration.Record(ctx, int64(elapsedTime), o) + }(time.Now()) + + resp, err := method(ctx, unmarshal) + if err != nil { + s, _ := status.FromError(err) + statusCode, msg := serverStatus(s) + span.SetStatus(statusCode, msg) + span.SetAttributes(statusCodeAttr(s.Code())) + if cfg.SentEvent { + messageSent.Event(ctx, 1, s.Proto()) + } + } else { + statusCode = grpc_codes.OK + span.SetAttributes(statusCodeAttr(grpc_codes.OK)) + if cfg.SentEvent { + messageSent.Event(ctx, 1, resp) + } + } + + return resp, err + } + +} + +// spanInfo returns a span name and all appropriate attributes from the ttRPC +// method and peer address. +func spanInfo(fullMethod, peerAddress string) (string, []attribute.KeyValue) { + attrs := []attribute.KeyValue{RPCSystemTTRPC} + name, mAttrs := internal.ParseFullMethod(fullMethod) + attrs = append(attrs, mAttrs...) + attrs = append(attrs, peerAttr(peerAddress)...) + return name, attrs +} + +// peerAttr returns attributes about the peer address. +func peerAttr(addr string) []attribute.KeyValue { + host, p, err := net.SplitHostPort(addr) + if err != nil { + return []attribute.KeyValue(nil) + } + + if host == "" { + host = "127.0.0.1" + } + port, err := strconv.Atoi(p) + if err != nil { + return []attribute.KeyValue(nil) + } + + var attr []attribute.KeyValue + if ip := net.ParseIP(host); ip != nil { + attr = []attribute.KeyValue{ + semconv.NetSockPeerAddr(host), + semconv.NetSockPeerPort(port), + } + } else { + attr = []attribute.KeyValue{ + semconv.NetPeerName(host), + semconv.NetPeerPort(port), + } + } + + return attr +} + +// peerFromCtx returns a peer address from a context, if one exists. +func peerFromCtx(_ context.Context) string { + // TODO(klihub): we can't get our peer address here now. + // One possiblity would be to have the client set it in + // the metadata in Call(). + return "" +} + +// statusCodeAttr returns status code attribute based on given RPC code. +func statusCodeAttr(c grpc_codes.Code) attribute.KeyValue { + return TTRPCStatusCodeKey.Int64(int64(c)) +} + +// serverStatus returns a span status code and message for a given RPC +// status code. It maps specific RPC status codes to a corresponding span +// status code and message. This function is intended for use on the server +// side of a RPC connection. +// +// If the RPC status code is Unknown, DeadlineExceeded, Unimplemented, +// Internal, Unavailable, or DataLoss, it returns a span status code of Error +// and the message from the RPC status. Otherwise, it returns a span status +// code of Unset and an empty message. +func serverStatus(rpcStatus *status.Status) (codes.Code, string) { + switch rpcStatus.Code() { + case grpc_codes.Unknown, + grpc_codes.DeadlineExceeded, + grpc_codes.Unimplemented, + grpc_codes.Internal, + grpc_codes.Unavailable, + grpc_codes.DataLoss: + return codes.Error, rpcStatus.Message() + default: + return codes.Unset, "" + } +} diff --git a/vendor/github.com/containerd/otelttrpc/internal/parse.go b/vendor/github.com/containerd/otelttrpc/internal/parse.go new file mode 100644 index 0000000000..694aeba801 --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/internal/parse.go @@ -0,0 +1,65 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package internal + +import ( + "strings" + + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +// ParseFullMethod returns a span name following the OpenTelemetry semantic +// conventions as well as all applicable span attribute.KeyValue attributes based +// on a ttRPC FullMethod. +func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) { + var ( + attrs []attribute.KeyValue + name = strings.TrimLeft(fullMethod, "/") + ) + + service, method, ok := strings.Cut(name, "/") + if !ok { + // Invalid format, does not follow `/package.service/method`. + return name, attrs + } + + if service != "" { + attrs = append(attrs, semconv.RPCService(service)) + } + if method != "" { + attrs = append(attrs, semconv.RPCMethod(method)) + } + + return name, attrs +} diff --git a/vendor/github.com/containerd/otelttrpc/internal/test.pb.go b/vendor/github.com/containerd/otelttrpc/internal/test.pb.go new file mode 100644 index 0000000000..8e5a85bf16 --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/internal/test.pb.go @@ -0,0 +1,235 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.20.1 +// source: github.com/containerd/ttrpc/test.proto + +package internal + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TestPayload struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` + Deadline int64 `protobuf:"varint,2,opt,name=deadline,proto3" json:"deadline,omitempty"` + Metadata string `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` +} + +func (x *TestPayload) Reset() { + *x = TestPayload{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_ttrpc_test_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestPayload) ProtoMessage() {} + +func (x *TestPayload) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_ttrpc_test_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestPayload.ProtoReflect.Descriptor instead. +func (*TestPayload) Descriptor() ([]byte, []int) { + return file_github_com_containerd_ttrpc_test_proto_rawDescGZIP(), []int{0} +} + +func (x *TestPayload) GetFoo() string { + if x != nil { + return x.Foo + } + return "" +} + +func (x *TestPayload) GetDeadline() int64 { + if x != nil { + return x.Deadline + } + return 0 +} + +func (x *TestPayload) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + +type EchoPayload struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Seq int64 `protobuf:"varint,1,opt,name=seq,proto3" json:"seq,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (x *EchoPayload) Reset() { + *x = EchoPayload{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_ttrpc_test_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EchoPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EchoPayload) ProtoMessage() {} + +func (x *EchoPayload) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_ttrpc_test_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EchoPayload.ProtoReflect.Descriptor instead. +func (*EchoPayload) Descriptor() ([]byte, []int) { + return file_github_com_containerd_ttrpc_test_proto_rawDescGZIP(), []int{1} +} + +func (x *EchoPayload) GetSeq() int64 { + if x != nil { + return x.Seq + } + return 0 +} + +func (x *EchoPayload) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +var File_github_com_containerd_ttrpc_test_proto protoreflect.FileDescriptor + +var file_github_com_containerd_ttrpc_test_proto_rawDesc = []byte{ + 0x0a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x65, + 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x74, 0x72, 0x70, 0x63, 0x22, + 0x57, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x66, 0x6f, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x66, 0x6f, 0x6f, + 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, + 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x71, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x73, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x42, 0x26, 0x5a, 0x24, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_github_com_containerd_ttrpc_test_proto_rawDescOnce sync.Once + file_github_com_containerd_ttrpc_test_proto_rawDescData = file_github_com_containerd_ttrpc_test_proto_rawDesc +) + +func file_github_com_containerd_ttrpc_test_proto_rawDescGZIP() []byte { + file_github_com_containerd_ttrpc_test_proto_rawDescOnce.Do(func() { + file_github_com_containerd_ttrpc_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_test_proto_rawDescData) + }) + return file_github_com_containerd_ttrpc_test_proto_rawDescData +} + +var file_github_com_containerd_ttrpc_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_github_com_containerd_ttrpc_test_proto_goTypes = []interface{}{ + (*TestPayload)(nil), // 0: ttrpc.TestPayload + (*EchoPayload)(nil), // 1: ttrpc.EchoPayload +} +var file_github_com_containerd_ttrpc_test_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_github_com_containerd_ttrpc_test_proto_init() } +func file_github_com_containerd_ttrpc_test_proto_init() { + if File_github_com_containerd_ttrpc_test_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_github_com_containerd_ttrpc_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestPayload); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_github_com_containerd_ttrpc_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EchoPayload); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_github_com_containerd_ttrpc_test_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_github_com_containerd_ttrpc_test_proto_goTypes, + DependencyIndexes: file_github_com_containerd_ttrpc_test_proto_depIdxs, + MessageInfos: file_github_com_containerd_ttrpc_test_proto_msgTypes, + }.Build() + File_github_com_containerd_ttrpc_test_proto = out.File + file_github_com_containerd_ttrpc_test_proto_rawDesc = nil + file_github_com_containerd_ttrpc_test_proto_goTypes = nil + file_github_com_containerd_ttrpc_test_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/otelttrpc/internal/test.proto b/vendor/github.com/containerd/otelttrpc/internal/test.proto new file mode 100644 index 0000000000..0e114d5568 --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/internal/test.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package ttrpc; + +option go_package = "github.com/containerd/ttrpc/internal"; + +message TestPayload { + string foo = 1; + int64 deadline = 2; + string metadata = 3; +} + +message EchoPayload { + int64 seq = 1; + string msg = 2; +} diff --git a/vendor/github.com/containerd/otelttrpc/metadata_supplier.go b/vendor/github.com/containerd/otelttrpc/metadata_supplier.go new file mode 100644 index 0000000000..3b116389ac --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/metadata_supplier.go @@ -0,0 +1,111 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package otelttrpc + +import ( + "context" + + "github.com/containerd/ttrpc" + "go.opentelemetry.io/otel/propagation" +) + +type metadataSupplier struct { + metadata *ttrpc.MD +} + +// assert that metadataSupplier implements the TextMapCarrier interface. +var _ propagation.TextMapCarrier = &metadataSupplier{} + +func (s *metadataSupplier) Get(key string) string { + values, _ := s.metadata.Get(key) + if len(values) == 0 { + return "" + } + return values[0] +} + +func (s *metadataSupplier) Set(key string, value string) { + s.metadata.Set(key, value) +} + +func (s *metadataSupplier) Keys() []string { + out := make([]string, 0, len(*s.metadata)) + for key := range *s.metadata { + out = append(out, key) + } + return out +} + +func inject(ctx context.Context, propagators propagation.TextMapPropagator, req *ttrpc.Request) context.Context { + md, ok := ttrpc.GetMetadata(ctx) + if !ok { + md = make(ttrpc.MD) + } else { + // make a copy to avoid concurrent read/write panic + md = md.Clone() + } + + propagators.Inject(ctx, &metadataSupplier{ + metadata: &md, + }) + + // keep non-conflicting metadata from req, update others from context + newMD := make([]*ttrpc.KeyValue, 0) + for _, kv := range req.Metadata { + if _, found := md.Get(kv.Key); !found { + newMD = append(newMD, kv) + } + } + for k, values := range md { + for _, v := range values { + newMD = append(newMD, &ttrpc.KeyValue{ + Key: k, + Value: v, + }) + } + } + req.Metadata = newMD + + return ttrpc.WithMetadata(ctx, md) +} + +func extract(ctx context.Context, propagators propagation.TextMapPropagator) context.Context { + md, ok := ttrpc.GetMetadata(ctx) + if !ok { + md = make(ttrpc.MD) + } + + return propagators.Extract(ctx, &metadataSupplier{ + metadata: &md, + }) +} diff --git a/vendor/github.com/containerd/otelttrpc/semconv.go b/vendor/github.com/containerd/otelttrpc/semconv.go new file mode 100644 index 0000000000..64f0fe8f30 --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/semconv.go @@ -0,0 +1,60 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package otelttrpc + +import ( + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +// Semantic conventions for attribute keys for ttRPC. +const ( + // Name of message transmitted or received. + RPCNameKey = attribute.Key("name") + + // Type of message transmitted or received. + RPCMessageTypeKey = attribute.Key("message.type") + + // Identifier of message transmitted or received. + RPCMessageIDKey = attribute.Key("message.id") +) + +// Semantic conventions for common RPC attributes. +var ( + // Semantic convention for ttRPC as the remoting system. + RPCSystemTTRPC = semconv.RPCSystemKey.String("ttrpc") + + // Semantic conventions for RPC message types. + RPCMessageTypeSent = RPCMessageTypeKey.String("SENT") + RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED") +) diff --git a/vendor/github.com/containerd/otelttrpc/version.go b/vendor/github.com/containerd/otelttrpc/version.go new file mode 100644 index 0000000000..bbf2bbcc5e --- /dev/null +++ b/vendor/github.com/containerd/otelttrpc/version.go @@ -0,0 +1,38 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package otelttrpc + +// Version is the current release version of the ttRPC instrumentation. +func Version() string { + return "0.0.0" +} diff --git a/vendor/github.com/containerd/plugin/registry/register.go b/vendor/github.com/containerd/plugin/registry/register.go new file mode 100644 index 0000000000..57bb201bfc --- /dev/null +++ b/vendor/github.com/containerd/plugin/registry/register.go @@ -0,0 +1,50 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package registry + +import ( + "sync" + + "github.com/containerd/plugin" +) + +var register = struct { + sync.RWMutex + r plugin.Registry +}{} + +// Register allows plugins to register +func Register(r *plugin.Registration) { + register.Lock() + defer register.Unlock() + register.r = register.r.Register(r) +} + +// Reset removes all global registrations +func Reset() { + register.Lock() + defer register.Unlock() + register.r = nil +} + +// Graph returns an ordered list of registered plugins for initialization. +// Plugins in disableList specified by id will be disabled. +func Graph(filter plugin.DisableFilter) []plugin.Registration { + register.RLock() + defer register.RUnlock() + return register.r.Graph(filter) +} diff --git a/vendor/github.com/fxamacker/cbor/v2/.gitignore b/vendor/github.com/fxamacker/cbor/v2/.gitignore new file mode 100644 index 0000000000..f1c181ec9c --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/github.com/fxamacker/cbor/v2/.golangci.yml b/vendor/github.com/fxamacker/cbor/v2/.golangci.yml new file mode 100644 index 0000000000..38cb9ae101 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/.golangci.yml @@ -0,0 +1,104 @@ +# Do not delete linter settings. Linters like gocritic can be enabled on the command line. + +linters-settings: + depguard: + rules: + prevent_unmaintained_packages: + list-mode: strict + files: + - $all + - "!$test" + allow: + - $gostd + - github.com/x448/float16 + deny: + - pkg: io/ioutil + desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil" + dupl: + threshold: 100 + funlen: + lines: 100 + statements: 50 + goconst: + ignore-tests: true + min-len: 2 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - commentedOutCode + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - ifElseChain + - octalLiteral + - paramTypeCombine + - whyNoLint + gofmt: + simplify: false + goimports: + local-prefixes: github.com/fxamacker/cbor + golint: + min-confidence: 0 + govet: + check-shadowing: true + lll: + line-length: 140 + maligned: + suggest-new: true + misspell: + locale: US + staticcheck: + checks: ["all"] + +linters: + disable-all: true + enable: + - asciicheck + - bidichk + - depguard + - errcheck + - exportloopref + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - misspell + - nilerr + - revive + - staticcheck + - stylecheck + - typecheck + - unconvert + - unused + +issues: + # max-issues-per-linter default is 50. Set to 0 to disable limit. + max-issues-per-linter: 0 + # max-same-issues default is 3. Set to 0 to disable limit. + max-same-issues: 0 + + exclude-rules: + - path: decode.go + text: "string ` overflows ` has (\\d+) occurrences, make it a constant" + - path: decode.go + text: "string ` \\(range is \\[` has (\\d+) occurrences, make it a constant" + - path: decode.go + text: "string `, ` has (\\d+) occurrences, make it a constant" + - path: decode.go + text: "string ` overflows Go's int64` has (\\d+) occurrences, make it a constant" + - path: decode.go + text: "string `\\]\\)` has (\\d+) occurrences, make it a constant" + - path: valid.go + text: "string ` for type ` has (\\d+) occurrences, make it a constant" + - path: valid.go + text: "string `cbor: ` has (\\d+) occurrences, make it a constant" diff --git a/vendor/github.com/fxamacker/cbor/v2/CODE_OF_CONDUCT.md b/vendor/github.com/fxamacker/cbor/v2/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..c794b2b0c6 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +faye.github@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/vendor/github.com/fxamacker/cbor/v2/CONTRIBUTING.md b/vendor/github.com/fxamacker/cbor/v2/CONTRIBUTING.md new file mode 100644 index 0000000000..de0965e12d --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# How to contribute + +You can contribute by using the library, opening issues, or opening pull requests. + +## Bug reports and security vulnerabilities + +Most issues are tracked publicly on [GitHub](https://github.com/fxamacker/cbor/issues). + +To report security vulnerabilities, please email faye.github@gmail.com and allow time for the problem to be resolved before disclosing it to the public. For more info, see [Security Policy](https://github.com/fxamacker/cbor#security-policy). + +Please do not send data that might contain personally identifiable information, even if you think you have permission. That type of support requires payment and a signed contract where I'm indemnified, held harmless, and defended by you for any data you send to me. + +## Pull requests + +Please [create an issue](https://github.com/fxamacker/cbor/issues/new/choose) before you begin work on a PR. The improvement may have already been considered, etc. + +Pull requests have signing requirements and must not be anonymous. Exceptions are usually made for docs and CI scripts. + +See the [Pull Request Template](https://github.com/fxamacker/cbor/blob/master/.github/pull_request_template.md) for details. + +Pull requests have a greater chance of being approved if: +- it does not reduce speed, increase memory use, reduce security, etc. for people not using the new option or feature. +- it has > 97% code coverage. + +## Describe your issue + +Clearly describe the issue: +* If it's a bug, please provide: **version of this library** and **Go** (`go version`), **unmodified error message**, and describe **how to reproduce it**. Also state **what you expected to happen** instead of the error. +* If you propose a change or addition, try to give an example how the improved code could look like or how to use it. +* If you found a compilation error, please confirm you're using a supported version of Go. If you are, then provide the output of `go version` first, followed by the complete error message. + +## Please don't + +Please don't send data containing personally identifiable information, even if you think you have permission. That type of support requires payment and a contract where I'm indemnified, held harmless, and defended for any data you send to me. + +Please don't send CBOR data larger than 1024 bytes by email. If you want to send crash-producing CBOR data > 1024 bytes by email, please get my permission before sending it to me. + +## Credits + +- This guide used nlohmann/json contribution guidelines for inspiration as suggested in issue #22. +- Special thanks to @lukseven for pointing out the contribution guidelines didn't mention signing requirements. diff --git a/vendor/github.com/fxamacker/cbor/v2/LICENSE b/vendor/github.com/fxamacker/cbor/v2/LICENSE new file mode 100644 index 0000000000..eaa8504921 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-present Faye Amacker + +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. \ No newline at end of file diff --git a/vendor/github.com/fxamacker/cbor/v2/README.md b/vendor/github.com/fxamacker/cbor/v2/README.md new file mode 100644 index 0000000000..d072b81c73 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/README.md @@ -0,0 +1,934 @@ +

CBOR Codec Go logo

+ +[fxamacker/cbor](https://github.com/fxamacker/cbor) is a library for encoding and decoding [CBOR](https://www.rfc-editor.org/info/std94) and [CBOR Sequences](https://www.rfc-editor.org/rfc/rfc8742.html). + +CBOR is a [trusted alternative](https://www.rfc-editor.org/rfc/rfc8949.html#name-comparison-of-other-binary-) to JSON, MessagePack, Protocol Buffers, etc.  CBOR is an Internet Standard defined by [IETF STD 94 (RFC 8949)](https://www.rfc-editor.org/info/std94) and is designed to be relevant for decades. + +`fxamacker/cbor` is used in projects by Arm Ltd., EdgeX Foundry, Flow Foundation, Fraunhofer‑AISEC, IBM, Kubernetes[*](https://github.com/search?q=org%3Akubernetes%20fxamacker%2Fcbor&type=code), Let's Encrypt, Linux Foundation, Microsoft, Oasis Protocol, Red Hat[*](https://github.com/search?q=org%3Aopenshift+fxamacker%2Fcbor&type=code), Tailscale[*](https://github.com/search?q=org%3Atailscale+fxamacker%2Fcbor&type=code), Veraison[*](https://github.com/search?q=org%3Averaison+fxamacker%2Fcbor&type=code), [etc](https://github.com/fxamacker/cbor#who-uses-fxamackercbor). + +See [Quick Start](#quick-start) and [Releases](https://github.com/fxamacker/cbor/releases/). 🆕 `UnmarshalFirst` and `DiagnoseFirst` can decode CBOR Sequences. `MarshalToBuffer` and `UserBufferEncMode` accepts user-specified buffer. + +## fxamacker/cbor + +[![](https://github.com/fxamacker/cbor/workflows/ci/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3Aci) +[![](https://github.com/fxamacker/cbor/workflows/cover%20%E2%89%A597%25/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A597%25%22) +[![CodeQL](https://github.com/fxamacker/cbor/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/fxamacker/cbor/actions/workflows/codeql-analysis.yml) +[![](https://img.shields.io/badge/fuzzing-passing-44c010)](#fuzzing-and-code-coverage) +[![Go Report Card](https://goreportcard.com/badge/github.com/fxamacker/cbor)](https://goreportcard.com/report/github.com/fxamacker/cbor) +[![](https://img.shields.io/ossf-scorecard/github.com/fxamacker/cbor?label=openssf%20scorecard)](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) + +`fxamacker/cbor` is a CBOR codec in full conformance with [IETF STD 94 (RFC 8949)](https://www.rfc-editor.org/info/std94). It also supports CBOR Sequences ([RFC 8742](https://www.rfc-editor.org/rfc/rfc8742.html)) and Extended Diagnostic Notation ([Appendix G of RFC 8610](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G)). + +Features include full support for CBOR tags, [Core Deterministic Encoding](https://www.rfc-editor.org/rfc/rfc8949.html#name-core-deterministic-encoding), duplicate map key detection, etc. + +API is mostly same as `encoding/json`, plus interfaces that simplify concurrency and CBOR options. + +Design balances trade-offs between security, speed, concurrency, encoded data size, usability, etc. + +
🔎  Highlights

+ +__🚀  Speed__ + +Encoding and decoding is fast without using Go's `unsafe` package. Slower settings are opt-in. Default limits allow very fast and memory efficient rejection of malformed CBOR data. + +__🔒  Security__ + +Decoder has configurable limits that defend against malicious inputs. Duplicate map key detection is supported. By contrast, `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security). + +Codec passed multiple confidential security assessments in 2022. No vulnerabilities found in subset of codec in a [nonconfidential security assessment](https://github.com/veraison/go-cose/blob/v1.0.0-rc.1/reports/NCC_Microsoft-go-cose-Report_2022-05-26_v1.0.pdf) prepared by NCC Group for Microsoft Corporation. + +__🗜️  Data Size__ + +Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) and field tag "-" automatically reduce size of encoded structs. Encoding optionally shrinks float64→32→16 when values fit. + +__:jigsaw:  Usability__ + +API is mostly same as `encoding/json` plus interfaces that simplify concurrency for CBOR options. Encoding and decoding modes can be created at startup and reused by any goroutines. + +Presets include Core Deterministic Encoding, Preferred Serialization, CTAP2 Canonical CBOR, etc. + +__📆  Extensibility__ + +Features include CBOR [extension points](https://www.rfc-editor.org/rfc/rfc8949.html#section-7.1) (e.g. CBOR tags) and extensive settings. API has interfaces that allow users to create custom encoding and decoding without modifying this library. + +


+ +
+ +### Secure Decoding with Configurable Settings + +`fxamacker/cbor` has configurable limits, etc. that defend against malicious CBOR data. + +Notably, `fxamacker/cbor` is fast at rejecting malformed CBOR data. + +> [!NOTE] +> Benchmarks rejecting 10 bytes of malicious CBOR data decoding to `[]byte`: +> +> | Codec | Speed (ns/op) | Memory | Allocs | +> | :---- | ------------: | -----: | -----: | +> | fxamacker/cbor 2.7.0 | 47 ± 7% | 32 B/op | 2 allocs/op | +> | ugorji/go 1.2.12 | 5878187 ± 3% | 67111556 B/op | 13 allocs/op | +> +> Faster hardware (overclocked DDR4 or DDR5) can reduce speed difference. +> +>
🔎  Benchmark details

+> +> Latest comparison for decoding CBOR data to Go `[]byte`: +> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` +> - go1.22.7, linux/amd64, i5-13600K (DDR4-2933, disabled e-cores) +> - go test -bench=. -benchmem -count=20 +> +> #### Prior comparisons +> +> | Codec | Speed (ns/op) | Memory | Allocs | +> | :---- | ------------: | -----: | -----: | +> | fxamacker/cbor 2.5.0-beta2 | 44.33 ± 2% | 32 B/op | 2 allocs/op | +> | fxamacker/cbor 0.1.0 - 2.4.0 | ~44.68 ± 6% | 32 B/op | 2 allocs/op | +> | ugorji/go 1.2.10 | 5524792.50 ± 3% | 67110491 B/op | 12 allocs/op | +> | ugorji/go 1.1.0 - 1.2.6 | 💥 runtime: | out of memory: | cannot allocate | +> +> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` +> - go1.19.6, linux/amd64, i5-13600K (DDR4) +> - go test -bench=. -benchmem -count=20 +> +>

+ +In contrast, some codecs can crash or use excessive resources while decoding bad data. + +> [!WARNING] +> Go's `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security). +> +>
🔎  gob fatal error (out of memory) 💥 decoding 181 bytes

+> +> ```Go +> // Example of encoding/gob having "fatal error: runtime: out of memory" +> // while decoding 181 bytes (all Go versions as of Dec. 8, 2024). +> package main +> import ( +> "bytes" +> "encoding/gob" +> "encoding/hex" +> "fmt" +> ) +> +> // Example data is from https://github.com/golang/go/issues/24446 +> // (shortened to 181 bytes). +> const data = "4dffb503010102303001ff30000109010130010800010130010800010130" + +> "01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" + +> "860001013001ff860001013001ffb80000001eff850401010e3030303030" + +> "30303030303030303001ff3000010c0104000016ffb70201010830303030" + +> "3030303001ff3000010c000030ffb6040405fcff00303030303030303030" + +> "303030303030303030303030303030303030303030303030303030303030" + +> "30" +> +> type X struct { +> J *X +> K map[string]int +> } +> +> func main() { +> raw, _ := hex.DecodeString(data) +> decoder := gob.NewDecoder(bytes.NewReader(raw)) +> +> var x X +> decoder.Decode(&x) // fatal error: runtime: out of memory +> fmt.Println("Decoding finished.") +> } +> ``` +> +> +>

+ +### Smaller Encodings with Struct Tag Options + +Struct tags automatically reduce encoded size of structs and improve speed. + +We can write less code by using struct tag options: +- `toarray`: encode without field names (decode back to original struct) +- `keyasint`: encode field names as integers (decode back to original struct) +- `omitempty`: omit empty field when encoding +- `omitzero`: omit zero-value field when encoding + +As a special case, struct field tag "-" omits the field. + +NOTE: When a struct uses `toarray`, the encoder will ignore `omitempty` and `omitzero` to prevent position of encoded array elements from changing. This allows decoder to match encoded elements to their Go struct field. + +![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags") + +> [!NOTE] +> `fxamacker/cbor` can encode a 3-level nested Go struct to 1 byte! +> - `encoding/json`: 18 bytes of JSON +> - `fxamacker/cbor`: 1 byte of CBOR +> +>
🔎  Encoding 3-level nested Go struct with omitempty

+> +> https://go.dev/play/p/YxwvfPdFQG2 +> +> ```Go +> // Example encoding nested struct (with omitempty tag) +> // - encoding/json: 18 byte JSON +> // - fxamacker/cbor: 1 byte CBOR +> +> package main +> +> import ( +> "encoding/hex" +> "encoding/json" +> "fmt" +> +> "github.com/fxamacker/cbor/v2" +> ) +> +> type GrandChild struct { +> Quux int `json:",omitempty"` +> } +> +> type Child struct { +> Baz int `json:",omitempty"` +> Qux GrandChild `json:",omitempty"` +> } +> +> type Parent struct { +> Foo Child `json:",omitempty"` +> Bar int `json:",omitempty"` +> } +> +> func cb() { +> results, _ := cbor.Marshal(Parent{}) +> fmt.Println("hex(CBOR): " + hex.EncodeToString(results)) +> +> text, _ := cbor.Diagnose(results) // Diagnostic Notation +> fmt.Println("DN: " + text) +> } +> +> func js() { +> results, _ := json.Marshal(Parent{}) +> fmt.Println("hex(JSON): " + hex.EncodeToString(results)) +> +> text := string(results) // JSON +> fmt.Println("JSON: " + text) +> } +> +> func main() { +> cb() +> fmt.Println("-------------") +> js() +> } +> ``` +> +> Output (DN is Diagnostic Notation): +> ``` +> hex(CBOR): a0 +> DN: {} +> ------------- +> hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d +> JSON: {"Foo":{"Qux":{}}} +> ``` +> +>

+ + +## Quick Start + +__Install__: `go get github.com/fxamacker/cbor/v2` and `import "github.com/fxamacker/cbor/v2"`. + +> [!TIP] +> +> Tinygo users can try beta/experimental branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta). +> +>
🔎  More about tinygo feature branch +> +> ### Tinygo +> +> Branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta) is based on fxamacker/cbor v2.7.0 and it can be compiled using tinygo v0.33 (also compiles with golang/go). +> +> It passes unit tests (with both go1.22 and tinygo v0.33) and is considered beta/experimental for tinygo. +> +> :warning: The `feature/cbor-tinygo-beta` branch does not get fuzz tested yet. +> +> Changes in this feature branch only affect tinygo compiled software. Summary of changes: +> - default `DecOptions.MaxNestedLevels` is reduced to 16 (was 32). User can specify higher limit but 24+ crashes tests when compiled with tinygo v0.33. +> - disabled decoding CBOR tag data to Go interface because tinygo v0.33 is missing needed feature. +> - encoding error message can be different when encoding function type. +> +> Related tinygo issues: +> - https://github.com/tinygo-org/tinygo/issues/4277 +> - https://github.com/tinygo-org/tinygo/issues/4458 +> +>
+ + +### Key Points + +This library can encode and decode CBOR (RFC 8949) and CBOR Sequences (RFC 8742). + +- __CBOR data item__ is a single piece of CBOR data and its structure may contain 0 or more nested data items. +- __CBOR sequence__ is a concatenation of 0 or more encoded CBOR data items. + +Configurable limits and options can be used to balance trade-offs. + +- Encoding and decoding modes are created from options (settings). +- Modes can be created at startup and reused. +- Modes are safe for concurrent use. + +### Default Mode + +Package level functions only use this library's default settings. +They provide the "default mode" of encoding and decoding. + +```go +// API matches encoding/json for Marshal, Unmarshal, Encode, Decode, etc. +b, err = cbor.Marshal(v) // encode v to []byte b +err = cbor.Unmarshal(b, &v) // decode []byte b to v +decoder = cbor.NewDecoder(r) // create decoder with io.Reader r +err = decoder.Decode(&v) // decode a CBOR data item to v + +// v2.7.0 added MarshalToBuffer() and UserBufferEncMode interface. +err = cbor.MarshalToBuffer(v, b) // encode v to b instead of using built-in buf pool. + +// v2.5.0 added new functions that return remaining bytes. + +// UnmarshalFirst decodes first CBOR data item and returns remaining bytes. +rest, err = cbor.UnmarshalFirst(b, &v) // decode []byte b to v + +// DiagnoseFirst translates first CBOR data item to text and returns remaining bytes. +text, rest, err = cbor.DiagnoseFirst(b) // decode []byte b to Diagnostic Notation text + +// NOTE: Unmarshal() returns ExtraneousDataError if there are remaining bytes, but +// UnmarshalFirst() and DiagnoseFirst() allow trailing bytes. +``` + +> [!IMPORTANT] +> CBOR settings allow trade-offs between speed, security, encoding size, etc. +> +> - Different CBOR libraries may use different default settings. +> - CBOR-based formats or protocols usually require specific settings. +> +> For example, WebAuthn uses "CTAP2 Canonical CBOR" which is available as a preset. + +### Presets + +Presets can be used as-is or as a starting point for custom settings. + +```go +// EncOptions is a struct of encoder settings. +func CoreDetEncOptions() EncOptions // RFC 8949 Core Deterministic Encoding +func PreferredUnsortedEncOptions() EncOptions // RFC 8949 Preferred Serialization +func CanonicalEncOptions() EncOptions // RFC 7049 Canonical CBOR +func CTAP2EncOptions() EncOptions // FIDO2 CTAP2 Canonical CBOR +``` + +Presets are used to create custom modes. + +### Custom Modes + +Modes are created from settings. Once created, modes have immutable settings. + +💡 Create the mode at startup and reuse it. It is safe for concurrent use. + +```Go +// Create encoding mode. +opts := cbor.CoreDetEncOptions() // use preset options as a starting point +opts.Time = cbor.TimeUnix // change any settings if needed +em, err := opts.EncMode() // create an immutable encoding mode + +// Reuse the encoding mode. It is safe for concurrent use. + +// API matches encoding/json. +b, err := em.Marshal(v) // encode v to []byte b +encoder := em.NewEncoder(w) // create encoder with io.Writer w +err := encoder.Encode(v) // encode v to io.Writer w +``` + +Default mode and custom modes automatically apply struct tags. + +### User Specified Buffer for Encoding (v2.7.0) + +`UserBufferEncMode` interface extends `EncMode` interface to add `MarshalToBuffer()`. It accepts a user-specified buffer instead of using built-in buffer pool. + +```Go +em, err := myEncOptions.UserBufferEncMode() // create UserBufferEncMode mode + +var buf bytes.Buffer +err = em.MarshalToBuffer(v, &buf) // encode v to provided buf +``` + +### Struct Tags + +Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) reduce encoded size of structs. + +As a special case, struct field tag "-" omits the field. + +
🔎  Example encoding with struct field tag "-"

+ +https://go.dev/play/p/aWEIFxd7InX + +```Go +// https://github.com/fxamacker/cbor/issues/652 +package main + +import ( + "encoding/json" + "fmt" + + "github.com/fxamacker/cbor/v2" +) + +// The `cbor:"-"` tag omits the Type field when encoding to CBOR. +type Entity struct { + _ struct{} `cbor:",toarray"` + ID uint64 `json:"id"` + Type string `cbor:"-" json:"typeOf"` + Name string `json:"name"` +} + +func main() { + entity := Entity{ + ID: 1, + Type: "int64", + Name: "Identifier", + } + + c, _ := cbor.Marshal(entity) + diag, _ := cbor.Diagnose(c) + fmt.Printf("CBOR in hex: %x\n", c) + fmt.Printf("CBOR in edn: %s\n", diag) + + j, _ := json.Marshal(entity) + fmt.Printf("JSON: %s\n", string(j)) + + fmt.Printf("JSON encoding is %d bytes\n", len(j)) + fmt.Printf("CBOR encoding is %d bytes\n", len(c)) + + // Output: + // CBOR in hex: 82016a4964656e746966696572 + // CBOR in edn: [1, "Identifier"] + // JSON: {"id":1,"typeOf":"int64","name":"Identifier"} + // JSON encoding is 45 bytes + // CBOR encoding is 13 bytes +} +``` + +

+ +
🔎  Example encoding 3-level nested Go struct to 1 byte CBOR

+ +https://go.dev/play/p/YxwvfPdFQG2 + +```Go +// Example encoding nested struct (with omitempty tag) +// - encoding/json: 18 byte JSON +// - fxamacker/cbor: 1 byte CBOR +package main + +import ( + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/fxamacker/cbor/v2" +) + +type GrandChild struct { + Quux int `json:",omitempty"` +} + +type Child struct { + Baz int `json:",omitempty"` + Qux GrandChild `json:",omitempty"` +} + +type Parent struct { + Foo Child `json:",omitempty"` + Bar int `json:",omitempty"` +} + +func cb() { + results, _ := cbor.Marshal(Parent{}) + fmt.Println("hex(CBOR): " + hex.EncodeToString(results)) + + text, _ := cbor.Diagnose(results) // Diagnostic Notation + fmt.Println("DN: " + text) +} + +func js() { + results, _ := json.Marshal(Parent{}) + fmt.Println("hex(JSON): " + hex.EncodeToString(results)) + + text := string(results) // JSON + fmt.Println("JSON: " + text) +} + +func main() { + cb() + fmt.Println("-------------") + js() +} +``` + +Output (DN is Diagnostic Notation): +``` +hex(CBOR): a0 +DN: {} +------------- +hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d +JSON: {"Foo":{"Qux":{}}} +``` + +


+ +
+ +
🔎  Example using struct tag options

+ +![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags") + +

+ +Struct tag options simplify use of CBOR-based protocols that require CBOR arrays or maps with integer keys. + +### CBOR Tags + +CBOR tags are specified in a `TagSet`. + +Custom modes can be created with a `TagSet` to handle CBOR tags. + +```go +em, err := opts.EncMode() // no CBOR tags +em, err := opts.EncModeWithTags(ts) // immutable CBOR tags +em, err := opts.EncModeWithSharedTags(ts) // mutable shared CBOR tags +``` + +`TagSet` and modes using it are safe for concurrent use. Equivalent API is available for `DecMode`. + +
🔎  Example using TagSet and TagOptions

+ +```go +// Use signedCWT struct defined in "Decoding CWT" example. + +// Create TagSet (safe for concurrency). +tags := cbor.NewTagSet() +// Register tag COSE_Sign1 18 with signedCWT type. +tags.Add( + cbor.TagOptions{EncTag: cbor.EncTagRequired, DecTag: cbor.DecTagRequired}, + reflect.TypeOf(signedCWT{}), + 18) + +// Create DecMode with immutable tags. +dm, _ := cbor.DecOptions{}.DecModeWithTags(tags) + +// Unmarshal to signedCWT with tag support. +var v signedCWT +if err := dm.Unmarshal(data, &v); err != nil { + return err +} + +// Create EncMode with immutable tags. +em, _ := cbor.EncOptions{}.EncModeWithTags(tags) + +// Marshal signedCWT with tag number. +if data, err := em.Marshal(v); err != nil { + return err +} +``` + +

+ +👉 `fxamacker/cbor` allows user apps to use almost any current or future CBOR tag number by implementing `cbor.Marshaler` and `cbor.Unmarshaler` interfaces. + +Basically, `MarshalCBOR` and `UnmarshalCBOR` functions can be implemented by user apps and those functions will automatically be called by this CBOR codec's `Marshal`, `Unmarshal`, etc. + +The following [example](https://github.com/fxamacker/cbor/blob/master/example_embedded_json_tag_for_cbor_test.go) shows how to encode and decode a tagged CBOR data item with tag number 262. The tag content is a JSON object "embedded" as a CBOR byte string (major type 2). + +
🔎  Example using Embedded JSON Tag for CBOR (tag 262) + +```go +// https://github.com/fxamacker/cbor/issues/657 + +package cbor_test + +// NOTE: RFC 8949 does not mention tag number 262. IANA assigned +// CBOR tag number 262 as "Embedded JSON Object" specified by the +// document Embedded JSON Tag for CBOR: +// +// "Tag 262 can be applied to a byte string (major type 2) to indicate +// that the byte string is a JSON Object. The length of the byte string +// indicates the content." +// +// For more info, see Embedded JSON Tag for CBOR at: +// https://github.com/toravir/CBOR-Tag-Specs/blob/master/embeddedJSON.md + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/fxamacker/cbor/v2" +) + +// cborTagNumForEmbeddedJSON is the CBOR tag number 262. +const cborTagNumForEmbeddedJSON = 262 + +// EmbeddedJSON represents a Go value to be encoded as a tagged CBOR data item +// with tag number 262 and the tag content is a JSON object "embedded" as a +// CBOR byte string (major type 2). +type EmbeddedJSON struct { + any +} + +func NewEmbeddedJSON(val any) EmbeddedJSON { + return EmbeddedJSON{val} +} + +// MarshalCBOR encodes EmbeddedJSON to a tagged CBOR data item with the +// tag number 262 and the tag content is a JSON object that is +// "embedded" as a CBOR byte string. +func (v EmbeddedJSON) MarshalCBOR() ([]byte, error) { + // Encode v to JSON object. + data, err := json.Marshal(v) + if err != nil { + return nil, err + } + + // Create cbor.Tag representing a tagged CBOR data item. + tag := cbor.Tag{ + Number: cborTagNumForEmbeddedJSON, + Content: data, + } + + // Marshal to a tagged CBOR data item. + return cbor.Marshal(tag) +} + +// UnmarshalCBOR decodes a tagged CBOR data item to EmbeddedJSON. +// The byte slice provided to this function must contain a single +// tagged CBOR data item with the tag number 262 and tag content +// must be a JSON object "embedded" as a CBOR byte string. +func (v *EmbeddedJSON) UnmarshalCBOR(b []byte) error { + // Unmarshal tagged CBOR data item. + var tag cbor.Tag + if err := cbor.Unmarshal(b, &tag); err != nil { + return err + } + + // Check tag number. + if tag.Number != cborTagNumForEmbeddedJSON { + return fmt.Errorf("got tag number %d, expect tag number %d", tag.Number, cborTagNumForEmbeddedJSON) + } + + // Check tag content. + jsonData, isByteString := tag.Content.([]byte) + if !isByteString { + return fmt.Errorf("got tag content type %T, expect tag content []byte", tag.Content) + } + + // Unmarshal JSON object. + return json.Unmarshal(jsonData, v) +} + +// MarshalJSON encodes EmbeddedJSON to a JSON object. +func (v EmbeddedJSON) MarshalJSON() ([]byte, error) { + return json.Marshal(v.any) +} + +// UnmarshalJSON decodes a JSON object. +func (v *EmbeddedJSON) UnmarshalJSON(b []byte) error { + dec := json.NewDecoder(bytes.NewReader(b)) + dec.UseNumber() + return dec.Decode(&v.any) +} + +func Example_embeddedJSONTagForCBOR() { + value := NewEmbeddedJSON(map[string]any{ + "name": "gopher", + "id": json.Number("42"), + }) + + data, err := cbor.Marshal(value) + if err != nil { + panic(err) + } + + fmt.Printf("cbor: %x\n", data) + + var v EmbeddedJSON + err = cbor.Unmarshal(data, &v) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", v.any) + for k, v := range v.any.(map[string]any) { + fmt.Printf(" %s: %v (%T)\n", k, v, v) + } +} +``` + +
+ + +### Functions and Interfaces + +
🔎  Functions and interfaces at a glance

+ +Common functions with same API as `encoding/json`: +- `Marshal`, `Unmarshal` +- `NewEncoder`, `(*Encoder).Encode` +- `NewDecoder`, `(*Decoder).Decode` + +NOTE: `Unmarshal` will return `ExtraneousDataError` if there are remaining bytes +because RFC 8949 treats CBOR data item with remaining bytes as malformed. +- 💡 Use `UnmarshalFirst` to decode first CBOR data item and return any remaining bytes. + +Other useful functions: +- `Diagnose`, `DiagnoseFirst` produce human-readable [Extended Diagnostic Notation](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G) from CBOR data. +- `UnmarshalFirst` decodes first CBOR data item and return any remaining bytes. +- `Wellformed` returns true if the CBOR data item is well-formed. + +Interfaces identical or comparable to Go `encoding` packages include: +`Marshaler`, `Unmarshaler`, `BinaryMarshaler`, and `BinaryUnmarshaler`. + +The `RawMessage` type can be used to delay CBOR decoding or precompute CBOR encoding. + +

+ +### Security Tips + +🔒 Use Go's `io.LimitReader` to limit size when decoding very large or indefinite size data. + +Default limits may need to be increased for systems handling very large data (e.g. blockchains). + +`DecOptions` can be used to modify default limits for `MaxArrayElements`, `MaxMapPairs`, and `MaxNestedLevels`. + +## Status + +[v2.9.0](https://github.com/fxamacker/cbor/releases/tag/v2.9.0) (Jul 13, 2025) improved interoperability/transcoding between CBOR & JSON, refactored tests, and improved docs. +- Add opt-in support for `encoding.TextMarshaler` and `encoding.TextUnmarshaler` to encode and decode from CBOR text string. +- Add opt-in support for `json.Marshaler` and `json.Unmarshaler` via user-provided transcoding function. +- Update docs for TimeMode, Tag, RawTag, and add example for Embedded JSON Tag for CBOR. + +v2.9.0 passed fuzz tests and is production quality. + +The minimum version of Go required to build: +- v2.8.0 and newer releases require go 1.20+. +- v2.7.1 and older releases require go 1.17+. + +For more details, see [release notes](https://github.com/fxamacker/cbor/releases). + +### Prior Releases + +[v2.8.0](https://github.com/fxamacker/cbor/releases/tag/v2.8.0) (March 30, 2025) is a small release primarily to add `omitzero` option to struct field tags and fix bugs. It passed fuzz tests (billions of executions) and is production quality. + +[v2.7.0](https://github.com/fxamacker/cbor/releases/tag/v2.7.0) (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality. + +[v2.6.0](https://github.com/fxamacker/cbor/releases/tag/v2.6.0) (February 2024) adds important new features, optimizations, and bug fixes. It is especially useful to systems that need to convert data between CBOR and JSON. New options and optimizations improve handling of bignum, integers, maps, and strings. + +[v2.5.0](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) was released on Sunday, August 13, 2023 with new features and important bug fixes. It is fuzz tested and production quality after extended beta [v2.5.0-beta](https://github.com/fxamacker/cbor/releases/tag/v2.5.0-beta) (Dec 2022) -> [v2.5.0](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) (Aug 2023). + +__IMPORTANT__: 👉 Before upgrading from v2.4 or older release, please read the notable changes highlighted in the release notes. v2.5.0 is a large release with bug fixes to error handling for extraneous data in `Unmarshal`, etc. that should be reviewed before upgrading. + +See [v2.5.0 release notes](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) for list of new features, improvements, and bug fixes. + +See ["Version and API Changes"](https://github.com/fxamacker/cbor#versions-and-api-changes) section for more info about version numbering, etc. + + + +## Who uses fxamacker/cbor + +`fxamacker/cbor` is used in projects by Arm Ltd., Berlin Institute of Health at Charité, Chainlink, Confidential Computing Consortium, ConsenSys, EdgeX Foundry, F5, Flow Foundation, Fraunhofer‑AISEC, IBM, Kubernetes, Let's Encrypt (ISRG), Linaro, Linux Foundation, Matrix.org, Microsoft, National Cybersecurity Agency of France (govt), Netherlands (govt), Oasis Protocol, Red Hat OpenShift, Smallstep, Tailscale, Taurus SA, TIBCO, Veraison, and others. + +`fxamacker/cbor` passed multiple confidential security assessments in 2022. A [nonconfidential security assessment](https://github.com/veraison/go-cose/blob/v1.0.0-rc.1/reports/NCC_Microsoft-go-cose-Report_2022-05-26_v1.0.pdf) (prepared by NCC Group for Microsoft Corporation) assessed a subset of fxamacker/cbor v2.4. + +## Standards + +`fxamacker/cbor` is a CBOR codec in full conformance with [IETF STD 94 (RFC 8949)](https://www.rfc-editor.org/info/std94). It also supports CBOR Sequences ([RFC 8742](https://www.rfc-editor.org/rfc/rfc8742.html)) and Extended Diagnostic Notation ([Appendix G of RFC 8610](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G)). + +Notable CBOR features include: + +| CBOR Feature | Description | +| :--- | :--- | +| CBOR tags | API supports built-in and user-defined tags. | +| Preferred serialization | Integers encode to fewest bytes. Optional float64 → float32 → float16. | +| Map key sorting | Unsorted, length-first (Canonical CBOR), and bytewise-lexicographic (CTAP2). | +| Duplicate map keys | Always forbid for encoding and option to allow/forbid for decoding. | +| Indefinite length data | Option to allow/forbid for encoding and decoding. | +| Well-formedness | Always checked and enforced. | +| Basic validity checks | Optionally check UTF-8 validity and duplicate map keys. | +| Security considerations | Prevent integer overflow and resource exhaustion (RFC 8949 Section 10). | + +Known limitations are noted in the [Limitations section](#limitations). + +Go nil values for slices, maps, pointers, etc. are encoded as CBOR null. Empty slices, maps, etc. are encoded as empty CBOR arrays and maps. + +Decoder checks for all required well-formedness errors, including all "subkinds" of syntax errors and too little data. + +After well-formedness is verified, basic validity errors are handled as follows: + +* Invalid UTF-8 string: Decoder has option to check and return invalid UTF-8 string error. This check is enabled by default. +* Duplicate keys in a map: Decoder has options to ignore or enforce rejection of duplicate map keys. + +When decoding well-formed CBOR arrays and maps, decoder saves the first error it encounters and continues with the next item. Options to handle this differently may be added in the future. + +By default, decoder treats time values of floating-point NaN and Infinity as if they are CBOR Null or CBOR Undefined. + +__Click to expand topic:__ + +
+ 🔎  Duplicate Map Keys

+ +This library provides options for fast detection and rejection of duplicate map keys based on applying a Go-specific data model to CBOR's extended generic data model in order to determine duplicate vs distinct map keys. Detection relies on whether the CBOR map key would be a duplicate "key" when decoded and applied to the user-provided Go map or struct. + +`DupMapKeyQuiet` turns off detection of duplicate map keys. It tries to use a "keep fastest" method by choosing either "keep first" or "keep last" depending on the Go data type. + +`DupMapKeyEnforcedAPF` enforces detection and rejection of duplidate map keys. Decoding stops immediately and returns `DupMapKeyError` when the first duplicate key is detected. The error includes the duplicate map key and the index number. + +APF suffix means "Allow Partial Fill" so the destination map or struct can contain some decoded values at the time of error. It is the caller's responsibility to respond to the `DupMapKeyError` by discarding the partially filled result if that's required by their protocol. + +

+ +
+ 🔎  Tag Validity

+ +This library checks tag validity for built-in tags (currently tag numbers 0, 1, 2, 3, and 55799): + +* Inadmissible type for tag content +* Inadmissible value for tag content + +Unknown tag data items (not tag number 0, 1, 2, 3, or 55799) are handled in two ways: + +* When decoding into an empty interface, unknown tag data item will be decoded into `cbor.Tag` data type, which contains tag number and tag content. The tag content will be decoded into the default Go data type for the CBOR data type. +* When decoding into other Go types, unknown tag data item is decoded into the specified Go type. If Go type is registered with a tag number, the tag number can optionally be verified. + +Decoder also has an option to forbid tag data items (treat any tag data item as error) which is specified by protocols such as CTAP2 Canonical CBOR. + +For more information, see [decoding options](#decoding-options-1) and [tag options](#tag-options). + +

+ +## Limitations + +If any of these limitations prevent you from using this library, please open an issue along with a link to your project. + +* CBOR `Undefined` (0xf7) value decodes to Go's `nil` value. CBOR `Null` (0xf6) more closely matches Go's `nil`. +* CBOR map keys with data types not supported by Go for map keys are ignored and an error is returned after continuing to decode remaining items. +* When decoding registered CBOR tag data to interface type, decoder creates a pointer to registered Go type matching CBOR tag number. Requiring a pointer for this is a Go limitation. + +## Fuzzing and Code Coverage + +__Code coverage__ is always 95% or higher (with `go test -cover`) when tagging a release. + +__Coverage-guided fuzzing__ must pass billions of execs using before tagging a release. Fuzzing is done using nonpublic code which may eventually get merged into this project. Until then, reports like OpenSSF Scorecard can't detect fuzz tests being used by this project. + +
+ +## Versions and API Changes +This project uses [Semantic Versioning](https://semver.org), so the API is always backwards compatible unless the major version number changes. + +These functions have signatures identical to encoding/json and their API will continue to match `encoding/json` even after major new releases: +`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `(*Encoder).Encode`, and `(*Decoder).Decode`. + +Exclusions from SemVer: +- Newly added API documented as "subject to change". +- Newly added API in the master branch that has never been tagged in non-beta release. +- If function parameters are unchanged, bug fixes that change behavior (e.g. return error for edge case was missed in prior version). We try to highlight these in the release notes and add extended beta period. E.g. [v2.5.0-beta](https://github.com/fxamacker/cbor/releases/tag/v2.5.0-beta) (Dec 2022) -> [v2.5.0](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) (Aug 2023). + +This project avoids breaking changes to behavior of encoding and decoding functions unless required to improve conformance with supported RFCs (e.g. RFC 8949, RFC 8742, etc.) Visible changes that don't improve conformance to standards are typically made available as new opt-in settings or new functions. + +## Code of Conduct + +This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). Contact [faye.github@gmail.com](mailto:faye.github@gmail.com) with any questions or comments. + +## Contributing + +Please open an issue before beginning work on a PR. The improvement may have already been considered, etc. + +For more info, see [How to Contribute](CONTRIBUTING.md). + +## Security Policy + +Security fixes are provided for the latest released version of fxamacker/cbor. + +For the full text of the Security Policy, see [SECURITY.md](SECURITY.md). + +## Acknowledgements + +Many thanks to all the contributors on this project! + +I'm especially grateful to Bastian Müller and Dieter Shirley for suggesting and collaborating on CBOR stream mode, and much more. + +I'm very grateful to Stefan Tatschner, Yawning Angel, Jernej Kos, x448, ZenGround0, and Jakob Borg for their contributions or support in the very early days. + +Big thanks to Ben Luddy for his contributions in v2.6.0 and v2.7.0. + +This library clearly wouldn't be possible without Carsten Bormann authoring CBOR RFCs. + +Special thanks to Laurence Lundblade and Jeffrey Yasskin for their help on IETF mailing list or at [7049bis](https://github.com/cbor-wg/CBORbis). + +Huge thanks to The Go Authors for creating a fun and practical programming language with batteries included! + +This library uses `x448/float16` which used to be included. As a standalone package, `x448/float16` is useful to other projects as well. + +## License + +Copyright © 2019-2024 [Faye Amacker](https://github.com/fxamacker). + +fxamacker/cbor is licensed under the MIT License. See [LICENSE](LICENSE) for the full license text. + +
diff --git a/vendor/github.com/fxamacker/cbor/v2/SECURITY.md b/vendor/github.com/fxamacker/cbor/v2/SECURITY.md new file mode 100644 index 0000000000..9c05146d16 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +Security fixes are provided for the latest released version of fxamacker/cbor. + +If the security vulnerability is already known to the public, then you can open an issue as a bug report. + +To report security vulnerabilities not yet known to the public, please email faye.github@gmail.com and allow time for the problem to be resolved before reporting it to the public. diff --git a/vendor/github.com/fxamacker/cbor/v2/bytestring.go b/vendor/github.com/fxamacker/cbor/v2/bytestring.go new file mode 100644 index 0000000000..23c5724d2e --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/bytestring.go @@ -0,0 +1,90 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "errors" +) + +// ByteString represents CBOR byte string (major type 2). ByteString can be used +// when using a Go []byte is not possible or convenient. For example, Go doesn't +// allow []byte as map key, so ByteString can be used to support data formats +// having CBOR map with byte string keys. ByteString can also be used to +// encode invalid UTF-8 string as CBOR byte string. +// See DecOption.MapKeyByteStringMode for more details. +type ByteString string + +// Bytes returns bytes representing ByteString. +func (bs ByteString) Bytes() []byte { + return []byte(bs) +} + +// MarshalCBOR encodes ByteString as CBOR byte string (major type 2). +func (bs ByteString) MarshalCBOR() ([]byte, error) { + e := getEncodeBuffer() + defer putEncodeBuffer(e) + + // Encode length + encodeHead(e, byte(cborTypeByteString), uint64(len(bs))) + + // Encode data + buf := make([]byte, e.Len()+len(bs)) + n := copy(buf, e.Bytes()) + copy(buf[n:], bs) + + return buf, nil +} + +// UnmarshalCBOR decodes CBOR byte string (major type 2) to ByteString. +// Decoding CBOR null and CBOR undefined sets ByteString to be empty. +// +// Deprecated: No longer used by this codec; kept for compatibility +// with user apps that directly call this function. +func (bs *ByteString) UnmarshalCBOR(data []byte) error { + if bs == nil { + return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer") + } + + d := decoder{data: data, dm: defaultDecMode} + + // Check well-formedness of CBOR data item. + // ByteString.UnmarshalCBOR() is exported, so + // the codec needs to support same behavior for: + // - Unmarshal(data, *ByteString) + // - ByteString.UnmarshalCBOR(data) + err := d.wellformed(false, false) + if err != nil { + return err + } + + return bs.unmarshalCBOR(data) +} + +// unmarshalCBOR decodes CBOR byte string (major type 2) to ByteString. +// Decoding CBOR null and CBOR undefined sets ByteString to be empty. +// This function assumes data is well-formed, and does not perform bounds checking. +// This function is called by Unmarshal(). +func (bs *ByteString) unmarshalCBOR(data []byte) error { + if bs == nil { + return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer") + } + + // Decoding CBOR null and CBOR undefined to ByteString resets data. + // This behavior is similar to decoding CBOR null and CBOR undefined to []byte. + if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) { + *bs = "" + return nil + } + + d := decoder{data: data, dm: defaultDecMode} + + // Check if CBOR data type is byte string + if typ := d.nextCBORType(); typ != cborTypeByteString { + return &UnmarshalTypeError{CBORType: typ.String(), GoType: typeByteString.String()} + } + + b, _ := d.parseByteString() + *bs = ByteString(b) + return nil +} diff --git a/vendor/github.com/fxamacker/cbor/v2/cache.go b/vendor/github.com/fxamacker/cbor/v2/cache.go new file mode 100644 index 0000000000..5051f110fb --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/cache.go @@ -0,0 +1,370 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "bytes" + "errors" + "fmt" + "reflect" + "sort" + "strconv" + "strings" + "sync" +) + +type encodeFuncs struct { + ef encodeFunc + ief isEmptyFunc + izf isZeroFunc +} + +var ( + decodingStructTypeCache sync.Map // map[reflect.Type]*decodingStructType + encodingStructTypeCache sync.Map // map[reflect.Type]*encodingStructType + encodeFuncCache sync.Map // map[reflect.Type]encodeFuncs + typeInfoCache sync.Map // map[reflect.Type]*typeInfo +) + +type specialType int + +const ( + specialTypeNone specialType = iota + specialTypeUnmarshalerIface + specialTypeUnexportedUnmarshalerIface + specialTypeEmptyIface + specialTypeIface + specialTypeTag + specialTypeTime + specialTypeJSONUnmarshalerIface +) + +type typeInfo struct { + elemTypeInfo *typeInfo + keyTypeInfo *typeInfo + typ reflect.Type + kind reflect.Kind + nonPtrType reflect.Type + nonPtrKind reflect.Kind + spclType specialType +} + +func newTypeInfo(t reflect.Type) *typeInfo { + tInfo := typeInfo{typ: t, kind: t.Kind()} + + for t.Kind() == reflect.Pointer { + t = t.Elem() + } + + k := t.Kind() + + tInfo.nonPtrType = t + tInfo.nonPtrKind = k + + if k == reflect.Interface { + if t.NumMethod() == 0 { + tInfo.spclType = specialTypeEmptyIface + } else { + tInfo.spclType = specialTypeIface + } + } else if t == typeTag { + tInfo.spclType = specialTypeTag + } else if t == typeTime { + tInfo.spclType = specialTypeTime + } else if reflect.PointerTo(t).Implements(typeUnexportedUnmarshaler) { + tInfo.spclType = specialTypeUnexportedUnmarshalerIface + } else if reflect.PointerTo(t).Implements(typeUnmarshaler) { + tInfo.spclType = specialTypeUnmarshalerIface + } else if reflect.PointerTo(t).Implements(typeJSONUnmarshaler) { + tInfo.spclType = specialTypeJSONUnmarshalerIface + } + + switch k { + case reflect.Array, reflect.Slice: + tInfo.elemTypeInfo = getTypeInfo(t.Elem()) + case reflect.Map: + tInfo.keyTypeInfo = getTypeInfo(t.Key()) + tInfo.elemTypeInfo = getTypeInfo(t.Elem()) + } + + return &tInfo +} + +type decodingStructType struct { + fields fields + fieldIndicesByName map[string]int + err error + toArray bool +} + +// The stdlib errors.Join was introduced in Go 1.20, and we still support Go 1.17, so instead, +// here's a very basic implementation of an aggregated error. +type multierror []error + +func (m multierror) Error() string { + var sb strings.Builder + for i, err := range m { + sb.WriteString(err.Error()) + if i < len(m)-1 { + sb.WriteString(", ") + } + } + return sb.String() +} + +func getDecodingStructType(t reflect.Type) *decodingStructType { + if v, _ := decodingStructTypeCache.Load(t); v != nil { + return v.(*decodingStructType) + } + + flds, structOptions := getFields(t) + + toArray := hasToArrayOption(structOptions) + + var errs []error + for i := 0; i < len(flds); i++ { + if flds[i].keyAsInt { + nameAsInt, numErr := strconv.Atoi(flds[i].name) + if numErr != nil { + errs = append(errs, errors.New("cbor: failed to parse field name \""+flds[i].name+"\" to int ("+numErr.Error()+")")) + break + } + flds[i].nameAsInt = int64(nameAsInt) + } + + flds[i].typInfo = getTypeInfo(flds[i].typ) + } + + fieldIndicesByName := make(map[string]int, len(flds)) + for i, fld := range flds { + if _, ok := fieldIndicesByName[fld.name]; ok { + errs = append(errs, fmt.Errorf("cbor: two or more fields of %v have the same name %q", t, fld.name)) + continue + } + fieldIndicesByName[fld.name] = i + } + + var err error + { + var multi multierror + for _, each := range errs { + if each != nil { + multi = append(multi, each) + } + } + if len(multi) == 1 { + err = multi[0] + } else if len(multi) > 1 { + err = multi + } + } + + structType := &decodingStructType{ + fields: flds, + fieldIndicesByName: fieldIndicesByName, + err: err, + toArray: toArray, + } + decodingStructTypeCache.Store(t, structType) + return structType +} + +type encodingStructType struct { + fields fields + bytewiseFields fields + lengthFirstFields fields + omitEmptyFieldsIdx []int + err error + toArray bool +} + +func (st *encodingStructType) getFields(em *encMode) fields { + switch em.sort { + case SortNone, SortFastShuffle: + return st.fields + case SortLengthFirst: + return st.lengthFirstFields + default: + return st.bytewiseFields + } +} + +type bytewiseFieldSorter struct { + fields fields +} + +func (x *bytewiseFieldSorter) Len() int { + return len(x.fields) +} + +func (x *bytewiseFieldSorter) Swap(i, j int) { + x.fields[i], x.fields[j] = x.fields[j], x.fields[i] +} + +func (x *bytewiseFieldSorter) Less(i, j int) bool { + return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0 +} + +type lengthFirstFieldSorter struct { + fields fields +} + +func (x *lengthFirstFieldSorter) Len() int { + return len(x.fields) +} + +func (x *lengthFirstFieldSorter) Swap(i, j int) { + x.fields[i], x.fields[j] = x.fields[j], x.fields[i] +} + +func (x *lengthFirstFieldSorter) Less(i, j int) bool { + if len(x.fields[i].cborName) != len(x.fields[j].cborName) { + return len(x.fields[i].cborName) < len(x.fields[j].cborName) + } + return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0 +} + +func getEncodingStructType(t reflect.Type) (*encodingStructType, error) { + if v, _ := encodingStructTypeCache.Load(t); v != nil { + structType := v.(*encodingStructType) + return structType, structType.err + } + + flds, structOptions := getFields(t) + + if hasToArrayOption(structOptions) { + return getEncodingStructToArrayType(t, flds) + } + + var err error + var hasKeyAsInt bool + var hasKeyAsStr bool + var omitEmptyIdx []int + e := getEncodeBuffer() + for i := 0; i < len(flds); i++ { + // Get field's encodeFunc + flds[i].ef, flds[i].ief, flds[i].izf = getEncodeFunc(flds[i].typ) + if flds[i].ef == nil { + err = &UnsupportedTypeError{t} + break + } + + // Encode field name + if flds[i].keyAsInt { + nameAsInt, numErr := strconv.Atoi(flds[i].name) + if numErr != nil { + err = errors.New("cbor: failed to parse field name \"" + flds[i].name + "\" to int (" + numErr.Error() + ")") + break + } + flds[i].nameAsInt = int64(nameAsInt) + if nameAsInt >= 0 { + encodeHead(e, byte(cborTypePositiveInt), uint64(nameAsInt)) + } else { + n := nameAsInt*(-1) - 1 + encodeHead(e, byte(cborTypeNegativeInt), uint64(n)) + } + flds[i].cborName = make([]byte, e.Len()) + copy(flds[i].cborName, e.Bytes()) + e.Reset() + + hasKeyAsInt = true + } else { + encodeHead(e, byte(cborTypeTextString), uint64(len(flds[i].name))) + flds[i].cborName = make([]byte, e.Len()+len(flds[i].name)) + n := copy(flds[i].cborName, e.Bytes()) + copy(flds[i].cborName[n:], flds[i].name) + e.Reset() + + // If cborName contains a text string, then cborNameByteString contains a + // string that has the byte string major type but is otherwise identical to + // cborName. + flds[i].cborNameByteString = make([]byte, len(flds[i].cborName)) + copy(flds[i].cborNameByteString, flds[i].cborName) + // Reset encoded CBOR type to byte string, preserving the "additional + // information" bits: + flds[i].cborNameByteString[0] = byte(cborTypeByteString) | + getAdditionalInformation(flds[i].cborNameByteString[0]) + + hasKeyAsStr = true + } + + // Check if field can be omitted when empty + if flds[i].omitEmpty { + omitEmptyIdx = append(omitEmptyIdx, i) + } + } + putEncodeBuffer(e) + + if err != nil { + structType := &encodingStructType{err: err} + encodingStructTypeCache.Store(t, structType) + return structType, structType.err + } + + // Sort fields by canonical order + bytewiseFields := make(fields, len(flds)) + copy(bytewiseFields, flds) + sort.Sort(&bytewiseFieldSorter{bytewiseFields}) + + lengthFirstFields := bytewiseFields + if hasKeyAsInt && hasKeyAsStr { + lengthFirstFields = make(fields, len(flds)) + copy(lengthFirstFields, flds) + sort.Sort(&lengthFirstFieldSorter{lengthFirstFields}) + } + + structType := &encodingStructType{ + fields: flds, + bytewiseFields: bytewiseFields, + lengthFirstFields: lengthFirstFields, + omitEmptyFieldsIdx: omitEmptyIdx, + } + + encodingStructTypeCache.Store(t, structType) + return structType, structType.err +} + +func getEncodingStructToArrayType(t reflect.Type, flds fields) (*encodingStructType, error) { + for i := 0; i < len(flds); i++ { + // Get field's encodeFunc + flds[i].ef, flds[i].ief, flds[i].izf = getEncodeFunc(flds[i].typ) + if flds[i].ef == nil { + structType := &encodingStructType{err: &UnsupportedTypeError{t}} + encodingStructTypeCache.Store(t, structType) + return structType, structType.err + } + } + + structType := &encodingStructType{ + fields: flds, + toArray: true, + } + encodingStructTypeCache.Store(t, structType) + return structType, structType.err +} + +func getEncodeFunc(t reflect.Type) (encodeFunc, isEmptyFunc, isZeroFunc) { + if v, _ := encodeFuncCache.Load(t); v != nil { + fs := v.(encodeFuncs) + return fs.ef, fs.ief, fs.izf + } + ef, ief, izf := getEncodeFuncInternal(t) + encodeFuncCache.Store(t, encodeFuncs{ef, ief, izf}) + return ef, ief, izf +} + +func getTypeInfo(t reflect.Type) *typeInfo { + if v, _ := typeInfoCache.Load(t); v != nil { + return v.(*typeInfo) + } + tInfo := newTypeInfo(t) + typeInfoCache.Store(t, tInfo) + return tInfo +} + +func hasToArrayOption(tag string) bool { + s := ",toarray" + idx := strings.Index(tag, s) + return idx >= 0 && (len(tag) == idx+len(s) || tag[idx+len(s)] == ',') +} diff --git a/vendor/github.com/fxamacker/cbor/v2/common.go b/vendor/github.com/fxamacker/cbor/v2/common.go new file mode 100644 index 0000000000..9cf33cd209 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/common.go @@ -0,0 +1,191 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "fmt" + "io" + "strconv" +) + +type cborType uint8 + +const ( + cborTypePositiveInt cborType = 0x00 + cborTypeNegativeInt cborType = 0x20 + cborTypeByteString cborType = 0x40 + cborTypeTextString cborType = 0x60 + cborTypeArray cborType = 0x80 + cborTypeMap cborType = 0xa0 + cborTypeTag cborType = 0xc0 + cborTypePrimitives cborType = 0xe0 +) + +func (t cborType) String() string { + switch t { + case cborTypePositiveInt: + return "positive integer" + case cborTypeNegativeInt: + return "negative integer" + case cborTypeByteString: + return "byte string" + case cborTypeTextString: + return "UTF-8 text string" + case cborTypeArray: + return "array" + case cborTypeMap: + return "map" + case cborTypeTag: + return "tag" + case cborTypePrimitives: + return "primitives" + default: + return "Invalid type " + strconv.Itoa(int(t)) + } +} + +type additionalInformation uint8 + +const ( + maxAdditionalInformationWithoutArgument = 23 + additionalInformationWith1ByteArgument = 24 + additionalInformationWith2ByteArgument = 25 + additionalInformationWith4ByteArgument = 26 + additionalInformationWith8ByteArgument = 27 + + // For major type 7. + additionalInformationAsFalse = 20 + additionalInformationAsTrue = 21 + additionalInformationAsNull = 22 + additionalInformationAsUndefined = 23 + additionalInformationAsFloat16 = 25 + additionalInformationAsFloat32 = 26 + additionalInformationAsFloat64 = 27 + + // For major type 2, 3, 4, 5. + additionalInformationAsIndefiniteLengthFlag = 31 +) + +const ( + maxSimpleValueInAdditionalInformation = 23 + minSimpleValueIn1ByteArgument = 32 +) + +func (ai additionalInformation) isIndefiniteLength() bool { + return ai == additionalInformationAsIndefiniteLengthFlag +} + +const ( + // From RFC 8949 Section 3: + // "The initial byte of each encoded data item contains both information about the major type + // (the high-order 3 bits, described in Section 3.1) and additional information + // (the low-order 5 bits)." + + // typeMask is used to extract major type in initial byte of encoded data item. + typeMask = 0xe0 + + // additionalInformationMask is used to extract additional information in initial byte of encoded data item. + additionalInformationMask = 0x1f +) + +func getType(raw byte) cborType { + return cborType(raw & typeMask) +} + +func getAdditionalInformation(raw byte) byte { + return raw & additionalInformationMask +} + +func isBreakFlag(raw byte) bool { + return raw == cborBreakFlag +} + +func parseInitialByte(b byte) (t cborType, ai byte) { + return getType(b), getAdditionalInformation(b) +} + +const ( + tagNumRFC3339Time = 0 + tagNumEpochTime = 1 + tagNumUnsignedBignum = 2 + tagNumNegativeBignum = 3 + tagNumExpectedLaterEncodingBase64URL = 21 + tagNumExpectedLaterEncodingBase64 = 22 + tagNumExpectedLaterEncodingBase16 = 23 + tagNumSelfDescribedCBOR = 55799 +) + +const ( + cborBreakFlag = byte(0xff) + cborByteStringWithIndefiniteLengthHead = byte(0x5f) + cborTextStringWithIndefiniteLengthHead = byte(0x7f) + cborArrayWithIndefiniteLengthHead = byte(0x9f) + cborMapWithIndefiniteLengthHead = byte(0xbf) +) + +var ( + cborFalse = []byte{0xf4} + cborTrue = []byte{0xf5} + cborNil = []byte{0xf6} + cborNaN = []byte{0xf9, 0x7e, 0x00} + cborPositiveInfinity = []byte{0xf9, 0x7c, 0x00} + cborNegativeInfinity = []byte{0xf9, 0xfc, 0x00} +) + +// validBuiltinTag checks that supported built-in tag numbers are followed by expected content types. +func validBuiltinTag(tagNum uint64, contentHead byte) error { + t := getType(contentHead) + switch tagNum { + case tagNumRFC3339Time: + // Tag content (date/time text string in RFC 3339 format) must be string type. + if t != cborTypeTextString { + return newInadmissibleTagContentTypeError( + tagNumRFC3339Time, + "text string", + t.String()) + } + return nil + + case tagNumEpochTime: + // Tag content (epoch date/time) must be uint, int, or float type. + if t != cborTypePositiveInt && t != cborTypeNegativeInt && (contentHead < 0xf9 || contentHead > 0xfb) { + return newInadmissibleTagContentTypeError( + tagNumEpochTime, + "integer or floating-point number", + t.String()) + } + return nil + + case tagNumUnsignedBignum, tagNumNegativeBignum: + // Tag content (bignum) must be byte type. + if t != cborTypeByteString { + return newInadmissibleTagContentTypeErrorf( + fmt.Sprintf( + "tag number %d or %d must be followed by byte string, got %s", + tagNumUnsignedBignum, + tagNumNegativeBignum, + t.String(), + )) + } + return nil + + case tagNumExpectedLaterEncodingBase64URL, tagNumExpectedLaterEncodingBase64, tagNumExpectedLaterEncodingBase16: + // From RFC 8949 3.4.5.2: + // The data item tagged can be a byte string or any other data item. In the latter + // case, the tag applies to all of the byte string data items contained in the data + // item, except for those contained in a nested data item tagged with an expected + // conversion. + return nil + } + + return nil +} + +// Transcoder is a scheme for transcoding a single CBOR encoded data item to or from a different +// data format. +type Transcoder interface { + // Transcode reads the data item in its source format from a Reader and writes a + // corresponding representation in its destination format to a Writer. + Transcode(dst io.Writer, src io.Reader) error +} diff --git a/vendor/github.com/fxamacker/cbor/v2/decode.go b/vendor/github.com/fxamacker/cbor/v2/decode.go new file mode 100644 index 0000000000..f0bdc3b38d --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/decode.go @@ -0,0 +1,3318 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "bytes" + "encoding" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "io" + "math" + "math/big" + "reflect" + "strconv" + "strings" + "time" + "unicode/utf8" + + "github.com/x448/float16" +) + +// Unmarshal parses the CBOR-encoded data into the value pointed to by v +// using default decoding options. If v is nil, not a pointer, or +// a nil pointer, Unmarshal returns an error. +// +// To unmarshal CBOR into a value implementing the Unmarshaler interface, +// Unmarshal calls that value's UnmarshalCBOR method with a valid +// CBOR value. +// +// To unmarshal CBOR byte string into a value implementing the +// encoding.BinaryUnmarshaler interface, Unmarshal calls that value's +// UnmarshalBinary method with decoded CBOR byte string. +// +// To unmarshal CBOR into a pointer, Unmarshal sets the pointer to nil +// if CBOR data is null (0xf6) or undefined (0xf7). Otherwise, Unmarshal +// unmarshals CBOR into the value pointed to by the pointer. If the +// pointer is nil, Unmarshal creates a new value for it to point to. +// +// To unmarshal CBOR into an empty interface value, Unmarshal uses the +// following rules: +// +// CBOR booleans decode to bool. +// CBOR positive integers decode to uint64. +// CBOR negative integers decode to int64 (big.Int if value overflows). +// CBOR floating points decode to float64. +// CBOR byte strings decode to []byte. +// CBOR text strings decode to string. +// CBOR arrays decode to []interface{}. +// CBOR maps decode to map[interface{}]interface{}. +// CBOR null and undefined values decode to nil. +// CBOR times (tag 0 and 1) decode to time.Time. +// CBOR bignums (tag 2 and 3) decode to big.Int. +// CBOR tags with an unrecognized number decode to cbor.Tag +// +// To unmarshal a CBOR array into a slice, Unmarshal allocates a new slice +// if the CBOR array is empty or slice capacity is less than CBOR array length. +// Otherwise Unmarshal overwrites existing elements, and sets slice length +// to CBOR array length. +// +// To unmarshal a CBOR array into a Go array, Unmarshal decodes CBOR array +// elements into Go array elements. If the Go array is smaller than the +// CBOR array, the extra CBOR array elements are discarded. If the CBOR +// array is smaller than the Go array, the extra Go array elements are +// set to zero values. +// +// To unmarshal a CBOR array into a struct, struct must have a special field "_" +// with struct tag `cbor:",toarray"`. Go array elements are decoded into struct +// fields. Any "omitempty" struct field tag option is ignored in this case. +// +// To unmarshal a CBOR map into a map, Unmarshal allocates a new map only if the +// map is nil. Otherwise Unmarshal reuses the existing map and keeps existing +// entries. Unmarshal stores key-value pairs from the CBOR map into Go map. +// See DecOptions.DupMapKey to enable duplicate map key detection. +// +// To unmarshal a CBOR map into a struct, Unmarshal matches CBOR map keys to the +// keys in the following priority: +// +// 1. "cbor" key in struct field tag, +// 2. "json" key in struct field tag, +// 3. struct field name. +// +// Unmarshal tries an exact match for field name, then a case-insensitive match. +// Map key-value pairs without corresponding struct fields are ignored. See +// DecOptions.ExtraReturnErrors to return error at unknown field. +// +// To unmarshal a CBOR text string into a time.Time value, Unmarshal parses text +// string formatted in RFC3339. To unmarshal a CBOR integer/float into a +// time.Time value, Unmarshal creates an unix time with integer/float as seconds +// and fractional seconds since January 1, 1970 UTC. As a special case, Infinite +// and NaN float values decode to time.Time's zero value. +// +// To unmarshal CBOR null (0xf6) and undefined (0xf7) values into a +// slice/map/pointer, Unmarshal sets Go value to nil. Because null is often +// used to mean "not present", unmarshaling CBOR null and undefined value +// into any other Go type has no effect and returns no error. +// +// Unmarshal supports CBOR tag 55799 (self-describe CBOR), tag 0 and 1 (time), +// and tag 2 and 3 (bignum). +// +// Unmarshal returns ExtraneousDataError error (without decoding into v) +// if there are any remaining bytes following the first valid CBOR data item. +// See UnmarshalFirst, if you want to unmarshal only the first +// CBOR data item without ExtraneousDataError caused by remaining bytes. +func Unmarshal(data []byte, v any) error { + return defaultDecMode.Unmarshal(data, v) +} + +// UnmarshalFirst parses the first CBOR data item into the value pointed to by v +// using default decoding options. Any remaining bytes are returned in rest. +// +// If v is nil, not a pointer, or a nil pointer, UnmarshalFirst returns an error. +// +// See the documentation for Unmarshal for details. +func UnmarshalFirst(data []byte, v any) (rest []byte, err error) { + return defaultDecMode.UnmarshalFirst(data, v) +} + +// Valid checks whether data is a well-formed encoded CBOR data item and +// that it complies with default restrictions such as MaxNestedLevels, +// MaxArrayElements, MaxMapPairs, etc. +// +// If there are any remaining bytes after the CBOR data item, +// an ExtraneousDataError is returned. +// +// WARNING: Valid doesn't check if encoded CBOR data item is valid (i.e. validity) +// and RFC 8949 distinctly defines what is "Valid" and what is "Well-formed". +// +// Deprecated: Valid is kept for compatibility and should not be used. +// Use Wellformed instead because it has a more appropriate name. +func Valid(data []byte) error { + return defaultDecMode.Valid(data) +} + +// Wellformed checks whether data is a well-formed encoded CBOR data item and +// that it complies with default restrictions such as MaxNestedLevels, +// MaxArrayElements, MaxMapPairs, etc. +// +// If there are any remaining bytes after the CBOR data item, +// an ExtraneousDataError is returned. +func Wellformed(data []byte) error { + return defaultDecMode.Wellformed(data) +} + +// Unmarshaler is the interface implemented by types that wish to unmarshal +// CBOR data themselves. The input is a valid CBOR value. UnmarshalCBOR +// must copy the CBOR data if it needs to use it after returning. +type Unmarshaler interface { + UnmarshalCBOR([]byte) error +} + +type unmarshaler interface { + unmarshalCBOR([]byte) error +} + +// InvalidUnmarshalError describes an invalid argument passed to Unmarshal. +type InvalidUnmarshalError struct { + s string +} + +func (e *InvalidUnmarshalError) Error() string { + return e.s +} + +// UnmarshalTypeError describes a CBOR value that can't be decoded to a Go type. +type UnmarshalTypeError struct { + CBORType string // type of CBOR value + GoType string // type of Go value it could not be decoded into + StructFieldName string // name of the struct field holding the Go value (optional) + errorMsg string // additional error message (optional) +} + +func (e *UnmarshalTypeError) Error() string { + var s string + if e.StructFieldName != "" { + s = "cbor: cannot unmarshal " + e.CBORType + " into Go struct field " + e.StructFieldName + " of type " + e.GoType + } else { + s = "cbor: cannot unmarshal " + e.CBORType + " into Go value of type " + e.GoType + } + if e.errorMsg != "" { + s += " (" + e.errorMsg + ")" + } + return s +} + +// InvalidMapKeyTypeError describes invalid Go map key type when decoding CBOR map. +// For example, Go doesn't allow slice as map key. +type InvalidMapKeyTypeError struct { + GoType string +} + +func (e *InvalidMapKeyTypeError) Error() string { + return "cbor: invalid map key type: " + e.GoType +} + +// DupMapKeyError describes detected duplicate map key in CBOR map. +type DupMapKeyError struct { + Key any + Index int +} + +func (e *DupMapKeyError) Error() string { + return fmt.Sprintf("cbor: found duplicate map key %#v at map element index %d", e.Key, e.Index) +} + +// UnknownFieldError describes detected unknown field in CBOR map when decoding to Go struct. +type UnknownFieldError struct { + Index int +} + +func (e *UnknownFieldError) Error() string { + return fmt.Sprintf("cbor: found unknown field at map element index %d", e.Index) +} + +// UnacceptableDataItemError is returned when unmarshaling a CBOR input that contains a data item +// that is not acceptable to a specific CBOR-based application protocol ("invalid or unexpected" as +// described in RFC 8949 Section 5 Paragraph 3). +type UnacceptableDataItemError struct { + CBORType string + Message string +} + +func (e UnacceptableDataItemError) Error() string { + return fmt.Sprintf("cbor: data item of cbor type %s is not accepted by protocol: %s", e.CBORType, e.Message) +} + +// ByteStringExpectedFormatError is returned when unmarshaling CBOR byte string fails when +// using non-default ByteStringExpectedFormat decoding option that makes decoder expect +// a specified format such as base64, hex, etc. +type ByteStringExpectedFormatError struct { + expectedFormatOption ByteStringExpectedFormatMode + err error +} + +func newByteStringExpectedFormatError(expectedFormatOption ByteStringExpectedFormatMode, err error) *ByteStringExpectedFormatError { + return &ByteStringExpectedFormatError{expectedFormatOption, err} +} + +func (e *ByteStringExpectedFormatError) Error() string { + switch e.expectedFormatOption { + case ByteStringExpectedBase64URL: + return fmt.Sprintf("cbor: failed to decode base64url from byte string: %s", e.err) + + case ByteStringExpectedBase64: + return fmt.Sprintf("cbor: failed to decode base64 from byte string: %s", e.err) + + case ByteStringExpectedBase16: + return fmt.Sprintf("cbor: failed to decode hex from byte string: %s", e.err) + + default: + return fmt.Sprintf("cbor: failed to decode byte string in expected format %d: %s", e.expectedFormatOption, e.err) + } +} + +func (e *ByteStringExpectedFormatError) Unwrap() error { + return e.err +} + +// InadmissibleTagContentTypeError is returned when unmarshaling built-in CBOR tags +// fails because of inadmissible type for tag content. Currently, the built-in +// CBOR tags in this codec are tags 0-3 and 21-23. +// See "Tag validity" in RFC 8949 Section 5.3.2. +type InadmissibleTagContentTypeError struct { + s string + tagNum int + expectedTagContentType string + gotTagContentType string +} + +func newInadmissibleTagContentTypeError( + tagNum int, + expectedTagContentType string, + gotTagContentType string, +) *InadmissibleTagContentTypeError { + return &InadmissibleTagContentTypeError{ + tagNum: tagNum, + expectedTagContentType: expectedTagContentType, + gotTagContentType: gotTagContentType, + } +} + +func newInadmissibleTagContentTypeErrorf(s string) *InadmissibleTagContentTypeError { + return &InadmissibleTagContentTypeError{s: "cbor: " + s} //nolint:goconst // ignore "cbor" +} + +func (e *InadmissibleTagContentTypeError) Error() string { + if e.s == "" { + return fmt.Sprintf( + "cbor: tag number %d must be followed by %s, got %s", + e.tagNum, + e.expectedTagContentType, + e.gotTagContentType, + ) + } + return e.s +} + +// DupMapKeyMode specifies how to enforce duplicate map key. Two map keys are considered duplicates if: +// 1. When decoding into a struct, both keys match the same struct field. The keys are also +// considered duplicates if neither matches any field and decoding to interface{} would produce +// equal (==) values for both keys. +// 2. When decoding into a map, both keys are equal (==) when decoded into values of the +// destination map's key type. +type DupMapKeyMode int + +const ( + // DupMapKeyQuiet doesn't enforce duplicate map key. Decoder quietly (no error) + // uses faster of "keep first" or "keep last" depending on Go data type and other factors. + DupMapKeyQuiet DupMapKeyMode = iota + + // DupMapKeyEnforcedAPF enforces detection and rejection of duplicate map keys. + // APF means "Allow Partial Fill" and the destination map or struct can be partially filled. + // If a duplicate map key is detected, DupMapKeyError is returned without further decoding + // of the map. It's the caller's responsibility to respond to DupMapKeyError by + // discarding the partially filled result if their protocol requires it. + // WARNING: using DupMapKeyEnforcedAPF will decrease performance and increase memory use. + DupMapKeyEnforcedAPF + + maxDupMapKeyMode +) + +func (dmkm DupMapKeyMode) valid() bool { + return dmkm >= 0 && dmkm < maxDupMapKeyMode +} + +// IndefLengthMode specifies whether to allow indefinite length items. +type IndefLengthMode int + +const ( + // IndefLengthAllowed allows indefinite length items. + IndefLengthAllowed IndefLengthMode = iota + + // IndefLengthForbidden disallows indefinite length items. + IndefLengthForbidden + + maxIndefLengthMode +) + +func (m IndefLengthMode) valid() bool { + return m >= 0 && m < maxIndefLengthMode +} + +// TagsMode specifies whether to allow CBOR tags. +type TagsMode int + +const ( + // TagsAllowed allows CBOR tags. + TagsAllowed TagsMode = iota + + // TagsForbidden disallows CBOR tags. + TagsForbidden + + maxTagsMode +) + +func (tm TagsMode) valid() bool { + return tm >= 0 && tm < maxTagsMode +} + +// IntDecMode specifies which Go type (int64, uint64, or big.Int) should +// be used when decoding CBOR integers (major type 0 and 1) to Go interface{}. +type IntDecMode int + +const ( + // IntDecConvertNone affects how CBOR integers (major type 0 and 1) decode to Go interface{}. + // It decodes CBOR unsigned integer (major type 0) to: + // - uint64 + // It decodes CBOR negative integer (major type 1) to: + // - int64 if value fits + // - big.Int or *big.Int (see BigIntDecMode) if value doesn't fit into int64 + IntDecConvertNone IntDecMode = iota + + // IntDecConvertSigned affects how CBOR integers (major type 0 and 1) decode to Go interface{}. + // It decodes CBOR integers (major type 0 and 1) to: + // - int64 if value fits + // - big.Int or *big.Int (see BigIntDecMode) if value < math.MinInt64 + // - return UnmarshalTypeError if value > math.MaxInt64 + // Deprecated: IntDecConvertSigned should not be used. + // Please use other options, such as IntDecConvertSignedOrError, IntDecConvertSignedOrBigInt, IntDecConvertNone. + IntDecConvertSigned + + // IntDecConvertSignedOrFail affects how CBOR integers (major type 0 and 1) decode to Go interface{}. + // It decodes CBOR integers (major type 0 and 1) to: + // - int64 if value fits + // - return UnmarshalTypeError if value doesn't fit into int64 + IntDecConvertSignedOrFail + + // IntDecConvertSignedOrBigInt affects how CBOR integers (major type 0 and 1) decode to Go interface{}. + // It makes CBOR integers (major type 0 and 1) decode to: + // - int64 if value fits + // - big.Int or *big.Int (see BigIntDecMode) if value doesn't fit into int64 + IntDecConvertSignedOrBigInt + + maxIntDec +) + +func (idm IntDecMode) valid() bool { + return idm >= 0 && idm < maxIntDec +} + +// MapKeyByteStringMode specifies how to decode CBOR byte string (major type 2) +// as Go map key when decoding CBOR map key into an empty Go interface value. +// Specifically, this option applies when decoding CBOR map into +// - Go empty interface, or +// - Go map with empty interface as key type. +// The CBOR map key types handled by this option are +// - byte string +// - tagged byte string +// - nested tagged byte string +type MapKeyByteStringMode int + +const ( + // MapKeyByteStringAllowed allows CBOR byte string to be decoded as Go map key. + // Since Go doesn't allow []byte as map key, CBOR byte string is decoded to + // ByteString which has underlying string type. + // This is the default setting. + MapKeyByteStringAllowed MapKeyByteStringMode = iota + + // MapKeyByteStringForbidden forbids CBOR byte string being decoded as Go map key. + // Attempting to decode CBOR byte string as map key into empty interface value + // returns a decoding error. + MapKeyByteStringForbidden + + maxMapKeyByteStringMode +) + +func (mkbsm MapKeyByteStringMode) valid() bool { + return mkbsm >= 0 && mkbsm < maxMapKeyByteStringMode +} + +// ExtraDecErrorCond specifies extra conditions that should be treated as errors. +type ExtraDecErrorCond uint + +// ExtraDecErrorNone indicates no extra error condition. +const ExtraDecErrorNone ExtraDecErrorCond = 0 + +const ( + // ExtraDecErrorUnknownField indicates error condition when destination + // Go struct doesn't have a field matching a CBOR map key. + ExtraDecErrorUnknownField ExtraDecErrorCond = 1 << iota + + maxExtraDecError +) + +func (ec ExtraDecErrorCond) valid() bool { + return ec < maxExtraDecError +} + +// UTF8Mode option specifies if decoder should +// decode CBOR Text containing invalid UTF-8 string. +type UTF8Mode int + +const ( + // UTF8RejectInvalid rejects CBOR Text containing + // invalid UTF-8 string. + UTF8RejectInvalid UTF8Mode = iota + + // UTF8DecodeInvalid allows decoding CBOR Text containing + // invalid UTF-8 string. + UTF8DecodeInvalid + + maxUTF8Mode +) + +func (um UTF8Mode) valid() bool { + return um >= 0 && um < maxUTF8Mode +} + +// FieldNameMatchingMode specifies how string keys in CBOR maps are matched to Go struct field names. +type FieldNameMatchingMode int + +const ( + // FieldNameMatchingPreferCaseSensitive prefers to decode map items into struct fields whose names (or tag + // names) exactly match the item's key. If there is no such field, a map item will be decoded into a field whose + // name is a case-insensitive match for the item's key. + FieldNameMatchingPreferCaseSensitive FieldNameMatchingMode = iota + + // FieldNameMatchingCaseSensitive decodes map items only into a struct field whose name (or tag name) is an + // exact match for the item's key. + FieldNameMatchingCaseSensitive + + maxFieldNameMatchingMode +) + +func (fnmm FieldNameMatchingMode) valid() bool { + return fnmm >= 0 && fnmm < maxFieldNameMatchingMode +} + +// BigIntDecMode specifies how to decode CBOR bignum to Go interface{}. +type BigIntDecMode int + +const ( + // BigIntDecodeValue makes CBOR bignum decode to big.Int (instead of *big.Int) + // when unmarshaling into a Go interface{}. + BigIntDecodeValue BigIntDecMode = iota + + // BigIntDecodePointer makes CBOR bignum decode to *big.Int when + // unmarshaling into a Go interface{}. + BigIntDecodePointer + + maxBigIntDecMode +) + +func (bidm BigIntDecMode) valid() bool { + return bidm >= 0 && bidm < maxBigIntDecMode +} + +// ByteStringToStringMode specifies the behavior when decoding a CBOR byte string into a Go string. +type ByteStringToStringMode int + +const ( + // ByteStringToStringForbidden generates an error on an attempt to decode a CBOR byte string into a Go string. + ByteStringToStringForbidden ByteStringToStringMode = iota + + // ByteStringToStringAllowed permits decoding a CBOR byte string into a Go string. + ByteStringToStringAllowed + + // ByteStringToStringAllowedWithExpectedLaterEncoding permits decoding a CBOR byte string + // into a Go string. Also, if the byte string is enclosed (directly or indirectly) by one of + // the "expected later encoding" tags (numbers 21 through 23), the destination string will + // be populated by applying the designated text encoding to the contents of the input byte + // string. + ByteStringToStringAllowedWithExpectedLaterEncoding + + maxByteStringToStringMode +) + +func (bstsm ByteStringToStringMode) valid() bool { + return bstsm >= 0 && bstsm < maxByteStringToStringMode +} + +// FieldNameByteStringMode specifies the behavior when decoding a CBOR byte string map key as a Go struct field name. +type FieldNameByteStringMode int + +const ( + // FieldNameByteStringForbidden generates an error on an attempt to decode a CBOR byte string map key as a Go struct field name. + FieldNameByteStringForbidden FieldNameByteStringMode = iota + + // FieldNameByteStringAllowed permits CBOR byte string map keys to be recognized as Go struct field names. + FieldNameByteStringAllowed + + maxFieldNameByteStringMode +) + +func (fnbsm FieldNameByteStringMode) valid() bool { + return fnbsm >= 0 && fnbsm < maxFieldNameByteStringMode +} + +// UnrecognizedTagToAnyMode specifies how to decode unrecognized CBOR tag into an empty interface (any). +// Currently, recognized CBOR tag numbers are 0, 1, 2, 3, or registered by TagSet. +type UnrecognizedTagToAnyMode int + +const ( + // UnrecognizedTagNumAndContentToAny decodes CBOR tag number and tag content to cbor.Tag + // when decoding unrecognized CBOR tag into an empty interface. + UnrecognizedTagNumAndContentToAny UnrecognizedTagToAnyMode = iota + + // UnrecognizedTagContentToAny decodes only CBOR tag content (into its default type) + // when decoding unrecognized CBOR tag into an empty interface. + UnrecognizedTagContentToAny + + maxUnrecognizedTagToAny +) + +func (uttam UnrecognizedTagToAnyMode) valid() bool { + return uttam >= 0 && uttam < maxUnrecognizedTagToAny +} + +// TimeTagToAnyMode specifies how to decode CBOR tag 0 and 1 into an empty interface (any). +// Based on the specified mode, Unmarshal can return a time.Time value or a time string in a specific format. +type TimeTagToAnyMode int + +const ( + // TimeTagToTime decodes CBOR tag 0 and 1 into a time.Time value + // when decoding tag 0 or 1 into an empty interface. + TimeTagToTime TimeTagToAnyMode = iota + + // TimeTagToRFC3339 decodes CBOR tag 0 and 1 into a time string in RFC3339 format + // when decoding tag 0 or 1 into an empty interface. + TimeTagToRFC3339 + + // TimeTagToRFC3339Nano decodes CBOR tag 0 and 1 into a time string in RFC3339Nano format + // when decoding tag 0 or 1 into an empty interface. + TimeTagToRFC3339Nano + + maxTimeTagToAnyMode +) + +func (tttam TimeTagToAnyMode) valid() bool { + return tttam >= 0 && tttam < maxTimeTagToAnyMode +} + +// SimpleValueRegistry is a registry of unmarshaling behaviors for each possible CBOR simple value +// number (0...23 and 32...255). +type SimpleValueRegistry struct { + rejected [256]bool +} + +// WithRejectedSimpleValue registers the given simple value as rejected. If the simple value is +// encountered in a CBOR input during unmarshaling, an UnacceptableDataItemError is returned. +func WithRejectedSimpleValue(sv SimpleValue) func(*SimpleValueRegistry) error { + return func(r *SimpleValueRegistry) error { + if sv >= 24 && sv <= 31 { + return fmt.Errorf("cbor: cannot set analog for reserved simple value %d", sv) + } + r.rejected[sv] = true + return nil + } +} + +// Creates a new SimpleValueRegistry. The registry state is initialized by executing the provided +// functions in order against a registry that is pre-populated with the defaults for all well-formed +// simple value numbers. +func NewSimpleValueRegistryFromDefaults(fns ...func(*SimpleValueRegistry) error) (*SimpleValueRegistry, error) { + var r SimpleValueRegistry + for _, fn := range fns { + if err := fn(&r); err != nil { + return nil, err + } + } + return &r, nil +} + +// NaNMode specifies how to decode floating-point values (major type 7, additional information 25 +// through 27) representing NaN (not-a-number). +type NaNMode int + +const ( + // NaNDecodeAllowed will decode NaN values to Go float32 or float64. + NaNDecodeAllowed NaNMode = iota + + // NaNDecodeForbidden will return an UnacceptableDataItemError on an attempt to decode a NaN value. + NaNDecodeForbidden + + maxNaNDecode +) + +func (ndm NaNMode) valid() bool { + return ndm >= 0 && ndm < maxNaNDecode +} + +// InfMode specifies how to decode floating-point values (major type 7, additional information 25 +// through 27) representing positive or negative infinity. +type InfMode int + +const ( + // InfDecodeAllowed will decode infinite values to Go float32 or float64. + InfDecodeAllowed InfMode = iota + + // InfDecodeForbidden will return an UnacceptableDataItemError on an attempt to decode an + // infinite value. + InfDecodeForbidden + + maxInfDecode +) + +func (idm InfMode) valid() bool { + return idm >= 0 && idm < maxInfDecode +} + +// ByteStringToTimeMode specifies the behavior when decoding a CBOR byte string into a Go time.Time. +type ByteStringToTimeMode int + +const ( + // ByteStringToTimeForbidden generates an error on an attempt to decode a CBOR byte string into a Go time.Time. + ByteStringToTimeForbidden ByteStringToTimeMode = iota + + // ByteStringToTimeAllowed permits decoding a CBOR byte string into a Go time.Time. + ByteStringToTimeAllowed + + maxByteStringToTimeMode +) + +func (bttm ByteStringToTimeMode) valid() bool { + return bttm >= 0 && bttm < maxByteStringToTimeMode +} + +// ByteStringExpectedFormatMode specifies how to decode CBOR byte string into Go byte slice +// when the byte string is NOT enclosed in CBOR tag 21, 22, or 23. An error is returned if +// the CBOR byte string does not contain the expected format (e.g. base64) specified. +// For tags 21-23, see "Expected Later Encoding for CBOR-to-JSON Converters" +// in RFC 8949 Section 3.4.5.2. +type ByteStringExpectedFormatMode int + +const ( + // ByteStringExpectedFormatNone copies the unmodified CBOR byte string into Go byte slice + // if the byte string is not tagged by CBOR tag 21-23. + ByteStringExpectedFormatNone ByteStringExpectedFormatMode = iota + + // ByteStringExpectedBase64URL expects CBOR byte strings to contain base64url-encoded bytes + // if the byte string is not tagged by CBOR tag 21-23. The decoder will attempt to decode + // the base64url-encoded bytes into Go slice. + ByteStringExpectedBase64URL + + // ByteStringExpectedBase64 expects CBOR byte strings to contain base64-encoded bytes + // if the byte string is not tagged by CBOR tag 21-23. The decoder will attempt to decode + // the base64-encoded bytes into Go slice. + ByteStringExpectedBase64 + + // ByteStringExpectedBase16 expects CBOR byte strings to contain base16-encoded bytes + // if the byte string is not tagged by CBOR tag 21-23. The decoder will attempt to decode + // the base16-encoded bytes into Go slice. + ByteStringExpectedBase16 + + maxByteStringExpectedFormatMode +) + +func (bsefm ByteStringExpectedFormatMode) valid() bool { + return bsefm >= 0 && bsefm < maxByteStringExpectedFormatMode +} + +// BignumTagMode specifies whether or not the "bignum" tags 2 and 3 (RFC 8949 Section 3.4.3) can be +// decoded. +type BignumTagMode int + +const ( + // BignumTagAllowed allows bignum tags to be decoded. + BignumTagAllowed BignumTagMode = iota + + // BignumTagForbidden produces an UnacceptableDataItemError during Unmarshal if a bignum tag + // is encountered in the input. + BignumTagForbidden + + maxBignumTag +) + +func (btm BignumTagMode) valid() bool { + return btm >= 0 && btm < maxBignumTag +} + +// BinaryUnmarshalerMode specifies how to decode into types that implement +// encoding.BinaryUnmarshaler. +type BinaryUnmarshalerMode int + +const ( + // BinaryUnmarshalerByteString will invoke UnmarshalBinary on the contents of a CBOR byte + // string when decoding into a value that implements BinaryUnmarshaler. + BinaryUnmarshalerByteString BinaryUnmarshalerMode = iota + + // BinaryUnmarshalerNone does not recognize BinaryUnmarshaler implementations during decode. + BinaryUnmarshalerNone + + maxBinaryUnmarshalerMode +) + +func (bum BinaryUnmarshalerMode) valid() bool { + return bum >= 0 && bum < maxBinaryUnmarshalerMode +} + +// TextUnmarshalerMode specifies how to decode into types that implement +// encoding.TextUnmarshaler. +type TextUnmarshalerMode int + +const ( + // TextUnmarshalerNone does not recognize TextUnmarshaler implementations during decode. + TextUnmarshalerNone TextUnmarshalerMode = iota + + // TextUnmarshalerTextString will invoke UnmarshalText on the contents of a CBOR text + // string when decoding into a value that implements TextUnmarshaler. + TextUnmarshalerTextString + + maxTextUnmarshalerMode +) + +func (tum TextUnmarshalerMode) valid() bool { + return tum >= 0 && tum < maxTextUnmarshalerMode +} + +// DecOptions specifies decoding options. +type DecOptions struct { + // DupMapKey specifies whether to enforce duplicate map key. + DupMapKey DupMapKeyMode + + // TimeTag specifies whether or not untagged data items, or tags other + // than tag 0 and tag 1, can be decoded to time.Time. If tag 0 or tag 1 + // appears in an input, the type of its content is always validated as + // specified in RFC 8949. That behavior is not controlled by this + // option. The behavior of the supported modes are: + // + // DecTagIgnored (default): Untagged text strings and text strings + // enclosed in tags other than 0 and 1 are decoded as though enclosed + // in tag 0. Untagged unsigned integers, negative integers, and + // floating-point numbers (or those enclosed in tags other than 0 and + // 1) are decoded as though enclosed in tag 1. Decoding a tag other + // than 0 or 1 enclosing simple values null or undefined into a + // time.Time does not modify the destination value. + // + // DecTagOptional: Untagged text strings are decoded as though + // enclosed in tag 0. Untagged unsigned integers, negative integers, + // and floating-point numbers are decoded as though enclosed in tag + // 1. Tags other than 0 and 1 will produce an error on attempts to + // decode them into a time.Time. + // + // DecTagRequired: Only tags 0 and 1 can be decoded to time.Time. Any + // other input will produce an error. + TimeTag DecTagMode + + // MaxNestedLevels specifies the max nested levels allowed for any combination of CBOR array, maps, and tags. + // Default is 32 levels and it can be set to [4, 65535]. Note that higher maximum levels of nesting can + // require larger amounts of stack to deserialize. Don't increase this higher than you require. + MaxNestedLevels int + + // MaxArrayElements specifies the max number of elements for CBOR arrays. + // Default is 128*1024=131072 and it can be set to [16, 2147483647] + MaxArrayElements int + + // MaxMapPairs specifies the max number of key-value pairs for CBOR maps. + // Default is 128*1024=131072 and it can be set to [16, 2147483647] + MaxMapPairs int + + // IndefLength specifies whether to allow indefinite length CBOR items. + IndefLength IndefLengthMode + + // TagsMd specifies whether to allow CBOR tags (major type 6). + TagsMd TagsMode + + // IntDec specifies which Go integer type (int64, uint64, or [big.Int]) to use + // when decoding CBOR int (major type 0 and 1) to Go interface{}. + IntDec IntDecMode + + // MapKeyByteString specifies how to decode CBOR byte string as map key + // when decoding CBOR map with byte string key into an empty interface value. + // By default, an error is returned when attempting to decode CBOR byte string + // as map key because Go doesn't allow []byte as map key. + MapKeyByteString MapKeyByteStringMode + + // ExtraReturnErrors specifies extra conditions that should be treated as errors. + ExtraReturnErrors ExtraDecErrorCond + + // DefaultMapType specifies Go map type to create and decode to + // when unmarshaling CBOR into an empty interface value. + // By default, unmarshal uses map[interface{}]interface{}. + DefaultMapType reflect.Type + + // UTF8 specifies if decoder should decode CBOR Text containing invalid UTF-8. + // By default, unmarshal rejects CBOR text containing invalid UTF-8. + UTF8 UTF8Mode + + // FieldNameMatching specifies how string keys in CBOR maps are matched to Go struct field names. + FieldNameMatching FieldNameMatchingMode + + // BigIntDec specifies how to decode CBOR bignum to Go interface{}. + BigIntDec BigIntDecMode + + // DefaultByteStringType is the Go type that should be produced when decoding a CBOR byte + // string into an empty interface value. Types to which a []byte is convertible are valid + // for this option, except for array and pointer-to-array types. If nil, the default is + // []byte. + DefaultByteStringType reflect.Type + + // ByteStringToString specifies the behavior when decoding a CBOR byte string into a Go string. + ByteStringToString ByteStringToStringMode + + // FieldNameByteString specifies the behavior when decoding a CBOR byte string map key as a + // Go struct field name. + FieldNameByteString FieldNameByteStringMode + + // UnrecognizedTagToAny specifies how to decode unrecognized CBOR tag into an empty interface. + // Currently, recognized CBOR tag numbers are 0, 1, 2, 3, or registered by TagSet. + UnrecognizedTagToAny UnrecognizedTagToAnyMode + + // TimeTagToAny specifies how to decode CBOR tag 0 and 1 into an empty interface (any). + // Based on the specified mode, Unmarshal can return a time.Time value or a time string in a specific format. + TimeTagToAny TimeTagToAnyMode + + // SimpleValues is an immutable mapping from each CBOR simple value to a corresponding + // unmarshal behavior. If nil, the simple values false, true, null, and undefined are mapped + // to the Go analog values false, true, nil, and nil, respectively, and all other simple + // values N (except the reserved simple values 24 through 31) are mapped to + // cbor.SimpleValue(N). In other words, all well-formed simple values can be decoded. + // + // Users may provide a custom SimpleValueRegistry constructed via + // NewSimpleValueRegistryFromDefaults. + SimpleValues *SimpleValueRegistry + + // NaN specifies how to decode floating-point values (major type 7, additional information + // 25 through 27) representing NaN (not-a-number). + NaN NaNMode + + // Inf specifies how to decode floating-point values (major type 7, additional information + // 25 through 27) representing positive or negative infinity. + Inf InfMode + + // ByteStringToTime specifies how to decode CBOR byte string into Go time.Time. + ByteStringToTime ByteStringToTimeMode + + // ByteStringExpectedFormat specifies how to decode CBOR byte string into Go byte slice + // when the byte string is NOT enclosed in CBOR tag 21, 22, or 23. An error is returned if + // the CBOR byte string does not contain the expected format (e.g. base64) specified. + // For tags 21-23, see "Expected Later Encoding for CBOR-to-JSON Converters" + // in RFC 8949 Section 3.4.5.2. + ByteStringExpectedFormat ByteStringExpectedFormatMode + + // BignumTag specifies whether or not the "bignum" tags 2 and 3 (RFC 8949 Section 3.4.3) can + // be decoded. Unlike BigIntDec, this option applies to all bignum tags encountered in a + // CBOR input, independent of the type of the destination value of a particular Unmarshal + // operation. + BignumTag BignumTagMode + + // BinaryUnmarshaler specifies how to decode into types that implement + // encoding.BinaryUnmarshaler. + BinaryUnmarshaler BinaryUnmarshalerMode + + // TextUnmarshaler specifies how to decode into types that implement + // encoding.TextUnmarshaler. + TextUnmarshaler TextUnmarshalerMode + + // JSONUnmarshalerTranscoder sets the transcoding scheme used to unmarshal types that + // implement json.Unmarshaler but do not also implement cbor.Unmarshaler. If nil, decoding + // behavior is not influenced by whether or not a type implements json.Unmarshaler. + JSONUnmarshalerTranscoder Transcoder +} + +// DecMode returns DecMode with immutable options and no tags (safe for concurrency). +func (opts DecOptions) DecMode() (DecMode, error) { //nolint:gocritic // ignore hugeParam + return opts.decMode() +} + +// validForTags checks that the provided tag set is compatible with these options and returns a +// non-nil error if and only if the provided tag set is incompatible. +func (opts DecOptions) validForTags(tags TagSet) error { //nolint:gocritic // ignore hugeParam + if opts.TagsMd == TagsForbidden { + return errors.New("cbor: cannot create DecMode with TagSet when TagsMd is TagsForbidden") + } + if tags == nil { + return errors.New("cbor: cannot create DecMode with nil value as TagSet") + } + if opts.ByteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding || + opts.ByteStringExpectedFormat != ByteStringExpectedFormatNone { + for _, tagNum := range []uint64{ + tagNumExpectedLaterEncodingBase64URL, + tagNumExpectedLaterEncodingBase64, + tagNumExpectedLaterEncodingBase16, + } { + if rt := tags.getTypeFromTagNum([]uint64{tagNum}); rt != nil { + return fmt.Errorf("cbor: DecMode with non-default StringExpectedEncoding or ByteSliceExpectedEncoding treats tag %d as built-in and conflicts with the provided TagSet's registration of %v", tagNum, rt) + } + } + + } + return nil +} + +// DecModeWithTags returns DecMode with options and tags that are both immutable (safe for concurrency). +func (opts DecOptions) DecModeWithTags(tags TagSet) (DecMode, error) { //nolint:gocritic // ignore hugeParam + if err := opts.validForTags(tags); err != nil { + return nil, err + } + dm, err := opts.decMode() + if err != nil { + return nil, err + } + + // Copy tags + ts := tagSet(make(map[reflect.Type]*tagItem)) + syncTags := tags.(*syncTagSet) + syncTags.RLock() + for contentType, tag := range syncTags.t { + if tag.opts.DecTag != DecTagIgnored { + ts[contentType] = tag + } + } + syncTags.RUnlock() + + if len(ts) > 0 { + dm.tags = ts + } + + return dm, nil +} + +// DecModeWithSharedTags returns DecMode with immutable options and mutable shared tags (safe for concurrency). +func (opts DecOptions) DecModeWithSharedTags(tags TagSet) (DecMode, error) { //nolint:gocritic // ignore hugeParam + if err := opts.validForTags(tags); err != nil { + return nil, err + } + dm, err := opts.decMode() + if err != nil { + return nil, err + } + dm.tags = tags + return dm, nil +} + +const ( + defaultMaxArrayElements = 131072 + minMaxArrayElements = 16 + maxMaxArrayElements = 2147483647 + + defaultMaxMapPairs = 131072 + minMaxMapPairs = 16 + maxMaxMapPairs = 2147483647 + + defaultMaxNestedLevels = 32 + minMaxNestedLevels = 4 + maxMaxNestedLevels = 65535 +) + +var defaultSimpleValues = func() *SimpleValueRegistry { + registry, err := NewSimpleValueRegistryFromDefaults() + if err != nil { + panic(err) + } + return registry +}() + +//nolint:gocyclo // Each option comes with some manageable boilerplate +func (opts DecOptions) decMode() (*decMode, error) { //nolint:gocritic // ignore hugeParam + if !opts.DupMapKey.valid() { + return nil, errors.New("cbor: invalid DupMapKey " + strconv.Itoa(int(opts.DupMapKey))) + } + + if !opts.TimeTag.valid() { + return nil, errors.New("cbor: invalid TimeTag " + strconv.Itoa(int(opts.TimeTag))) + } + + if !opts.IndefLength.valid() { + return nil, errors.New("cbor: invalid IndefLength " + strconv.Itoa(int(opts.IndefLength))) + } + + if !opts.TagsMd.valid() { + return nil, errors.New("cbor: invalid TagsMd " + strconv.Itoa(int(opts.TagsMd))) + } + + if !opts.IntDec.valid() { + return nil, errors.New("cbor: invalid IntDec " + strconv.Itoa(int(opts.IntDec))) + } + + if !opts.MapKeyByteString.valid() { + return nil, errors.New("cbor: invalid MapKeyByteString " + strconv.Itoa(int(opts.MapKeyByteString))) + } + + if opts.MaxNestedLevels == 0 { + opts.MaxNestedLevels = defaultMaxNestedLevels + } else if opts.MaxNestedLevels < minMaxNestedLevels || opts.MaxNestedLevels > maxMaxNestedLevels { + return nil, errors.New("cbor: invalid MaxNestedLevels " + strconv.Itoa(opts.MaxNestedLevels) + + " (range is [" + strconv.Itoa(minMaxNestedLevels) + ", " + strconv.Itoa(maxMaxNestedLevels) + "])") + } + + if opts.MaxArrayElements == 0 { + opts.MaxArrayElements = defaultMaxArrayElements + } else if opts.MaxArrayElements < minMaxArrayElements || opts.MaxArrayElements > maxMaxArrayElements { + return nil, errors.New("cbor: invalid MaxArrayElements " + strconv.Itoa(opts.MaxArrayElements) + + " (range is [" + strconv.Itoa(minMaxArrayElements) + ", " + strconv.Itoa(maxMaxArrayElements) + "])") + } + + if opts.MaxMapPairs == 0 { + opts.MaxMapPairs = defaultMaxMapPairs + } else if opts.MaxMapPairs < minMaxMapPairs || opts.MaxMapPairs > maxMaxMapPairs { + return nil, errors.New("cbor: invalid MaxMapPairs " + strconv.Itoa(opts.MaxMapPairs) + + " (range is [" + strconv.Itoa(minMaxMapPairs) + ", " + strconv.Itoa(maxMaxMapPairs) + "])") + } + + if !opts.ExtraReturnErrors.valid() { + return nil, errors.New("cbor: invalid ExtraReturnErrors " + strconv.Itoa(int(opts.ExtraReturnErrors))) + } + + if opts.DefaultMapType != nil && opts.DefaultMapType.Kind() != reflect.Map { + return nil, fmt.Errorf("cbor: invalid DefaultMapType %s", opts.DefaultMapType) + } + + if !opts.UTF8.valid() { + return nil, errors.New("cbor: invalid UTF8 " + strconv.Itoa(int(opts.UTF8))) + } + + if !opts.FieldNameMatching.valid() { + return nil, errors.New("cbor: invalid FieldNameMatching " + strconv.Itoa(int(opts.FieldNameMatching))) + } + + if !opts.BigIntDec.valid() { + return nil, errors.New("cbor: invalid BigIntDec " + strconv.Itoa(int(opts.BigIntDec))) + } + + if opts.DefaultByteStringType != nil && + opts.DefaultByteStringType.Kind() != reflect.String && + (opts.DefaultByteStringType.Kind() != reflect.Slice || opts.DefaultByteStringType.Elem().Kind() != reflect.Uint8) { + return nil, fmt.Errorf("cbor: invalid DefaultByteStringType: %s is not of kind string or []uint8", opts.DefaultByteStringType) + } + + if !opts.ByteStringToString.valid() { + return nil, errors.New("cbor: invalid ByteStringToString " + strconv.Itoa(int(opts.ByteStringToString))) + } + + if !opts.FieldNameByteString.valid() { + return nil, errors.New("cbor: invalid FieldNameByteString " + strconv.Itoa(int(opts.FieldNameByteString))) + } + + if !opts.UnrecognizedTagToAny.valid() { + return nil, errors.New("cbor: invalid UnrecognizedTagToAnyMode " + strconv.Itoa(int(opts.UnrecognizedTagToAny))) + } + simpleValues := opts.SimpleValues + if simpleValues == nil { + simpleValues = defaultSimpleValues + } + + if !opts.TimeTagToAny.valid() { + return nil, errors.New("cbor: invalid TimeTagToAny " + strconv.Itoa(int(opts.TimeTagToAny))) + } + + if !opts.NaN.valid() { + return nil, errors.New("cbor: invalid NaNDec " + strconv.Itoa(int(opts.NaN))) + } + + if !opts.Inf.valid() { + return nil, errors.New("cbor: invalid InfDec " + strconv.Itoa(int(opts.Inf))) + } + + if !opts.ByteStringToTime.valid() { + return nil, errors.New("cbor: invalid ByteStringToTime " + strconv.Itoa(int(opts.ByteStringToTime))) + } + + if !opts.ByteStringExpectedFormat.valid() { + return nil, errors.New("cbor: invalid ByteStringExpectedFormat " + strconv.Itoa(int(opts.ByteStringExpectedFormat))) + } + + if !opts.BignumTag.valid() { + return nil, errors.New("cbor: invalid BignumTag " + strconv.Itoa(int(opts.BignumTag))) + } + + if !opts.BinaryUnmarshaler.valid() { + return nil, errors.New("cbor: invalid BinaryUnmarshaler " + strconv.Itoa(int(opts.BinaryUnmarshaler))) + } + + if !opts.TextUnmarshaler.valid() { + return nil, errors.New("cbor: invalid TextUnmarshaler " + strconv.Itoa(int(opts.TextUnmarshaler))) + } + + dm := decMode{ + dupMapKey: opts.DupMapKey, + timeTag: opts.TimeTag, + maxNestedLevels: opts.MaxNestedLevels, + maxArrayElements: opts.MaxArrayElements, + maxMapPairs: opts.MaxMapPairs, + indefLength: opts.IndefLength, + tagsMd: opts.TagsMd, + intDec: opts.IntDec, + mapKeyByteString: opts.MapKeyByteString, + extraReturnErrors: opts.ExtraReturnErrors, + defaultMapType: opts.DefaultMapType, + utf8: opts.UTF8, + fieldNameMatching: opts.FieldNameMatching, + bigIntDec: opts.BigIntDec, + defaultByteStringType: opts.DefaultByteStringType, + byteStringToString: opts.ByteStringToString, + fieldNameByteString: opts.FieldNameByteString, + unrecognizedTagToAny: opts.UnrecognizedTagToAny, + timeTagToAny: opts.TimeTagToAny, + simpleValues: simpleValues, + nanDec: opts.NaN, + infDec: opts.Inf, + byteStringToTime: opts.ByteStringToTime, + byteStringExpectedFormat: opts.ByteStringExpectedFormat, + bignumTag: opts.BignumTag, + binaryUnmarshaler: opts.BinaryUnmarshaler, + textUnmarshaler: opts.TextUnmarshaler, + jsonUnmarshalerTranscoder: opts.JSONUnmarshalerTranscoder, + } + + return &dm, nil +} + +// DecMode is the main interface for CBOR decoding. +type DecMode interface { + // Unmarshal parses the CBOR-encoded data into the value pointed to by v + // using the decoding mode. If v is nil, not a pointer, or a nil pointer, + // Unmarshal returns an error. + // + // See the documentation for Unmarshal for details. + Unmarshal(data []byte, v any) error + + // UnmarshalFirst parses the first CBOR data item into the value pointed to by v + // using the decoding mode. Any remaining bytes are returned in rest. + // + // If v is nil, not a pointer, or a nil pointer, UnmarshalFirst returns an error. + // + // See the documentation for Unmarshal for details. + UnmarshalFirst(data []byte, v any) (rest []byte, err error) + + // Valid checks whether data is a well-formed encoded CBOR data item and + // that it complies with configurable restrictions such as MaxNestedLevels, + // MaxArrayElements, MaxMapPairs, etc. + // + // If there are any remaining bytes after the CBOR data item, + // an ExtraneousDataError is returned. + // + // WARNING: Valid doesn't check if encoded CBOR data item is valid (i.e. validity) + // and RFC 8949 distinctly defines what is "Valid" and what is "Well-formed". + // + // Deprecated: Valid is kept for compatibility and should not be used. + // Use Wellformed instead because it has a more appropriate name. + Valid(data []byte) error + + // Wellformed checks whether data is a well-formed encoded CBOR data item and + // that it complies with configurable restrictions such as MaxNestedLevels, + // MaxArrayElements, MaxMapPairs, etc. + // + // If there are any remaining bytes after the CBOR data item, + // an ExtraneousDataError is returned. + Wellformed(data []byte) error + + // NewDecoder returns a new decoder that reads from r using dm DecMode. + NewDecoder(r io.Reader) *Decoder + + // DecOptions returns user specified options used to create this DecMode. + DecOptions() DecOptions +} + +type decMode struct { + tags tagProvider + dupMapKey DupMapKeyMode + timeTag DecTagMode + maxNestedLevels int + maxArrayElements int + maxMapPairs int + indefLength IndefLengthMode + tagsMd TagsMode + intDec IntDecMode + mapKeyByteString MapKeyByteStringMode + extraReturnErrors ExtraDecErrorCond + defaultMapType reflect.Type + utf8 UTF8Mode + fieldNameMatching FieldNameMatchingMode + bigIntDec BigIntDecMode + defaultByteStringType reflect.Type + byteStringToString ByteStringToStringMode + fieldNameByteString FieldNameByteStringMode + unrecognizedTagToAny UnrecognizedTagToAnyMode + timeTagToAny TimeTagToAnyMode + simpleValues *SimpleValueRegistry + nanDec NaNMode + infDec InfMode + byteStringToTime ByteStringToTimeMode + byteStringExpectedFormat ByteStringExpectedFormatMode + bignumTag BignumTagMode + binaryUnmarshaler BinaryUnmarshalerMode + textUnmarshaler TextUnmarshalerMode + jsonUnmarshalerTranscoder Transcoder +} + +var defaultDecMode, _ = DecOptions{}.decMode() + +// DecOptions returns user specified options used to create this DecMode. +func (dm *decMode) DecOptions() DecOptions { + simpleValues := dm.simpleValues + if simpleValues == defaultSimpleValues { + // Users can't explicitly set this to defaultSimpleValues. It must have been nil in + // the original DecOptions. + simpleValues = nil + } + + return DecOptions{ + DupMapKey: dm.dupMapKey, + TimeTag: dm.timeTag, + MaxNestedLevels: dm.maxNestedLevels, + MaxArrayElements: dm.maxArrayElements, + MaxMapPairs: dm.maxMapPairs, + IndefLength: dm.indefLength, + TagsMd: dm.tagsMd, + IntDec: dm.intDec, + MapKeyByteString: dm.mapKeyByteString, + ExtraReturnErrors: dm.extraReturnErrors, + DefaultMapType: dm.defaultMapType, + UTF8: dm.utf8, + FieldNameMatching: dm.fieldNameMatching, + BigIntDec: dm.bigIntDec, + DefaultByteStringType: dm.defaultByteStringType, + ByteStringToString: dm.byteStringToString, + FieldNameByteString: dm.fieldNameByteString, + UnrecognizedTagToAny: dm.unrecognizedTagToAny, + TimeTagToAny: dm.timeTagToAny, + SimpleValues: simpleValues, + NaN: dm.nanDec, + Inf: dm.infDec, + ByteStringToTime: dm.byteStringToTime, + ByteStringExpectedFormat: dm.byteStringExpectedFormat, + BignumTag: dm.bignumTag, + BinaryUnmarshaler: dm.binaryUnmarshaler, + TextUnmarshaler: dm.textUnmarshaler, + JSONUnmarshalerTranscoder: dm.jsonUnmarshalerTranscoder, + } +} + +// Unmarshal parses the CBOR-encoded data into the value pointed to by v +// using dm decoding mode. If v is nil, not a pointer, or a nil pointer, +// Unmarshal returns an error. +// +// See the documentation for Unmarshal for details. +func (dm *decMode) Unmarshal(data []byte, v any) error { + d := decoder{data: data, dm: dm} + + // Check well-formedness. + off := d.off // Save offset before data validation + err := d.wellformed(false, false) // don't allow any extra data after valid data item. + d.off = off // Restore offset + if err != nil { + return err + } + + return d.value(v) +} + +// UnmarshalFirst parses the first CBOR data item into the value pointed to by v +// using dm decoding mode. Any remaining bytes are returned in rest. +// +// If v is nil, not a pointer, or a nil pointer, UnmarshalFirst returns an error. +// +// See the documentation for Unmarshal for details. +func (dm *decMode) UnmarshalFirst(data []byte, v any) (rest []byte, err error) { + d := decoder{data: data, dm: dm} + + // check well-formedness. + off := d.off // Save offset before data validation + err = d.wellformed(true, false) // allow extra data after well-formed data item + d.off = off // Restore offset + + // If it is well-formed, parse the value. This is structured like this to allow + // better test coverage + if err == nil { + err = d.value(v) + } + + // If either wellformed or value returned an error, do not return rest bytes + if err != nil { + return nil, err + } + + // Return the rest of the data slice (which might be len 0) + return d.data[d.off:], nil +} + +// Valid checks whether data is a well-formed encoded CBOR data item and +// that it complies with configurable restrictions such as MaxNestedLevels, +// MaxArrayElements, MaxMapPairs, etc. +// +// If there are any remaining bytes after the CBOR data item, +// an ExtraneousDataError is returned. +// +// WARNING: Valid doesn't check if encoded CBOR data item is valid (i.e. validity) +// and RFC 8949 distinctly defines what is "Valid" and what is "Well-formed". +// +// Deprecated: Valid is kept for compatibility and should not be used. +// Use Wellformed instead because it has a more appropriate name. +func (dm *decMode) Valid(data []byte) error { + return dm.Wellformed(data) +} + +// Wellformed checks whether data is a well-formed encoded CBOR data item and +// that it complies with configurable restrictions such as MaxNestedLevels, +// MaxArrayElements, MaxMapPairs, etc. +// +// If there are any remaining bytes after the CBOR data item, +// an ExtraneousDataError is returned. +func (dm *decMode) Wellformed(data []byte) error { + d := decoder{data: data, dm: dm} + return d.wellformed(false, false) +} + +// NewDecoder returns a new decoder that reads from r using dm DecMode. +func (dm *decMode) NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r, d: decoder{dm: dm}} +} + +type decoder struct { + data []byte + off int // next read offset in data + dm *decMode + + // expectedLaterEncodingTags stores a stack of encountered "Expected Later Encoding" tags, + // if any. + // + // The "Expected Later Encoding" tags (21 to 23) are valid for any data item. When decoding + // byte strings, the effective encoding comes from the tag nearest to the byte string being + // decoded. For example, the effective encoding of the byte string 21(22(h'41')) would be + // controlled by tag 22,and in the data item 23(h'42', 22([21(h'43')])]) the effective + // encoding of the byte strings h'42' and h'43' would be controlled by tag 23 and 21, + // respectively. + expectedLaterEncodingTags []uint64 +} + +// value decodes CBOR data item into the value pointed to by v. +// If CBOR data item fails to be decoded into v, +// error is returned and offset is moved to the next CBOR data item. +// Precondition: d.data contains at least one well-formed CBOR data item. +func (d *decoder) value(v any) error { + // v can't be nil, non-pointer, or nil pointer value. + if v == nil { + return &InvalidUnmarshalError{"cbor: Unmarshal(nil)"} + } + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Pointer { + return &InvalidUnmarshalError{"cbor: Unmarshal(non-pointer " + rv.Type().String() + ")"} + } else if rv.IsNil() { + return &InvalidUnmarshalError{"cbor: Unmarshal(nil " + rv.Type().String() + ")"} + } + rv = rv.Elem() + return d.parseToValue(rv, getTypeInfo(rv.Type())) +} + +// parseToValue decodes CBOR data to value. It assumes data is well-formed, +// and does not perform bounds checking. +func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo + + // Decode CBOR nil or CBOR undefined to pointer value by setting pointer value to nil. + if d.nextCBORNil() && v.Kind() == reflect.Pointer { + d.skip() + v.SetZero() + return nil + } + + if tInfo.spclType == specialTypeIface { + if !v.IsNil() { + // Use value type + v = v.Elem() + tInfo = getTypeInfo(v.Type()) + } else { //nolint:gocritic + // Create and use registered type if CBOR data is registered tag + if d.dm.tags != nil && d.nextCBORType() == cborTypeTag { + + off := d.off + var tagNums []uint64 + for d.nextCBORType() == cborTypeTag { + _, _, tagNum := d.getHead() + tagNums = append(tagNums, tagNum) + } + d.off = off + + registeredType := d.dm.tags.getTypeFromTagNum(tagNums) + if registeredType != nil { + if registeredType.Implements(tInfo.nonPtrType) || + reflect.PointerTo(registeredType).Implements(tInfo.nonPtrType) { + v.Set(reflect.New(registeredType)) + v = v.Elem() + tInfo = getTypeInfo(registeredType) + } + } + } + } + } + + // Create new value for the pointer v to point to. + // At this point, CBOR value is not nil/undefined if v is a pointer. + for v.Kind() == reflect.Pointer { + if v.IsNil() { + if !v.CanSet() { + d.skip() + return errors.New("cbor: cannot set new value for " + v.Type().String()) + } + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + + // Strip self-described CBOR tag number. + for d.nextCBORType() == cborTypeTag { + off := d.off + _, _, tagNum := d.getHead() + if tagNum != tagNumSelfDescribedCBOR { + d.off = off + break + } + } + + // Check validity of supported built-in tags. + off := d.off + for d.nextCBORType() == cborTypeTag { + _, _, tagNum := d.getHead() + if err := validBuiltinTag(tagNum, d.data[d.off]); err != nil { + d.skip() + return err + } + } + d.off = off + + if tInfo.spclType != specialTypeNone { + switch tInfo.spclType { + case specialTypeEmptyIface: + iv, err := d.parse(false) // Skipped self-described CBOR tag number already. + if iv != nil { + v.Set(reflect.ValueOf(iv)) + } + return err + + case specialTypeTag: + return d.parseToTag(v) + + case specialTypeTime: + if d.nextCBORNil() { + // Decoding CBOR null and undefined to time.Time is no-op. + d.skip() + return nil + } + tm, ok, err := d.parseToTime() + if err != nil { + return err + } + if ok { + v.Set(reflect.ValueOf(tm)) + } + return nil + + case specialTypeUnmarshalerIface: + return d.parseToUnmarshaler(v) + + case specialTypeUnexportedUnmarshalerIface: + return d.parseToUnexportedUnmarshaler(v) + + case specialTypeJSONUnmarshalerIface: + // This special type implies that the type does not also implement + // cbor.Umarshaler. + if d.dm.jsonUnmarshalerTranscoder == nil { + break + } + return d.parseToJSONUnmarshaler(v) + } + } + + // Check registered tag number + if tagItem := d.getRegisteredTagItem(tInfo.nonPtrType); tagItem != nil { + t := d.nextCBORType() + if t != cborTypeTag { + if tagItem.opts.DecTag == DecTagRequired { + d.skip() // Required tag number is absent, skip entire tag + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: tInfo.typ.String(), + errorMsg: "expect CBOR tag value"} + } + } else if err := d.validRegisteredTagNums(tagItem); err != nil { + d.skip() // Skip tag content + return err + } + } + + t := d.nextCBORType() + + switch t { + case cborTypePositiveInt: + _, _, val := d.getHead() + return fillPositiveInt(t, val, v) + + case cborTypeNegativeInt: + _, _, val := d.getHead() + if val > math.MaxInt64 { + // CBOR negative integer overflows int64, use big.Int to store value. + bi := new(big.Int) + bi.SetUint64(val) + bi.Add(bi, big.NewInt(1)) + bi.Neg(bi) + + if tInfo.nonPtrType == typeBigInt { + v.Set(reflect.ValueOf(*bi)) + return nil + } + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: tInfo.nonPtrType.String(), + errorMsg: bi.String() + " overflows Go's int64", + } + } + nValue := int64(-1) ^ int64(val) + return fillNegativeInt(t, nValue, v) + + case cborTypeByteString: + b, copied := d.parseByteString() + b, converted, err := d.applyByteStringTextConversion(b, v.Type()) + if err != nil { + return err + } + copied = copied || converted + return fillByteString(t, b, !copied, v, d.dm.byteStringToString, d.dm.binaryUnmarshaler, d.dm.textUnmarshaler) + + case cborTypeTextString: + b, err := d.parseTextString() + if err != nil { + return err + } + return fillTextString(t, b, v, d.dm.textUnmarshaler) + + case cborTypePrimitives: + _, ai, val := d.getHead() + switch ai { + case additionalInformationAsFloat16: + f := float64(float16.Frombits(uint16(val)).Float32()) + return fillFloat(t, f, v) + + case additionalInformationAsFloat32: + f := float64(math.Float32frombits(uint32(val))) + return fillFloat(t, f, v) + + case additionalInformationAsFloat64: + f := math.Float64frombits(val) + return fillFloat(t, f, v) + + default: // ai <= 24 + if d.dm.simpleValues.rejected[SimpleValue(val)] { + return &UnacceptableDataItemError{ + CBORType: t.String(), + Message: "simple value " + strconv.FormatInt(int64(val), 10) + " is not recognized", + } + } + + switch ai { + case additionalInformationAsFalse, + additionalInformationAsTrue: + return fillBool(t, ai == additionalInformationAsTrue, v) + + case additionalInformationAsNull, + additionalInformationAsUndefined: + return fillNil(t, v) + + default: + return fillPositiveInt(t, val, v) + } + } + + case cborTypeTag: + _, _, tagNum := d.getHead() + switch tagNum { + case tagNumUnsignedBignum: + // Bignum (tag 2) can be decoded to uint, int, float, slice, array, or big.Int. + b, copied := d.parseByteString() + bi := new(big.Int).SetBytes(b) + + if tInfo.nonPtrType == typeBigInt { + v.Set(reflect.ValueOf(*bi)) + return nil + } + if tInfo.nonPtrKind == reflect.Slice || tInfo.nonPtrKind == reflect.Array { + return fillByteString(t, b, !copied, v, ByteStringToStringForbidden, d.dm.binaryUnmarshaler, d.dm.textUnmarshaler) + } + if bi.IsUint64() { + return fillPositiveInt(t, bi.Uint64(), v) + } + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: tInfo.nonPtrType.String(), + errorMsg: bi.String() + " overflows " + v.Type().String(), + } + + case tagNumNegativeBignum: + // Bignum (tag 3) can be decoded to int, float, slice, array, or big.Int. + b, copied := d.parseByteString() + bi := new(big.Int).SetBytes(b) + bi.Add(bi, big.NewInt(1)) + bi.Neg(bi) + + if tInfo.nonPtrType == typeBigInt { + v.Set(reflect.ValueOf(*bi)) + return nil + } + if tInfo.nonPtrKind == reflect.Slice || tInfo.nonPtrKind == reflect.Array { + return fillByteString(t, b, !copied, v, ByteStringToStringForbidden, d.dm.binaryUnmarshaler, d.dm.textUnmarshaler) + } + if bi.IsInt64() { + return fillNegativeInt(t, bi.Int64(), v) + } + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: tInfo.nonPtrType.String(), + errorMsg: bi.String() + " overflows " + v.Type().String(), + } + + case tagNumExpectedLaterEncodingBase64URL, tagNumExpectedLaterEncodingBase64, tagNumExpectedLaterEncodingBase16: + // If conversion for interoperability with text encodings is not configured, + // treat tags 21-23 as unregistered tags. + if d.dm.byteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding || d.dm.byteStringExpectedFormat != ByteStringExpectedFormatNone { + d.expectedLaterEncodingTags = append(d.expectedLaterEncodingTags, tagNum) + defer func() { + d.expectedLaterEncodingTags = d.expectedLaterEncodingTags[:len(d.expectedLaterEncodingTags)-1] + }() + } + } + + return d.parseToValue(v, tInfo) + + case cborTypeArray: + if tInfo.nonPtrKind == reflect.Slice { + return d.parseArrayToSlice(v, tInfo) + } else if tInfo.nonPtrKind == reflect.Array { + return d.parseArrayToArray(v, tInfo) + } else if tInfo.nonPtrKind == reflect.Struct { + return d.parseArrayToStruct(v, tInfo) + } + d.skip() + return &UnmarshalTypeError{CBORType: t.String(), GoType: tInfo.nonPtrType.String()} + + case cborTypeMap: + if tInfo.nonPtrKind == reflect.Struct { + return d.parseMapToStruct(v, tInfo) + } else if tInfo.nonPtrKind == reflect.Map { + return d.parseMapToMap(v, tInfo) + } + d.skip() + return &UnmarshalTypeError{CBORType: t.String(), GoType: tInfo.nonPtrType.String()} + } + + return nil +} + +func (d *decoder) parseToTag(v reflect.Value) error { + if d.nextCBORNil() { + // Decoding CBOR null and undefined to cbor.Tag is no-op. + d.skip() + return nil + } + + t := d.nextCBORType() + if t != cborTypeTag { + d.skip() + return &UnmarshalTypeError{CBORType: t.String(), GoType: typeTag.String()} + } + + // Unmarshal tag number + _, _, num := d.getHead() + + // Unmarshal tag content + content, err := d.parse(false) + if err != nil { + return err + } + + v.Set(reflect.ValueOf(Tag{num, content})) + return nil +} + +// parseToTime decodes the current data item as a time.Time. The bool return value is false if and +// only if the destination value should remain unmodified. +func (d *decoder) parseToTime() (time.Time, bool, error) { + // Verify that tag number or absence of tag number is acceptable to specified timeTag. + if t := d.nextCBORType(); t == cborTypeTag { + if d.dm.timeTag == DecTagIgnored { + // Skip all enclosing tags + for t == cborTypeTag { + d.getHead() + t = d.nextCBORType() + } + if d.nextCBORNil() { + d.skip() + return time.Time{}, false, nil + } + } else { + // Read tag number + _, _, tagNum := d.getHead() + if tagNum != 0 && tagNum != 1 { + d.skip() // skip tag content + return time.Time{}, false, errors.New("cbor: wrong tag number for time.Time, got " + strconv.Itoa(int(tagNum)) + ", expect 0 or 1") + } + } + } else { + if d.dm.timeTag == DecTagRequired { + d.skip() + return time.Time{}, false, &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String(), errorMsg: "expect CBOR tag value"} + } + } + + switch t := d.nextCBORType(); t { + case cborTypeByteString: + if d.dm.byteStringToTime == ByteStringToTimeAllowed { + b, _ := d.parseByteString() + t, err := time.Parse(time.RFC3339, string(b)) + if err != nil { + return time.Time{}, false, fmt.Errorf("cbor: cannot set %q for time.Time: %w", string(b), err) + } + return t, true, nil + } + return time.Time{}, false, &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String()} + + case cborTypeTextString: + s, err := d.parseTextString() + if err != nil { + return time.Time{}, false, err + } + t, err := time.Parse(time.RFC3339, string(s)) + if err != nil { + return time.Time{}, false, errors.New("cbor: cannot set " + string(s) + " for time.Time: " + err.Error()) + } + return t, true, nil + + case cborTypePositiveInt: + _, _, val := d.getHead() + if val > math.MaxInt64 { + return time.Time{}, false, &UnmarshalTypeError{ + CBORType: t.String(), + GoType: typeTime.String(), + errorMsg: fmt.Sprintf("%d overflows Go's int64", val), + } + } + return time.Unix(int64(val), 0), true, nil + + case cborTypeNegativeInt: + _, _, val := d.getHead() + if val > math.MaxInt64 { + if val == math.MaxUint64 { + // Maximum absolute value representable by negative integer is 2^64, + // not 2^64-1, so it overflows uint64. + return time.Time{}, false, &UnmarshalTypeError{ + CBORType: t.String(), + GoType: typeTime.String(), + errorMsg: "-18446744073709551616 overflows Go's int64", + } + } + return time.Time{}, false, &UnmarshalTypeError{ + CBORType: t.String(), + GoType: typeTime.String(), + errorMsg: fmt.Sprintf("-%d overflows Go's int64", val+1), + } + } + return time.Unix(int64(-1)^int64(val), 0), true, nil + + case cborTypePrimitives: + _, ai, val := d.getHead() + var f float64 + switch ai { + case additionalInformationAsFloat16: + f = float64(float16.Frombits(uint16(val)).Float32()) + + case additionalInformationAsFloat32: + f = float64(math.Float32frombits(uint32(val))) + + case additionalInformationAsFloat64: + f = math.Float64frombits(val) + + default: + return time.Time{}, false, &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String()} + } + + if math.IsNaN(f) || math.IsInf(f, 0) { + // https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.2-6 + return time.Time{}, true, nil + } + seconds, fractional := math.Modf(f) + return time.Unix(int64(seconds), int64(fractional*1e9)), true, nil + + default: + return time.Time{}, false, &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String()} + } +} + +// parseToUnmarshaler parses CBOR data to value implementing Unmarshaler interface. +// It assumes data is well-formed, and does not perform bounds checking. +func (d *decoder) parseToUnmarshaler(v reflect.Value) error { + if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() { + d.skip() + return nil + } + + if v.Kind() != reflect.Pointer && v.CanAddr() { + v = v.Addr() + } + if u, ok := v.Interface().(Unmarshaler); ok { + start := d.off + d.skip() + return u.UnmarshalCBOR(d.data[start:d.off]) + } + d.skip() + return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.Unmarshaler") +} + +// parseToUnexportedUnmarshaler parses CBOR data to value implementing unmarshaler interface. +// It assumes data is well-formed, and does not perform bounds checking. +func (d *decoder) parseToUnexportedUnmarshaler(v reflect.Value) error { + if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() { + d.skip() + return nil + } + + if v.Kind() != reflect.Pointer && v.CanAddr() { + v = v.Addr() + } + if u, ok := v.Interface().(unmarshaler); ok { + start := d.off + d.skip() + return u.unmarshalCBOR(d.data[start:d.off]) + } + d.skip() + return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.unmarshaler") +} + +// parseToJSONUnmarshaler parses CBOR data to be transcoded to JSON and passed to the value's +// implementation of the json.Unmarshaler interface. It assumes data is well-formed, and does not +// perform bounds checking. +func (d *decoder) parseToJSONUnmarshaler(v reflect.Value) error { + if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() { + d.skip() + return nil + } + + if v.Kind() != reflect.Pointer && v.CanAddr() { + v = v.Addr() + } + if u, ok := v.Interface().(jsonUnmarshaler); ok { + start := d.off + d.skip() + e := getEncodeBuffer() + defer putEncodeBuffer(e) + if err := d.dm.jsonUnmarshalerTranscoder.Transcode(e, bytes.NewReader(d.data[start:d.off])); err != nil { + return &TranscodeError{err: err, rtype: v.Type(), sourceFormat: "cbor", targetFormat: "json"} + } + return u.UnmarshalJSON(e.Bytes()) + } + d.skip() + return errors.New("cbor: failed to assert " + v.Type().String() + " as json.Unmarshaler") +} + +// parse parses CBOR data and returns value in default Go type. +// It assumes data is well-formed, and does not perform bounds checking. +func (d *decoder) parse(skipSelfDescribedTag bool) (any, error) { //nolint:gocyclo + // Strip self-described CBOR tag number. + if skipSelfDescribedTag { + for d.nextCBORType() == cborTypeTag { + off := d.off + _, _, tagNum := d.getHead() + if tagNum != tagNumSelfDescribedCBOR { + d.off = off + break + } + } + } + + // Check validity of supported built-in tags. + off := d.off + for d.nextCBORType() == cborTypeTag { + _, _, tagNum := d.getHead() + if err := validBuiltinTag(tagNum, d.data[d.off]); err != nil { + d.skip() + return nil, err + } + } + d.off = off + + t := d.nextCBORType() + switch t { + case cborTypePositiveInt: + _, _, val := d.getHead() + + switch d.dm.intDec { + case IntDecConvertNone: + return val, nil + + case IntDecConvertSigned, IntDecConvertSignedOrFail: + if val > math.MaxInt64 { + return nil, &UnmarshalTypeError{ + CBORType: t.String(), + GoType: reflect.TypeOf(int64(0)).String(), + errorMsg: strconv.FormatUint(val, 10) + " overflows Go's int64", + } + } + + return int64(val), nil + + case IntDecConvertSignedOrBigInt: + if val > math.MaxInt64 { + bi := new(big.Int).SetUint64(val) + if d.dm.bigIntDec == BigIntDecodePointer { + return bi, nil + } + return *bi, nil + } + + return int64(val), nil + + default: + // not reachable + } + + case cborTypeNegativeInt: + _, _, val := d.getHead() + + if val > math.MaxInt64 { + // CBOR negative integer value overflows Go int64, use big.Int instead. + bi := new(big.Int).SetUint64(val) + bi.Add(bi, big.NewInt(1)) + bi.Neg(bi) + + if d.dm.intDec == IntDecConvertSignedOrFail { + return nil, &UnmarshalTypeError{ + CBORType: t.String(), + GoType: reflect.TypeOf(int64(0)).String(), + errorMsg: bi.String() + " overflows Go's int64", + } + } + + if d.dm.bigIntDec == BigIntDecodePointer { + return bi, nil + } + return *bi, nil + } + + nValue := int64(-1) ^ int64(val) + return nValue, nil + + case cborTypeByteString: + b, copied := d.parseByteString() + var effectiveByteStringType = d.dm.defaultByteStringType + if effectiveByteStringType == nil { + effectiveByteStringType = typeByteSlice + } + b, converted, err := d.applyByteStringTextConversion(b, effectiveByteStringType) + if err != nil { + return nil, err + } + copied = copied || converted + + switch effectiveByteStringType { + case typeByteSlice: + if copied { + return b, nil + } + clone := make([]byte, len(b)) + copy(clone, b) + return clone, nil + + case typeString: + return string(b), nil + + default: + if copied || d.dm.defaultByteStringType.Kind() == reflect.String { + // Avoid an unnecessary copy since the conversion to string must + // copy the underlying bytes. + return reflect.ValueOf(b).Convert(d.dm.defaultByteStringType).Interface(), nil + } + clone := make([]byte, len(b)) + copy(clone, b) + return reflect.ValueOf(clone).Convert(d.dm.defaultByteStringType).Interface(), nil + } + + case cborTypeTextString: + b, err := d.parseTextString() + if err != nil { + return nil, err + } + return string(b), nil + + case cborTypeTag: + tagOff := d.off + _, _, tagNum := d.getHead() + contentOff := d.off + + switch tagNum { + case tagNumRFC3339Time, tagNumEpochTime: + d.off = tagOff + tm, _, err := d.parseToTime() + if err != nil { + return nil, err + } + + switch d.dm.timeTagToAny { + case TimeTagToTime: + return tm, nil + + case TimeTagToRFC3339: + if tagNum == 1 { + tm = tm.UTC() + } + // Call time.MarshalText() to format decoded time to RFC3339 format, + // and return error on time value that cannot be represented in + // RFC3339 format. E.g. year cannot exceed 9999, etc. + text, err := tm.Truncate(time.Second).MarshalText() + if err != nil { + return nil, fmt.Errorf("cbor: decoded time cannot be represented in RFC3339 format: %v", err) + } + return string(text), nil + + case TimeTagToRFC3339Nano: + if tagNum == 1 { + tm = tm.UTC() + } + // Call time.MarshalText() to format decoded time to RFC3339 format, + // and return error on time value that cannot be represented in + // RFC3339 format with sub-second precision. + text, err := tm.MarshalText() + if err != nil { + return nil, fmt.Errorf("cbor: decoded time cannot be represented in RFC3339 format with sub-second precision: %v", err) + } + return string(text), nil + + default: + // not reachable + } + + case tagNumUnsignedBignum: + b, _ := d.parseByteString() + bi := new(big.Int).SetBytes(b) + + if d.dm.bigIntDec == BigIntDecodePointer { + return bi, nil + } + return *bi, nil + + case tagNumNegativeBignum: + b, _ := d.parseByteString() + bi := new(big.Int).SetBytes(b) + bi.Add(bi, big.NewInt(1)) + bi.Neg(bi) + + if d.dm.bigIntDec == BigIntDecodePointer { + return bi, nil + } + return *bi, nil + + case tagNumExpectedLaterEncodingBase64URL, tagNumExpectedLaterEncodingBase64, tagNumExpectedLaterEncodingBase16: + // If conversion for interoperability with text encodings is not configured, + // treat tags 21-23 as unregistered tags. + if d.dm.byteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding || + d.dm.byteStringExpectedFormat != ByteStringExpectedFormatNone { + d.expectedLaterEncodingTags = append(d.expectedLaterEncodingTags, tagNum) + defer func() { + d.expectedLaterEncodingTags = d.expectedLaterEncodingTags[:len(d.expectedLaterEncodingTags)-1] + }() + return d.parse(false) + } + } + + if d.dm.tags != nil { + // Parse to specified type if tag number is registered. + tagNums := []uint64{tagNum} + for d.nextCBORType() == cborTypeTag { + _, _, num := d.getHead() + tagNums = append(tagNums, num) + } + registeredType := d.dm.tags.getTypeFromTagNum(tagNums) + if registeredType != nil { + d.off = tagOff + rv := reflect.New(registeredType) + if err := d.parseToValue(rv.Elem(), getTypeInfo(registeredType)); err != nil { + return nil, err + } + return rv.Elem().Interface(), nil + } + } + + // Parse tag content + d.off = contentOff + content, err := d.parse(false) + if err != nil { + return nil, err + } + if d.dm.unrecognizedTagToAny == UnrecognizedTagContentToAny { + return content, nil + } + return Tag{tagNum, content}, nil + + case cborTypePrimitives: + _, ai, val := d.getHead() + if ai <= 24 && d.dm.simpleValues.rejected[SimpleValue(val)] { + return nil, &UnacceptableDataItemError{ + CBORType: t.String(), + Message: "simple value " + strconv.FormatInt(int64(val), 10) + " is not recognized", + } + } + if ai < 20 || ai == 24 { + return SimpleValue(val), nil + } + + switch ai { + case additionalInformationAsFalse, + additionalInformationAsTrue: + return (ai == additionalInformationAsTrue), nil + + case additionalInformationAsNull, + additionalInformationAsUndefined: + return nil, nil + + case additionalInformationAsFloat16: + f := float64(float16.Frombits(uint16(val)).Float32()) + return f, nil + + case additionalInformationAsFloat32: + f := float64(math.Float32frombits(uint32(val))) + return f, nil + + case additionalInformationAsFloat64: + f := math.Float64frombits(val) + return f, nil + } + + case cborTypeArray: + return d.parseArray() + + case cborTypeMap: + if d.dm.defaultMapType != nil { + m := reflect.New(d.dm.defaultMapType) + err := d.parseToValue(m, getTypeInfo(m.Elem().Type())) + if err != nil { + return nil, err + } + return m.Elem().Interface(), nil + } + return d.parseMap() + } + + return nil, nil +} + +// parseByteString parses a CBOR encoded byte string. The returned byte slice +// may be backed directly by the input. The second return value will be true if +// and only if the slice is backed by a copy of the input. Callers are +// responsible for making a copy if necessary. +func (d *decoder) parseByteString() ([]byte, bool) { + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + if !indefiniteLength { + b := d.data[d.off : d.off+int(val)] + d.off += int(val) + return b, false + } + // Process indefinite length string chunks. + b := []byte{} + for !d.foundBreak() { + _, _, val = d.getHead() + b = append(b, d.data[d.off:d.off+int(val)]...) + d.off += int(val) + } + return b, true +} + +// applyByteStringTextConversion converts bytes read from a byte string to or from a configured text +// encoding. If no transformation was performed (because it was not required), the original byte +// slice is returned and the bool return value is false. Otherwise, a new slice containing the +// converted bytes is returned along with the bool value true. +func (d *decoder) applyByteStringTextConversion( + src []byte, + dstType reflect.Type, +) ( + dst []byte, + transformed bool, + err error, +) { + switch dstType.Kind() { + case reflect.String: + if d.dm.byteStringToString != ByteStringToStringAllowedWithExpectedLaterEncoding || len(d.expectedLaterEncodingTags) == 0 { + return src, false, nil + } + + switch d.expectedLaterEncodingTags[len(d.expectedLaterEncodingTags)-1] { + case tagNumExpectedLaterEncodingBase64URL: + encoded := make([]byte, base64.RawURLEncoding.EncodedLen(len(src))) + base64.RawURLEncoding.Encode(encoded, src) + return encoded, true, nil + + case tagNumExpectedLaterEncodingBase64: + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(src))) + base64.StdEncoding.Encode(encoded, src) + return encoded, true, nil + + case tagNumExpectedLaterEncodingBase16: + encoded := make([]byte, hex.EncodedLen(len(src))) + hex.Encode(encoded, src) + return encoded, true, nil + + default: + // If this happens, there is a bug: the decoder has pushed an invalid + // "expected later encoding" tag to the stack. + panic(fmt.Sprintf("unrecognized expected later encoding tag: %d", d.expectedLaterEncodingTags)) + } + + case reflect.Slice: + if dstType.Elem().Kind() != reflect.Uint8 || len(d.expectedLaterEncodingTags) > 0 { + // Either the destination is not a slice of bytes, or the encoder that + // produced the input indicated an expected text encoding tag and therefore + // the content of the byte string has NOT been text encoded. + return src, false, nil + } + + switch d.dm.byteStringExpectedFormat { + case ByteStringExpectedBase64URL: + decoded := make([]byte, base64.RawURLEncoding.DecodedLen(len(src))) + n, err := base64.RawURLEncoding.Decode(decoded, src) + if err != nil { + return nil, false, newByteStringExpectedFormatError(ByteStringExpectedBase64URL, err) + } + return decoded[:n], true, nil + + case ByteStringExpectedBase64: + decoded := make([]byte, base64.StdEncoding.DecodedLen(len(src))) + n, err := base64.StdEncoding.Decode(decoded, src) + if err != nil { + return nil, false, newByteStringExpectedFormatError(ByteStringExpectedBase64, err) + } + return decoded[:n], true, nil + + case ByteStringExpectedBase16: + decoded := make([]byte, hex.DecodedLen(len(src))) + n, err := hex.Decode(decoded, src) + if err != nil { + return nil, false, newByteStringExpectedFormatError(ByteStringExpectedBase16, err) + } + return decoded[:n], true, nil + } + } + + return src, false, nil +} + +// parseTextString parses CBOR encoded text string. It returns a byte slice +// to prevent creating an extra copy of string. Caller should wrap returned +// byte slice as string when needed. +func (d *decoder) parseTextString() ([]byte, error) { + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + if !indefiniteLength { + b := d.data[d.off : d.off+int(val)] + d.off += int(val) + if d.dm.utf8 == UTF8RejectInvalid && !utf8.Valid(b) { + return nil, &SemanticError{"cbor: invalid UTF-8 string"} + } + return b, nil + } + // Process indefinite length string chunks. + b := []byte{} + for !d.foundBreak() { + _, _, val = d.getHead() + x := d.data[d.off : d.off+int(val)] + d.off += int(val) + if d.dm.utf8 == UTF8RejectInvalid && !utf8.Valid(x) { + for !d.foundBreak() { + d.skip() // Skip remaining chunk on error + } + return nil, &SemanticError{"cbor: invalid UTF-8 string"} + } + b = append(b, x...) + } + return b, nil +} + +func (d *decoder) parseArray() ([]any, error) { + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + hasSize := !indefiniteLength + count := int(val) + if !hasSize { + count = d.numOfItemsUntilBreak() // peek ahead to get array size to preallocate slice for better performance + } + v := make([]any, count) + var e any + var err, lastErr error + for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ { + if e, lastErr = d.parse(true); lastErr != nil { + if err == nil { + err = lastErr + } + continue + } + v[i] = e + } + return v, err +} + +func (d *decoder) parseArrayToSlice(v reflect.Value, tInfo *typeInfo) error { + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + hasSize := !indefiniteLength + count := int(val) + if !hasSize { + count = d.numOfItemsUntilBreak() // peek ahead to get array size to preallocate slice for better performance + } + if v.IsNil() || v.Cap() < count || count == 0 { + v.Set(reflect.MakeSlice(tInfo.nonPtrType, count, count)) + } + v.SetLen(count) + var err error + for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ { + if lastErr := d.parseToValue(v.Index(i), tInfo.elemTypeInfo); lastErr != nil { + if err == nil { + err = lastErr + } + } + } + return err +} + +func (d *decoder) parseArrayToArray(v reflect.Value, tInfo *typeInfo) error { + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + hasSize := !indefiniteLength + count := int(val) + gi := 0 + vLen := v.Len() + var err error + for ci := 0; (hasSize && ci < count) || (!hasSize && !d.foundBreak()); ci++ { + if gi < vLen { + // Read CBOR array element and set array element + if lastErr := d.parseToValue(v.Index(gi), tInfo.elemTypeInfo); lastErr != nil { + if err == nil { + err = lastErr + } + } + gi++ + } else { + d.skip() // Skip remaining CBOR array element + } + } + // Set remaining Go array elements to zero values. + if gi < vLen { + for ; gi < vLen; gi++ { + v.Index(gi).SetZero() + } + } + return err +} + +func (d *decoder) parseMap() (any, error) { + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + hasSize := !indefiniteLength + count := int(val) + m := make(map[any]any) + var k, e any + var err, lastErr error + keyCount := 0 + for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ { + // Parse CBOR map key. + if k, lastErr = d.parse(true); lastErr != nil { + if err == nil { + err = lastErr + } + d.skip() + continue + } + + // Detect if CBOR map key can be used as Go map key. + rv := reflect.ValueOf(k) + if !isHashableValue(rv) { + var converted bool + if d.dm.mapKeyByteString == MapKeyByteStringAllowed { + k, converted = convertByteSliceToByteString(k) + } + if !converted { + if err == nil { + err = &InvalidMapKeyTypeError{rv.Type().String()} + } + d.skip() + continue + } + } + + // Parse CBOR map value. + if e, lastErr = d.parse(true); lastErr != nil { + if err == nil { + err = lastErr + } + continue + } + + // Add key-value pair to Go map. + m[k] = e + + // Detect duplicate map key. + if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + newKeyCount := len(m) + if newKeyCount == keyCount { + m[k] = nil + err = &DupMapKeyError{k, i} + i++ + // skip the rest of the map + for ; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ { + d.skip() // Skip map key + d.skip() // Skip map value + } + return m, err + } + keyCount = newKeyCount + } + } + return m, err +} + +func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + hasSize := !indefiniteLength + count := int(val) + if v.IsNil() { + mapsize := count + if !hasSize { + mapsize = 0 + } + v.Set(reflect.MakeMapWithSize(tInfo.nonPtrType, mapsize)) + } + keyType, eleType := tInfo.keyTypeInfo.typ, tInfo.elemTypeInfo.typ + reuseKey, reuseEle := isImmutableKind(tInfo.keyTypeInfo.kind), isImmutableKind(tInfo.elemTypeInfo.kind) + var keyValue, eleValue reflect.Value + keyIsInterfaceType := keyType == typeIntf // If key type is interface{}, need to check if key value is hashable. + var err, lastErr error + keyCount := v.Len() + var existingKeys map[any]bool // Store existing map keys, used for detecting duplicate map key. + if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + existingKeys = make(map[any]bool, keyCount) + if keyCount > 0 { + vKeys := v.MapKeys() + for i := 0; i < len(vKeys); i++ { + existingKeys[vKeys[i].Interface()] = true + } + } + } + for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ { + // Parse CBOR map key. + if !keyValue.IsValid() { + keyValue = reflect.New(keyType).Elem() + } else if !reuseKey { + keyValue.SetZero() + } + if lastErr = d.parseToValue(keyValue, tInfo.keyTypeInfo); lastErr != nil { + if err == nil { + err = lastErr + } + d.skip() + continue + } + + // Detect if CBOR map key can be used as Go map key. + if keyIsInterfaceType && keyValue.Elem().IsValid() { + if !isHashableValue(keyValue.Elem()) { + var converted bool + if d.dm.mapKeyByteString == MapKeyByteStringAllowed { + var k any + k, converted = convertByteSliceToByteString(keyValue.Elem().Interface()) + if converted { + keyValue.Set(reflect.ValueOf(k)) + } + } + if !converted { + if err == nil { + err = &InvalidMapKeyTypeError{keyValue.Elem().Type().String()} + } + d.skip() + continue + } + } + } + + // Parse CBOR map value. + if !eleValue.IsValid() { + eleValue = reflect.New(eleType).Elem() + } else if !reuseEle { + eleValue.SetZero() + } + if lastErr := d.parseToValue(eleValue, tInfo.elemTypeInfo); lastErr != nil { + if err == nil { + err = lastErr + } + continue + } + + // Add key-value pair to Go map. + v.SetMapIndex(keyValue, eleValue) + + // Detect duplicate map key. + if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + newKeyCount := v.Len() + if newKeyCount == keyCount { + kvi := keyValue.Interface() + if !existingKeys[kvi] { + v.SetMapIndex(keyValue, reflect.New(eleType).Elem()) + err = &DupMapKeyError{kvi, i} + i++ + // skip the rest of the map + for ; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ { + d.skip() // skip map key + d.skip() // skip map value + } + return err + } + delete(existingKeys, kvi) + } + keyCount = newKeyCount + } + } + return err +} + +func (d *decoder) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error { + structType := getDecodingStructType(tInfo.nonPtrType) + if structType.err != nil { + return structType.err + } + + if !structType.toArray { + t := d.nextCBORType() + d.skip() + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: tInfo.nonPtrType.String(), + errorMsg: "cannot decode CBOR array to struct without toarray option", + } + } + + start := d.off + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + hasSize := !indefiniteLength + count := int(val) + if !hasSize { + count = d.numOfItemsUntilBreak() // peek ahead to get array size + } + if count != len(structType.fields) { + d.off = start + d.skip() + return &UnmarshalTypeError{ + CBORType: cborTypeArray.String(), + GoType: tInfo.typ.String(), + errorMsg: "cannot decode CBOR array to struct with different number of elements", + } + } + var err, lastErr error + for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ { + f := structType.fields[i] + + // Get field value by index + var fv reflect.Value + if len(f.idx) == 1 { + fv = v.Field(f.idx[0]) + } else { + fv, lastErr = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) { + // Return a new value for embedded field null pointer to point to, or return error. + if !v.CanSet() { + return reflect.Value{}, errors.New("cbor: cannot set embedded pointer to unexported struct: " + v.Type().String()) + } + v.Set(reflect.New(v.Type().Elem())) + return v, nil + }) + if lastErr != nil && err == nil { + err = lastErr + } + if !fv.IsValid() { + d.skip() + continue + } + } + + if lastErr = d.parseToValue(fv, f.typInfo); lastErr != nil { + if err == nil { + if typeError, ok := lastErr.(*UnmarshalTypeError); ok { + typeError.StructFieldName = tInfo.typ.String() + "." + f.name + err = typeError + } else { + err = lastErr + } + } + } + } + return err +} + +// parseMapToStruct needs to be fast so gocyclo can be ignored for now. +func (d *decoder) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo + structType := getDecodingStructType(tInfo.nonPtrType) + if structType.err != nil { + return structType.err + } + + if structType.toArray { + t := d.nextCBORType() + d.skip() + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: tInfo.nonPtrType.String(), + errorMsg: "cannot decode CBOR map to struct with toarray option", + } + } + + var err, lastErr error + + // Get CBOR map size + _, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + hasSize := !indefiniteLength + count := int(val) + + // Keeps track of matched struct fields + var foundFldIdx []bool + { + const maxStackFields = 128 + if nfields := len(structType.fields); nfields <= maxStackFields { + // For structs with typical field counts, expect that this can be + // stack-allocated. + var a [maxStackFields]bool + foundFldIdx = a[:nfields] + } else { + foundFldIdx = make([]bool, len(structType.fields)) + } + } + + // Keeps track of CBOR map keys to detect duplicate map key + keyCount := 0 + var mapKeys map[any]struct{} + + errOnUnknownField := (d.dm.extraReturnErrors & ExtraDecErrorUnknownField) > 0 + +MapEntryLoop: + for j := 0; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ { + var f *field + + // If duplicate field detection is enabled and the key at index j did not match any + // field, k will hold the map key. + var k any + + t := d.nextCBORType() + if t == cborTypeTextString || (t == cborTypeByteString && d.dm.fieldNameByteString == FieldNameByteStringAllowed) { + var keyBytes []byte + if t == cborTypeTextString { + keyBytes, lastErr = d.parseTextString() + if lastErr != nil { + if err == nil { + err = lastErr + } + d.skip() // skip value + continue + } + } else { // cborTypeByteString + keyBytes, _ = d.parseByteString() + } + + // Check for exact match on field name. + if i, ok := structType.fieldIndicesByName[string(keyBytes)]; ok { + fld := structType.fields[i] + + if !foundFldIdx[i] { + f = fld + foundFldIdx[i] = true + } else if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + err = &DupMapKeyError{fld.name, j} + d.skip() // skip value + j++ + // skip the rest of the map + for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ { + d.skip() + d.skip() + } + return err + } else { + // discard repeated match + d.skip() + continue MapEntryLoop + } + } + + // Find field with case-insensitive match + if f == nil && d.dm.fieldNameMatching == FieldNameMatchingPreferCaseSensitive { + keyLen := len(keyBytes) + keyString := string(keyBytes) + for i := 0; i < len(structType.fields); i++ { + fld := structType.fields[i] + if len(fld.name) == keyLen && strings.EqualFold(fld.name, keyString) { + if !foundFldIdx[i] { + f = fld + foundFldIdx[i] = true + } else if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + err = &DupMapKeyError{keyString, j} + d.skip() // skip value + j++ + // skip the rest of the map + for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ { + d.skip() + d.skip() + } + return err + } else { + // discard repeated match + d.skip() + continue MapEntryLoop + } + break + } + } + } + + if d.dm.dupMapKey == DupMapKeyEnforcedAPF && f == nil { + k = string(keyBytes) + } + } else if t <= cborTypeNegativeInt { // uint/int + var nameAsInt int64 + + if t == cborTypePositiveInt { + _, _, val := d.getHead() + nameAsInt = int64(val) + } else { + _, _, val := d.getHead() + if val > math.MaxInt64 { + if err == nil { + err = &UnmarshalTypeError{ + CBORType: t.String(), + GoType: reflect.TypeOf(int64(0)).String(), + errorMsg: "-1-" + strconv.FormatUint(val, 10) + " overflows Go's int64", + } + } + d.skip() // skip value + continue + } + nameAsInt = int64(-1) ^ int64(val) + } + + // Find field + for i := 0; i < len(structType.fields); i++ { + fld := structType.fields[i] + if fld.keyAsInt && fld.nameAsInt == nameAsInt { + if !foundFldIdx[i] { + f = fld + foundFldIdx[i] = true + } else if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + err = &DupMapKeyError{nameAsInt, j} + d.skip() // skip value + j++ + // skip the rest of the map + for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ { + d.skip() + d.skip() + } + return err + } else { + // discard repeated match + d.skip() + continue MapEntryLoop + } + break + } + } + + if d.dm.dupMapKey == DupMapKeyEnforcedAPF && f == nil { + k = nameAsInt + } + } else { + if err == nil { + err = &UnmarshalTypeError{ + CBORType: t.String(), + GoType: reflect.TypeOf("").String(), + errorMsg: "map key is of type " + t.String() + " and cannot be used to match struct field name", + } + } + if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + // parse key + k, lastErr = d.parse(true) + if lastErr != nil { + d.skip() // skip value + continue + } + // Detect if CBOR map key can be used as Go map key. + if !isHashableValue(reflect.ValueOf(k)) { + d.skip() // skip value + continue + } + } else { + d.skip() // skip key + } + } + + if f == nil { + if errOnUnknownField { + err = &UnknownFieldError{j} + d.skip() // Skip value + j++ + // skip the rest of the map + for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ { + d.skip() + d.skip() + } + return err + } + + // Two map keys that match the same struct field are immediately considered + // duplicates. This check detects duplicates between two map keys that do + // not match a struct field. If unknown field errors are enabled, then this + // check is never reached. + if d.dm.dupMapKey == DupMapKeyEnforcedAPF { + if mapKeys == nil { + mapKeys = make(map[any]struct{}, 1) + } + mapKeys[k] = struct{}{} + newKeyCount := len(mapKeys) + if newKeyCount == keyCount { + err = &DupMapKeyError{k, j} + d.skip() // skip value + j++ + // skip the rest of the map + for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ { + d.skip() + d.skip() + } + return err + } + keyCount = newKeyCount + } + + d.skip() // Skip value + continue + } + + // Get field value by index + var fv reflect.Value + if len(f.idx) == 1 { + fv = v.Field(f.idx[0]) + } else { + fv, lastErr = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) { + // Return a new value for embedded field null pointer to point to, or return error. + if !v.CanSet() { + return reflect.Value{}, errors.New("cbor: cannot set embedded pointer to unexported struct: " + v.Type().String()) + } + v.Set(reflect.New(v.Type().Elem())) + return v, nil + }) + if lastErr != nil && err == nil { + err = lastErr + } + if !fv.IsValid() { + d.skip() + continue + } + } + + if lastErr = d.parseToValue(fv, f.typInfo); lastErr != nil { + if err == nil { + if typeError, ok := lastErr.(*UnmarshalTypeError); ok { + typeError.StructFieldName = tInfo.nonPtrType.String() + "." + f.name + err = typeError + } else { + err = lastErr + } + } + } + } + return err +} + +// validRegisteredTagNums verifies that tag numbers match registered tag numbers of type t. +// validRegisteredTagNums assumes next CBOR data type is tag. It scans all tag numbers, and stops at tag content. +func (d *decoder) validRegisteredTagNums(registeredTag *tagItem) error { + // Scan until next cbor data is tag content. + tagNums := make([]uint64, 0, 1) + for d.nextCBORType() == cborTypeTag { + _, _, val := d.getHead() + tagNums = append(tagNums, val) + } + + if !registeredTag.equalTagNum(tagNums) { + return &WrongTagError{registeredTag.contentType, registeredTag.num, tagNums} + } + return nil +} + +func (d *decoder) getRegisteredTagItem(vt reflect.Type) *tagItem { + if d.dm.tags != nil { + return d.dm.tags.getTagItemFromType(vt) + } + return nil +} + +// skip moves data offset to the next item. skip assumes data is well-formed, +// and does not perform bounds checking. +func (d *decoder) skip() { + t, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag() + + if indefiniteLength { + switch t { + case cborTypeByteString, cborTypeTextString, cborTypeArray, cborTypeMap: + for { + if isBreakFlag(d.data[d.off]) { + d.off++ + return + } + d.skip() + } + } + } + + switch t { + case cborTypeByteString, cborTypeTextString: + d.off += int(val) + + case cborTypeArray: + for i := 0; i < int(val); i++ { + d.skip() + } + + case cborTypeMap: + for i := 0; i < int(val)*2; i++ { + d.skip() + } + + case cborTypeTag: + d.skip() + } +} + +func (d *decoder) getHeadWithIndefiniteLengthFlag() ( + t cborType, + ai byte, + val uint64, + indefiniteLength bool, +) { + t, ai, val = d.getHead() + indefiniteLength = additionalInformation(ai).isIndefiniteLength() + return +} + +// getHead assumes data is well-formed, and does not perform bounds checking. +func (d *decoder) getHead() (t cborType, ai byte, val uint64) { + t, ai = parseInitialByte(d.data[d.off]) + val = uint64(ai) + d.off++ + + if ai <= maxAdditionalInformationWithoutArgument { + return + } + + if ai == additionalInformationWith1ByteArgument { + val = uint64(d.data[d.off]) + d.off++ + return + } + + if ai == additionalInformationWith2ByteArgument { + const argumentSize = 2 + val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize + return + } + + if ai == additionalInformationWith4ByteArgument { + const argumentSize = 4 + val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize + return + } + + if ai == additionalInformationWith8ByteArgument { + const argumentSize = 8 + val = binary.BigEndian.Uint64(d.data[d.off : d.off+argumentSize]) + d.off += argumentSize + return + } + return +} + +func (d *decoder) numOfItemsUntilBreak() int { + savedOff := d.off + i := 0 + for !d.foundBreak() { + d.skip() + i++ + } + d.off = savedOff + return i +} + +// foundBreak returns true if next byte is CBOR break code and moves cursor by 1, +// otherwise it returns false. +// foundBreak assumes data is well-formed, and does not perform bounds checking. +func (d *decoder) foundBreak() bool { + if isBreakFlag(d.data[d.off]) { + d.off++ + return true + } + return false +} + +func (d *decoder) reset(data []byte) { + d.data = data + d.off = 0 + d.expectedLaterEncodingTags = d.expectedLaterEncodingTags[:0] +} + +func (d *decoder) nextCBORType() cborType { + return getType(d.data[d.off]) +} + +func (d *decoder) nextCBORNil() bool { + return d.data[d.off] == 0xf6 || d.data[d.off] == 0xf7 +} + +type jsonUnmarshaler interface{ UnmarshalJSON([]byte) error } + +var ( + typeIntf = reflect.TypeOf([]any(nil)).Elem() + typeTime = reflect.TypeOf(time.Time{}) + typeBigInt = reflect.TypeOf(big.Int{}) + typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + typeUnexportedUnmarshaler = reflect.TypeOf((*unmarshaler)(nil)).Elem() + typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem() + typeTextUnmarshaler = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + typeJSONUnmarshaler = reflect.TypeOf((*jsonUnmarshaler)(nil)).Elem() + typeString = reflect.TypeOf("") + typeByteSlice = reflect.TypeOf([]byte(nil)) +) + +func fillNil(_ cborType, v reflect.Value) error { + switch v.Kind() { + case reflect.Slice, reflect.Map, reflect.Interface, reflect.Pointer: + v.SetZero() + return nil + } + return nil +} + +func fillPositiveInt(t cborType, val uint64, v reflect.Value) error { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if val > math.MaxInt64 { + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: v.Type().String(), + errorMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String(), + } + } + if v.OverflowInt(int64(val)) { + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: v.Type().String(), + errorMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String(), + } + } + v.SetInt(int64(val)) + return nil + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if v.OverflowUint(val) { + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: v.Type().String(), + errorMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String(), + } + } + v.SetUint(val) + return nil + + case reflect.Float32, reflect.Float64: + f := float64(val) + v.SetFloat(f) + return nil + } + + if v.Type() == typeBigInt { + i := new(big.Int).SetUint64(val) + v.Set(reflect.ValueOf(*i)) + return nil + } + return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()} +} + +func fillNegativeInt(t cborType, val int64, v reflect.Value) error { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if v.OverflowInt(val) { + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: v.Type().String(), + errorMsg: strconv.FormatInt(val, 10) + " overflows " + v.Type().String(), + } + } + v.SetInt(val) + return nil + + case reflect.Float32, reflect.Float64: + f := float64(val) + v.SetFloat(f) + return nil + } + if v.Type() == typeBigInt { + i := new(big.Int).SetInt64(val) + v.Set(reflect.ValueOf(*i)) + return nil + } + return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()} +} + +func fillBool(t cborType, val bool, v reflect.Value) error { + if v.Kind() == reflect.Bool { + v.SetBool(val) + return nil + } + return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()} +} + +func fillFloat(t cborType, val float64, v reflect.Value) error { + switch v.Kind() { + case reflect.Float32, reflect.Float64: + if v.OverflowFloat(val) { + return &UnmarshalTypeError{ + CBORType: t.String(), + GoType: v.Type().String(), + errorMsg: strconv.FormatFloat(val, 'E', -1, 64) + " overflows " + v.Type().String(), + } + } + v.SetFloat(val) + return nil + } + return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()} +} + +func fillByteString(t cborType, val []byte, shared bool, v reflect.Value, bsts ByteStringToStringMode, bum BinaryUnmarshalerMode, tum TextUnmarshalerMode) error { + if bum == BinaryUnmarshalerByteString && reflect.PointerTo(v.Type()).Implements(typeBinaryUnmarshaler) { + if v.CanAddr() { + v = v.Addr() + if u, ok := v.Interface().(encoding.BinaryUnmarshaler); ok { + // The contract of BinaryUnmarshaler forbids + // retaining the input bytes, so no copying is + // required even if val is shared. + return u.UnmarshalBinary(val) + } + } + return errors.New("cbor: cannot set new value for " + v.Type().String()) + } + if bsts != ByteStringToStringForbidden { + if tum == TextUnmarshalerTextString && reflect.PointerTo(v.Type()).Implements(typeTextUnmarshaler) { + if v.CanAddr() { + v = v.Addr() + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + // The contract of TextUnmarshaler forbids retaining the input + // bytes, so no copying is required even if val is shared. + if err := u.UnmarshalText(val); err != nil { + return fmt.Errorf("cbor: cannot unmarshal text for %s: %w", v.Type(), err) + } + return nil + } + } + return errors.New("cbor: cannot set new value for " + v.Type().String()) + } + + if v.Kind() == reflect.String { + v.SetString(string(val)) + return nil + } + } + if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 { + src := val + if shared { + // SetBytes shares the underlying bytes of the source slice. + src = make([]byte, len(val)) + copy(src, val) + } + v.SetBytes(src) + return nil + } + if v.Kind() == reflect.Array && v.Type().Elem().Kind() == reflect.Uint8 { + vLen := v.Len() + i := 0 + for ; i < vLen && i < len(val); i++ { + v.Index(i).SetUint(uint64(val[i])) + } + // Set remaining Go array elements to zero values. + if i < vLen { + for ; i < vLen; i++ { + v.Index(i).SetZero() + } + } + return nil + } + return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()} +} + +func fillTextString(t cborType, val []byte, v reflect.Value, tum TextUnmarshalerMode) error { + // Check if the value implements TextUnmarshaler and the mode allows it + if tum == TextUnmarshalerTextString && reflect.PointerTo(v.Type()).Implements(typeTextUnmarshaler) { + if v.CanAddr() { + v = v.Addr() + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + // The contract of TextUnmarshaler forbids retaining the input + // bytes, so no copying is required even if val is shared. + if err := u.UnmarshalText(val); err != nil { + return fmt.Errorf("cbor: cannot unmarshal text for %s: %w", v.Type(), err) + } + return nil + } + } + return errors.New("cbor: cannot set new value for " + v.Type().String()) + } + + if v.Kind() == reflect.String { + v.SetString(string(val)) + return nil + } + + return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()} +} + +func isImmutableKind(k reflect.Kind) bool { + switch k { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64, + reflect.String: + return true + + default: + return false + } +} + +func isHashableValue(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Slice, reflect.Map, reflect.Func: + return false + + case reflect.Struct: + switch rv.Type() { + case typeTag: + tag := rv.Interface().(Tag) + return isHashableValue(reflect.ValueOf(tag.Content)) + case typeBigInt: + return false + } + } + return true +} + +// convertByteSliceToByteString converts []byte to ByteString if +// - v is []byte type, or +// - v is Tag type and tag content type is []byte +// This function also handles nested tags. +// CBOR data is already verified to be well-formed before this function is used, +// so the recursion won't exceed max nested levels. +func convertByteSliceToByteString(v any) (any, bool) { + switch v := v.(type) { + case []byte: + return ByteString(v), true + + case Tag: + content, converted := convertByteSliceToByteString(v.Content) + if converted { + return Tag{Number: v.Number, Content: content}, true + } + } + return v, false +} diff --git a/vendor/github.com/fxamacker/cbor/v2/diagnose.go b/vendor/github.com/fxamacker/cbor/v2/diagnose.go new file mode 100644 index 0000000000..44afb86608 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/diagnose.go @@ -0,0 +1,724 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "bytes" + "encoding/base32" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "io" + "math" + "math/big" + "strconv" + "unicode/utf16" + "unicode/utf8" + + "github.com/x448/float16" +) + +// DiagMode is the main interface for CBOR diagnostic notation. +type DiagMode interface { + // Diagnose returns extended diagnostic notation (EDN) of CBOR data items using this DiagMode. + Diagnose([]byte) (string, error) + + // DiagnoseFirst returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest. + DiagnoseFirst([]byte) (string, []byte, error) + + // DiagOptions returns user specified options used to create this DiagMode. + DiagOptions() DiagOptions +} + +// ByteStringEncoding specifies the base encoding that byte strings are notated. +type ByteStringEncoding uint8 + +const ( + // ByteStringBase16Encoding encodes byte strings in base16, without padding. + ByteStringBase16Encoding ByteStringEncoding = iota + + // ByteStringBase32Encoding encodes byte strings in base32, without padding. + ByteStringBase32Encoding + + // ByteStringBase32HexEncoding encodes byte strings in base32hex, without padding. + ByteStringBase32HexEncoding + + // ByteStringBase64Encoding encodes byte strings in base64url, without padding. + ByteStringBase64Encoding + + maxByteStringEncoding +) + +func (bse ByteStringEncoding) valid() error { + if bse >= maxByteStringEncoding { + return errors.New("cbor: invalid ByteStringEncoding " + strconv.Itoa(int(bse))) + } + return nil +} + +// DiagOptions specifies Diag options. +type DiagOptions struct { + // ByteStringEncoding specifies the base encoding that byte strings are notated. + // Default is ByteStringBase16Encoding. + ByteStringEncoding ByteStringEncoding + + // ByteStringHexWhitespace specifies notating with whitespace in byte string + // when ByteStringEncoding is ByteStringBase16Encoding. + ByteStringHexWhitespace bool + + // ByteStringText specifies notating with text in byte string + // if it is a valid UTF-8 text. + ByteStringText bool + + // ByteStringEmbeddedCBOR specifies notating embedded CBOR in byte string + // if it is a valid CBOR bytes. + ByteStringEmbeddedCBOR bool + + // CBORSequence specifies notating CBOR sequences. + // otherwise, it returns an error if there are more bytes after the first CBOR. + CBORSequence bool + + // FloatPrecisionIndicator specifies appending a suffix to indicate float precision. + // Refer to https://www.rfc-editor.org/rfc/rfc8949.html#name-encoding-indicators. + FloatPrecisionIndicator bool + + // MaxNestedLevels specifies the max nested levels allowed for any combination of CBOR array, maps, and tags. + // Default is 32 levels and it can be set to [4, 65535]. Note that higher maximum levels of nesting can + // require larger amounts of stack to deserialize. Don't increase this higher than you require. + MaxNestedLevels int + + // MaxArrayElements specifies the max number of elements for CBOR arrays. + // Default is 128*1024=131072 and it can be set to [16, 2147483647] + MaxArrayElements int + + // MaxMapPairs specifies the max number of key-value pairs for CBOR maps. + // Default is 128*1024=131072 and it can be set to [16, 2147483647] + MaxMapPairs int +} + +// DiagMode returns a DiagMode with immutable options. +func (opts DiagOptions) DiagMode() (DiagMode, error) { + return opts.diagMode() +} + +func (opts DiagOptions) diagMode() (*diagMode, error) { + if err := opts.ByteStringEncoding.valid(); err != nil { + return nil, err + } + + decMode, err := DecOptions{ + MaxNestedLevels: opts.MaxNestedLevels, + MaxArrayElements: opts.MaxArrayElements, + MaxMapPairs: opts.MaxMapPairs, + }.decMode() + if err != nil { + return nil, err + } + + return &diagMode{ + byteStringEncoding: opts.ByteStringEncoding, + byteStringHexWhitespace: opts.ByteStringHexWhitespace, + byteStringText: opts.ByteStringText, + byteStringEmbeddedCBOR: opts.ByteStringEmbeddedCBOR, + cborSequence: opts.CBORSequence, + floatPrecisionIndicator: opts.FloatPrecisionIndicator, + decMode: decMode, + }, nil +} + +type diagMode struct { + byteStringEncoding ByteStringEncoding + byteStringHexWhitespace bool + byteStringText bool + byteStringEmbeddedCBOR bool + cborSequence bool + floatPrecisionIndicator bool + decMode *decMode +} + +// DiagOptions returns user specified options used to create this DiagMode. +func (dm *diagMode) DiagOptions() DiagOptions { + return DiagOptions{ + ByteStringEncoding: dm.byteStringEncoding, + ByteStringHexWhitespace: dm.byteStringHexWhitespace, + ByteStringText: dm.byteStringText, + ByteStringEmbeddedCBOR: dm.byteStringEmbeddedCBOR, + CBORSequence: dm.cborSequence, + FloatPrecisionIndicator: dm.floatPrecisionIndicator, + MaxNestedLevels: dm.decMode.maxNestedLevels, + MaxArrayElements: dm.decMode.maxArrayElements, + MaxMapPairs: dm.decMode.maxMapPairs, + } +} + +// Diagnose returns extended diagnostic notation (EDN) of CBOR data items using the DiagMode. +func (dm *diagMode) Diagnose(data []byte) (string, error) { + return newDiagnose(data, dm.decMode, dm).diag(dm.cborSequence) +} + +// DiagnoseFirst returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest. +func (dm *diagMode) DiagnoseFirst(data []byte) (diagNotation string, rest []byte, err error) { + return newDiagnose(data, dm.decMode, dm).diagFirst() +} + +var defaultDiagMode, _ = DiagOptions{}.diagMode() + +// Diagnose returns extended diagnostic notation (EDN) of CBOR data items +// using the default diagnostic mode. +// +// Refer to https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation. +func Diagnose(data []byte) (string, error) { + return defaultDiagMode.Diagnose(data) +} + +// Diagnose returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest. +func DiagnoseFirst(data []byte) (diagNotation string, rest []byte, err error) { + return defaultDiagMode.DiagnoseFirst(data) +} + +type diagnose struct { + dm *diagMode + d *decoder + w *bytes.Buffer +} + +func newDiagnose(data []byte, decm *decMode, diagm *diagMode) *diagnose { + return &diagnose{ + dm: diagm, + d: &decoder{data: data, dm: decm}, + w: &bytes.Buffer{}, + } +} + +func (di *diagnose) diag(cborSequence bool) (string, error) { + // CBOR Sequence + firstItem := true + for { + switch err := di.wellformed(cborSequence); err { + case nil: + if !firstItem { + di.w.WriteString(", ") + } + firstItem = false + if itemErr := di.item(); itemErr != nil { + return di.w.String(), itemErr + } + + case io.EOF: + if firstItem { + return di.w.String(), err + } + return di.w.String(), nil + + default: + return di.w.String(), err + } + } +} + +func (di *diagnose) diagFirst() (diagNotation string, rest []byte, err error) { + err = di.wellformed(true) + if err == nil { + err = di.item() + } + + if err == nil { + // Return EDN and the rest of the data slice (which might be len 0) + return di.w.String(), di.d.data[di.d.off:], nil + } + + return di.w.String(), nil, err +} + +func (di *diagnose) wellformed(allowExtraData bool) error { + off := di.d.off + err := di.d.wellformed(allowExtraData, false) + di.d.off = off + return err +} + +func (di *diagnose) item() error { //nolint:gocyclo + initialByte := di.d.data[di.d.off] + switch initialByte { + case cborByteStringWithIndefiniteLengthHead, + cborTextStringWithIndefiniteLengthHead: // indefinite-length byte/text string + di.d.off++ + if isBreakFlag(di.d.data[di.d.off]) { + di.d.off++ + switch initialByte { + case cborByteStringWithIndefiniteLengthHead: + // indefinite-length bytes with no chunks. + di.w.WriteString(`''_`) + return nil + case cborTextStringWithIndefiniteLengthHead: + // indefinite-length text with no chunks. + di.w.WriteString(`""_`) + return nil + } + } + + di.w.WriteString("(_ ") + + i := 0 + for !di.d.foundBreak() { + if i > 0 { + di.w.WriteString(", ") + } + + i++ + // wellformedIndefiniteString() already checked that the next item is a byte/text string. + if err := di.item(); err != nil { + return err + } + } + + di.w.WriteByte(')') + return nil + + case cborArrayWithIndefiniteLengthHead: // indefinite-length array + di.d.off++ + di.w.WriteString("[_ ") + + i := 0 + for !di.d.foundBreak() { + if i > 0 { + di.w.WriteString(", ") + } + + i++ + if err := di.item(); err != nil { + return err + } + } + + di.w.WriteByte(']') + return nil + + case cborMapWithIndefiniteLengthHead: // indefinite-length map + di.d.off++ + di.w.WriteString("{_ ") + + i := 0 + for !di.d.foundBreak() { + if i > 0 { + di.w.WriteString(", ") + } + + i++ + // key + if err := di.item(); err != nil { + return err + } + + di.w.WriteString(": ") + + // value + if err := di.item(); err != nil { + return err + } + } + + di.w.WriteByte('}') + return nil + } + + t := di.d.nextCBORType() + switch t { + case cborTypePositiveInt: + _, _, val := di.d.getHead() + di.w.WriteString(strconv.FormatUint(val, 10)) + return nil + + case cborTypeNegativeInt: + _, _, val := di.d.getHead() + if val > math.MaxInt64 { + // CBOR negative integer overflows int64, use big.Int to store value. + bi := new(big.Int) + bi.SetUint64(val) + bi.Add(bi, big.NewInt(1)) + bi.Neg(bi) + di.w.WriteString(bi.String()) + return nil + } + + nValue := int64(-1) ^ int64(val) + di.w.WriteString(strconv.FormatInt(nValue, 10)) + return nil + + case cborTypeByteString: + b, _ := di.d.parseByteString() + return di.encodeByteString(b) + + case cborTypeTextString: + b, err := di.d.parseTextString() + if err != nil { + return err + } + return di.encodeTextString(string(b), '"') + + case cborTypeArray: + _, _, val := di.d.getHead() + count := int(val) + di.w.WriteByte('[') + + for i := 0; i < count; i++ { + if i > 0 { + di.w.WriteString(", ") + } + if err := di.item(); err != nil { + return err + } + } + di.w.WriteByte(']') + return nil + + case cborTypeMap: + _, _, val := di.d.getHead() + count := int(val) + di.w.WriteByte('{') + + for i := 0; i < count; i++ { + if i > 0 { + di.w.WriteString(", ") + } + // key + if err := di.item(); err != nil { + return err + } + di.w.WriteString(": ") + // value + if err := di.item(); err != nil { + return err + } + } + di.w.WriteByte('}') + return nil + + case cborTypeTag: + _, _, tagNum := di.d.getHead() + switch tagNum { + case tagNumUnsignedBignum: + if nt := di.d.nextCBORType(); nt != cborTypeByteString { + return newInadmissibleTagContentTypeError( + tagNumUnsignedBignum, + "byte string", + nt.String()) + } + + b, _ := di.d.parseByteString() + bi := new(big.Int).SetBytes(b) + di.w.WriteString(bi.String()) + return nil + + case tagNumNegativeBignum: + if nt := di.d.nextCBORType(); nt != cborTypeByteString { + return newInadmissibleTagContentTypeError( + tagNumNegativeBignum, + "byte string", + nt.String(), + ) + } + + b, _ := di.d.parseByteString() + bi := new(big.Int).SetBytes(b) + bi.Add(bi, big.NewInt(1)) + bi.Neg(bi) + di.w.WriteString(bi.String()) + return nil + + default: + di.w.WriteString(strconv.FormatUint(tagNum, 10)) + di.w.WriteByte('(') + if err := di.item(); err != nil { + return err + } + di.w.WriteByte(')') + return nil + } + + case cborTypePrimitives: + _, ai, val := di.d.getHead() + switch ai { + case additionalInformationAsFalse: + di.w.WriteString("false") + return nil + + case additionalInformationAsTrue: + di.w.WriteString("true") + return nil + + case additionalInformationAsNull: + di.w.WriteString("null") + return nil + + case additionalInformationAsUndefined: + di.w.WriteString("undefined") + return nil + + case additionalInformationAsFloat16, + additionalInformationAsFloat32, + additionalInformationAsFloat64: + return di.encodeFloat(ai, val) + + default: + di.w.WriteString("simple(") + di.w.WriteString(strconv.FormatUint(val, 10)) + di.w.WriteByte(')') + return nil + } + } + + return nil +} + +// writeU16 format a rune as "\uxxxx" +func (di *diagnose) writeU16(val rune) { + di.w.WriteString("\\u") + var in [2]byte + in[0] = byte(val >> 8) + in[1] = byte(val) + sz := hex.EncodedLen(len(in)) + di.w.Grow(sz) + dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz] + hex.Encode(dst, in[:]) + di.w.Write(dst) +} + +var rawBase32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding) +var rawBase32HexEncoding = base32.HexEncoding.WithPadding(base32.NoPadding) + +func (di *diagnose) encodeByteString(val []byte) error { + if len(val) > 0 { + if di.dm.byteStringText && utf8.Valid(val) { + return di.encodeTextString(string(val), '\'') + } + + if di.dm.byteStringEmbeddedCBOR { + di2 := newDiagnose(val, di.dm.decMode, di.dm) + // should always notating embedded CBOR sequence. + if str, err := di2.diag(true); err == nil { + di.w.WriteString("<<") + di.w.WriteString(str) + di.w.WriteString(">>") + return nil + } + } + } + + switch di.dm.byteStringEncoding { + case ByteStringBase16Encoding: + di.w.WriteString("h'") + if di.dm.byteStringHexWhitespace { + sz := hex.EncodedLen(len(val)) + if len(val) > 0 { + sz += len(val) - 1 + } + di.w.Grow(sz) + + dst := di.w.Bytes()[di.w.Len():] + for i := range val { + if i > 0 { + dst = append(dst, ' ') + } + hex.Encode(dst[len(dst):len(dst)+2], val[i:i+1]) + dst = dst[:len(dst)+2] + } + di.w.Write(dst) + } else { + sz := hex.EncodedLen(len(val)) + di.w.Grow(sz) + dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz] + hex.Encode(dst, val) + di.w.Write(dst) + } + di.w.WriteByte('\'') + return nil + + case ByteStringBase32Encoding: + di.w.WriteString("b32'") + sz := rawBase32Encoding.EncodedLen(len(val)) + di.w.Grow(sz) + dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz] + rawBase32Encoding.Encode(dst, val) + di.w.Write(dst) + di.w.WriteByte('\'') + return nil + + case ByteStringBase32HexEncoding: + di.w.WriteString("h32'") + sz := rawBase32HexEncoding.EncodedLen(len(val)) + di.w.Grow(sz) + dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz] + rawBase32HexEncoding.Encode(dst, val) + di.w.Write(dst) + di.w.WriteByte('\'') + return nil + + case ByteStringBase64Encoding: + di.w.WriteString("b64'") + sz := base64.RawURLEncoding.EncodedLen(len(val)) + di.w.Grow(sz) + dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz] + base64.RawURLEncoding.Encode(dst, val) + di.w.Write(dst) + di.w.WriteByte('\'') + return nil + + default: + // It should not be possible for users to construct a *diagMode with an invalid byte + // string encoding. + panic(fmt.Sprintf("diagmode has invalid ByteStringEncoding %v", di.dm.byteStringEncoding)) + } +} + +const utf16SurrSelf = rune(0x10000) + +// quote should be either `'` or `"` +func (di *diagnose) encodeTextString(val string, quote byte) error { + di.w.WriteByte(quote) + + for i := 0; i < len(val); { + if b := val[i]; b < utf8.RuneSelf { + switch { + case b == '\t', b == '\n', b == '\r', b == '\\', b == quote: + di.w.WriteByte('\\') + + switch b { + case '\t': + b = 't' + case '\n': + b = 'n' + case '\r': + b = 'r' + } + di.w.WriteByte(b) + + case b >= ' ' && b <= '~': + di.w.WriteByte(b) + + default: + di.writeU16(rune(b)) + } + + i++ + continue + } + + c, size := utf8.DecodeRuneInString(val[i:]) + switch { + case c == utf8.RuneError: + return &SemanticError{"cbor: invalid UTF-8 string"} + + case c < utf16SurrSelf: + di.writeU16(c) + + default: + c1, c2 := utf16.EncodeRune(c) + di.writeU16(c1) + di.writeU16(c2) + } + + i += size + } + + di.w.WriteByte(quote) + return nil +} + +func (di *diagnose) encodeFloat(ai byte, val uint64) error { + f64 := float64(0) + switch ai { + case additionalInformationAsFloat16: + f16 := float16.Frombits(uint16(val)) + switch { + case f16.IsNaN(): + di.w.WriteString("NaN") + return nil + case f16.IsInf(1): + di.w.WriteString("Infinity") + return nil + case f16.IsInf(-1): + di.w.WriteString("-Infinity") + return nil + default: + f64 = float64(f16.Float32()) + } + + case additionalInformationAsFloat32: + f32 := math.Float32frombits(uint32(val)) + switch { + case f32 != f32: + di.w.WriteString("NaN") + return nil + case f32 > math.MaxFloat32: + di.w.WriteString("Infinity") + return nil + case f32 < -math.MaxFloat32: + di.w.WriteString("-Infinity") + return nil + default: + f64 = float64(f32) + } + + case additionalInformationAsFloat64: + f64 = math.Float64frombits(val) + switch { + case f64 != f64: + di.w.WriteString("NaN") + return nil + case f64 > math.MaxFloat64: + di.w.WriteString("Infinity") + return nil + case f64 < -math.MaxFloat64: + di.w.WriteString("-Infinity") + return nil + } + } + // Use ES6 number to string conversion which should match most JSON generators. + // Inspired by https://github.com/golang/go/blob/4df10fba1687a6d4f51d7238a403f8f2298f6a16/src/encoding/json/encode.go#L585 + const bitSize = 64 + b := make([]byte, 0, 32) + if abs := math.Abs(f64); abs != 0 && (abs < 1e-6 || abs >= 1e21) { + b = strconv.AppendFloat(b, f64, 'e', -1, bitSize) + // clean up e-09 to e-9 + n := len(b) + if n >= 4 && string(b[n-4:n-1]) == "e-0" { + b = append(b[:n-2], b[n-1]) + } + } else { + b = strconv.AppendFloat(b, f64, 'f', -1, bitSize) + } + + // add decimal point and trailing zero if needed + if bytes.IndexByte(b, '.') < 0 { + if i := bytes.IndexByte(b, 'e'); i < 0 { + b = append(b, '.', '0') + } else { + b = append(b[:i+2], b[i:]...) + b[i] = '.' + b[i+1] = '0' + } + } + + di.w.WriteString(string(b)) + + if di.dm.floatPrecisionIndicator { + switch ai { + case additionalInformationAsFloat16: + di.w.WriteString("_1") + return nil + + case additionalInformationAsFloat32: + di.w.WriteString("_2") + return nil + + case additionalInformationAsFloat64: + di.w.WriteString("_3") + return nil + } + } + + return nil +} diff --git a/vendor/github.com/fxamacker/cbor/v2/doc.go b/vendor/github.com/fxamacker/cbor/v2/doc.go new file mode 100644 index 0000000000..c758b73748 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/doc.go @@ -0,0 +1,152 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/* +Package cbor is a modern CBOR codec (RFC 8949 & RFC 8742) with CBOR tags, +Go struct tag options (toarray/keyasint/omitempty/omitzero), Core Deterministic Encoding, +CTAP2, Canonical CBOR, float64->32->16, and duplicate map key detection. + +Encoding options allow "preferred serialization" by encoding integers and floats +to their smallest forms (e.g. float16) when values fit. + +Struct tag options "keyasint", "toarray", "omitempty", and "omitzero" reduce encoding size +and reduce programming effort. + +For example, "toarray" tag makes struct fields encode to CBOR array elements. And +"keyasint" makes a field encode to an element of CBOR map with specified int key. + +Latest docs can be viewed at https://github.com/fxamacker/cbor#cbor-library-in-go + +# Basics + +The Quick Start guide is at https://github.com/fxamacker/cbor#quick-start + +Function signatures identical to encoding/json include: + + Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode + +Standard interfaces include: + + BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler + +Diagnostic functions translate CBOR data item into Diagnostic Notation: + + Diagnose, DiagnoseFirst + +Functions that simplify using CBOR Sequences (RFC 8742) include: + + UnmarshalFirst + +Custom encoding and decoding is possible by implementing standard interfaces for +user-defined Go types. + +Codec functions are available at package-level (using defaults options) or by +creating modes from options at runtime. + +"Mode" in this API means definite way of encoding (EncMode) or decoding (DecMode). + +EncMode and DecMode interfaces are created from EncOptions or DecOptions structs. + + em, err := cbor.EncOptions{...}.EncMode() + em, err := cbor.CanonicalEncOptions().EncMode() + em, err := cbor.CTAP2EncOptions().EncMode() + +Modes use immutable options to avoid side-effects and simplify concurrency. Behavior of +modes won't accidentally change at runtime after they're created. + +Modes are intended to be reused and are safe for concurrent use. + +EncMode and DecMode Interfaces + + // EncMode interface uses immutable options and is safe for concurrent use. + type EncMode interface { + Marshal(v interface{}) ([]byte, error) + NewEncoder(w io.Writer) *Encoder + EncOptions() EncOptions // returns copy of options + } + + // DecMode interface uses immutable options and is safe for concurrent use. + type DecMode interface { + Unmarshal(data []byte, v interface{}) error + NewDecoder(r io.Reader) *Decoder + DecOptions() DecOptions // returns copy of options + } + +Using Default Encoding Mode + + b, err := cbor.Marshal(v) + + encoder := cbor.NewEncoder(w) + err = encoder.Encode(v) + +Using Default Decoding Mode + + err := cbor.Unmarshal(b, &v) + + decoder := cbor.NewDecoder(r) + err = decoder.Decode(&v) + +Using Default Mode of UnmarshalFirst to Decode CBOR Sequences + + // Decode the first CBOR data item and return remaining bytes: + rest, err = cbor.UnmarshalFirst(b, &v) // decode []byte b to v + +Using Extended Diagnostic Notation (EDN) to represent CBOR data + + // Translate the first CBOR data item into text and return remaining bytes. + text, rest, err = cbor.DiagnoseFirst(b) // decode []byte b to text + +Creating and Using Encoding Modes + + // Create EncOptions using either struct literal or a function. + opts := cbor.CanonicalEncOptions() + + // If needed, modify encoding options + opts.Time = cbor.TimeUnix + + // Create reusable EncMode interface with immutable options, safe for concurrent use. + em, err := opts.EncMode() + + // Use EncMode like encoding/json, with same function signatures. + b, err := em.Marshal(v) + // or + encoder := em.NewEncoder(w) + err := encoder.Encode(v) + + // NOTE: Both em.Marshal(v) and encoder.Encode(v) use encoding options + // specified during creation of em (encoding mode). + +# CBOR Options + +Predefined Encoding Options: https://github.com/fxamacker/cbor#predefined-encoding-options + +Encoding Options: https://github.com/fxamacker/cbor#encoding-options + +Decoding Options: https://github.com/fxamacker/cbor#decoding-options + +# Struct Tags + +Struct tags like `cbor:"name,omitempty"` and `json:"name,omitempty"` work as expected. +If both struct tags are specified then `cbor` is used. + +Struct tag options like "keyasint", "toarray", "omitempty", and "omitzero" make it easy to use +very compact formats like COSE and CWT (CBOR Web Tokens) with structs. + +The "omitzero" option omits zero values from encoding, matching +[stdlib encoding/json behavior](https://pkg.go.dev/encoding/json#Marshal). +When specified in the `cbor` tag, the option is always honored. +When specified in the `json` tag, the option is honored when building with Go 1.24+. + +For example, "toarray" makes struct fields encode to array elements. And "keyasint" +makes struct fields encode to elements of CBOR map with int keys. + +https://raw.githubusercontent.com/fxamacker/images/master/cbor/v2.0.0/cbor_easy_api.png + +Struct tag options are listed at https://github.com/fxamacker/cbor#struct-tags-1 + +# Tests and Fuzzing + +Over 375 tests are included in this package. Cover-guided fuzzing is handled by +a private fuzzer that replaced fxamacker/cbor-fuzz years ago. +*/ +package cbor diff --git a/vendor/github.com/fxamacker/cbor/v2/encode.go b/vendor/github.com/fxamacker/cbor/v2/encode.go new file mode 100644 index 0000000000..c550617c38 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/encode.go @@ -0,0 +1,2299 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "bytes" + "encoding" + "encoding/binary" + "errors" + "fmt" + "io" + "math" + "math/big" + "math/rand" + "reflect" + "sort" + "strconv" + "sync" + "time" + + "github.com/x448/float16" +) + +// Marshal returns the CBOR encoding of v using default encoding options. +// See EncOptions for encoding options. +// +// Marshal uses the following encoding rules: +// +// If value implements the Marshaler interface, Marshal calls its +// MarshalCBOR method. +// +// If value implements encoding.BinaryMarshaler, Marhsal calls its +// MarshalBinary method and encode it as CBOR byte string. +// +// Boolean values encode as CBOR booleans (type 7). +// +// Positive integer values encode as CBOR positive integers (type 0). +// +// Negative integer values encode as CBOR negative integers (type 1). +// +// Floating point values encode as CBOR floating points (type 7). +// +// String values encode as CBOR text strings (type 3). +// +// []byte values encode as CBOR byte strings (type 2). +// +// Array and slice values encode as CBOR arrays (type 4). +// +// Map values encode as CBOR maps (type 5). +// +// Struct values encode as CBOR maps (type 5). Each exported struct field +// becomes a pair with field name encoded as CBOR text string (type 3) and +// field value encoded based on its type. See struct tag option "keyasint" +// to encode field name as CBOR integer (type 0 and 1). Also see struct +// tag option "toarray" for special field "_" to encode struct values as +// CBOR array (type 4). +// +// Marshal supports format string stored under the "cbor" key in the struct +// field's tag. CBOR format string can specify the name of the field, +// "omitempty", "omitzero" and "keyasint" options, and special case "-" for +// field omission. If "cbor" key is absent, Marshal uses "json" key. +// When using the "json" key, the "omitzero" option is honored when building +// with Go 1.24+ to match stdlib encoding/json behavior. +// +// Struct field name is treated as integer if it has "keyasint" option in +// its format string. The format string must specify an integer as its +// field name. +// +// Special struct field "_" is used to specify struct level options, such as +// "toarray". "toarray" option enables Go struct to be encoded as CBOR array. +// "omitempty" and "omitzero" are disabled by "toarray" to ensure that the +// same number of elements are encoded every time. +// +// Anonymous struct fields are marshaled as if their exported fields +// were fields in the outer struct. Marshal follows the same struct fields +// visibility rules used by JSON encoding package. +// +// time.Time values encode as text strings specified in RFC3339 or numerical +// representation of seconds since January 1, 1970 UTC depending on +// EncOptions.Time setting. Also See EncOptions.TimeTag to encode +// time.Time as CBOR tag with tag number 0 or 1. +// +// big.Int values encode as CBOR integers (type 0 and 1) if values fit. +// Otherwise, big.Int values encode as CBOR bignums (tag 2 and 3). See +// EncOptions.BigIntConvert to always encode big.Int values as CBOR +// bignums. +// +// Pointer values encode as the value pointed to. +// +// Interface values encode as the value stored in the interface. +// +// Nil slice/map/pointer/interface values encode as CBOR nulls (type 7). +// +// Values of other types cannot be encoded in CBOR. Attempting +// to encode such a value causes Marshal to return an UnsupportedTypeError. +func Marshal(v any) ([]byte, error) { + return defaultEncMode.Marshal(v) +} + +// MarshalToBuffer encodes v into provided buffer (instead of using built-in buffer pool) +// and uses default encoding options. +// +// NOTE: Unlike Marshal, the buffer provided to MarshalToBuffer can contain +// partially encoded data if error is returned. +// +// See Marshal for more details. +func MarshalToBuffer(v any, buf *bytes.Buffer) error { + return defaultEncMode.MarshalToBuffer(v, buf) +} + +// Marshaler is the interface implemented by types that can marshal themselves +// into valid CBOR. +type Marshaler interface { + MarshalCBOR() ([]byte, error) +} + +// MarshalerError represents error from checking encoded CBOR data item +// returned from MarshalCBOR for well-formedness and some very limited tag validation. +type MarshalerError struct { + typ reflect.Type + err error +} + +func (e *MarshalerError) Error() string { + return "cbor: error calling MarshalCBOR for type " + + e.typ.String() + + ": " + e.err.Error() +} + +func (e *MarshalerError) Unwrap() error { + return e.err +} + +type TranscodeError struct { + err error + rtype reflect.Type + sourceFormat, targetFormat string +} + +func (e TranscodeError) Error() string { + return "cbor: cannot transcode from " + e.sourceFormat + " to " + e.targetFormat + ": " + e.err.Error() +} + +func (e TranscodeError) Unwrap() error { + return e.err +} + +// UnsupportedTypeError is returned by Marshal when attempting to encode value +// of an unsupported type. +type UnsupportedTypeError struct { + Type reflect.Type +} + +func (e *UnsupportedTypeError) Error() string { + return "cbor: unsupported type: " + e.Type.String() +} + +// UnsupportedValueError is returned by Marshal when attempting to encode an +// unsupported value. +type UnsupportedValueError struct { + msg string +} + +func (e *UnsupportedValueError) Error() string { + return "cbor: unsupported value: " + e.msg +} + +// SortMode identifies supported sorting order. +type SortMode int + +const ( + // SortNone encodes map pairs and struct fields in an arbitrary order. + SortNone SortMode = 0 + + // SortLengthFirst causes map keys or struct fields to be sorted such that: + // - If two keys have different lengths, the shorter one sorts earlier; + // - If two keys have the same length, the one with the lower value in + // (byte-wise) lexical order sorts earlier. + // It is used in "Canonical CBOR" encoding in RFC 7049 3.9. + SortLengthFirst SortMode = 1 + + // SortBytewiseLexical causes map keys or struct fields to be sorted in the + // bytewise lexicographic order of their deterministic CBOR encodings. + // It is used in "CTAP2 Canonical CBOR" and "Core Deterministic Encoding" + // in RFC 7049bis. + SortBytewiseLexical SortMode = 2 + + // SortShuffle encodes map pairs and struct fields in a shuffled + // order. This mode does not guarantee an unbiased permutation, but it + // does guarantee that the runtime of the shuffle algorithm used will be + // constant. + SortFastShuffle SortMode = 3 + + // SortCanonical is used in "Canonical CBOR" encoding in RFC 7049 3.9. + SortCanonical SortMode = SortLengthFirst + + // SortCTAP2 is used in "CTAP2 Canonical CBOR". + SortCTAP2 SortMode = SortBytewiseLexical + + // SortCoreDeterministic is used in "Core Deterministic Encoding" in RFC 7049bis. + SortCoreDeterministic SortMode = SortBytewiseLexical + + maxSortMode SortMode = 4 +) + +func (sm SortMode) valid() bool { + return sm >= 0 && sm < maxSortMode +} + +// StringMode specifies how to encode Go string values. +type StringMode int + +const ( + // StringToTextString encodes Go string to CBOR text string (major type 3). + StringToTextString StringMode = iota + + // StringToByteString encodes Go string to CBOR byte string (major type 2). + StringToByteString +) + +func (st StringMode) cborType() (cborType, error) { + switch st { + case StringToTextString: + return cborTypeTextString, nil + + case StringToByteString: + return cborTypeByteString, nil + } + return 0, errors.New("cbor: invalid StringType " + strconv.Itoa(int(st))) +} + +// ShortestFloatMode specifies which floating-point format should +// be used as the shortest possible format for CBOR encoding. +// It is not used for encoding Infinity and NaN values. +type ShortestFloatMode int + +const ( + // ShortestFloatNone makes float values encode without any conversion. + // This is the default for ShortestFloatMode in v1. + // E.g. a float32 in Go will encode to CBOR float32. And + // a float64 in Go will encode to CBOR float64. + ShortestFloatNone ShortestFloatMode = iota + + // ShortestFloat16 specifies float16 as the shortest form that preserves value. + // E.g. if float64 can convert to float32 while preserving value, then + // encoding will also try to convert float32 to float16. So a float64 might + // encode as CBOR float64, float32 or float16 depending on the value. + ShortestFloat16 + + maxShortestFloat +) + +func (sfm ShortestFloatMode) valid() bool { + return sfm >= 0 && sfm < maxShortestFloat +} + +// NaNConvertMode specifies how to encode NaN and overrides ShortestFloatMode. +// ShortestFloatMode is not used for encoding Infinity and NaN values. +type NaNConvertMode int + +const ( + // NaNConvert7e00 always encodes NaN to 0xf97e00 (CBOR float16 = 0x7e00). + NaNConvert7e00 NaNConvertMode = iota + + // NaNConvertNone never modifies or converts NaN to other representations + // (float64 NaN stays float64, etc. even if it can use float16 without losing + // any bits). + NaNConvertNone + + // NaNConvertPreserveSignal converts NaN to the smallest form that preserves + // value (quiet bit + payload) as described in RFC 7049bis Draft 12. + NaNConvertPreserveSignal + + // NaNConvertQuiet always forces quiet bit = 1 and shortest form that preserves + // NaN payload. + NaNConvertQuiet + + // NaNConvertReject returns UnsupportedValueError on attempts to encode a NaN value. + NaNConvertReject + + maxNaNConvert +) + +func (ncm NaNConvertMode) valid() bool { + return ncm >= 0 && ncm < maxNaNConvert +} + +// InfConvertMode specifies how to encode Infinity and overrides ShortestFloatMode. +// ShortestFloatMode is not used for encoding Infinity and NaN values. +type InfConvertMode int + +const ( + // InfConvertFloat16 always converts Inf to lossless IEEE binary16 (float16). + InfConvertFloat16 InfConvertMode = iota + + // InfConvertNone never converts (used by CTAP2 Canonical CBOR). + InfConvertNone + + // InfConvertReject returns UnsupportedValueError on attempts to encode an infinite value. + InfConvertReject + + maxInfConvert +) + +func (icm InfConvertMode) valid() bool { + return icm >= 0 && icm < maxInfConvert +} + +// TimeMode specifies how to encode time.Time values in compliance with RFC 8949 (CBOR): +// - Section 3.4.1: Standard Date/Time String +// - Section 3.4.2: Epoch-Based Date/Time +// For more info, see: +// - https://www.rfc-editor.org/rfc/rfc8949.html +// NOTE: User applications that prefer to encode time with fractional seconds to an integer +// (instead of floating point or text string) can use a CBOR tag number not assigned by IANA: +// 1. Define a user-defined type in Go with just a time.Time or int64 as its data. +// 2. Implement the cbor.Marshaler and cbor.Unmarshaler interface for that user-defined type +// to encode or decode the tagged data item with an enclosed integer content. +type TimeMode int + +const ( + // TimeUnix causes time.Time to encode to a CBOR time (tag 1) with an integer content + // representing seconds elapsed (with 1-second precision) since UNIX Epoch UTC. + // The TimeUnix option is location independent and has a clear precision guarantee. + TimeUnix TimeMode = iota + + // TimeUnixMicro causes time.Time to encode to a CBOR time (tag 1) with a floating point content + // representing seconds elapsed (with up to 1-microsecond precision) since UNIX Epoch UTC. + // NOTE: The floating point content is encoded to the shortest floating-point encoding that preserves + // the 64-bit floating point value. I.e., the floating point encoding can be IEEE 764: + // binary64, binary32, or binary16 depending on the content's value. + TimeUnixMicro + + // TimeUnixDynamic causes time.Time to encode to a CBOR time (tag 1) with either an integer content or + // a floating point content, depending on the content's value. This option is equivalent to dynamically + // choosing TimeUnix if time.Time doesn't have fractional seconds, and using TimeUnixMicro if time.Time + // has fractional seconds. + TimeUnixDynamic + + // TimeRFC3339 causes time.Time to encode to a CBOR time (tag 0) with a text string content + // representing the time using 1-second precision in RFC3339 format. If the time.Time has a + // non-UTC timezone then a "localtime - UTC" numeric offset will be included as specified in RFC3339. + // NOTE: User applications can avoid including the RFC3339 numeric offset by: + // - providing a time.Time value set to UTC, or + // - using the TimeUnix, TimeUnixMicro, or TimeUnixDynamic option instead of TimeRFC3339. + TimeRFC3339 + + // TimeRFC3339Nano causes time.Time to encode to a CBOR time (tag 0) with a text string content + // representing the time using 1-nanosecond precision in RFC3339 format. If the time.Time has a + // non-UTC timezone then a "localtime - UTC" numeric offset will be included as specified in RFC3339. + // NOTE: User applications can avoid including the RFC3339 numeric offset by: + // - providing a time.Time value set to UTC, or + // - using the TimeUnix, TimeUnixMicro, or TimeUnixDynamic option instead of TimeRFC3339Nano. + TimeRFC3339Nano + + maxTimeMode +) + +func (tm TimeMode) valid() bool { + return tm >= 0 && tm < maxTimeMode +} + +// BigIntConvertMode specifies how to encode big.Int values. +type BigIntConvertMode int + +const ( + // BigIntConvertShortest makes big.Int encode to CBOR integer if value fits. + // E.g. if big.Int value can be converted to CBOR integer while preserving + // value, encoder will encode it to CBOR integer (major type 0 or 1). + BigIntConvertShortest BigIntConvertMode = iota + + // BigIntConvertNone makes big.Int encode to CBOR bignum (tag 2 or 3) without + // converting it to another CBOR type. + BigIntConvertNone + + // BigIntConvertReject returns an UnsupportedTypeError instead of marshaling a big.Int. + BigIntConvertReject + + maxBigIntConvert +) + +func (bim BigIntConvertMode) valid() bool { + return bim >= 0 && bim < maxBigIntConvert +} + +// NilContainersMode specifies how to encode nil slices and maps. +type NilContainersMode int + +const ( + // NilContainerAsNull encodes nil slices and maps as CBOR null. + // This is the default. + NilContainerAsNull NilContainersMode = iota + + // NilContainerAsEmpty encodes nil slices and maps as + // empty container (CBOR bytestring, array, or map). + NilContainerAsEmpty + + maxNilContainersMode +) + +func (m NilContainersMode) valid() bool { + return m >= 0 && m < maxNilContainersMode +} + +// OmitEmptyMode specifies how to encode struct fields with omitempty tag. +// The default behavior omits if field value would encode as empty CBOR value. +type OmitEmptyMode int + +const ( + // OmitEmptyCBORValue specifies that struct fields tagged with "omitempty" + // should be omitted from encoding if the field would be encoded as an empty + // CBOR value, such as CBOR false, 0, 0.0, nil, empty byte, empty string, + // empty array, or empty map. + OmitEmptyCBORValue OmitEmptyMode = iota + + // OmitEmptyGoValue specifies that struct fields tagged with "omitempty" + // should be omitted from encoding if the field has an empty Go value, + // defined as false, 0, 0.0, a nil pointer, a nil interface value, and + // any empty array, slice, map, or string. + // This behavior is the same as the current (aka v1) encoding/json package + // included in Go. + OmitEmptyGoValue + + maxOmitEmptyMode +) + +func (om OmitEmptyMode) valid() bool { + return om >= 0 && om < maxOmitEmptyMode +} + +// FieldNameMode specifies the CBOR type to use when encoding struct field names. +type FieldNameMode int + +const ( + // FieldNameToTextString encodes struct fields to CBOR text string (major type 3). + FieldNameToTextString FieldNameMode = iota + + // FieldNameToTextString encodes struct fields to CBOR byte string (major type 2). + FieldNameToByteString + + maxFieldNameMode +) + +func (fnm FieldNameMode) valid() bool { + return fnm >= 0 && fnm < maxFieldNameMode +} + +// ByteSliceLaterFormatMode specifies which later format conversion hint (CBOR tag 21-23) +// to include (if any) when encoding Go byte slice to CBOR byte string. The encoder will +// always encode unmodified bytes from the byte slice and just wrap it within +// CBOR tag 21, 22, or 23 if specified. +// See "Expected Later Encoding for CBOR-to-JSON Converters" in RFC 8949 Section 3.4.5.2. +type ByteSliceLaterFormatMode int + +const ( + // ByteSliceLaterFormatNone encodes unmodified bytes from Go byte slice to CBOR byte string (major type 2) + // without adding CBOR tag 21, 22, or 23. + ByteSliceLaterFormatNone ByteSliceLaterFormatMode = iota + + // ByteSliceLaterFormatBase64URL encodes unmodified bytes from Go byte slice to CBOR byte string (major type 2) + // inside CBOR tag 21 (expected later conversion to base64url encoding, see RFC 8949 Section 3.4.5.2). + ByteSliceLaterFormatBase64URL + + // ByteSliceLaterFormatBase64 encodes unmodified bytes from Go byte slice to CBOR byte string (major type 2) + // inside CBOR tag 22 (expected later conversion to base64 encoding, see RFC 8949 Section 3.4.5.2). + ByteSliceLaterFormatBase64 + + // ByteSliceLaterFormatBase16 encodes unmodified bytes from Go byte slice to CBOR byte string (major type 2) + // inside CBOR tag 23 (expected later conversion to base16 encoding, see RFC 8949 Section 3.4.5.2). + ByteSliceLaterFormatBase16 +) + +func (bsefm ByteSliceLaterFormatMode) encodingTag() (uint64, error) { + switch bsefm { + case ByteSliceLaterFormatNone: + return 0, nil + + case ByteSliceLaterFormatBase64URL: + return tagNumExpectedLaterEncodingBase64URL, nil + + case ByteSliceLaterFormatBase64: + return tagNumExpectedLaterEncodingBase64, nil + + case ByteSliceLaterFormatBase16: + return tagNumExpectedLaterEncodingBase16, nil + } + return 0, errors.New("cbor: invalid ByteSliceLaterFormat " + strconv.Itoa(int(bsefm))) +} + +// ByteArrayMode specifies how to encode byte arrays. +type ByteArrayMode int + +const ( + // ByteArrayToByteSlice encodes byte arrays the same way that a byte slice with identical + // length and contents is encoded. + ByteArrayToByteSlice ByteArrayMode = iota + + // ByteArrayToArray encodes byte arrays to the CBOR array type with one unsigned integer + // item for each byte in the array. + ByteArrayToArray + + maxByteArrayMode +) + +func (bam ByteArrayMode) valid() bool { + return bam >= 0 && bam < maxByteArrayMode +} + +// BinaryMarshalerMode specifies how to encode types that implement encoding.BinaryMarshaler. +type BinaryMarshalerMode int + +const ( + // BinaryMarshalerByteString encodes the output of MarshalBinary to a CBOR byte string. + BinaryMarshalerByteString BinaryMarshalerMode = iota + + // BinaryMarshalerNone does not recognize BinaryMarshaler implementations during encode. + BinaryMarshalerNone + + maxBinaryMarshalerMode +) + +func (bmm BinaryMarshalerMode) valid() bool { + return bmm >= 0 && bmm < maxBinaryMarshalerMode +} + +// TextMarshalerMode specifies how to encode types that implement encoding.TextMarshaler. +type TextMarshalerMode int + +const ( + // TextMarshalerNone does not recognize TextMarshaler implementations during encode. + // This is the default behavior. + TextMarshalerNone TextMarshalerMode = iota + + // TextMarshalerTextString encodes the output of MarshalText to a CBOR text string. + TextMarshalerTextString + + maxTextMarshalerMode +) + +func (tmm TextMarshalerMode) valid() bool { + return tmm >= 0 && tmm < maxTextMarshalerMode +} + +// EncOptions specifies encoding options. +type EncOptions struct { + // Sort specifies sorting order. + Sort SortMode + + // ShortestFloat specifies the shortest floating-point encoding that preserves + // the value being encoded. + ShortestFloat ShortestFloatMode + + // NaNConvert specifies how to encode NaN and it overrides ShortestFloatMode. + NaNConvert NaNConvertMode + + // InfConvert specifies how to encode Inf and it overrides ShortestFloatMode. + InfConvert InfConvertMode + + // BigIntConvert specifies how to encode big.Int values. + BigIntConvert BigIntConvertMode + + // Time specifies how to encode time.Time. + Time TimeMode + + // TimeTag allows time.Time to be encoded with a tag number. + // RFC3339 format gets tag number 0, and numeric epoch time tag number 1. + TimeTag EncTagMode + + // IndefLength specifies whether to allow indefinite length CBOR items. + IndefLength IndefLengthMode + + // NilContainers specifies how to encode nil slices and maps. + NilContainers NilContainersMode + + // TagsMd specifies whether to allow CBOR tags (major type 6). + TagsMd TagsMode + + // OmitEmptyMode specifies how to encode struct fields with omitempty tag. + OmitEmpty OmitEmptyMode + + // String specifies which CBOR type to use when encoding Go strings. + // - CBOR text string (major type 3) is default + // - CBOR byte string (major type 2) + String StringMode + + // FieldName specifies the CBOR type to use when encoding struct field names. + FieldName FieldNameMode + + // ByteSliceLaterFormat specifies which later format conversion hint (CBOR tag 21-23) + // to include (if any) when encoding Go byte slice to CBOR byte string. The encoder will + // always encode unmodified bytes from the byte slice and just wrap it within + // CBOR tag 21, 22, or 23 if specified. + // See "Expected Later Encoding for CBOR-to-JSON Converters" in RFC 8949 Section 3.4.5.2. + ByteSliceLaterFormat ByteSliceLaterFormatMode + + // ByteArray specifies how to encode byte arrays. + ByteArray ByteArrayMode + + // BinaryMarshaler specifies how to encode types that implement encoding.BinaryMarshaler. + BinaryMarshaler BinaryMarshalerMode + + // TextMarshaler specifies how to encode types that implement encoding.TextMarshaler. + TextMarshaler TextMarshalerMode + + // JSONMarshalerTranscoder sets the transcoding scheme used to marshal types that implement + // json.Marshaler but do not also implement cbor.Marshaler. If nil, encoding behavior is not + // influenced by whether or not a type implements json.Marshaler. + JSONMarshalerTranscoder Transcoder +} + +// CanonicalEncOptions returns EncOptions for "Canonical CBOR" encoding, +// defined in RFC 7049 Section 3.9 with the following rules: +// +// 1. "Integers must be as small as possible." +// 2. "The expression of lengths in major types 2 through 5 must be as short as possible." +// 3. The keys in every map must be sorted in length-first sorting order. +// See SortLengthFirst for details. +// 4. "Indefinite-length items must be made into definite-length items." +// 5. "If a protocol allows for IEEE floats, then additional canonicalization rules might +// need to be added. One example rule might be to have all floats start as a 64-bit +// float, then do a test conversion to a 32-bit float; if the result is the same numeric +// value, use the shorter value and repeat the process with a test conversion to a +// 16-bit float. (This rule selects 16-bit float for positive and negative Infinity +// as well.) Also, there are many representations for NaN. If NaN is an allowed value, +// it must always be represented as 0xf97e00." +func CanonicalEncOptions() EncOptions { + return EncOptions{ + Sort: SortCanonical, + ShortestFloat: ShortestFloat16, + NaNConvert: NaNConvert7e00, + InfConvert: InfConvertFloat16, + IndefLength: IndefLengthForbidden, + } +} + +// CTAP2EncOptions returns EncOptions for "CTAP2 Canonical CBOR" encoding, +// defined in CTAP specification, with the following rules: +// +// 1. "Integers must be encoded as small as possible." +// 2. "The representations of any floating-point values are not changed." +// 3. "The expression of lengths in major types 2 through 5 must be as short as possible." +// 4. "Indefinite-length items must be made into definite-length items."" +// 5. The keys in every map must be sorted in bytewise lexicographic order. +// See SortBytewiseLexical for details. +// 6. "Tags as defined in Section 2.4 in [RFC7049] MUST NOT be present." +func CTAP2EncOptions() EncOptions { + return EncOptions{ + Sort: SortCTAP2, + ShortestFloat: ShortestFloatNone, + NaNConvert: NaNConvertNone, + InfConvert: InfConvertNone, + IndefLength: IndefLengthForbidden, + TagsMd: TagsForbidden, + } +} + +// CoreDetEncOptions returns EncOptions for "Core Deterministic" encoding, +// defined in RFC 7049bis with the following rules: +// +// 1. "Preferred serialization MUST be used. In particular, this means that arguments +// (see Section 3) for integers, lengths in major types 2 through 5, and tags MUST +// be as short as possible" +// "Floating point values also MUST use the shortest form that preserves the value" +// 2. "Indefinite-length items MUST NOT appear." +// 3. "The keys in every map MUST be sorted in the bytewise lexicographic order of +// their deterministic encodings." +func CoreDetEncOptions() EncOptions { + return EncOptions{ + Sort: SortCoreDeterministic, + ShortestFloat: ShortestFloat16, + NaNConvert: NaNConvert7e00, + InfConvert: InfConvertFloat16, + IndefLength: IndefLengthForbidden, + } +} + +// PreferredUnsortedEncOptions returns EncOptions for "Preferred Serialization" encoding, +// defined in RFC 7049bis with the following rules: +// +// 1. "The preferred serialization always uses the shortest form of representing the argument +// (Section 3);" +// 2. "it also uses the shortest floating-point encoding that preserves the value being +// encoded (see Section 5.5)." +// "The preferred encoding for a floating-point value is the shortest floating-point encoding +// that preserves its value, e.g., 0xf94580 for the number 5.5, and 0xfa45ad9c00 for the +// number 5555.5, unless the CBOR-based protocol specifically excludes the use of the shorter +// floating-point encodings. For NaN values, a shorter encoding is preferred if zero-padding +// the shorter significand towards the right reconstitutes the original NaN value (for many +// applications, the single NaN encoding 0xf97e00 will suffice)." +// 3. "Definite length encoding is preferred whenever the length is known at the time the +// serialization of the item starts." +func PreferredUnsortedEncOptions() EncOptions { + return EncOptions{ + Sort: SortNone, + ShortestFloat: ShortestFloat16, + NaNConvert: NaNConvert7e00, + InfConvert: InfConvertFloat16, + } +} + +// EncMode returns EncMode with immutable options and no tags (safe for concurrency). +func (opts EncOptions) EncMode() (EncMode, error) { //nolint:gocritic // ignore hugeParam + return opts.encMode() +} + +// UserBufferEncMode returns UserBufferEncMode with immutable options and no tags (safe for concurrency). +func (opts EncOptions) UserBufferEncMode() (UserBufferEncMode, error) { //nolint:gocritic // ignore hugeParam + return opts.encMode() +} + +// EncModeWithTags returns EncMode with options and tags that are both immutable (safe for concurrency). +func (opts EncOptions) EncModeWithTags(tags TagSet) (EncMode, error) { //nolint:gocritic // ignore hugeParam + return opts.UserBufferEncModeWithTags(tags) +} + +// UserBufferEncModeWithTags returns UserBufferEncMode with options and tags that are both immutable (safe for concurrency). +func (opts EncOptions) UserBufferEncModeWithTags(tags TagSet) (UserBufferEncMode, error) { //nolint:gocritic // ignore hugeParam + if opts.TagsMd == TagsForbidden { + return nil, errors.New("cbor: cannot create EncMode with TagSet when TagsMd is TagsForbidden") + } + if tags == nil { + return nil, errors.New("cbor: cannot create EncMode with nil value as TagSet") + } + em, err := opts.encMode() + if err != nil { + return nil, err + } + // Copy tags + ts := tagSet(make(map[reflect.Type]*tagItem)) + syncTags := tags.(*syncTagSet) + syncTags.RLock() + for contentType, tag := range syncTags.t { + if tag.opts.EncTag != EncTagNone { + ts[contentType] = tag + } + } + syncTags.RUnlock() + if len(ts) > 0 { + em.tags = ts + } + return em, nil +} + +// EncModeWithSharedTags returns EncMode with immutable options and mutable shared tags (safe for concurrency). +func (opts EncOptions) EncModeWithSharedTags(tags TagSet) (EncMode, error) { //nolint:gocritic // ignore hugeParam + return opts.UserBufferEncModeWithSharedTags(tags) +} + +// UserBufferEncModeWithSharedTags returns UserBufferEncMode with immutable options and mutable shared tags (safe for concurrency). +func (opts EncOptions) UserBufferEncModeWithSharedTags(tags TagSet) (UserBufferEncMode, error) { //nolint:gocritic // ignore hugeParam + if opts.TagsMd == TagsForbidden { + return nil, errors.New("cbor: cannot create EncMode with TagSet when TagsMd is TagsForbidden") + } + if tags == nil { + return nil, errors.New("cbor: cannot create EncMode with nil value as TagSet") + } + em, err := opts.encMode() + if err != nil { + return nil, err + } + em.tags = tags + return em, nil +} + +func (opts EncOptions) encMode() (*encMode, error) { //nolint:gocritic // ignore hugeParam + if !opts.Sort.valid() { + return nil, errors.New("cbor: invalid SortMode " + strconv.Itoa(int(opts.Sort))) + } + if !opts.ShortestFloat.valid() { + return nil, errors.New("cbor: invalid ShortestFloatMode " + strconv.Itoa(int(opts.ShortestFloat))) + } + if !opts.NaNConvert.valid() { + return nil, errors.New("cbor: invalid NaNConvertMode " + strconv.Itoa(int(opts.NaNConvert))) + } + if !opts.InfConvert.valid() { + return nil, errors.New("cbor: invalid InfConvertMode " + strconv.Itoa(int(opts.InfConvert))) + } + if !opts.BigIntConvert.valid() { + return nil, errors.New("cbor: invalid BigIntConvertMode " + strconv.Itoa(int(opts.BigIntConvert))) + } + if !opts.Time.valid() { + return nil, errors.New("cbor: invalid TimeMode " + strconv.Itoa(int(opts.Time))) + } + if !opts.TimeTag.valid() { + return nil, errors.New("cbor: invalid TimeTag " + strconv.Itoa(int(opts.TimeTag))) + } + if !opts.IndefLength.valid() { + return nil, errors.New("cbor: invalid IndefLength " + strconv.Itoa(int(opts.IndefLength))) + } + if !opts.NilContainers.valid() { + return nil, errors.New("cbor: invalid NilContainers " + strconv.Itoa(int(opts.NilContainers))) + } + if !opts.TagsMd.valid() { + return nil, errors.New("cbor: invalid TagsMd " + strconv.Itoa(int(opts.TagsMd))) + } + if opts.TagsMd == TagsForbidden && opts.TimeTag == EncTagRequired { + return nil, errors.New("cbor: cannot set TagsMd to TagsForbidden when TimeTag is EncTagRequired") + } + if !opts.OmitEmpty.valid() { + return nil, errors.New("cbor: invalid OmitEmpty " + strconv.Itoa(int(opts.OmitEmpty))) + } + stringMajorType, err := opts.String.cborType() + if err != nil { + return nil, err + } + if !opts.FieldName.valid() { + return nil, errors.New("cbor: invalid FieldName " + strconv.Itoa(int(opts.FieldName))) + } + byteSliceLaterEncodingTag, err := opts.ByteSliceLaterFormat.encodingTag() + if err != nil { + return nil, err + } + if !opts.ByteArray.valid() { + return nil, errors.New("cbor: invalid ByteArray " + strconv.Itoa(int(opts.ByteArray))) + } + if !opts.BinaryMarshaler.valid() { + return nil, errors.New("cbor: invalid BinaryMarshaler " + strconv.Itoa(int(opts.BinaryMarshaler))) + } + if !opts.TextMarshaler.valid() { + return nil, errors.New("cbor: invalid TextMarshaler " + strconv.Itoa(int(opts.TextMarshaler))) + } + em := encMode{ + sort: opts.Sort, + shortestFloat: opts.ShortestFloat, + nanConvert: opts.NaNConvert, + infConvert: opts.InfConvert, + bigIntConvert: opts.BigIntConvert, + time: opts.Time, + timeTag: opts.TimeTag, + indefLength: opts.IndefLength, + nilContainers: opts.NilContainers, + tagsMd: opts.TagsMd, + omitEmpty: opts.OmitEmpty, + stringType: opts.String, + stringMajorType: stringMajorType, + fieldName: opts.FieldName, + byteSliceLaterFormat: opts.ByteSliceLaterFormat, + byteSliceLaterEncodingTag: byteSliceLaterEncodingTag, + byteArray: opts.ByteArray, + binaryMarshaler: opts.BinaryMarshaler, + textMarshaler: opts.TextMarshaler, + jsonMarshalerTranscoder: opts.JSONMarshalerTranscoder, + } + return &em, nil +} + +// EncMode is the main interface for CBOR encoding. +type EncMode interface { + Marshal(v any) ([]byte, error) + NewEncoder(w io.Writer) *Encoder + EncOptions() EncOptions +} + +// UserBufferEncMode is an interface for CBOR encoding, which extends EncMode by +// adding MarshalToBuffer to support user specified buffer rather than encoding +// into the built-in buffer pool. +type UserBufferEncMode interface { + EncMode + MarshalToBuffer(v any, buf *bytes.Buffer) error + + // This private method is to prevent users implementing + // this interface and so future additions to it will + // not be breaking changes. + // See https://go.dev/blog/module-compatibility + unexport() +} + +type encMode struct { + tags tagProvider + sort SortMode + shortestFloat ShortestFloatMode + nanConvert NaNConvertMode + infConvert InfConvertMode + bigIntConvert BigIntConvertMode + time TimeMode + timeTag EncTagMode + indefLength IndefLengthMode + nilContainers NilContainersMode + tagsMd TagsMode + omitEmpty OmitEmptyMode + stringType StringMode + stringMajorType cborType + fieldName FieldNameMode + byteSliceLaterFormat ByteSliceLaterFormatMode + byteSliceLaterEncodingTag uint64 + byteArray ByteArrayMode + binaryMarshaler BinaryMarshalerMode + textMarshaler TextMarshalerMode + jsonMarshalerTranscoder Transcoder +} + +var defaultEncMode, _ = EncOptions{}.encMode() + +// These four decoding modes are used by getMarshalerDecMode. +// maxNestedLevels, maxArrayElements, and maxMapPairs are +// set to max allowed limits to avoid rejecting Marshaler +// output that would have been the allowable output of a +// non-Marshaler object that exceeds default limits. +var ( + marshalerForbidIndefLengthForbidTagsDecMode = decMode{ + maxNestedLevels: maxMaxNestedLevels, + maxArrayElements: maxMaxArrayElements, + maxMapPairs: maxMaxMapPairs, + indefLength: IndefLengthForbidden, + tagsMd: TagsForbidden, + } + + marshalerAllowIndefLengthForbidTagsDecMode = decMode{ + maxNestedLevels: maxMaxNestedLevels, + maxArrayElements: maxMaxArrayElements, + maxMapPairs: maxMaxMapPairs, + indefLength: IndefLengthAllowed, + tagsMd: TagsForbidden, + } + + marshalerForbidIndefLengthAllowTagsDecMode = decMode{ + maxNestedLevels: maxMaxNestedLevels, + maxArrayElements: maxMaxArrayElements, + maxMapPairs: maxMaxMapPairs, + indefLength: IndefLengthForbidden, + tagsMd: TagsAllowed, + } + + marshalerAllowIndefLengthAllowTagsDecMode = decMode{ + maxNestedLevels: maxMaxNestedLevels, + maxArrayElements: maxMaxArrayElements, + maxMapPairs: maxMaxMapPairs, + indefLength: IndefLengthAllowed, + tagsMd: TagsAllowed, + } +) + +// getMarshalerDecMode returns one of four existing decoding modes +// which can be reused (safe for parallel use) for the purpose of +// checking if data returned by Marshaler is well-formed. +func getMarshalerDecMode(indefLength IndefLengthMode, tagsMd TagsMode) *decMode { + switch { + case indefLength == IndefLengthAllowed && tagsMd == TagsAllowed: + return &marshalerAllowIndefLengthAllowTagsDecMode + + case indefLength == IndefLengthAllowed && tagsMd == TagsForbidden: + return &marshalerAllowIndefLengthForbidTagsDecMode + + case indefLength == IndefLengthForbidden && tagsMd == TagsAllowed: + return &marshalerForbidIndefLengthAllowTagsDecMode + + case indefLength == IndefLengthForbidden && tagsMd == TagsForbidden: + return &marshalerForbidIndefLengthForbidTagsDecMode + + default: + // This should never happen, unless we add new options to + // IndefLengthMode or TagsMode without updating this function. + return &decMode{ + maxNestedLevels: maxMaxNestedLevels, + maxArrayElements: maxMaxArrayElements, + maxMapPairs: maxMaxMapPairs, + indefLength: indefLength, + tagsMd: tagsMd, + } + } +} + +// EncOptions returns user specified options used to create this EncMode. +func (em *encMode) EncOptions() EncOptions { + return EncOptions{ + Sort: em.sort, + ShortestFloat: em.shortestFloat, + NaNConvert: em.nanConvert, + InfConvert: em.infConvert, + BigIntConvert: em.bigIntConvert, + Time: em.time, + TimeTag: em.timeTag, + IndefLength: em.indefLength, + NilContainers: em.nilContainers, + TagsMd: em.tagsMd, + OmitEmpty: em.omitEmpty, + String: em.stringType, + FieldName: em.fieldName, + ByteSliceLaterFormat: em.byteSliceLaterFormat, + ByteArray: em.byteArray, + BinaryMarshaler: em.binaryMarshaler, + TextMarshaler: em.textMarshaler, + JSONMarshalerTranscoder: em.jsonMarshalerTranscoder, + } +} + +func (em *encMode) unexport() {} + +func (em *encMode) encTagBytes(t reflect.Type) []byte { + if em.tags != nil { + if tagItem := em.tags.getTagItemFromType(t); tagItem != nil { + return tagItem.cborTagNum + } + } + return nil +} + +// Marshal returns the CBOR encoding of v using em encoding mode. +// +// See the documentation for Marshal for details. +func (em *encMode) Marshal(v any) ([]byte, error) { + e := getEncodeBuffer() + + if err := encode(e, em, reflect.ValueOf(v)); err != nil { + putEncodeBuffer(e) + return nil, err + } + + buf := make([]byte, e.Len()) + copy(buf, e.Bytes()) + + putEncodeBuffer(e) + return buf, nil +} + +// MarshalToBuffer encodes v into provided buffer (instead of using built-in buffer pool) +// and uses em encoding mode. +// +// NOTE: Unlike Marshal, the buffer provided to MarshalToBuffer can contain +// partially encoded data if error is returned. +// +// See Marshal for more details. +func (em *encMode) MarshalToBuffer(v any, buf *bytes.Buffer) error { + if buf == nil { + return fmt.Errorf("cbor: encoding buffer provided by user is nil") + } + return encode(buf, em, reflect.ValueOf(v)) +} + +// NewEncoder returns a new encoder that writes to w using em EncMode. +func (em *encMode) NewEncoder(w io.Writer) *Encoder { + return &Encoder{w: w, em: em} +} + +// encodeBufferPool caches unused bytes.Buffer objects for later reuse. +var encodeBufferPool = sync.Pool{ + New: func() any { + e := new(bytes.Buffer) + e.Grow(32) // TODO: make this configurable + return e + }, +} + +func getEncodeBuffer() *bytes.Buffer { + return encodeBufferPool.Get().(*bytes.Buffer) +} + +func putEncodeBuffer(e *bytes.Buffer) { + e.Reset() + encodeBufferPool.Put(e) +} + +type encodeFunc func(e *bytes.Buffer, em *encMode, v reflect.Value) error +type isEmptyFunc func(em *encMode, v reflect.Value) (empty bool, err error) +type isZeroFunc func(v reflect.Value) (zero bool, err error) + +func encode(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if !v.IsValid() { + // v is zero value + e.Write(cborNil) + return nil + } + vt := v.Type() + f, _, _ := getEncodeFunc(vt) + if f == nil { + return &UnsupportedTypeError{vt} + } + + return f(e, em, v) +} + +func encodeBool(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + b := cborFalse + if v.Bool() { + b = cborTrue + } + e.Write(b) + return nil +} + +func encodeInt(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + i := v.Int() + if i >= 0 { + encodeHead(e, byte(cborTypePositiveInt), uint64(i)) + return nil + } + i = i*(-1) - 1 + encodeHead(e, byte(cborTypeNegativeInt), uint64(i)) + return nil +} + +func encodeUint(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + encodeHead(e, byte(cborTypePositiveInt), v.Uint()) + return nil +} + +func encodeFloat(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + f64 := v.Float() + if math.IsNaN(f64) { + return encodeNaN(e, em, v) + } + if math.IsInf(f64, 0) { + return encodeInf(e, em, v) + } + fopt := em.shortestFloat + if v.Kind() == reflect.Float64 && (fopt == ShortestFloatNone || cannotFitFloat32(f64)) { + // Encode float64 + // Don't use encodeFloat64() because it cannot be inlined. + const argumentSize = 8 + const headSize = 1 + argumentSize + var scratch [headSize]byte + scratch[0] = byte(cborTypePrimitives) | byte(additionalInformationAsFloat64) + binary.BigEndian.PutUint64(scratch[1:], math.Float64bits(f64)) + e.Write(scratch[:]) + return nil + } + + f32 := float32(f64) + if fopt == ShortestFloat16 { + var f16 float16.Float16 + p := float16.PrecisionFromfloat32(f32) + if p == float16.PrecisionExact { + // Roundtrip float32->float16->float32 test isn't needed. + f16 = float16.Fromfloat32(f32) + } else if p == float16.PrecisionUnknown { + // Try roundtrip float32->float16->float32 to determine if float32 can fit into float16. + f16 = float16.Fromfloat32(f32) + if f16.Float32() == f32 { + p = float16.PrecisionExact + } + } + if p == float16.PrecisionExact { + // Encode float16 + // Don't use encodeFloat16() because it cannot be inlined. + const argumentSize = 2 + const headSize = 1 + argumentSize + var scratch [headSize]byte + scratch[0] = byte(cborTypePrimitives) | additionalInformationAsFloat16 + binary.BigEndian.PutUint16(scratch[1:], uint16(f16)) + e.Write(scratch[:]) + return nil + } + } + + // Encode float32 + // Don't use encodeFloat32() because it cannot be inlined. + const argumentSize = 4 + const headSize = 1 + argumentSize + var scratch [headSize]byte + scratch[0] = byte(cborTypePrimitives) | additionalInformationAsFloat32 + binary.BigEndian.PutUint32(scratch[1:], math.Float32bits(f32)) + e.Write(scratch[:]) + return nil +} + +func encodeInf(e *bytes.Buffer, em *encMode, v reflect.Value) error { + f64 := v.Float() + switch em.infConvert { + case InfConvertReject: + return &UnsupportedValueError{msg: "floating-point infinity"} + + case InfConvertFloat16: + if f64 > 0 { + e.Write(cborPositiveInfinity) + } else { + e.Write(cborNegativeInfinity) + } + return nil + } + if v.Kind() == reflect.Float64 { + return encodeFloat64(e, f64) + } + return encodeFloat32(e, float32(f64)) +} + +func encodeNaN(e *bytes.Buffer, em *encMode, v reflect.Value) error { + switch em.nanConvert { + case NaNConvert7e00: + e.Write(cborNaN) + return nil + + case NaNConvertNone: + if v.Kind() == reflect.Float64 { + return encodeFloat64(e, v.Float()) + } + f32 := float32NaNFromReflectValue(v) + return encodeFloat32(e, f32) + + case NaNConvertReject: + return &UnsupportedValueError{msg: "floating-point NaN"} + + default: // NaNConvertPreserveSignal, NaNConvertQuiet + if v.Kind() == reflect.Float64 { + f64 := v.Float() + f64bits := math.Float64bits(f64) + if em.nanConvert == NaNConvertQuiet && f64bits&(1<<51) == 0 { + f64bits |= 1 << 51 // Set quiet bit = 1 + f64 = math.Float64frombits(f64bits) + } + // The lower 29 bits are dropped when converting from float64 to float32. + if f64bits&0x1fffffff != 0 { + // Encode NaN as float64 because dropped coef bits from float64 to float32 are not all 0s. + return encodeFloat64(e, f64) + } + // Create float32 from float64 manually because float32(f64) always turns on NaN's quiet bits. + sign := uint32(f64bits>>32) & (1 << 31) + exp := uint32(0x7f800000) + coef := uint32((f64bits & 0xfffffffffffff) >> 29) + f32bits := sign | exp | coef + f32 := math.Float32frombits(f32bits) + // The lower 13 bits are dropped when converting from float32 to float16. + if f32bits&0x1fff != 0 { + // Encode NaN as float32 because dropped coef bits from float32 to float16 are not all 0s. + return encodeFloat32(e, f32) + } + // Encode NaN as float16 + f16, _ := float16.FromNaN32ps(f32) // Ignore err because it only returns error when f32 is not a NaN. + return encodeFloat16(e, f16) + } + + f32 := float32NaNFromReflectValue(v) + f32bits := math.Float32bits(f32) + if em.nanConvert == NaNConvertQuiet && f32bits&(1<<22) == 0 { + f32bits |= 1 << 22 // Set quiet bit = 1 + f32 = math.Float32frombits(f32bits) + } + // The lower 13 bits are dropped coef bits when converting from float32 to float16. + if f32bits&0x1fff != 0 { + // Encode NaN as float32 because dropped coef bits from float32 to float16 are not all 0s. + return encodeFloat32(e, f32) + } + f16, _ := float16.FromNaN32ps(f32) // Ignore err because it only returns error when f32 is not a NaN. + return encodeFloat16(e, f16) + } +} + +func encodeFloat16(e *bytes.Buffer, f16 float16.Float16) error { + const argumentSize = 2 + const headSize = 1 + argumentSize + var scratch [headSize]byte + scratch[0] = byte(cborTypePrimitives) | additionalInformationAsFloat16 + binary.BigEndian.PutUint16(scratch[1:], uint16(f16)) + e.Write(scratch[:]) + return nil +} + +func encodeFloat32(e *bytes.Buffer, f32 float32) error { + const argumentSize = 4 + const headSize = 1 + argumentSize + var scratch [headSize]byte + scratch[0] = byte(cborTypePrimitives) | additionalInformationAsFloat32 + binary.BigEndian.PutUint32(scratch[1:], math.Float32bits(f32)) + e.Write(scratch[:]) + return nil +} + +func encodeFloat64(e *bytes.Buffer, f64 float64) error { + const argumentSize = 8 + const headSize = 1 + argumentSize + var scratch [headSize]byte + scratch[0] = byte(cborTypePrimitives) | additionalInformationAsFloat64 + binary.BigEndian.PutUint64(scratch[1:], math.Float64bits(f64)) + e.Write(scratch[:]) + return nil +} + +func encodeByteString(e *bytes.Buffer, em *encMode, v reflect.Value) error { + vk := v.Kind() + if vk == reflect.Slice && v.IsNil() && em.nilContainers == NilContainerAsNull { + e.Write(cborNil) + return nil + } + if vk == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && em.byteSliceLaterEncodingTag != 0 { + encodeHead(e, byte(cborTypeTag), em.byteSliceLaterEncodingTag) + } + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + slen := v.Len() + if slen == 0 { + return e.WriteByte(byte(cborTypeByteString)) + } + encodeHead(e, byte(cborTypeByteString), uint64(slen)) + if vk == reflect.Array { + for i := 0; i < slen; i++ { + e.WriteByte(byte(v.Index(i).Uint())) + } + return nil + } + e.Write(v.Bytes()) + return nil +} + +func encodeString(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + s := v.String() + encodeHead(e, byte(em.stringMajorType), uint64(len(s))) + e.WriteString(s) + return nil +} + +type arrayEncodeFunc struct { + f encodeFunc +} + +func (ae arrayEncodeFunc) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if em.byteArray == ByteArrayToByteSlice && v.Type().Elem().Kind() == reflect.Uint8 { + return encodeByteString(e, em, v) + } + if v.Kind() == reflect.Slice && v.IsNil() && em.nilContainers == NilContainerAsNull { + e.Write(cborNil) + return nil + } + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + alen := v.Len() + if alen == 0 { + return e.WriteByte(byte(cborTypeArray)) + } + encodeHead(e, byte(cborTypeArray), uint64(alen)) + for i := 0; i < alen; i++ { + if err := ae.f(e, em, v.Index(i)); err != nil { + return err + } + } + return nil +} + +// encodeKeyValueFunc encodes key/value pairs in map (v). +// If kvs is provided (having the same length as v), length of encoded key and value are stored in kvs. +// kvs is used for canonical encoding of map. +type encodeKeyValueFunc func(e *bytes.Buffer, em *encMode, v reflect.Value, kvs []keyValue) error + +type mapEncodeFunc struct { + e encodeKeyValueFunc +} + +func (me mapEncodeFunc) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if v.IsNil() && em.nilContainers == NilContainerAsNull { + e.Write(cborNil) + return nil + } + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + mlen := v.Len() + if mlen == 0 { + return e.WriteByte(byte(cborTypeMap)) + } + + encodeHead(e, byte(cborTypeMap), uint64(mlen)) + if em.sort == SortNone || em.sort == SortFastShuffle || mlen <= 1 { + return me.e(e, em, v, nil) + } + + kvsp := getKeyValues(v.Len()) // for sorting keys + defer putKeyValues(kvsp) + kvs := *kvsp + + kvBeginOffset := e.Len() + if err := me.e(e, em, v, kvs); err != nil { + return err + } + kvTotalLen := e.Len() - kvBeginOffset + + // Use the capacity at the tail of the encode buffer as a staging area to rearrange the + // encoded pairs into sorted order. + e.Grow(kvTotalLen) + tmp := e.Bytes()[e.Len() : e.Len()+kvTotalLen] // Can use e.AvailableBuffer() in Go 1.21+. + dst := e.Bytes()[kvBeginOffset:] + + if em.sort == SortBytewiseLexical { + sort.Sort(&bytewiseKeyValueSorter{kvs: kvs, data: dst}) + } else { + sort.Sort(&lengthFirstKeyValueSorter{kvs: kvs, data: dst}) + } + + // This is where the encoded bytes are actually rearranged in the output buffer to reflect + // the desired order. + sortedOffset := 0 + for _, kv := range kvs { + copy(tmp[sortedOffset:], dst[kv.offset:kv.nextOffset]) + sortedOffset += kv.nextOffset - kv.offset + } + copy(dst, tmp[:kvTotalLen]) + + return nil + +} + +// keyValue is the position of an encoded pair in a buffer. All offsets are zero-based and relative +// to the first byte of the first encoded pair. +type keyValue struct { + offset int + valueOffset int + nextOffset int +} + +type bytewiseKeyValueSorter struct { + kvs []keyValue + data []byte +} + +func (x *bytewiseKeyValueSorter) Len() int { + return len(x.kvs) +} + +func (x *bytewiseKeyValueSorter) Swap(i, j int) { + x.kvs[i], x.kvs[j] = x.kvs[j], x.kvs[i] +} + +func (x *bytewiseKeyValueSorter) Less(i, j int) bool { + kvi, kvj := x.kvs[i], x.kvs[j] + return bytes.Compare(x.data[kvi.offset:kvi.valueOffset], x.data[kvj.offset:kvj.valueOffset]) <= 0 +} + +type lengthFirstKeyValueSorter struct { + kvs []keyValue + data []byte +} + +func (x *lengthFirstKeyValueSorter) Len() int { + return len(x.kvs) +} + +func (x *lengthFirstKeyValueSorter) Swap(i, j int) { + x.kvs[i], x.kvs[j] = x.kvs[j], x.kvs[i] +} + +func (x *lengthFirstKeyValueSorter) Less(i, j int) bool { + kvi, kvj := x.kvs[i], x.kvs[j] + if keyLengthDifference := (kvi.valueOffset - kvi.offset) - (kvj.valueOffset - kvj.offset); keyLengthDifference != 0 { + return keyLengthDifference < 0 + } + return bytes.Compare(x.data[kvi.offset:kvi.valueOffset], x.data[kvj.offset:kvj.valueOffset]) <= 0 +} + +var keyValuePool = sync.Pool{} + +func getKeyValues(length int) *[]keyValue { + v := keyValuePool.Get() + if v == nil { + y := make([]keyValue, length) + return &y + } + x := v.(*[]keyValue) + if cap(*x) >= length { + *x = (*x)[:length] + return x + } + // []keyValue from the pool does not have enough capacity. + // Return it back to the pool and create a new one. + keyValuePool.Put(x) + y := make([]keyValue, length) + return &y +} + +func putKeyValues(x *[]keyValue) { + *x = (*x)[:0] + keyValuePool.Put(x) +} + +func encodeStructToArray(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) { + structType, err := getEncodingStructType(v.Type()) + if err != nil { + return err + } + + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + + flds := structType.fields + + encodeHead(e, byte(cborTypeArray), uint64(len(flds))) + for i := 0; i < len(flds); i++ { + f := flds[i] + + var fv reflect.Value + if len(f.idx) == 1 { + fv = v.Field(f.idx[0]) + } else { + // Get embedded field value. No error is expected. + fv, _ = getFieldValue(v, f.idx, func(reflect.Value) (reflect.Value, error) { + // Write CBOR nil for null pointer to embedded struct + e.Write(cborNil) + return reflect.Value{}, nil + }) + if !fv.IsValid() { + continue + } + } + + if err := f.ef(e, em, fv); err != nil { + return err + } + } + return nil +} + +func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) { + structType, err := getEncodingStructType(v.Type()) + if err != nil { + return err + } + + flds := structType.getFields(em) + + start := 0 + if em.sort == SortFastShuffle && len(flds) > 0 { + start = rand.Intn(len(flds)) //nolint:gosec // Don't need a CSPRNG for deck cutting. + } + + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + + // Encode head with struct field count. + // Head is rewritten later if actual encoded field count is different from struct field count. + encodedHeadLen := encodeHead(e, byte(cborTypeMap), uint64(len(flds))) + + kvbegin := e.Len() + kvcount := 0 + for offset := 0; offset < len(flds); offset++ { + f := flds[(start+offset)%len(flds)] + + var fv reflect.Value + if len(f.idx) == 1 { + fv = v.Field(f.idx[0]) + } else { + // Get embedded field value. No error is expected. + fv, _ = getFieldValue(v, f.idx, func(reflect.Value) (reflect.Value, error) { + // Skip null pointer to embedded struct + return reflect.Value{}, nil + }) + if !fv.IsValid() { + continue + } + } + if f.omitEmpty { + empty, err := f.ief(em, fv) + if err != nil { + return err + } + if empty { + continue + } + } + if f.omitZero { + zero, err := f.izf(fv) + if err != nil { + return err + } + if zero { + continue + } + } + + if !f.keyAsInt && em.fieldName == FieldNameToByteString { + e.Write(f.cborNameByteString) + } else { // int or text string + e.Write(f.cborName) + } + + if err := f.ef(e, em, fv); err != nil { + return err + } + + kvcount++ + } + + if len(flds) == kvcount { + // Encoded element count in head is the same as actual element count. + return nil + } + + // Overwrite the bytes that were reserved for the head before encoding the map entries. + var actualHeadLen int + { + headbuf := *bytes.NewBuffer(e.Bytes()[kvbegin-encodedHeadLen : kvbegin-encodedHeadLen : kvbegin]) + actualHeadLen = encodeHead(&headbuf, byte(cborTypeMap), uint64(kvcount)) + } + + if actualHeadLen == encodedHeadLen { + // The bytes reserved for the encoded head were exactly the right size, so the + // encoded entries are already in their final positions. + return nil + } + + // We reserved more bytes than needed for the encoded head, based on the number of fields + // encoded. The encoded entries are offset to the right by the number of excess reserved + // bytes. Shift the entries left to remove the gap. + excessReservedBytes := encodedHeadLen - actualHeadLen + dst := e.Bytes()[kvbegin-excessReservedBytes : e.Len()-excessReservedBytes] + src := e.Bytes()[kvbegin:e.Len()] + copy(dst, src) + + // After shifting, the excess bytes are at the end of the output buffer and they are + // garbage. + e.Truncate(e.Len() - excessReservedBytes) + return nil +} + +func encodeIntf(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if v.IsNil() { + e.Write(cborNil) + return nil + } + return encode(e, em, v.Elem()) +} + +func encodeTime(e *bytes.Buffer, em *encMode, v reflect.Value) error { + t := v.Interface().(time.Time) + if t.IsZero() { + e.Write(cborNil) // Even if tag is required, encode as CBOR null. + return nil + } + if em.timeTag == EncTagRequired { + tagNumber := 1 + if em.time == TimeRFC3339 || em.time == TimeRFC3339Nano { + tagNumber = 0 + } + encodeHead(e, byte(cborTypeTag), uint64(tagNumber)) + } + switch em.time { + case TimeUnix: + secs := t.Unix() + return encodeInt(e, em, reflect.ValueOf(secs)) + + case TimeUnixMicro: + t = t.UTC().Round(time.Microsecond) + f := float64(t.UnixNano()) / 1e9 + return encodeFloat(e, em, reflect.ValueOf(f)) + + case TimeUnixDynamic: + t = t.UTC().Round(time.Microsecond) + secs, nsecs := t.Unix(), uint64(t.Nanosecond()) + if nsecs == 0 { + return encodeInt(e, em, reflect.ValueOf(secs)) + } + f := float64(secs) + float64(nsecs)/1e9 + return encodeFloat(e, em, reflect.ValueOf(f)) + + case TimeRFC3339: + s := t.Format(time.RFC3339) + return encodeString(e, em, reflect.ValueOf(s)) + + default: // TimeRFC3339Nano + s := t.Format(time.RFC3339Nano) + return encodeString(e, em, reflect.ValueOf(s)) + } +} + +func encodeBigInt(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if em.bigIntConvert == BigIntConvertReject { + return &UnsupportedTypeError{Type: typeBigInt} + } + + vbi := v.Interface().(big.Int) + sign := vbi.Sign() + bi := new(big.Int).SetBytes(vbi.Bytes()) // bi is absolute value of v + if sign < 0 { + // For negative number, convert to CBOR encoded number (-v-1). + bi.Sub(bi, big.NewInt(1)) + } + + if em.bigIntConvert == BigIntConvertShortest { + if bi.IsUint64() { + if sign >= 0 { + // Encode as CBOR pos int (major type 0) + encodeHead(e, byte(cborTypePositiveInt), bi.Uint64()) + return nil + } + // Encode as CBOR neg int (major type 1) + encodeHead(e, byte(cborTypeNegativeInt), bi.Uint64()) + return nil + } + } + + tagNum := 2 + if sign < 0 { + tagNum = 3 + } + // Write tag number + encodeHead(e, byte(cborTypeTag), uint64(tagNum)) + // Write bignum byte string + b := bi.Bytes() + encodeHead(e, byte(cborTypeByteString), uint64(len(b))) + e.Write(b) + return nil +} + +type binaryMarshalerEncoder struct { + alternateEncode encodeFunc + alternateIsEmpty isEmptyFunc +} + +func (bme binaryMarshalerEncoder) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if em.binaryMarshaler != BinaryMarshalerByteString { + return bme.alternateEncode(e, em, v) + } + + vt := v.Type() + m, ok := v.Interface().(encoding.BinaryMarshaler) + if !ok { + pv := reflect.New(vt) + pv.Elem().Set(v) + m = pv.Interface().(encoding.BinaryMarshaler) + } + data, err := m.MarshalBinary() + if err != nil { + return err + } + if b := em.encTagBytes(vt); b != nil { + e.Write(b) + } + encodeHead(e, byte(cborTypeByteString), uint64(len(data))) + e.Write(data) + return nil +} + +func (bme binaryMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, error) { + if em.binaryMarshaler != BinaryMarshalerByteString { + return bme.alternateIsEmpty(em, v) + } + + m, ok := v.Interface().(encoding.BinaryMarshaler) + if !ok { + pv := reflect.New(v.Type()) + pv.Elem().Set(v) + m = pv.Interface().(encoding.BinaryMarshaler) + } + data, err := m.MarshalBinary() + if err != nil { + return false, err + } + return len(data) == 0, nil +} + +type textMarshalerEncoder struct { + alternateEncode encodeFunc + alternateIsEmpty isEmptyFunc +} + +func (tme textMarshalerEncoder) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if em.textMarshaler == TextMarshalerNone { + return tme.alternateEncode(e, em, v) + } + + vt := v.Type() + m, ok := v.Interface().(encoding.TextMarshaler) + if !ok { + pv := reflect.New(vt) + pv.Elem().Set(v) + m = pv.Interface().(encoding.TextMarshaler) + } + data, err := m.MarshalText() + if err != nil { + return fmt.Errorf("cbor: cannot marshal text for %s: %w", vt, err) + } + if b := em.encTagBytes(vt); b != nil { + e.Write(b) + } + + encodeHead(e, byte(cborTypeTextString), uint64(len(data))) + e.Write(data) + return nil +} + +func (tme textMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, error) { + if em.textMarshaler == TextMarshalerNone { + return tme.alternateIsEmpty(em, v) + } + + m, ok := v.Interface().(encoding.TextMarshaler) + if !ok { + pv := reflect.New(v.Type()) + pv.Elem().Set(v) + m = pv.Interface().(encoding.TextMarshaler) + } + data, err := m.MarshalText() + if err != nil { + return false, fmt.Errorf("cbor: cannot marshal text for %s: %w", v.Type(), err) + } + return len(data) == 0, nil +} + +type jsonMarshalerEncoder struct { + alternateEncode encodeFunc + alternateIsEmpty isEmptyFunc +} + +func (jme jsonMarshalerEncoder) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if em.jsonMarshalerTranscoder == nil { + return jme.alternateEncode(e, em, v) + } + + vt := v.Type() + m, ok := v.Interface().(jsonMarshaler) + if !ok { + pv := reflect.New(vt) + pv.Elem().Set(v) + m = pv.Interface().(jsonMarshaler) + } + + json, err := m.MarshalJSON() + if err != nil { + return err + } + + offset := e.Len() + + if b := em.encTagBytes(vt); b != nil { + e.Write(b) + } + + if err := em.jsonMarshalerTranscoder.Transcode(e, bytes.NewReader(json)); err != nil { + return &TranscodeError{err: err, rtype: vt, sourceFormat: "json", targetFormat: "cbor"} + } + + // Validate that the transcode function has written exactly one well-formed data item. + d := decoder{data: e.Bytes()[offset:], dm: getMarshalerDecMode(em.indefLength, em.tagsMd)} + if err := d.wellformed(false, true); err != nil { + e.Truncate(offset) + return &TranscodeError{err: err, rtype: vt, sourceFormat: "json", targetFormat: "cbor"} + } + + return nil +} + +func (jme jsonMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, error) { + if em.jsonMarshalerTranscoder == nil { + return jme.alternateIsEmpty(em, v) + } + + // As with types implementing cbor.Marshaler, transcoded json.Marshaler values always encode + // as exactly one complete CBOR data item. + return false, nil +} + +func encodeMarshalerType(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if em.tagsMd == TagsForbidden && v.Type() == typeRawTag { + return errors.New("cbor: cannot encode cbor.RawTag when TagsMd is TagsForbidden") + } + m, ok := v.Interface().(Marshaler) + if !ok { + pv := reflect.New(v.Type()) + pv.Elem().Set(v) + m = pv.Interface().(Marshaler) + } + data, err := m.MarshalCBOR() + if err != nil { + return err + } + + // Verify returned CBOR data item from MarshalCBOR() is well-formed and passes tag validity for builtin tags 0-3. + d := decoder{data: data, dm: getMarshalerDecMode(em.indefLength, em.tagsMd)} + err = d.wellformed(false, true) + if err != nil { + return &MarshalerError{typ: v.Type(), err: err} + } + + e.Write(data) + return nil +} + +func encodeTag(e *bytes.Buffer, em *encMode, v reflect.Value) error { + if em.tagsMd == TagsForbidden { + return errors.New("cbor: cannot encode cbor.Tag when TagsMd is TagsForbidden") + } + + t := v.Interface().(Tag) + + if t.Number == 0 && t.Content == nil { + // Marshal uninitialized cbor.Tag + e.Write(cborNil) + return nil + } + + // Marshal tag number + encodeHead(e, byte(cborTypeTag), t.Number) + + vem := *em // shallow copy + + // For built-in tags, disable settings that may introduce tag validity errors when + // marshaling certain Content values. + switch t.Number { + case tagNumRFC3339Time: + vem.stringType = StringToTextString + vem.stringMajorType = cborTypeTextString + case tagNumUnsignedBignum, tagNumNegativeBignum: + vem.byteSliceLaterFormat = ByteSliceLaterFormatNone + vem.byteSliceLaterEncodingTag = 0 + } + + // Marshal tag content + return encode(e, &vem, reflect.ValueOf(t.Content)) +} + +// encodeHead writes CBOR head of specified type t and returns number of bytes written. +func encodeHead(e *bytes.Buffer, t byte, n uint64) int { + if n <= maxAdditionalInformationWithoutArgument { + const headSize = 1 + e.WriteByte(t | byte(n)) + return headSize + } + + if n <= math.MaxUint8 { + const headSize = 2 + scratch := [headSize]byte{ + t | byte(additionalInformationWith1ByteArgument), + byte(n), + } + e.Write(scratch[:]) + return headSize + } + + if n <= math.MaxUint16 { + const headSize = 3 + var scratch [headSize]byte + scratch[0] = t | byte(additionalInformationWith2ByteArgument) + binary.BigEndian.PutUint16(scratch[1:], uint16(n)) + e.Write(scratch[:]) + return headSize + } + + if n <= math.MaxUint32 { + const headSize = 5 + var scratch [headSize]byte + scratch[0] = t | byte(additionalInformationWith4ByteArgument) + binary.BigEndian.PutUint32(scratch[1:], uint32(n)) + e.Write(scratch[:]) + return headSize + } + + const headSize = 9 + var scratch [headSize]byte + scratch[0] = t | byte(additionalInformationWith8ByteArgument) + binary.BigEndian.PutUint64(scratch[1:], n) + e.Write(scratch[:]) + return headSize +} + +type jsonMarshaler interface{ MarshalJSON() ([]byte, error) } + +var ( + typeMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem() + typeBinaryMarshaler = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem() + typeTextMarshaler = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + typeJSONMarshaler = reflect.TypeOf((*jsonMarshaler)(nil)).Elem() + typeRawMessage = reflect.TypeOf(RawMessage(nil)) + typeByteString = reflect.TypeOf(ByteString("")) +) + +func getEncodeFuncInternal(t reflect.Type) (ef encodeFunc, ief isEmptyFunc, izf isZeroFunc) { + k := t.Kind() + if k == reflect.Pointer { + return getEncodeIndirectValueFunc(t), isEmptyPtr, getIsZeroFunc(t) + } + switch t { + case typeSimpleValue: + return encodeMarshalerType, isEmptyUint, getIsZeroFunc(t) + + case typeTag: + return encodeTag, alwaysNotEmpty, getIsZeroFunc(t) + + case typeTime: + return encodeTime, alwaysNotEmpty, getIsZeroFunc(t) + + case typeBigInt: + return encodeBigInt, alwaysNotEmpty, getIsZeroFunc(t) + + case typeRawMessage: + return encodeMarshalerType, isEmptySlice, getIsZeroFunc(t) + + case typeByteString: + return encodeMarshalerType, isEmptyString, getIsZeroFunc(t) + } + if reflect.PointerTo(t).Implements(typeMarshaler) { + return encodeMarshalerType, alwaysNotEmpty, getIsZeroFunc(t) + } + if reflect.PointerTo(t).Implements(typeBinaryMarshaler) { + defer func() { + // capture encoding method used for modes that disable BinaryMarshaler + bme := binaryMarshalerEncoder{ + alternateEncode: ef, + alternateIsEmpty: ief, + } + ef = bme.encode + ief = bme.isEmpty + }() + } + if reflect.PointerTo(t).Implements(typeTextMarshaler) { + defer func() { + // capture encoding method used for modes that disable TextMarshaler + tme := textMarshalerEncoder{ + alternateEncode: ef, + alternateIsEmpty: ief, + } + ef = tme.encode + ief = tme.isEmpty + }() + } + if reflect.PointerTo(t).Implements(typeJSONMarshaler) { + defer func() { + // capture encoding method used for modes that don't support transcoding + // from types that implement json.Marshaler. + jme := jsonMarshalerEncoder{ + alternateEncode: ef, + alternateIsEmpty: ief, + } + ef = jme.encode + ief = jme.isEmpty + }() + } + + switch k { + case reflect.Bool: + return encodeBool, isEmptyBool, getIsZeroFunc(t) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return encodeInt, isEmptyInt, getIsZeroFunc(t) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return encodeUint, isEmptyUint, getIsZeroFunc(t) + + case reflect.Float32, reflect.Float64: + return encodeFloat, isEmptyFloat, getIsZeroFunc(t) + + case reflect.String: + return encodeString, isEmptyString, getIsZeroFunc(t) + + case reflect.Slice: + if t.Elem().Kind() == reflect.Uint8 { + return encodeByteString, isEmptySlice, getIsZeroFunc(t) + } + fallthrough + + case reflect.Array: + f, _, _ := getEncodeFunc(t.Elem()) + if f == nil { + return nil, nil, nil + } + return arrayEncodeFunc{f: f}.encode, isEmptySlice, getIsZeroFunc(t) + + case reflect.Map: + f := getEncodeMapFunc(t) + if f == nil { + return nil, nil, nil + } + return f, isEmptyMap, getIsZeroFunc(t) + + case reflect.Struct: + // Get struct's special field "_" tag options + if f, ok := t.FieldByName("_"); ok { + tag := f.Tag.Get("cbor") + if tag != "-" { + if hasToArrayOption(tag) { + return encodeStructToArray, isEmptyStruct, isZeroFieldStruct + } + } + } + return encodeStruct, isEmptyStruct, getIsZeroFunc(t) + + case reflect.Interface: + return encodeIntf, isEmptyIntf, getIsZeroFunc(t) + } + return nil, nil, nil +} + +func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc { + for t.Kind() == reflect.Pointer { + t = t.Elem() + } + f, _, _ := getEncodeFunc(t) + if f == nil { + return nil + } + return func(e *bytes.Buffer, em *encMode, v reflect.Value) error { + for v.Kind() == reflect.Pointer && !v.IsNil() { + v = v.Elem() + } + if v.Kind() == reflect.Pointer && v.IsNil() { + e.Write(cborNil) + return nil + } + return f(e, em, v) + } +} + +func alwaysNotEmpty(_ *encMode, _ reflect.Value) (empty bool, err error) { + return false, nil +} + +func isEmptyBool(_ *encMode, v reflect.Value) (bool, error) { + return !v.Bool(), nil +} + +func isEmptyInt(_ *encMode, v reflect.Value) (bool, error) { + return v.Int() == 0, nil +} + +func isEmptyUint(_ *encMode, v reflect.Value) (bool, error) { + return v.Uint() == 0, nil +} + +func isEmptyFloat(_ *encMode, v reflect.Value) (bool, error) { + return v.Float() == 0.0, nil +} + +func isEmptyString(_ *encMode, v reflect.Value) (bool, error) { + return v.Len() == 0, nil +} + +func isEmptySlice(_ *encMode, v reflect.Value) (bool, error) { + return v.Len() == 0, nil +} + +func isEmptyMap(_ *encMode, v reflect.Value) (bool, error) { + return v.Len() == 0, nil +} + +func isEmptyPtr(_ *encMode, v reflect.Value) (bool, error) { + return v.IsNil(), nil +} + +func isEmptyIntf(_ *encMode, v reflect.Value) (bool, error) { + return v.IsNil(), nil +} + +func isEmptyStruct(em *encMode, v reflect.Value) (bool, error) { + structType, err := getEncodingStructType(v.Type()) + if err != nil { + return false, err + } + + if em.omitEmpty == OmitEmptyGoValue { + return false, nil + } + + if structType.toArray { + return len(structType.fields) == 0, nil + } + + if len(structType.fields) > len(structType.omitEmptyFieldsIdx) { + return false, nil + } + + for _, i := range structType.omitEmptyFieldsIdx { + f := structType.fields[i] + + // Get field value + var fv reflect.Value + if len(f.idx) == 1 { + fv = v.Field(f.idx[0]) + } else { + // Get embedded field value. No error is expected. + fv, _ = getFieldValue(v, f.idx, func(reflect.Value) (reflect.Value, error) { + // Skip null pointer to embedded struct + return reflect.Value{}, nil + }) + if !fv.IsValid() { + continue + } + } + + empty, err := f.ief(em, fv) + if err != nil { + return false, err + } + if !empty { + return false, nil + } + } + return true, nil +} + +func cannotFitFloat32(f64 float64) bool { + f32 := float32(f64) + return float64(f32) != f64 +} + +// float32NaNFromReflectValue extracts float32 NaN from reflect.Value while preserving NaN's quiet bit. +func float32NaNFromReflectValue(v reflect.Value) float32 { + // Keith Randall's workaround for issue https://github.com/golang/go/issues/36400 + p := reflect.New(v.Type()) + p.Elem().Set(v) + f32 := p.Convert(reflect.TypeOf((*float32)(nil))).Elem().Interface().(float32) + return f32 +} + +type isZeroer interface { + IsZero() bool +} + +var isZeroerType = reflect.TypeOf((*isZeroer)(nil)).Elem() + +// getIsZeroFunc returns a function for the given type that can be called to determine if a given value is zero. +// Types that implement `IsZero() bool` are delegated to for non-nil values. +// Types that do not implement `IsZero() bool` use the reflect.Value#IsZero() implementation. +// The returned function matches behavior of stdlib encoding/json behavior in Go 1.24+. +func getIsZeroFunc(t reflect.Type) isZeroFunc { + // Provide a function that uses a type's IsZero method if defined. + switch { + case t == nil: + return isZeroDefault + case t.Kind() == reflect.Interface && t.Implements(isZeroerType): + return isZeroInterfaceCustom + case t.Kind() == reflect.Pointer && t.Implements(isZeroerType): + return isZeroPointerCustom + case t.Implements(isZeroerType): + return isZeroCustom + case reflect.PointerTo(t).Implements(isZeroerType): + return isZeroAddrCustom + default: + return isZeroDefault + } +} + +// isZeroInterfaceCustom returns true for nil or pointer-to-nil values, +// and delegates to the custom IsZero() implementation otherwise. +func isZeroInterfaceCustom(v reflect.Value) (bool, error) { + kind := v.Kind() + + switch kind { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice: + if v.IsNil() { + return true, nil + } + } + + switch kind { + case reflect.Interface, reflect.Pointer: + if elem := v.Elem(); elem.Kind() == reflect.Pointer && elem.IsNil() { + return true, nil + } + } + + return v.Interface().(isZeroer).IsZero(), nil +} + +// isZeroPointerCustom returns true for nil values, +// and delegates to the custom IsZero() implementation otherwise. +func isZeroPointerCustom(v reflect.Value) (bool, error) { + if v.IsNil() { + return true, nil + } + return v.Interface().(isZeroer).IsZero(), nil +} + +// isZeroCustom delegates to the custom IsZero() implementation. +func isZeroCustom(v reflect.Value) (bool, error) { + return v.Interface().(isZeroer).IsZero(), nil +} + +// isZeroAddrCustom delegates to the custom IsZero() implementation of the addr of the value. +func isZeroAddrCustom(v reflect.Value) (bool, error) { + if !v.CanAddr() { + // Temporarily box v so we can take the address. + v2 := reflect.New(v.Type()).Elem() + v2.Set(v) + v = v2 + } + return v.Addr().Interface().(isZeroer).IsZero(), nil +} + +// isZeroDefault calls reflect.Value#IsZero() +func isZeroDefault(v reflect.Value) (bool, error) { + if !v.IsValid() { + // v is zero value + return true, nil + } + return v.IsZero(), nil +} + +// isZeroFieldStruct is used to determine whether to omit toarray structs +func isZeroFieldStruct(v reflect.Value) (bool, error) { + structType, err := getEncodingStructType(v.Type()) + if err != nil { + return false, err + } + return len(structType.fields) == 0, nil +} diff --git a/vendor/github.com/fxamacker/cbor/v2/encode_map.go b/vendor/github.com/fxamacker/cbor/v2/encode_map.go new file mode 100644 index 0000000000..2871bfdab9 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/encode_map.go @@ -0,0 +1,92 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "bytes" + "reflect" + "sync" +) + +type mapKeyValueEncodeFunc struct { + kf, ef encodeFunc + kpool, vpool sync.Pool +} + +func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *bytes.Buffer, em *encMode, v reflect.Value, kvs []keyValue) error { + iterk := me.kpool.Get().(*reflect.Value) + defer func() { + iterk.SetZero() + me.kpool.Put(iterk) + }() + iterv := me.vpool.Get().(*reflect.Value) + defer func() { + iterv.SetZero() + me.vpool.Put(iterv) + }() + + if kvs == nil { + for i, iter := 0, v.MapRange(); iter.Next(); i++ { + iterk.SetIterKey(iter) + iterv.SetIterValue(iter) + + if err := me.kf(e, em, *iterk); err != nil { + return err + } + if err := me.ef(e, em, *iterv); err != nil { + return err + } + } + return nil + } + + initial := e.Len() + for i, iter := 0, v.MapRange(); iter.Next(); i++ { + iterk.SetIterKey(iter) + iterv.SetIterValue(iter) + + offset := e.Len() + if err := me.kf(e, em, *iterk); err != nil { + return err + } + valueOffset := e.Len() + if err := me.ef(e, em, *iterv); err != nil { + return err + } + kvs[i] = keyValue{ + offset: offset - initial, + valueOffset: valueOffset - initial, + nextOffset: e.Len() - initial, + } + } + + return nil +} + +func getEncodeMapFunc(t reflect.Type) encodeFunc { + kf, _, _ := getEncodeFunc(t.Key()) + ef, _, _ := getEncodeFunc(t.Elem()) + if kf == nil || ef == nil { + return nil + } + mkv := &mapKeyValueEncodeFunc{ + kf: kf, + ef: ef, + kpool: sync.Pool{ + New: func() any { + rk := reflect.New(t.Key()).Elem() + return &rk + }, + }, + vpool: sync.Pool{ + New: func() any { + rv := reflect.New(t.Elem()).Elem() + return &rv + }, + }, + } + return mapEncodeFunc{ + e: mkv.encodeKeyValues, + }.encode +} diff --git a/vendor/github.com/fxamacker/cbor/v2/omitzero_go124.go b/vendor/github.com/fxamacker/cbor/v2/omitzero_go124.go new file mode 100644 index 0000000000..c893a411da --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/omitzero_go124.go @@ -0,0 +1,8 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +//go:build go1.24 + +package cbor + +var jsonStdlibSupportsOmitzero = true diff --git a/vendor/github.com/fxamacker/cbor/v2/omitzero_pre_go124.go b/vendor/github.com/fxamacker/cbor/v2/omitzero_pre_go124.go new file mode 100644 index 0000000000..db86a63217 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/omitzero_pre_go124.go @@ -0,0 +1,8 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +//go:build !go1.24 + +package cbor + +var jsonStdlibSupportsOmitzero = false diff --git a/vendor/github.com/fxamacker/cbor/v2/simplevalue.go b/vendor/github.com/fxamacker/cbor/v2/simplevalue.go new file mode 100644 index 0000000000..30f72814f6 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/simplevalue.go @@ -0,0 +1,98 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "errors" + "fmt" + "reflect" +) + +// SimpleValue represents CBOR simple value. +// CBOR simple value is: +// - an extension point like CBOR tag. +// - a subset of CBOR major type 7 that isn't floating-point. +// - "identified by a number between 0 and 255, but distinct from that number itself". +// For example, "a simple value 2 is not equivalent to an integer 2" as a CBOR map key. +// +// CBOR simple values identified by 20..23 are: "false", "true" , "null", and "undefined". +// Other CBOR simple values are currently unassigned/reserved by IANA. +type SimpleValue uint8 + +var ( + typeSimpleValue = reflect.TypeOf(SimpleValue(0)) +) + +// MarshalCBOR encodes SimpleValue as CBOR simple value (major type 7). +func (sv SimpleValue) MarshalCBOR() ([]byte, error) { + // RFC 8949 3.3. Floating-Point Numbers and Values with No Content says: + // "An encoder MUST NOT issue two-byte sequences that start with 0xf8 + // (major type 7, additional information 24) and continue with a byte + // less than 0x20 (32 decimal). Such sequences are not well-formed. + // (This implies that an encoder cannot encode false, true, null, or + // undefined in two-byte sequences and that only the one-byte variants + // of these are well-formed; more generally speaking, each simple value + // only has a single representation variant)." + + switch { + case sv <= maxSimpleValueInAdditionalInformation: + return []byte{byte(cborTypePrimitives) | byte(sv)}, nil + + case sv >= minSimpleValueIn1ByteArgument: + return []byte{byte(cborTypePrimitives) | additionalInformationWith1ByteArgument, byte(sv)}, nil + + default: + return nil, &UnsupportedValueError{msg: fmt.Sprintf("SimpleValue(%d)", sv)} + } +} + +// UnmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue. +// +// Deprecated: No longer used by this codec; kept for compatibility +// with user apps that directly call this function. +func (sv *SimpleValue) UnmarshalCBOR(data []byte) error { + if sv == nil { + return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer") + } + + d := decoder{data: data, dm: defaultDecMode} + + // Check well-formedness of CBOR data item. + // SimpleValue.UnmarshalCBOR() is exported, so + // the codec needs to support same behavior for: + // - Unmarshal(data, *SimpleValue) + // - SimpleValue.UnmarshalCBOR(data) + err := d.wellformed(false, false) + if err != nil { + return err + } + + return sv.unmarshalCBOR(data) +} + +// unmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue. +// This function assumes data is well-formed, and does not perform bounds checking. +// This function is called by Unmarshal(). +func (sv *SimpleValue) unmarshalCBOR(data []byte) error { + if sv == nil { + return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer") + } + + d := decoder{data: data, dm: defaultDecMode} + + typ, ai, val := d.getHead() + + if typ != cborTypePrimitives { + return &UnmarshalTypeError{CBORType: typ.String(), GoType: "SimpleValue"} + } + if ai > additionalInformationWith1ByteArgument { + return &UnmarshalTypeError{CBORType: typ.String(), GoType: "SimpleValue", errorMsg: "not simple values"} + } + + // It is safe to cast val to uint8 here because + // - data is already verified to be well-formed CBOR simple value and + // - val is <= math.MaxUint8. + *sv = SimpleValue(val) + return nil +} diff --git a/vendor/github.com/fxamacker/cbor/v2/stream.go b/vendor/github.com/fxamacker/cbor/v2/stream.go new file mode 100644 index 0000000000..7ac6d7d671 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/stream.go @@ -0,0 +1,277 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "bytes" + "errors" + "io" + "reflect" +) + +// Decoder reads and decodes CBOR values from io.Reader. +type Decoder struct { + r io.Reader + d decoder + buf []byte + off int // next read offset in buf + bytesRead int +} + +// NewDecoder returns a new decoder that reads and decodes from r using +// the default decoding options. +func NewDecoder(r io.Reader) *Decoder { + return defaultDecMode.NewDecoder(r) +} + +// Decode reads CBOR value and decodes it into the value pointed to by v. +func (dec *Decoder) Decode(v any) error { + _, err := dec.readNext() + if err != nil { + // Return validation error or read error. + return err + } + + dec.d.reset(dec.buf[dec.off:]) + err = dec.d.value(v) + + // Increment dec.off even if decoding err is not nil because + // dec.d.off points to the next CBOR data item if current + // CBOR data item is valid but failed to be decoded into v. + // This allows next CBOR data item to be decoded in next + // call to this function. + dec.off += dec.d.off + dec.bytesRead += dec.d.off + + return err +} + +// Skip skips to the next CBOR data item (if there is any), +// otherwise it returns error such as io.EOF, io.UnexpectedEOF, etc. +func (dec *Decoder) Skip() error { + n, err := dec.readNext() + if err != nil { + // Return validation error or read error. + return err + } + + dec.off += n + dec.bytesRead += n + return nil +} + +// NumBytesRead returns the number of bytes read. +func (dec *Decoder) NumBytesRead() int { + return dec.bytesRead +} + +// Buffered returns a reader for data remaining in Decoder's buffer. +// Returned reader is valid until the next call to Decode or Skip. +func (dec *Decoder) Buffered() io.Reader { + return bytes.NewReader(dec.buf[dec.off:]) +} + +// readNext() reads next CBOR data item from Reader to buffer. +// It returns the size of next CBOR data item. +// It also returns validation error or read error if any. +func (dec *Decoder) readNext() (int, error) { + var readErr error + var validErr error + + for { + // Process any unread data in dec.buf. + if dec.off < len(dec.buf) { + dec.d.reset(dec.buf[dec.off:]) + off := dec.off // Save offset before data validation + validErr = dec.d.wellformed(true, false) + dec.off = off // Restore offset + + if validErr == nil { + return dec.d.off, nil + } + + if validErr != io.ErrUnexpectedEOF { + return 0, validErr + } + + // Process last read error on io.ErrUnexpectedEOF. + if readErr != nil { + if readErr == io.EOF { + // current CBOR data item is incomplete. + return 0, io.ErrUnexpectedEOF + } + return 0, readErr + } + } + + // More data is needed and there was no read error. + var n int + for n == 0 { + n, readErr = dec.read() + if n == 0 && readErr != nil { + // No more data can be read and read error is encountered. + // At this point, validErr is either nil or io.ErrUnexpectedEOF. + if readErr == io.EOF { + if validErr == io.ErrUnexpectedEOF { + // current CBOR data item is incomplete. + return 0, io.ErrUnexpectedEOF + } + } + return 0, readErr + } + } + + // At this point, dec.buf contains new data from last read (n > 0). + } +} + +// read() reads data from Reader to buffer. +// It returns number of bytes read and any read error encountered. +// Postconditions: +// - dec.buf contains previously unread data and new data. +// - dec.off is 0. +func (dec *Decoder) read() (int, error) { + // Grow buf if needed. + const minRead = 512 + if cap(dec.buf)-len(dec.buf)+dec.off < minRead { + oldUnreadBuf := dec.buf[dec.off:] + dec.buf = make([]byte, len(dec.buf)-dec.off, 2*cap(dec.buf)+minRead) + dec.overwriteBuf(oldUnreadBuf) + } + + // Copy unread data over read data and reset off to 0. + if dec.off > 0 { + dec.overwriteBuf(dec.buf[dec.off:]) + } + + // Read from reader and reslice buf. + n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)]) + dec.buf = dec.buf[0 : len(dec.buf)+n] + return n, err +} + +func (dec *Decoder) overwriteBuf(newBuf []byte) { + n := copy(dec.buf, newBuf) + dec.buf = dec.buf[:n] + dec.off = 0 +} + +// Encoder writes CBOR values to io.Writer. +type Encoder struct { + w io.Writer + em *encMode + indefTypes []cborType +} + +// NewEncoder returns a new encoder that writes to w using the default encoding options. +func NewEncoder(w io.Writer) *Encoder { + return defaultEncMode.NewEncoder(w) +} + +// Encode writes the CBOR encoding of v. +func (enc *Encoder) Encode(v any) error { + if len(enc.indefTypes) > 0 && v != nil { + indefType := enc.indefTypes[len(enc.indefTypes)-1] + if indefType == cborTypeTextString { + k := reflect.TypeOf(v).Kind() + if k != reflect.String { + return errors.New("cbor: cannot encode item type " + k.String() + " for indefinite-length text string") + } + } else if indefType == cborTypeByteString { + t := reflect.TypeOf(v) + k := t.Kind() + if (k != reflect.Array && k != reflect.Slice) || t.Elem().Kind() != reflect.Uint8 { + return errors.New("cbor: cannot encode item type " + k.String() + " for indefinite-length byte string") + } + } + } + + buf := getEncodeBuffer() + + err := encode(buf, enc.em, reflect.ValueOf(v)) + if err == nil { + _, err = enc.w.Write(buf.Bytes()) + } + + putEncodeBuffer(buf) + return err +} + +// StartIndefiniteByteString starts byte string encoding of indefinite length. +// Subsequent calls of (*Encoder).Encode() encodes definite length byte strings +// ("chunks") as one contiguous string until EndIndefinite is called. +func (enc *Encoder) StartIndefiniteByteString() error { + return enc.startIndefinite(cborTypeByteString) +} + +// StartIndefiniteTextString starts text string encoding of indefinite length. +// Subsequent calls of (*Encoder).Encode() encodes definite length text strings +// ("chunks") as one contiguous string until EndIndefinite is called. +func (enc *Encoder) StartIndefiniteTextString() error { + return enc.startIndefinite(cborTypeTextString) +} + +// StartIndefiniteArray starts array encoding of indefinite length. +// Subsequent calls of (*Encoder).Encode() encodes elements of the array +// until EndIndefinite is called. +func (enc *Encoder) StartIndefiniteArray() error { + return enc.startIndefinite(cborTypeArray) +} + +// StartIndefiniteMap starts array encoding of indefinite length. +// Subsequent calls of (*Encoder).Encode() encodes elements of the map +// until EndIndefinite is called. +func (enc *Encoder) StartIndefiniteMap() error { + return enc.startIndefinite(cborTypeMap) +} + +// EndIndefinite closes last opened indefinite length value. +func (enc *Encoder) EndIndefinite() error { + if len(enc.indefTypes) == 0 { + return errors.New("cbor: cannot encode \"break\" code outside indefinite length values") + } + _, err := enc.w.Write([]byte{cborBreakFlag}) + if err == nil { + enc.indefTypes = enc.indefTypes[:len(enc.indefTypes)-1] + } + return err +} + +var cborIndefHeader = map[cborType][]byte{ + cborTypeByteString: {cborByteStringWithIndefiniteLengthHead}, + cborTypeTextString: {cborTextStringWithIndefiniteLengthHead}, + cborTypeArray: {cborArrayWithIndefiniteLengthHead}, + cborTypeMap: {cborMapWithIndefiniteLengthHead}, +} + +func (enc *Encoder) startIndefinite(typ cborType) error { + if enc.em.indefLength == IndefLengthForbidden { + return &IndefiniteLengthError{typ} + } + _, err := enc.w.Write(cborIndefHeader[typ]) + if err == nil { + enc.indefTypes = append(enc.indefTypes, typ) + } + return err +} + +// RawMessage is a raw encoded CBOR value. +type RawMessage []byte + +// MarshalCBOR returns m or CBOR nil if m is nil. +func (m RawMessage) MarshalCBOR() ([]byte, error) { + if len(m) == 0 { + return cborNil, nil + } + return m, nil +} + +// UnmarshalCBOR creates a copy of data and saves to *m. +func (m *RawMessage) UnmarshalCBOR(data []byte) error { + if m == nil { + return errors.New("cbor.RawMessage: UnmarshalCBOR on nil pointer") + } + *m = append((*m)[0:0], data...) + return nil +} diff --git a/vendor/github.com/fxamacker/cbor/v2/structfields.go b/vendor/github.com/fxamacker/cbor/v2/structfields.go new file mode 100644 index 0000000000..cf0a922cd7 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/structfields.go @@ -0,0 +1,268 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "reflect" + "sort" + "strings" +) + +type field struct { + name string + nameAsInt int64 // used to decoder to match field name with CBOR int + cborName []byte + cborNameByteString []byte // major type 2 name encoding iff cborName has major type 3 + idx []int + typ reflect.Type + ef encodeFunc + ief isEmptyFunc + izf isZeroFunc + typInfo *typeInfo // used to decoder to reuse type info + tagged bool // used to choose dominant field (at the same level tagged fields dominate untagged fields) + omitEmpty bool // used to skip empty field + omitZero bool // used to skip zero field + keyAsInt bool // used to encode/decode field name as int +} + +type fields []*field + +// indexFieldSorter sorts fields by field idx at each level, breaking ties with idx depth. +type indexFieldSorter struct { + fields fields +} + +func (x *indexFieldSorter) Len() int { + return len(x.fields) +} + +func (x *indexFieldSorter) Swap(i, j int) { + x.fields[i], x.fields[j] = x.fields[j], x.fields[i] +} + +func (x *indexFieldSorter) Less(i, j int) bool { + iIdx, jIdx := x.fields[i].idx, x.fields[j].idx + for k := 0; k < len(iIdx) && k < len(jIdx); k++ { + if iIdx[k] != jIdx[k] { + return iIdx[k] < jIdx[k] + } + } + return len(iIdx) <= len(jIdx) +} + +// nameLevelAndTagFieldSorter sorts fields by field name, idx depth, and presence of tag. +type nameLevelAndTagFieldSorter struct { + fields fields +} + +func (x *nameLevelAndTagFieldSorter) Len() int { + return len(x.fields) +} + +func (x *nameLevelAndTagFieldSorter) Swap(i, j int) { + x.fields[i], x.fields[j] = x.fields[j], x.fields[i] +} + +func (x *nameLevelAndTagFieldSorter) Less(i, j int) bool { + fi, fj := x.fields[i], x.fields[j] + if fi.name != fj.name { + return fi.name < fj.name + } + if len(fi.idx) != len(fj.idx) { + return len(fi.idx) < len(fj.idx) + } + if fi.tagged != fj.tagged { + return fi.tagged + } + return i < j // Field i and j have the same name, depth, and tagged status. Nothing else matters. +} + +// getFields returns visible fields of struct type t following visibility rules for JSON encoding. +func getFields(t reflect.Type) (flds fields, structOptions string) { + // Get special field "_" tag options + if f, ok := t.FieldByName("_"); ok { + tag := f.Tag.Get("cbor") + if tag != "-" { + structOptions = tag + } + } + + // nTypes contains next level anonymous fields' types and indexes + // (there can be multiple fields of the same type at the same level) + flds, nTypes := appendFields(t, nil, nil, nil) + + if len(nTypes) > 0 { + + var cTypes map[reflect.Type][][]int // current level anonymous fields' types and indexes + vTypes := map[reflect.Type]bool{t: true} // visited field types at less nested levels + + for len(nTypes) > 0 { + cTypes, nTypes = nTypes, nil + + for t, idx := range cTypes { + // If there are multiple anonymous fields of the same struct type at the same level, all are ignored. + if len(idx) > 1 { + continue + } + + // Anonymous field of the same type at deeper nested level is ignored. + if vTypes[t] { + continue + } + vTypes[t] = true + + flds, nTypes = appendFields(t, idx[0], flds, nTypes) + } + } + } + + sort.Sort(&nameLevelAndTagFieldSorter{flds}) + + // Keep visible fields. + j := 0 // index of next unique field + for i := 0; i < len(flds); { + name := flds[i].name + if i == len(flds)-1 || // last field + name != flds[i+1].name || // field i has unique field name + len(flds[i].idx) < len(flds[i+1].idx) || // field i is at a less nested level than field i+1 + (flds[i].tagged && !flds[i+1].tagged) { // field i is tagged while field i+1 is not + flds[j] = flds[i] + j++ + } + + // Skip fields with the same field name. + for i++; i < len(flds) && name == flds[i].name; i++ { //nolint:revive + } + } + if j != len(flds) { + flds = flds[:j] + } + + // Sort fields by field index + sort.Sort(&indexFieldSorter{flds}) + + return flds, structOptions +} + +// appendFields appends type t's exportable fields to flds and anonymous struct fields to nTypes . +func appendFields( + t reflect.Type, + idx []int, + flds fields, + nTypes map[reflect.Type][][]int, +) ( + _flds fields, + _nTypes map[reflect.Type][][]int, +) { + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + + ft := f.Type + for ft.Kind() == reflect.Pointer { + ft = ft.Elem() + } + + if !isFieldExportable(f, ft.Kind()) { + continue + } + + cborTag := true + tag := f.Tag.Get("cbor") + if tag == "" { + tag = f.Tag.Get("json") + cborTag = false + } + if tag == "-" { + continue + } + + tagged := tag != "" + + // Parse field tag options + var tagFieldName string + var omitempty, omitzero, keyasint bool + for j := 0; tag != ""; j++ { + var token string + idx := strings.IndexByte(tag, ',') + if idx == -1 { + token, tag = tag, "" + } else { + token, tag = tag[:idx], tag[idx+1:] + } + if j == 0 { + tagFieldName = token + } else { + switch token { + case "omitempty": + omitempty = true + case "omitzero": + if cborTag || jsonStdlibSupportsOmitzero { + omitzero = true + } + case "keyasint": + keyasint = true + } + } + } + + fieldName := tagFieldName + if tagFieldName == "" { + fieldName = f.Name + } + + fIdx := make([]int, len(idx)+1) + copy(fIdx, idx) + fIdx[len(fIdx)-1] = i + + if !f.Anonymous || ft.Kind() != reflect.Struct || tagFieldName != "" { + flds = append(flds, &field{ + name: fieldName, + idx: fIdx, + typ: f.Type, + omitEmpty: omitempty, + omitZero: omitzero, + keyAsInt: keyasint, + tagged: tagged}) + } else { + if nTypes == nil { + nTypes = make(map[reflect.Type][][]int) + } + nTypes[ft] = append(nTypes[ft], fIdx) + } + } + + return flds, nTypes +} + +// isFieldExportable returns true if f is an exportable (regular or anonymous) field or +// a nonexportable anonymous field of struct type. +// Nonexportable anonymous field of struct type can contain exportable fields. +func isFieldExportable(f reflect.StructField, fk reflect.Kind) bool { //nolint:gocritic // ignore hugeParam + return f.IsExported() || (f.Anonymous && fk == reflect.Struct) +} + +type embeddedFieldNullPtrFunc func(reflect.Value) (reflect.Value, error) + +// getFieldValue returns field value of struct v by index. When encountering null pointer +// to anonymous (embedded) struct field, f is called with the last traversed field value. +func getFieldValue(v reflect.Value, idx []int, f embeddedFieldNullPtrFunc) (fv reflect.Value, err error) { + fv = v + for i, n := range idx { + fv = fv.Field(n) + + if i < len(idx)-1 { + if fv.Kind() == reflect.Pointer && fv.Type().Elem().Kind() == reflect.Struct { + if fv.IsNil() { + // Null pointer to embedded struct field + fv, err = f(fv) + if err != nil || !fv.IsValid() { + return fv, err + } + } + fv = fv.Elem() + } + } + } + return fv, nil +} diff --git a/vendor/github.com/fxamacker/cbor/v2/tag.go b/vendor/github.com/fxamacker/cbor/v2/tag.go new file mode 100644 index 0000000000..bd8b773f54 --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/tag.go @@ -0,0 +1,329 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "errors" + "fmt" + "reflect" + "sync" +) + +// Tag represents a tagged data item (CBOR major type 6), comprising a tag number and the unmarshaled tag content. +// NOTE: The same encoding and decoding options that apply to untagged CBOR data items also applies to tag content +// during encoding and decoding. +type Tag struct { + Number uint64 + Content any +} + +// RawTag represents a tagged data item (CBOR major type 6), comprising a tag number and the raw tag content. +// The raw tag content (enclosed data item) is a CBOR-encoded data item. +// RawTag can be used to delay decoding a CBOR data item or precompute encoding a CBOR data item. +type RawTag struct { + Number uint64 + Content RawMessage +} + +// UnmarshalCBOR sets *t with the tag number and the raw tag content copied from data. +// +// Deprecated: No longer used by this codec; kept for compatibility +// with user apps that directly call this function. +func (t *RawTag) UnmarshalCBOR(data []byte) error { + if t == nil { + return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer") + } + + d := decoder{data: data, dm: defaultDecMode} + + // Check if data is a well-formed CBOR data item. + // RawTag.UnmarshalCBOR() is exported, so + // the codec needs to support same behavior for: + // - Unmarshal(data, *RawTag) + // - RawTag.UnmarshalCBOR(data) + err := d.wellformed(false, false) + if err != nil { + return err + } + + return t.unmarshalCBOR(data) +} + +// unmarshalCBOR sets *t with the tag number and the raw tag content copied from data. +// This function assumes data is well-formed, and does not perform bounds checking. +// This function is called by Unmarshal(). +func (t *RawTag) unmarshalCBOR(data []byte) error { + if t == nil { + return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer") + } + + // Decoding CBOR null and undefined to cbor.RawTag is no-op. + if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) { + return nil + } + + d := decoder{data: data, dm: defaultDecMode} + + // Unmarshal tag number. + typ, _, num := d.getHead() + if typ != cborTypeTag { + return &UnmarshalTypeError{CBORType: typ.String(), GoType: typeRawTag.String()} + } + t.Number = num + + // Unmarshal tag content. + c := d.data[d.off:] + t.Content = make([]byte, len(c)) + copy(t.Content, c) + return nil +} + +// MarshalCBOR returns CBOR encoding of t. +func (t RawTag) MarshalCBOR() ([]byte, error) { + if t.Number == 0 && len(t.Content) == 0 { + // Marshal uninitialized cbor.RawTag + b := make([]byte, len(cborNil)) + copy(b, cborNil) + return b, nil + } + + e := getEncodeBuffer() + + encodeHead(e, byte(cborTypeTag), t.Number) + + content := t.Content + if len(content) == 0 { + content = cborNil + } + + buf := make([]byte, len(e.Bytes())+len(content)) + n := copy(buf, e.Bytes()) + copy(buf[n:], content) + + putEncodeBuffer(e) + return buf, nil +} + +// DecTagMode specifies how decoder handles tag number. +type DecTagMode int + +const ( + // DecTagIgnored makes decoder ignore tag number (skips if present). + DecTagIgnored DecTagMode = iota + + // DecTagOptional makes decoder verify tag number if it's present. + DecTagOptional + + // DecTagRequired makes decoder verify tag number and tag number must be present. + DecTagRequired + + maxDecTagMode +) + +func (dtm DecTagMode) valid() bool { + return dtm >= 0 && dtm < maxDecTagMode +} + +// EncTagMode specifies how encoder handles tag number. +type EncTagMode int + +const ( + // EncTagNone makes encoder not encode tag number. + EncTagNone EncTagMode = iota + + // EncTagRequired makes encoder encode tag number. + EncTagRequired + + maxEncTagMode +) + +func (etm EncTagMode) valid() bool { + return etm >= 0 && etm < maxEncTagMode +} + +// TagOptions specifies how encoder and decoder handle tag number. +type TagOptions struct { + DecTag DecTagMode + EncTag EncTagMode +} + +// TagSet is an interface to add and remove tag info. It is used by EncMode and DecMode +// to provide CBOR tag support. +type TagSet interface { + // Add adds given tag number(s), content type, and tag options to TagSet. + Add(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) error + + // Remove removes given tag content type from TagSet. + Remove(contentType reflect.Type) + + tagProvider +} + +type tagProvider interface { + getTagItemFromType(t reflect.Type) *tagItem + getTypeFromTagNum(num []uint64) reflect.Type +} + +type tagItem struct { + num []uint64 + cborTagNum []byte + contentType reflect.Type + opts TagOptions +} + +func (t *tagItem) equalTagNum(num []uint64) bool { + // Fast path to compare 1 tag number + if len(t.num) == 1 && len(num) == 1 && t.num[0] == num[0] { + return true + } + + if len(t.num) != len(num) { + return false + } + + for i := 0; i < len(t.num); i++ { + if t.num[i] != num[i] { + return false + } + } + + return true +} + +type ( + tagSet map[reflect.Type]*tagItem + + syncTagSet struct { + sync.RWMutex + t tagSet + } +) + +func (t tagSet) getTagItemFromType(typ reflect.Type) *tagItem { + return t[typ] +} + +func (t tagSet) getTypeFromTagNum(num []uint64) reflect.Type { + for typ, tag := range t { + if tag.equalTagNum(num) { + return typ + } + } + return nil +} + +// NewTagSet returns TagSet (safe for concurrency). +func NewTagSet() TagSet { + return &syncTagSet{t: make(map[reflect.Type]*tagItem)} +} + +// Add adds given tag number(s), content type, and tag options to TagSet. +func (t *syncTagSet) Add(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) error { + if contentType == nil { + return errors.New("cbor: cannot add nil content type to TagSet") + } + for contentType.Kind() == reflect.Pointer { + contentType = contentType.Elem() + } + tag, err := newTagItem(opts, contentType, num, nestedNum...) + if err != nil { + return err + } + t.Lock() + defer t.Unlock() + for typ, ti := range t.t { + if typ == contentType { + return errors.New("cbor: content type " + contentType.String() + " already exists in TagSet") + } + if ti.equalTagNum(tag.num) { + return fmt.Errorf("cbor: tag number %v already exists in TagSet", tag.num) + } + } + t.t[contentType] = tag + return nil +} + +// Remove removes given tag content type from TagSet. +func (t *syncTagSet) Remove(contentType reflect.Type) { + for contentType.Kind() == reflect.Pointer { + contentType = contentType.Elem() + } + t.Lock() + delete(t.t, contentType) + t.Unlock() +} + +func (t *syncTagSet) getTagItemFromType(typ reflect.Type) *tagItem { + t.RLock() + ti := t.t[typ] + t.RUnlock() + return ti +} + +func (t *syncTagSet) getTypeFromTagNum(num []uint64) reflect.Type { + t.RLock() + rt := t.t.getTypeFromTagNum(num) + t.RUnlock() + return rt +} + +func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) (*tagItem, error) { + if opts.DecTag == DecTagIgnored && opts.EncTag == EncTagNone { + return nil, errors.New("cbor: cannot add tag with DecTagIgnored and EncTagNone options to TagSet") + } + if contentType.PkgPath() == "" || contentType.Kind() == reflect.Interface { + return nil, errors.New("cbor: can only add named types to TagSet, got " + contentType.String()) + } + if contentType == typeTime { + return nil, errors.New("cbor: cannot add time.Time to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead") + } + if contentType == typeBigInt { + return nil, errors.New("cbor: cannot add big.Int to TagSet, it's built-in and supported automatically") + } + if contentType == typeTag { + return nil, errors.New("cbor: cannot add cbor.Tag to TagSet") + } + if contentType == typeRawTag { + return nil, errors.New("cbor: cannot add cbor.RawTag to TagSet") + } + if num == 0 || num == 1 { + return nil, errors.New("cbor: cannot add tag number 0 or 1 to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead") + } + if num == 2 || num == 3 { + return nil, errors.New("cbor: cannot add tag number 2 or 3 to TagSet, it's built-in and supported automatically") + } + if num == tagNumSelfDescribedCBOR { + return nil, errors.New("cbor: cannot add tag number 55799 to TagSet, it's built-in and ignored automatically") + } + + te := tagItem{num: []uint64{num}, opts: opts, contentType: contentType} + te.num = append(te.num, nestedNum...) + + // Cache encoded tag numbers + e := getEncodeBuffer() + for _, n := range te.num { + encodeHead(e, byte(cborTypeTag), n) + } + te.cborTagNum = make([]byte, e.Len()) + copy(te.cborTagNum, e.Bytes()) + putEncodeBuffer(e) + + return &te, nil +} + +var ( + typeTag = reflect.TypeOf(Tag{}) + typeRawTag = reflect.TypeOf(RawTag{}) +) + +// WrongTagError describes mismatch between CBOR tag and registered tag. +type WrongTagError struct { + RegisteredType reflect.Type + RegisteredTagNum []uint64 + TagNum []uint64 +} + +func (e *WrongTagError) Error() string { + return fmt.Sprintf("cbor: wrong tag number for %s, got %v, expected %v", e.RegisteredType.String(), e.TagNum, e.RegisteredTagNum) +} diff --git a/vendor/github.com/fxamacker/cbor/v2/valid.go b/vendor/github.com/fxamacker/cbor/v2/valid.go new file mode 100644 index 0000000000..b40793b95e --- /dev/null +++ b/vendor/github.com/fxamacker/cbor/v2/valid.go @@ -0,0 +1,394 @@ +// Copyright (c) Faye Amacker. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +package cbor + +import ( + "encoding/binary" + "errors" + "io" + "math" + "strconv" + + "github.com/x448/float16" +) + +// SyntaxError is a description of a CBOR syntax error. +type SyntaxError struct { + msg string +} + +func (e *SyntaxError) Error() string { return e.msg } + +// SemanticError is a description of a CBOR semantic error. +type SemanticError struct { + msg string +} + +func (e *SemanticError) Error() string { return e.msg } + +// MaxNestedLevelError indicates exceeded max nested level of any combination of CBOR arrays/maps/tags. +type MaxNestedLevelError struct { + maxNestedLevels int +} + +func (e *MaxNestedLevelError) Error() string { + return "cbor: exceeded max nested level " + strconv.Itoa(e.maxNestedLevels) +} + +// MaxArrayElementsError indicates exceeded max number of elements for CBOR arrays. +type MaxArrayElementsError struct { + maxArrayElements int +} + +func (e *MaxArrayElementsError) Error() string { + return "cbor: exceeded max number of elements " + strconv.Itoa(e.maxArrayElements) + " for CBOR array" +} + +// MaxMapPairsError indicates exceeded max number of key-value pairs for CBOR maps. +type MaxMapPairsError struct { + maxMapPairs int +} + +func (e *MaxMapPairsError) Error() string { + return "cbor: exceeded max number of key-value pairs " + strconv.Itoa(e.maxMapPairs) + " for CBOR map" +} + +// IndefiniteLengthError indicates found disallowed indefinite length items. +type IndefiniteLengthError struct { + t cborType +} + +func (e *IndefiniteLengthError) Error() string { + return "cbor: indefinite-length " + e.t.String() + " isn't allowed" +} + +// TagsMdError indicates found disallowed CBOR tags. +type TagsMdError struct { +} + +func (e *TagsMdError) Error() string { + return "cbor: CBOR tag isn't allowed" +} + +// ExtraneousDataError indicates found extraneous data following well-formed CBOR data item. +type ExtraneousDataError struct { + numOfBytes int // number of bytes of extraneous data + index int // location of extraneous data +} + +func (e *ExtraneousDataError) Error() string { + return "cbor: " + strconv.Itoa(e.numOfBytes) + " bytes of extraneous data starting at index " + strconv.Itoa(e.index) +} + +// wellformed checks whether the CBOR data item is well-formed. +// allowExtraData indicates if extraneous data is allowed after the CBOR data item. +// - use allowExtraData = true when using Decoder.Decode() +// - use allowExtraData = false when using Unmarshal() +func (d *decoder) wellformed(allowExtraData bool, checkBuiltinTags bool) error { + if len(d.data) == d.off { + return io.EOF + } + _, err := d.wellformedInternal(0, checkBuiltinTags) + if err == nil { + if !allowExtraData && d.off != len(d.data) { + err = &ExtraneousDataError{len(d.data) - d.off, d.off} + } + } + return err +} + +// wellformedInternal checks data's well-formedness and returns max depth and error. +func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, error) { //nolint:gocyclo + t, _, val, indefiniteLength, err := d.wellformedHeadWithIndefiniteLengthFlag() + if err != nil { + return 0, err + } + + switch t { + case cborTypeByteString, cborTypeTextString: + if indefiniteLength { + if d.dm.indefLength == IndefLengthForbidden { + return 0, &IndefiniteLengthError{t} + } + return d.wellformedIndefiniteString(t, depth, checkBuiltinTags) + } + valInt := int(val) + if valInt < 0 { + // Detect integer overflow + return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow") + } + if len(d.data)-d.off < valInt { // valInt+off may overflow integer + return 0, io.ErrUnexpectedEOF + } + d.off += valInt + + case cborTypeArray, cborTypeMap: + depth++ + if depth > d.dm.maxNestedLevels { + return 0, &MaxNestedLevelError{d.dm.maxNestedLevels} + } + + if indefiniteLength { + if d.dm.indefLength == IndefLengthForbidden { + return 0, &IndefiniteLengthError{t} + } + return d.wellformedIndefiniteArrayOrMap(t, depth, checkBuiltinTags) + } + + valInt := int(val) + if valInt < 0 { + // Detect integer overflow + return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, it would cause integer overflow") + } + + if t == cborTypeArray { + if valInt > d.dm.maxArrayElements { + return 0, &MaxArrayElementsError{d.dm.maxArrayElements} + } + } else { + if valInt > d.dm.maxMapPairs { + return 0, &MaxMapPairsError{d.dm.maxMapPairs} + } + } + + count := 1 + if t == cborTypeMap { + count = 2 + } + maxDepth := depth + for j := 0; j < count; j++ { + for i := 0; i < valInt; i++ { + var dpt int + if dpt, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil { + return 0, err + } + if dpt > maxDepth { + maxDepth = dpt // Save max depth + } + } + } + depth = maxDepth + + case cborTypeTag: + if d.dm.tagsMd == TagsForbidden { + return 0, &TagsMdError{} + } + + tagNum := val + + // Scan nested tag numbers to avoid recursion. + for { + if len(d.data) == d.off { // Tag number must be followed by tag content. + return 0, io.ErrUnexpectedEOF + } + if checkBuiltinTags { + err = validBuiltinTag(tagNum, d.data[d.off]) + if err != nil { + return 0, err + } + } + if d.dm.bignumTag == BignumTagForbidden && (tagNum == 2 || tagNum == 3) { + return 0, &UnacceptableDataItemError{ + CBORType: cborTypeTag.String(), + Message: "bignum", + } + } + if getType(d.data[d.off]) != cborTypeTag { + break + } + if _, _, tagNum, err = d.wellformedHead(); err != nil { + return 0, err + } + depth++ + if depth > d.dm.maxNestedLevels { + return 0, &MaxNestedLevelError{d.dm.maxNestedLevels} + } + } + // Check tag content. + return d.wellformedInternal(depth, checkBuiltinTags) + } + + return depth, nil +} + +// wellformedIndefiniteString checks indefinite length byte/text string's well-formedness and returns max depth and error. +func (d *decoder) wellformedIndefiniteString(t cborType, depth int, checkBuiltinTags bool) (int, error) { + var err error + for { + if len(d.data) == d.off { + return 0, io.ErrUnexpectedEOF + } + if isBreakFlag(d.data[d.off]) { + d.off++ + break + } + // Peek ahead to get next type and indefinite length status. + nt, ai := parseInitialByte(d.data[d.off]) + if t != nt { + return 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()} + } + if additionalInformation(ai).isIndefiniteLength() { + return 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"} + } + if depth, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil { + return 0, err + } + } + return depth, nil +} + +// wellformedIndefiniteArrayOrMap checks indefinite length array/map's well-formedness and returns max depth and error. +func (d *decoder) wellformedIndefiniteArrayOrMap(t cborType, depth int, checkBuiltinTags bool) (int, error) { + var err error + maxDepth := depth + i := 0 + for { + if len(d.data) == d.off { + return 0, io.ErrUnexpectedEOF + } + if isBreakFlag(d.data[d.off]) { + d.off++ + break + } + var dpt int + if dpt, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil { + return 0, err + } + if dpt > maxDepth { + maxDepth = dpt + } + i++ + if t == cborTypeArray { + if i > d.dm.maxArrayElements { + return 0, &MaxArrayElementsError{d.dm.maxArrayElements} + } + } else { + if i%2 == 0 && i/2 > d.dm.maxMapPairs { + return 0, &MaxMapPairsError{d.dm.maxMapPairs} + } + } + } + if t == cborTypeMap && i%2 == 1 { + return 0, &SyntaxError{"cbor: unexpected \"break\" code"} + } + return maxDepth, nil +} + +func (d *decoder) wellformedHeadWithIndefiniteLengthFlag() ( + t cborType, + ai byte, + val uint64, + indefiniteLength bool, + err error, +) { + t, ai, val, err = d.wellformedHead() + if err != nil { + return + } + indefiniteLength = additionalInformation(ai).isIndefiniteLength() + return +} + +func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) { + dataLen := len(d.data) - d.off + if dataLen == 0 { + return 0, 0, 0, io.ErrUnexpectedEOF + } + + t, ai = parseInitialByte(d.data[d.off]) + val = uint64(ai) + d.off++ + dataLen-- + + if ai <= maxAdditionalInformationWithoutArgument { + return t, ai, val, nil + } + + if ai == additionalInformationWith1ByteArgument { + const argumentSize = 1 + if dataLen < argumentSize { + return 0, 0, 0, io.ErrUnexpectedEOF + } + val = uint64(d.data[d.off]) + d.off++ + if t == cborTypePrimitives && val < 32 { + return 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()} + } + return t, ai, val, nil + } + + if ai == additionalInformationWith2ByteArgument { + const argumentSize = 2 + if dataLen < argumentSize { + return 0, 0, 0, io.ErrUnexpectedEOF + } + val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize + if t == cborTypePrimitives { + if err := d.acceptableFloat(float64(float16.Frombits(uint16(val)).Float32())); err != nil { + return 0, 0, 0, err + } + } + return t, ai, val, nil + } + + if ai == additionalInformationWith4ByteArgument { + const argumentSize = 4 + if dataLen < argumentSize { + return 0, 0, 0, io.ErrUnexpectedEOF + } + val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+argumentSize])) + d.off += argumentSize + if t == cborTypePrimitives { + if err := d.acceptableFloat(float64(math.Float32frombits(uint32(val)))); err != nil { + return 0, 0, 0, err + } + } + return t, ai, val, nil + } + + if ai == additionalInformationWith8ByteArgument { + const argumentSize = 8 + if dataLen < argumentSize { + return 0, 0, 0, io.ErrUnexpectedEOF + } + val = binary.BigEndian.Uint64(d.data[d.off : d.off+argumentSize]) + d.off += argumentSize + if t == cborTypePrimitives { + if err := d.acceptableFloat(math.Float64frombits(val)); err != nil { + return 0, 0, 0, err + } + } + return t, ai, val, nil + } + + if additionalInformation(ai).isIndefiniteLength() { + switch t { + case cborTypePositiveInt, cborTypeNegativeInt, cborTypeTag: + return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()} + case cborTypePrimitives: // 0xff (break code) should not be outside wellformedIndefinite(). + return 0, 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"} + } + return t, ai, val, nil + } + + // ai == 28, 29, 30 + return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()} +} + +func (d *decoder) acceptableFloat(f float64) error { + switch { + case d.dm.nanDec == NaNDecodeForbidden && math.IsNaN(f): + return &UnacceptableDataItemError{ + CBORType: cborTypePrimitives.String(), + Message: "floating-point NaN", + } + case d.dm.infDec == InfDecodeForbidden && math.IsInf(f, 0): + return &UnacceptableDataItemError{ + CBORType: cborTypePrimitives.String(), + Message: "floating-point infinity", + } + } + return nil +} diff --git a/vendor/github.com/intel/goresctrl/LICENSE b/vendor/github.com/intel/goresctrl/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/github.com/intel/goresctrl/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/vendor/github.com/intel/goresctrl/pkg/blockio/blockio.go b/vendor/github.com/intel/goresctrl/pkg/blockio/blockio.go new file mode 100644 index 0000000000..694884b371 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/blockio/blockio.go @@ -0,0 +1,435 @@ +/* +Copyright 2019-2021 Intel Corporation + +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. +*/ + +// Package blockio implements class-based cgroup blockio controller +// management for containers. +// +// Input: configuration of classes with blockio controller parameters +// (weights, throttling) for sets of block devices. +// +// Outputs: +// Option 1: Write blockio parameters of a class to a cgroup directory. +// Option 2: Return blockio parameters of a class in a OCI LinuxBlockIO +// +// structure, that can be passed to OCI-compliant container +// runtime. +// +// Notes: +// - Using Weight requires bfq or cfq I/O scheduler to be +// effective for the block devices where Weight is used. +// +// Configuration example: +// +// Classes: +// +// # Define a blockio class "LowPrioThrottled". +// # Containers in this class will be throttled and handled as +// # low priority in the I/O scheduler. +// +// LowPrioThrottled: +// +// # Weight without a Devices list specifies the default +// # I/O scheduler weight for all devices +// # that are not explicitly mentioned in following items. +// # This will be written to cgroups(.bfq).weight. +// # Weights range from 10 to 1000, the default is 100. +// +// - Weight: 80 +// +// # Set all parameters for all /dev/sd* and /dev/vd* block +// # devices. +// +// - Devices: +// - /dev/sd[a-z] +// - /dev/vd[a-z] +// ThrottleReadBps: 50M # max read bytes per second +// ThrottleWriteBps: 10M # max write bytes per second +// ThrottleReadIOPS: 10k # max read io operations per second +// ThrottleWriteIOPS: 5k # max write io operations per second +// Weight: 50 # I/O scheduler (cfq/bfq) weight for +// # these devices will be written to +// # cgroups(.bfq).weight_device +// +// # Set parameters particularly for SSD devices. +// # This configuration overrides above configurations for those +// # /dev/sd* and /dev/vd* devices whose disk id contains "SSD". +// +// - Devices: +// - /dev/disk/by-id/*SSD* +// ThrottleReadBps: 100M +// ThrottleWriteBps: 40M +// # Not mentioning Throttle*IOPS means no I/O operations +// # throttling on matching devices. +// Weight: 50 +// +// # Define a blockio class "HighPrioFullSpeed". +// # There is no throttling on these containers, and +// # they will be prioritized by the I/O scheduler. +// +// HighPrioFullSpeed: +// - Weight: 400 +// +// Usage example: +// +// blockio.SetLogger(slog.Default().WithGroup("blockio")) +// if err := blockio.SetConfigFromFile("/etc/containers/blockio.yaml", false); err != nil { +// return err +// } +// // Output option 1: write directly to cgroup "/mytestgroup" +// if err := blockio.SetCgroupClass("/mytestgroup", "LowPrioThrottled"); err != nil { +// return err +// } +// // Output option 2: OCI LinuxBlockIO of a blockio class +// if lbio, err := blockio.OciLinuxBlockIO("LowPrioThrottled"); err != nil { +// return err +// } else { +// fmt.Printf("OCI LinuxBlockIO for LowPrioThrottled:\n%+v\n", lbio) +// } +package blockio + +import ( + "errors" + "fmt" + "log/slog" + "os" + "path/filepath" + "sort" + "strings" + "syscall" + + "golang.org/x/sys/unix" + + "k8s.io/apimachinery/pkg/api/resource" + "sigs.k8s.io/yaml" + + goresctrlpath "github.com/intel/goresctrl/pkg/path" +) + +const ( + // sysfsBlockDeviceIOSchedulerPaths expands (with glob) to block device scheduler files. + // If modified, check how to parse device node from expanded paths. + sysfsBlockDeviceIOSchedulerPaths = "/sys/block/*/queue/scheduler" +) + +// tBlockDeviceInfo holds information on a block device to be configured. +// As users can specify block devices using wildcards ("/dev/disk/by-id/*SSD*") +// tBlockDeviceInfo.Origin is maintained for traceability: why this +// block device is included in configuration. +// tBlockDeviceInfo.DevNode contains resolved device node, like "/dev/sda". +type tBlockDeviceInfo struct { + Major int64 + Minor int64 + DevNode string + Origin string +} + +// Our logger instance. +var log *slog.Logger = slog.Default() + +// classBlockIO connects user-defined block I/O classes to +// corresponding cgroups blockio controller parameters. +var classBlockIO = map[string]BlockIOParameters{} + +// SetLogger sets the logger instance to be used by the package. +// Examples: +// +// // Write structured records in text form to stderr, set verbosity to debug +// logger := slog.New(slog.NewTextHandler(os.Stderr, slog.HandlerOptions{Level: slog.LevelDebug})) +// blockio.SetLogger(logger) +func SetLogger(l *slog.Logger) { + log = l +} + +// SetConfigFromFile reads and applies blockio configuration from the +// filesystem. +func SetConfigFromFile(filename string, force bool) error { + if data, err := os.ReadFile(filename); err == nil { + if err = SetConfigFromData(data, force); err != nil { + return fmt.Errorf("failed to set configuration from file %q: %s", filename, err) + } + return nil + } else { + return fmt.Errorf("failed to read config file %q: %v", filename, err) + } +} + +// SetConfigFromData parses and applies configuration from data. +func SetConfigFromData(data []byte, force bool) error { + config := &Config{} + if err := yaml.UnmarshalStrict(data, &config); err != nil { + return err + } + return SetConfig(config, force) +} + +// SetConfig scans available block devices and applies new configuration. +func SetConfig(opt *Config, force bool) error { + if opt == nil { + // Setting nil configuration clears current configuration. + // SetConfigFromData([]byte(""), dontcare) arrives here. + classBlockIO = map[string]BlockIOParameters{} + return nil + } + + currentIOSchedulers, ioSchedulerDetectionError := getCurrentIOSchedulers() + if ioSchedulerDetectionError != nil { + log.Warn("configuration validation partly disabled due to I/O scheduler detection error", "error", ioSchedulerDetectionError) + } + + classBlockIO = map[string]BlockIOParameters{} + // Create cgroup blockio parameters for each blockio class + for class := range opt.Classes { + cgBlockIO, err := devicesParametersToCgBlockIO(opt.Classes[class], currentIOSchedulers) + if err != nil { + if force { + log.Warn("ignoring blockio class", "className", class, "error", err) + } else { + return err + } + } + classBlockIO[class] = cgBlockIO + } + return nil +} + +// GetClasses returns block I/O class names +func GetClasses() []string { + classNames := make([]string, 0, len(classBlockIO)) + for name := range classBlockIO { + classNames = append(classNames, name) + } + sort.Strings(classNames) + return classNames +} + +// getCurrentIOSchedulers returns currently active I/O scheduler used for each block device in the system. +// Returns schedulers in a map: {"/dev/sda": "bfq"} +func getCurrentIOSchedulers() (map[string]string, error) { + var ios = map[string]string{} + glob := goresctrlpath.Path(sysfsBlockDeviceIOSchedulerPaths) + schedulerFiles, err := filepath.Glob(glob) + if err != nil { + return ios, fmt.Errorf("error in I/O scheduler wildcards %#v: %w", glob, err) + } + for _, schedulerFile := range schedulerFiles { + devName := strings.SplitN(schedulerFile, "/", 5)[3] + schedulerDataB, err := os.ReadFile(schedulerFile) + if err != nil { + // A block device may be disconnected. + log.Error("failed to read current I/O scheduler", "path", schedulerFile, "error", err) + continue + } + schedulerData := strings.Trim(string(schedulerDataB), "\n") + currentScheduler := "" + if strings.IndexByte(schedulerData, ' ') == -1 { + currentScheduler = schedulerData + } else { + openB := strings.Index(schedulerData, "[") + closeB := strings.Index(schedulerData, "]") + if -1 < openB && openB < closeB { + currentScheduler = schedulerData[openB+1 : closeB] + } + } + if currentScheduler == "" { + log.Error("could not parse current scheduler", "path", schedulerFile) + continue + } + + ios["/dev/"+devName] = currentScheduler + } + return ios, nil +} + +// deviceParametersToCgBlockIO converts single blockio class parameters into cgroups blkio format. +func devicesParametersToCgBlockIO(dps []DevicesParameters, currentIOSchedulers map[string]string) (BlockIOParameters, error) { + errs := []error{} + blkio := NewBlockIOParameters() + for _, dp := range dps { + var err error + var weight, throttleReadBps, throttleWriteBps, throttleReadIOPS, throttleWriteIOPS int64 + weight, err = parseAndValidateQuantity("Weight", dp.Weight, -1, 10, 1000) + errs = append(errs, err) + throttleReadBps, err = parseAndValidateQuantity("ThrottleReadBps", dp.ThrottleReadBps, -1, 0, -1) + errs = append(errs, err) + throttleWriteBps, err = parseAndValidateQuantity("ThrottleWriteBps", dp.ThrottleWriteBps, -1, 0, -1) + errs = append(errs, err) + throttleReadIOPS, err = parseAndValidateQuantity("ThrottleReadIOPS", dp.ThrottleReadIOPS, -1, 0, -1) + errs = append(errs, err) + throttleWriteIOPS, err = parseAndValidateQuantity("ThrottleWriteIOPS", dp.ThrottleWriteIOPS, -1, 0, -1) + errs = append(errs, err) + if dp.Devices == nil { + if weight > -1 { + blkio.Weight = weight + } + if throttleReadBps > -1 || throttleWriteBps > -1 || throttleReadIOPS > -1 || throttleWriteIOPS > -1 { + errs = append(errs, fmt.Errorf("ignoring throttling (rbps=%#v wbps=%#v riops=%#v wiops=%#v): Devices not listed", + dp.ThrottleReadBps, dp.ThrottleWriteBps, dp.ThrottleReadIOPS, dp.ThrottleWriteIOPS)) + } + } else { + blockDevices, err := currentPlatform.configurableBlockDevices(dp.Devices) + if err != nil { + // Problems in matching block device wildcards and resolving symlinks + // are worth reporting, but must not block configuring blkio where possible. + log.Warn("failed to resolve block devices", "error", err) + } + if len(blockDevices) == 0 { + log.Warn("no match, parameters ignored", "devices", dp.Devices) + } + for _, blockDeviceInfo := range blockDevices { + if weight != -1 { + if ios, found := currentIOSchedulers[blockDeviceInfo.DevNode]; found { + if ios != "bfq" && ios != "cfq" { + log.Warn("weight has no effect due to incompatible I/O scheduler (bfq or cfq required)", + "path", blockDeviceInfo.DevNode, "scheduler", ios) + } + } + blkio.WeightDevice.Update(blockDeviceInfo.Major, blockDeviceInfo.Minor, weight) + } + if throttleReadBps != -1 { + blkio.ThrottleReadBpsDevice.Update(blockDeviceInfo.Major, blockDeviceInfo.Minor, throttleReadBps) + } + if throttleWriteBps != -1 { + blkio.ThrottleWriteBpsDevice.Update(blockDeviceInfo.Major, blockDeviceInfo.Minor, throttleWriteBps) + } + if throttleReadIOPS != -1 { + blkio.ThrottleReadIOPSDevice.Update(blockDeviceInfo.Major, blockDeviceInfo.Minor, throttleReadIOPS) + } + if throttleWriteIOPS != -1 { + blkio.ThrottleWriteIOPSDevice.Update(blockDeviceInfo.Major, blockDeviceInfo.Minor, throttleWriteIOPS) + } + } + } + } + return blkio, errors.Join(errs...) +} + +// parseAndValidateQuantity parses quantities, like "64 M", and validates that they are in given range. +func parseAndValidateQuantity(fieldName string, fieldContent string, + defaultValue int64, min int64, max int64) (int64, error) { + // Returns field content + if fieldContent == "" { + return defaultValue, nil + } + qty, err := resource.ParseQuantity(fieldContent) + if err != nil { + return defaultValue, fmt.Errorf("syntax error in %#v (%#v)", fieldName, fieldContent) + } + value := qty.Value() + if min != -1 && min > value { + return defaultValue, fmt.Errorf("value of %#v (%#v) smaller than minimum (%#v)", fieldName, value, min) + } + if max != -1 && value > max { + return defaultValue, fmt.Errorf("value of %#v (%#v) bigger than maximum (%#v)", fieldName, value, max) + } + return value, nil +} + +// platformInterface includes functions that access the system. Enables mocking the system. +type platformInterface interface { + configurableBlockDevices(devWildcards []string) ([]tBlockDeviceInfo, error) +} + +// defaultPlatform versions of platformInterface functions access the underlying system. +type defaultPlatform struct{} + +// currentPlatform defines which platformInterface is used: defaultPlatform or a mock, for instance. +var currentPlatform platformInterface = defaultPlatform{} + +// configurableBlockDevices finds major:minor numbers for device filenames. Wildcards are allowed in filenames. +func (dpm defaultPlatform) configurableBlockDevices(devWildcards []string) ([]tBlockDeviceInfo, error) { + // Return map {devNode: tBlockDeviceInfo} + // Example: {"/dev/sda": {Major:8, Minor:0, Origin:"from symlink /dev/disk/by-id/ata-VendorXSSD from wildcard /dev/disk/by-id/*SSD*"}} + errs := []error{} + blockDevices := []tBlockDeviceInfo{} + var origin string + + // 1. Expand wildcards to device filenames (may be symlinks) + // Example: devMatches["/dev/disk/by-id/ata-VendorSSD"] == "from wildcard \"dev/disk/by-id/*SSD*\"" + devMatches := map[string]string{} // {devNodeOrSymlink: origin} + for _, devWildcard := range devWildcards { + devWildcardMatches, err := filepath.Glob(devWildcard) + if err != nil { + errs = append(errs, fmt.Errorf("bad device wildcard %#v: %w", devWildcard, err)) + continue + } + if len(devWildcardMatches) == 0 { + errs = append(errs, fmt.Errorf("device wildcard %#v does not match any device nodes", devWildcard)) + continue + } + for _, devMatch := range devWildcardMatches { + if devMatch != devWildcard { + origin = fmt.Sprintf("from wildcard %#v", devWildcard) + } else { + origin = "" + } + devMatches[devMatch] = strings.TrimSpace(fmt.Sprintf("%v %v", devMatches[devMatch], origin)) + } + } + + // 2. Find out real device nodes behind symlinks + // Example: devRealPaths["/dev/sda"] == "from symlink \"/dev/disk/by-id/ata-VendorSSD\"" + devRealpaths := map[string]string{} // {devNode: origin} + for devMatch, devOrigin := range devMatches { + realDevNode, err := filepath.EvalSymlinks(devMatch) + if err != nil { + errs = append(errs, fmt.Errorf("cannot filepath.EvalSymlinks(%#v): %w", devMatch, err)) + continue + } + if realDevNode != devMatch { + origin = fmt.Sprintf("from symlink %#v %v", devMatch, devOrigin) + } else { + origin = devOrigin + } + devRealpaths[realDevNode] = strings.TrimSpace(fmt.Sprintf("%v %v", devRealpaths[realDevNode], origin)) + } + + // 3. Filter out everything but block devices that are not partitions + // Example: blockDevices[0] == {Major: 8, Minor: 0, DevNode: "/dev/sda", Origin: "..."} + for devRealpath, devOrigin := range devRealpaths { + origin := "" + if devOrigin != "" { + origin = fmt.Sprintf(" (origin: %s)", devOrigin) + } + fileInfo, err := os.Stat(devRealpath) + if err != nil { + errs = append(errs, fmt.Errorf("cannot os.Stat(%#v): %w%s", devRealpath, err, origin)) + continue + } + fileMode := fileInfo.Mode() + if fileMode&os.ModeDevice == 0 { + errs = append(errs, fmt.Errorf("file %#v is not a device%s", devRealpath, origin)) + continue + } + if fileMode&os.ModeCharDevice != 0 { + errs = append(errs, fmt.Errorf("file %#v is a character device%s", devRealpath, origin)) + continue + } + sys, ok := fileInfo.Sys().(*syscall.Stat_t) + major := unix.Major(uint64(sys.Rdev)) + minor := unix.Minor(uint64(sys.Rdev)) + if !ok { + errs = append(errs, fmt.Errorf("cannot get syscall stat_t from %#v: %w%s", devRealpath, err, origin)) + continue + } + blockDevices = append(blockDevices, tBlockDeviceInfo{ + Major: int64(major), + Minor: int64(minor), + DevNode: devRealpath, + Origin: devOrigin, + }) + } + return blockDevices, errors.Join(errs...) +} diff --git a/vendor/github.com/intel/goresctrl/pkg/blockio/cgroups.go b/vendor/github.com/intel/goresctrl/pkg/blockio/cgroups.go new file mode 100644 index 0000000000..d17751f91e --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/blockio/cgroups.go @@ -0,0 +1,103 @@ +// Copyright 2020-2021 Intel Corporation. All Rights Reserved. +// +// 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. + +package blockio + +// BlockIOParameters contains cgroups blockio controller parameters. +// +// Effects of Weight and Rate values in SetBlkioParameters(): +// Value | Effect +// -------+------------------------------------------------------------------- +// +// -1 | Do not write to cgroups, value is missing. +// 0 | Write to cgroups, will clear the setting as specified in cgroups blkio interface. +// other | Write to cgroups, sets the value. +type BlockIOParameters struct { + Weight int64 + WeightDevice DeviceWeights + ThrottleReadBpsDevice DeviceRates + ThrottleWriteBpsDevice DeviceRates + ThrottleReadIOPSDevice DeviceRates + ThrottleWriteIOPSDevice DeviceRates +} + +// DeviceWeight contains values for +// - blkio.[io-scheduler].weight +type DeviceWeight struct { + Major int64 + Minor int64 + Weight int64 +} + +// DeviceRate contains values for +// - blkio.throttle.read_bps_device +// - blkio.throttle.write_bps_device +// - blkio.throttle.read_iops_device +// - blkio.throttle.write_iops_device +type DeviceRate struct { + Major int64 + Minor int64 + Rate int64 +} + +// DeviceWeights contains weights for devices. +type DeviceWeights []DeviceWeight + +// DeviceRates contains throttling rates for devices. +type DeviceRates []DeviceRate + +// NewBlockIOParameters creates new BlockIOParameters instance. +func NewBlockIOParameters() BlockIOParameters { + return BlockIOParameters{ + Weight: -1, + } +} + +// DeviceParameters interface provides functions common to DeviceWeights and DeviceRates. +type DeviceParameters interface { + Append(maj, min, val int64) + Update(maj, min, val int64) +} + +// Append appends (major, minor, value) to DeviceWeights slice. +func (w *DeviceWeights) Append(maj, min, val int64) { + *w = append(*w, DeviceWeight{Major: maj, Minor: min, Weight: val}) +} + +// Append appends (major, minor, value) to DeviceRates slice. +func (r *DeviceRates) Append(maj, min, val int64) { + *r = append(*r, DeviceRate{Major: maj, Minor: min, Rate: val}) +} + +// Update updates device weight in DeviceWeights slice, or appends it if not found. +func (w *DeviceWeights) Update(maj, min, val int64) { + for index, devWeight := range *w { + if devWeight.Major == maj && devWeight.Minor == min { + (*w)[index].Weight = val + return + } + } + w.Append(maj, min, val) +} + +// Update updates device rate in DeviceRates slice, or appends it if not found. +func (r *DeviceRates) Update(maj, min, val int64) { + for index, devRate := range *r { + if devRate.Major == maj && devRate.Minor == min { + (*r)[index].Rate = val + return + } + } + r.Append(maj, min, val) +} diff --git a/vendor/github.com/intel/goresctrl/pkg/blockio/config.go b/vendor/github.com/intel/goresctrl/pkg/blockio/config.go new file mode 100644 index 0000000000..7531398d9e --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/blockio/config.go @@ -0,0 +1,33 @@ +/* +Copyright 2019-2021 Intel Corporation + +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. +*/ + +package blockio + +// Config contains a blockio configuration. +type Config struct { + // Classes define weights and throttling parameters for sets of devices. + Classes map[string][]DevicesParameters `json:",omitempty"` +} + +// DevicesParameters defines Block IO parameters for a set of devices. +type DevicesParameters struct { + Devices []string `json:",omitempty"` + ThrottleReadBps string `json:",omitempty"` + ThrottleWriteBps string `json:",omitempty"` + ThrottleReadIOPS string `json:",omitempty"` + ThrottleWriteIOPS string `json:",omitempty"` + Weight string `json:",omitempty"` +} diff --git a/vendor/github.com/intel/goresctrl/pkg/blockio/kubernetes.go b/vendor/github.com/intel/goresctrl/pkg/blockio/kubernetes.go new file mode 100644 index 0000000000..ce5709877c --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/blockio/kubernetes.go @@ -0,0 +1,47 @@ +/* +Copyright 2021 Intel Corporation + +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. +*/ + +package blockio + +import ( + "github.com/intel/goresctrl/pkg/kubernetes" +) + +const ( + // BlockioContainerAnnotation is the CRI level container annotation for setting + // the blockio class of a container + BlockioContainerAnnotation = "io.kubernetes.cri.blockio-class" + + // BlockioPodAnnotation is a Pod annotation for setting the blockio class of + // all containers of the pod + BlockioPodAnnotation = "blockio.resources.beta.kubernetes.io/pod" + + // BlockioPodAnnotationContainerPrefix is prefix for per-container Pod annotation + // for setting the blockio class of one container of the pod + BlockioPodAnnotationContainerPrefix = "blockio.resources.beta.kubernetes.io/container." +) + +// ContainerClassFromAnnotations determines the effective blockio +// class of a container from the Pod annotations and CRI level +// container annotations of a container. If the class is not specified +// by any annotation, returns empty class name. Returned error is +// reserved (nil). +func ContainerClassFromAnnotations(containerName string, containerAnnotations, podAnnotations map[string]string) (string, error) { + clsName, _ := kubernetes.ContainerClassFromAnnotations( + BlockioContainerAnnotation, BlockioPodAnnotation, BlockioPodAnnotationContainerPrefix, + containerName, containerAnnotations, podAnnotations) + return clsName, nil +} diff --git a/vendor/github.com/intel/goresctrl/pkg/blockio/oci.go b/vendor/github.com/intel/goresctrl/pkg/blockio/oci.go new file mode 100644 index 0000000000..9684e95208 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/blockio/oci.go @@ -0,0 +1,69 @@ +/* +Copyright 2019-2021 Intel Corporation + +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. +*/ + +package blockio + +import ( + "fmt" + + oci "github.com/opencontainers/runtime-spec/specs-go" +) + +// OciLinuxBlockIO returns OCI LinuxBlockIO structure corresponding to the class. +func OciLinuxBlockIO(class string) (*oci.LinuxBlockIO, error) { + blockio, ok := classBlockIO[class] + if !ok { + return nil, fmt.Errorf("no OCI BlockIO parameters for class %#v", class) + } + ociBlockio := oci.LinuxBlockIO{} + if blockio.Weight != -1 { + w := uint16(blockio.Weight) + ociBlockio.Weight = &w + } + ociBlockio.WeightDevice = ociLinuxWeightDevices(blockio.WeightDevice) + ociBlockio.ThrottleReadBpsDevice = ociLinuxThrottleDevices(blockio.ThrottleReadBpsDevice) + ociBlockio.ThrottleWriteBpsDevice = ociLinuxThrottleDevices(blockio.ThrottleWriteBpsDevice) + ociBlockio.ThrottleReadIOPSDevice = ociLinuxThrottleDevices(blockio.ThrottleReadIOPSDevice) + ociBlockio.ThrottleWriteIOPSDevice = ociLinuxThrottleDevices(blockio.ThrottleWriteIOPSDevice) + return &ociBlockio, nil +} + +func ociLinuxWeightDevices(dws DeviceWeights) []oci.LinuxWeightDevice { + if len(dws) == 0 { + return nil + } + olwds := make([]oci.LinuxWeightDevice, len(dws)) + for i, wd := range dws { + w := uint16(wd.Weight) + olwds[i].Major = wd.Major + olwds[i].Minor = wd.Minor + olwds[i].Weight = &w + } + return olwds +} + +func ociLinuxThrottleDevices(drs DeviceRates) []oci.LinuxThrottleDevice { + if len(drs) == 0 { + return nil + } + oltds := make([]oci.LinuxThrottleDevice, len(drs)) + for i, dr := range drs { + oltds[i].Major = dr.Major + oltds[i].Minor = dr.Minor + oltds[i].Rate = uint64(dr.Rate) + } + return oltds +} diff --git a/vendor/github.com/intel/goresctrl/pkg/kubernetes/annotations.go b/vendor/github.com/intel/goresctrl/pkg/kubernetes/annotations.go new file mode 100644 index 0000000000..a98e9b87b0 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/kubernetes/annotations.go @@ -0,0 +1,58 @@ +/* +Copyright 2021 Intel Corporation + +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. +*/ + +package kubernetes + +// ClassOrigin type indicates the source of container's class +// information: whether it is found from CRI level container +// annotations, Kubernetes' pod annotations, or it has not been found +// at all. +type ClassOrigin int + +const ( + ClassOriginNotFound ClassOrigin = iota + ClassOriginContainerAnnotation + ClassOriginPodAnnotation +) + +func (c ClassOrigin) String() string { + switch c { + case ClassOriginNotFound: + return "" + case ClassOriginContainerAnnotation: + return "container annotations" + case ClassOriginPodAnnotation: + return "pod annotations" + default: + return "" + } +} + +// ContainerClassFromAnnotations determines the effective class of a +// container from the Pod annotations and CRI level container +// annotations of a container. +func ContainerClassFromAnnotations(containerAnnotation, podAnnotation, podAnnotationContainerPrefix string, containerName string, containerAnnotations, podAnnotations map[string]string) (string, ClassOrigin) { + if clsName, ok := containerAnnotations[containerAnnotation]; ok { + return clsName, ClassOriginContainerAnnotation + } + if clsName, ok := podAnnotations[podAnnotationContainerPrefix+containerName]; ok { + return clsName, ClassOriginPodAnnotation + } + if clsName, ok := podAnnotations[podAnnotation]; ok { + return clsName, ClassOriginPodAnnotation + } + return "", ClassOriginNotFound +} diff --git a/vendor/github.com/intel/goresctrl/pkg/path/path.go b/vendor/github.com/intel/goresctrl/pkg/path/path.go new file mode 100644 index 0000000000..a217b01059 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/path/path.go @@ -0,0 +1,35 @@ +/* +Copyright 2023 Intel Corporation + +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. +*/ + +package path + +import "path/filepath" + +// RootDir is a helper for handling system directory paths +type RootDir string + +var prefix RootDir = "/" + +// Path returns a full path to a file under RootDir +func (d RootDir) Path(elems ...string) string { + return filepath.Join(append([]string{string(d)}, elems...)...) +} + +// SetPrefix sets the global path prefix to use for all system files. +func SetPrefix(p string) { prefix = RootDir(p) } + +// Path returns a path to a file, prefixed with the global prefix. +func Path(elems ...string) string { return prefix.Path(elems...) } diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/bitmask.go b/vendor/github.com/intel/goresctrl/pkg/rdt/bitmask.go new file mode 100644 index 0000000000..82b50775fc --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/bitmask.go @@ -0,0 +1,118 @@ +/* +Copyright 2019 Intel Corporation + +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. +*/ + +package rdt + +import ( + "fmt" + "math/bits" + "strconv" + "strings" +) + +// bitmask represents a generic 64 bit wide bitmask +type bitmask uint64 + +// MarshalJSON implements the Marshaler interface of "encoding/json" +func (b bitmask) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("\"%#x\"", b)), nil +} + +// listStr prints the bitmask in human-readable format, similar to e.g. the +// cpuset format of the Linux kernel +func (b bitmask) listStr() string { + str := "" + sep := "" + + shift := int(0) + lsbOne := b.lsbOne() + + // Process "ranges of ones" + for lsbOne != -1 { + b >>= uint(lsbOne) + + // Get range lenght from the position of the first zero + numOnes := b.lsbZero() + + if numOnes == 1 { + str += sep + strconv.Itoa(lsbOne+shift) + } else { + str += sep + strconv.Itoa(lsbOne+shift) + "-" + strconv.Itoa(lsbOne+numOnes-1+shift) + } + + // Shift away the bits that have been processed + b >>= uint(numOnes) + shift += lsbOne + numOnes + + // Get next bit that is set (if any) + lsbOne = b.lsbOne() + + sep = "," + } + + return str +} + +// listStrToBitmask parses a string containing a human-readable list of bit +// numbers into a bitmask +func listStrToBitmask(str string) (bitmask, error) { + b := bitmask(0) + + // Empty bitmask + if len(str) == 0 { + return b, nil + } + + ranges := strings.Split(str, ",") + for _, ran := range ranges { + split := strings.SplitN(ran, "-", 2) + + bitNum, err := strconv.ParseUint(split[0], 10, 6) + if err != nil { + return b, fmt.Errorf("invalid bitmask %q: %v", str, err) + } + + if len(split) == 1 { + b |= 1 << bitNum + } else { + endNum, err := strconv.ParseUint(split[1], 10, 6) + if err != nil { + return b, fmt.Errorf("invalid bitmask %q: %v", str, err) + } + if endNum <= bitNum { + return b, fmt.Errorf("invalid range %q in bitmask %q", ran, str) + } + b |= (1<<(endNum-bitNum+1) - 1) << bitNum + } + } + return b, nil +} + +func (b bitmask) lsbOne() int { + if b == 0 { + return -1 + } + return bits.TrailingZeros64(uint64(b)) +} + +func (b bitmask) msbOne() int { + // Returns -1 for b == 0 + return 63 - bits.LeadingZeros64(uint64(b)) +} + +func (b bitmask) lsbZero() int { + return bits.TrailingZeros64(^uint64(b)) +} diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/config.go b/vendor/github.com/intel/goresctrl/pkg/rdt/config.go new file mode 100644 index 0000000000..eae7f225f1 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/config.go @@ -0,0 +1,1203 @@ +/* +Copyright 2019-2021 Intel Corporation + +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. +*/ + +package rdt + +import ( + "encoding/json" + "fmt" + "math" + "math/bits" + "sort" + "strconv" + "strings" + + "github.com/intel/goresctrl/pkg/utils" +) + +// Config is the user-specified RDT configuration. +type Config struct { + Options Options `json:"options"` + Partitions map[string]struct { + L2Allocation CatConfig `json:"l2Allocation"` + L3Allocation CatConfig `json:"l3Allocation"` + MBAllocation MbaConfig `json:"mbAllocation"` + Classes map[string]struct { + L2Allocation CatConfig `json:"l2Allocation"` + L3Allocation CatConfig `json:"l3Allocation"` + MBAllocation MbaConfig `json:"mbAllocation"` + Kubernetes KubernetesOptions `json:"kubernetes"` + } `json:"classes"` + } `json:"partitions"` +} + +// CatConfig contains the L2 or L3 cache allocation configuration for one partition or class. +type CatConfig map[string]CacheIdCatConfig + +// MbaConfig contains the memory bandwidth configuration for one partition or class. +type MbaConfig map[string]CacheIdMbaConfig + +// CacheIdCatConfig is the cache allocation configuration for one cache id. +// Code and Data represent an optional configuration for separate code and data +// paths and only have effect when RDT CDP (Code and Data Prioritization) is +// enabled in the system. Code and Data go in tandem so that both or neither +// must be specified - only specifying the other is considered a configuration +// error. +type CacheIdCatConfig struct { + Unified CacheProportion + Code CacheProportion + Data CacheProportion +} + +// CacheIdMbaConfig is the memory bandwidth configuration for one cache id. +// It's an array of at most two values, specifying separate values to be used +// for percentage based and MBps based memory bandwidth allocation. For +// example, `{"80%", "1000MBps"}` would allocate 80% if percentage based +// allocation is used by the Linux kernel, or 1000 MBps in case MBps based +// allocation is in use. +type CacheIdMbaConfig []MbProportion + +// MbProportion specifies a share of available memory bandwidth. It's an +// integer value followed by a unit. Two units are supported: +// +// - percentage, e.g. `80%` +// - MBps, e.g. `1000MBps` +type MbProportion string + +// CacheProportion specifies a share of the available cache lines. +// Supported formats: +// +// - percentage, e.g. `50%` +// - percentage range, e.g. `50-60%` +// - bit numbers, e.g. `0-5`, `2,3`, must contain one contiguous block of bits set +// - hex bitmask, e.g. `0xff0`, must contain one contiguous block of bits set +type CacheProportion string + +// CacheIdAll is a special cache id used to denote a default, used as a +// fallback for all cache ids that are not explicitly specified. +const CacheIdAll = "all" + +// config represents the final (parsed and resolved) runtime configuration of +// RDT Control +type config struct { + Options Options + Partitions partitionSet + Classes classSet +} + +// partitionSet represents the pool of rdt partitions +type partitionSet map[string]*partitionConfig + +// classSet represents the pool of rdt classes +type classSet map[string]*classConfig + +// partitionConfig is the final configuration of one partition +type partitionConfig struct { + CAT map[cacheLevel]catSchema + MB mbSchema +} + +// classConfig represents configuration of one class, i.e. one CTRL group in +// the Linux resctrl interface +type classConfig struct { + Partition string + CATSchema map[cacheLevel]catSchema + MBSchema mbSchema + Kubernetes KubernetesOptions +} + +// Options contains common settings. +type Options struct { + L2 CatOptions `json:"l2"` + L3 CatOptions `json:"l3"` + MB MbOptions `json:"mb"` +} + +// CatOptions contains the common settings for cache allocation. +type CatOptions struct { + Optional bool +} + +// MbOptions contains the common settings for memory bandwidth allocation. +type MbOptions struct { + Optional bool +} + +// KubernetesOptions contains per-class settings for the Kubernetes-related functionality. +type KubernetesOptions struct { + DenyPodAnnotation bool `json:"denyPodAnnotation"` + DenyContainerAnnotation bool `json:"denyContainerAnnotation"` +} + +// catSchema represents a cache part of the schemata of a class (i.e. resctrl group) +type catSchema struct { + Lvl cacheLevel + Alloc catSchemaRaw +} + +// catSchemaRaw is the cache schemata without the information about cache level +type catSchemaRaw map[uint64]catAllocation + +// mbSchema represents the MB part of the schemata of a class (i.e. resctrl group) +type mbSchema map[uint64]uint64 + +// catAllocation describes the allocation configuration for one cache id +type catAllocation struct { + Unified cacheAllocation + Code cacheAllocation `json:",omitempty"` + Data cacheAllocation `json:",omitempty"` +} + +// cacheAllocation is the basic interface for handling cache allocations of one +// type (unified, code, data) +type cacheAllocation interface { + Overlay(bitmask, uint64) (bitmask, error) +} + +// catAbsoluteAllocation represents an explicitly specified cache allocation +// bitmask +type catAbsoluteAllocation bitmask + +// catPctAllocation represents a relative (percentage) share of the available +// bitmask +type catPctAllocation uint64 + +// catPctRangeAllocation represents a percentage range of the available bitmask +type catPctRangeAllocation struct { + lowPct uint64 + highPct uint64 +} + +// catSchemaType represents different L3 cache allocation schemes +type catSchemaType string + +const ( + // catSchemaTypeUnified is the schema type when CDP is not enabled + catSchemaTypeUnified catSchemaType = "unified" + // catSchemaTypeCode is the 'code' part of CDP schema + catSchemaTypeCode catSchemaType = "code" + // catSchemaTypeData is the 'data' part of CDP schema + catSchemaTypeData catSchemaType = "data" +) + +// cat returns CAT options for the specified cache level. +func (o Options) cat(lvl cacheLevel) CatOptions { + switch lvl { + case L2: + return o.L2 + case L3: + return o.L3 + } + return CatOptions{} +} + +func (t catSchemaType) toResctrlStr() string { + if t == catSchemaTypeUnified { + return "" + } + return strings.ToUpper(string(t)) +} + +const ( + mbSuffixPct = "%" + mbSuffixMbps = "MBps" +) + +func newCatSchema(typ cacheLevel) catSchema { + return catSchema{ + Lvl: typ, + Alloc: make(map[uint64]catAllocation), + } +} + +// toStr returns the CAT schema in a format accepted by the Linux kernel +// resctrl (schemata) interface +func (s catSchema) toStr(typ catSchemaType, baseSchema catSchema) (string, error) { + schema := string(s.Lvl) + typ.toResctrlStr() + ":" + sep := "" + + // Get a sorted slice of cache ids for deterministic output + ids := append([]uint64{}, info.cat[s.Lvl].cacheIds...) + utils.SortUint64s(ids) + + minBits := info.cat[s.Lvl].minCbmBits() + for _, id := range ids { + // Default to 100% + bmask := info.cat[s.Lvl].cbmMask() + + if base, ok := baseSchema.Alloc[id]; ok { + baseMask, ok := base.getEffective(typ).(catAbsoluteAllocation) + if !ok { + return "", fmt.Errorf("BUG: basemask not of type catAbsoluteAllocation") + } + bmask = bitmask(baseMask) + } + + if s.Alloc != nil { + var err error + + masks := s.Alloc[id] + overlayMask := masks.getEffective(typ) + + bmask, err = overlayMask.Overlay(bmask, minBits) + if err != nil { + return "", err + } + } + schema += fmt.Sprintf("%s%d=%x", sep, id, bmask) + sep = ";" + } + + return schema + "\n", nil +} + +func (a catAllocation) get(typ catSchemaType) cacheAllocation { + switch typ { + case catSchemaTypeCode: + return a.Code + case catSchemaTypeData: + return a.Data + } + return a.Unified +} + +func (a catAllocation) set(typ catSchemaType, v cacheAllocation) catAllocation { + switch typ { + case catSchemaTypeCode: + a.Code = v + case catSchemaTypeData: + a.Data = v + default: + a.Unified = v + } + + return a +} + +func (a catAllocation) getEffective(typ catSchemaType) cacheAllocation { + switch typ { + case catSchemaTypeCode: + if a.Code != nil { + return a.Code + } + case catSchemaTypeData: + if a.Data != nil { + return a.Data + } + } + // Use Unified as the default/fallback for Code and Data + return a.Unified +} + +// Overlay function of the cacheAllocation interface +func (a catAbsoluteAllocation) Overlay(baseMask bitmask, minBits uint64) (bitmask, error) { + if err := verifyCatBaseMask(baseMask, minBits); err != nil { + return 0, err + } + + shiftWidth := baseMask.lsbOne() + + // Treat our bitmask relative to the basemask + bmask := bitmask(a) << shiftWidth + + // Do bounds checking that we're "inside" the base mask + if bmask|baseMask != baseMask { + return 0, fmt.Errorf("bitmask %#x (%#x << %d) does not fit basemask %#x", bmask, a, shiftWidth, baseMask) + } + + return bmask, nil +} + +// MarshalJSON implements the Marshaler interface of "encoding/json" +func (a catAbsoluteAllocation) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("\"%#x\"", a)), nil +} + +// Overlay function of the cacheAllocation interface +func (a catPctAllocation) Overlay(baseMask bitmask, minBits uint64) (bitmask, error) { + return catPctRangeAllocation{highPct: uint64(a)}.Overlay(baseMask, minBits) +} + +// Overlay function of the cacheAllocation interface +func (a catPctRangeAllocation) Overlay(baseMask bitmask, minBits uint64) (bitmask, error) { + if err := verifyCatBaseMask(baseMask, minBits); err != nil { + return 0, err + } + + baseMaskMsb := uint64(baseMask.msbOne()) + baseMaskLsb := uint64(baseMask.lsbOne()) + baseMaskNumBits := baseMaskMsb - baseMaskLsb + 1 + + low, high := a.lowPct, a.highPct + if low == 0 { + low = 1 + } + if low > high || low > 100 || high > 100 { + return 0, fmt.Errorf("invalid percentage range in %v", a) + } + + // Convert percentage limits to bit numbers + // Our effective range is 1%-100%, use substraction (-1) because of + // arithmetics, so that we don't overflow on 100% + lsb := (low - 1) * baseMaskNumBits / 100 + msb := (high - 1) * baseMaskNumBits / 100 + + // Make sure the number of bits set satisfies the minimum requirement + numBits := msb - lsb + 1 + if numBits < minBits { + gap := minBits - numBits + + // First, widen the mask from the "lsb end" + if gap <= lsb { + lsb -= gap + gap = 0 + } else { + gap -= lsb + lsb = 0 + } + // If needed, widen the mask from the "msb end" + msbAvailable := baseMaskNumBits - msb - 1 + if gap <= msbAvailable { + msb += gap + } else { + return 0, fmt.Errorf("BUG: not enough bits available for cache bitmask (%v applied on basemask %#x)", a, baseMask) + } + } + + value := ((1 << (msb - lsb + 1)) - 1) << (lsb + baseMaskLsb) + + return bitmask(value), nil +} + +func verifyCatBaseMask(baseMask bitmask, minBits uint64) error { + if baseMask == 0 { + return fmt.Errorf("empty basemask not allowed") + } + + // Check that the basemask contains one (and only one) contiguous block of + // (enough) bits set + baseMaskWidth := baseMask.msbOne() - baseMask.lsbOne() + 1 + if bits.OnesCount64(uint64(baseMask)) != baseMaskWidth { + return fmt.Errorf("invalid basemask %#x: more than one block of bits set", baseMask) + } + if uint64(bits.OnesCount64(uint64(baseMask))) < minBits { + return fmt.Errorf("invalid basemask %#x: fewer than %d bits set", baseMask, minBits) + } + + return nil +} + +// MarshalJSON implements the Marshaler interface of "encoding/json" +func (a catPctAllocation) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("\"%d%%\"", a)), nil +} + +// MarshalJSON implements the Marshaler interface of "encoding/json" +func (a catPctRangeAllocation) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("\"%d-%d%%\"", a.lowPct, a.highPct)), nil +} + +// toStr returns the MB schema in a format accepted by the Linux kernel +// resctrl (schemata) interface +func (s mbSchema) toStr(base map[uint64]uint64) string { + schema := "MB:" + sep := "" + + // Get a sorted slice of cache ids for deterministic output + ids := append([]uint64{}, info.mb.cacheIds...) + utils.SortUint64s(ids) + + for _, id := range ids { + baseAllocation, ok := base[id] + if !ok { + if info.mb.mbpsEnabled { + baseAllocation = math.MaxUint32 + } else { + baseAllocation = 100 + } + } + + value := uint64(0) + if info.mb.mbpsEnabled { + value = math.MaxUint32 + if s != nil { + value = s[id] + } + // Limit to given base value + if value > baseAllocation { + value = baseAllocation + } + } else { + allocation := uint64(100) + if s != nil { + allocation = s[id] + } + value = allocation * baseAllocation / 100 + // Guarantee minimum bw so that writing out the schemata does not fail + if value < info.mb.minBandwidth { + value = info.mb.minBandwidth + } + } + + schema += fmt.Sprintf("%s%d=%d", sep, id, value) + sep = ";" + } + + return schema + "\n" +} + +// listStrToArray parses a string containing a human-readable list of numbers +// into an integer array +func listStrToArray(str string) ([]int, error) { + a := []int{} + + // Empty list + if len(str) == 0 { + return a, nil + } + + ranges := strings.Split(str, ",") + for _, ran := range ranges { + split := strings.SplitN(ran, "-", 2) + + // We limit to 8 bits in order to avoid accidental super long slices + num, err := strconv.ParseInt(split[0], 10, 8) + if err != nil { + return a, fmt.Errorf("invalid integer %q: %v", str, err) + } + + if len(split) == 1 { + a = append(a, int(num)) + } else { + endNum, err := strconv.ParseInt(split[1], 10, 8) + if err != nil { + return a, fmt.Errorf("invalid integer in range %q: %v", str, err) + } + if endNum <= num { + return a, fmt.Errorf("invalid integer range %q in %q", ran, str) + } + for i := num; i <= endNum; i++ { + a = append(a, int(i)) + } + } + } + sort.Ints(a) + return a, nil +} + +// resolve tries to resolve the requested configuration into a working +// configuration +func (c *Config) resolve() (config, error) { + var err error + conf := config{Options: c.Options} + + // TODO: Think a better (more structured) way to log this + log.Debug("resolving configuration:\n" + utils.DumpJSON(c)) + + conf.Partitions, err = c.resolvePartitions() + if err != nil { + return conf, err + } + + conf.Classes, err = c.resolveClasses() + + return conf, err +} + +// resolvePartitions tries to resolve the requested resource allocations of +// partitions +func (c *Config) resolvePartitions() (partitionSet, error) { + // Initialize empty partition configuration + conf := make(partitionSet, len(c.Partitions)) + for name := range c.Partitions { + conf[name] = &partitionConfig{ + CAT: map[cacheLevel]catSchema{ + L2: newCatSchema(L2), + L3: newCatSchema(L3), + }, + MB: make(mbSchema, len(info.mb.cacheIds))} + } + + // Resolve L2 partition allocations + err := c.resolveCatPartitions(L2, conf) + if err != nil { + return nil, err + } + + // Try to resolve L3 partition allocations + err = c.resolveCatPartitions(L3, conf) + if err != nil { + return nil, err + } + + // Try to resolve MB partition allocations + err = c.resolveMBPartitions(conf) + if err != nil { + return nil, err + } + + return conf, nil +} + +// resolveCatPartitions tries to resolve requested cache allocations between partitions +func (c *Config) resolveCatPartitions(lvl cacheLevel, conf partitionSet) error { + if len(c.Partitions) == 0 { + return nil + } + + // Resolve partitions in sorted order for reproducibility + names := make([]string, 0, len(c.Partitions)) + for name := range c.Partitions { + names = append(names, name) + } + sort.Strings(names) + + resolver := newCacheResolver(lvl, names) + + // Parse requested allocations from user config and load the resolver + for _, name := range names { + var allocations catSchema + var err error + switch lvl { + case L2: + allocations, err = c.Partitions[name].L2Allocation.toSchema(L2) + case L3: + allocations, err = c.Partitions[name].L3Allocation.toSchema(L3) + } + + if err != nil { + return fmt.Errorf("failed to parse %s allocation request for partition %q: %v", lvl, name, err) + } + + resolver.requests[name] = allocations.Alloc + } + + // Run resolver fo partition allocations + grants, err := resolver.resolve() + if err != nil { + return err + } + if grants == nil { + log.Debug("cache allocation disabled for all partitions", "cacheLevel", lvl) + return nil + } + + for name, grant := range grants { + conf[name].CAT[lvl] = grant + } + + infoStr := fmt.Sprintf("actual (and requested) %s allocations per partition and cache id:\n", lvl) + for name, partition := range resolver.requests { + infoStr += name + "\n" + for _, id := range resolver.ids { + infoStr += fmt.Sprintf(" %2d: ", id) + allocationReq := partition[id] + for _, typ := range []catSchemaType{catSchemaTypeUnified, catSchemaTypeCode, catSchemaTypeData} { + infoStr += string(typ) + " " + requested := allocationReq.get(typ) + switch v := requested.(type) { + case catAbsoluteAllocation: + infoStr += fmt.Sprintf(" ", v) + case catPctAllocation: + granted := grants[name].Alloc[id].get(typ).(catAbsoluteAllocation) + requestedPct := fmt.Sprintf("(%d%%)", v) + truePct := float64(bits.OnesCount64(uint64(granted))) * 100 / float64(resolver.bitsTotal) + infoStr += fmt.Sprintf("%5.1f%% %-6s ", truePct, requestedPct) + case nil: + infoStr += " " + } + } + infoStr += "\n" + } + } + // TODO: Think a better (more structured) way to log this + log.Debug("actual (and requested) allocations per partition and cache id\n" + infoStr) + + return nil +} + +// cacheResolver is a helper for resolving exclusive (partition) cache // allocation requests +type cacheResolver struct { + lvl cacheLevel + ids []uint64 + minBits uint64 + bitsTotal uint64 + partitions []string + requests map[string]catSchemaRaw + grants map[string]catSchema +} + +func newCacheResolver(lvl cacheLevel, partitions []string) *cacheResolver { + r := &cacheResolver{ + lvl: lvl, + ids: info.cat[lvl].cacheIds, + minBits: info.cat[lvl].minCbmBits(), + bitsTotal: uint64(info.cat[lvl].cbmMask().lsbZero()), + partitions: partitions, + requests: make(map[string]catSchemaRaw, len(partitions)), + grants: make(map[string]catSchema, len(partitions))} + + for _, p := range partitions { + r.grants[p] = catSchema{Lvl: lvl, Alloc: make(catSchemaRaw, len(r.ids))} + } + + return r +} + +func (r *cacheResolver) resolve() (map[string]catSchema, error) { + for _, id := range r.ids { + err := r.resolveID(id) + if err != nil { + return nil, err + } + } + return r.grants, nil +} + +// resolveCacheID resolves the partition allocations for one cache id +func (r *cacheResolver) resolveID(id uint64) error { + for _, typ := range []catSchemaType{catSchemaTypeUnified, catSchemaTypeCode, catSchemaTypeData} { + err := r.resolveType(id, typ) + if err != nil { + return err + } + } + return nil +} + +// resolveType resolve one schema type for one cache id +func (r *cacheResolver) resolveType(id uint64, typ catSchemaType) error { + // Sanity check: if any partition has l3 allocation of this schema type + // configured check that all other partitions have it, too + nils := []string{} + for _, partition := range r.partitions { + if r.requests[partition][id].get(typ) == nil { + nils = append(nils, partition) + } + } + if len(nils) > 0 && len(nils) != len(r.partitions) { + return fmt.Errorf("some partitions (%s) missing %s %q allocation request for cache id %d", + strings.Join(nils, ", "), r.lvl, typ, id) + } + + // Act depending on the type of the first request in the list + a := r.requests[r.partitions[0]][id].get(typ) + switch a.(type) { + case catAbsoluteAllocation: + return r.resolveAbsolute(id, typ) + case nil: + default: + return r.resolveRelative(id, typ) + } + return nil +} + +func (r *cacheResolver) resolveRelative(id uint64, typ catSchemaType) error { + type reqHelper struct { + name string + req uint64 + } + + // Sanity check: + // 1. allocation requests are of the same type (relative) + // 2. total allocation requested for this cache id does not exceed 100 percent + // Additionally fill a helper structure for sorting partitions + percentageTotal := uint64(0) + reqs := make([]reqHelper, 0, len(r.partitions)) + for _, partition := range r.partitions { + switch a := r.requests[partition][id].get(typ).(type) { + case catPctAllocation: + percentageTotal += uint64(a) + reqs = append(reqs, reqHelper{name: partition, req: uint64(a)}) + case catAbsoluteAllocation: + return fmt.Errorf("error resolving %s allocation for cache id %d: mixing "+ + "relative and absolute allocations between partitions not supported", r.lvl, id) + case catPctRangeAllocation: + return fmt.Errorf("percentage ranges in partition allocation not supported") + default: + return fmt.Errorf("BUG: unknown cacheAllocation type %T", a) + } + } + if percentageTotal < 100 { + log.Info("requested total partition allocation <100%%", "cacheLevel", r.lvl, "cacheID", id, "schemaType", typ, "percentage", percentageTotal) + } else if percentageTotal > 100 { + return fmt.Errorf("accumulated %s %q partition allocation requests for cache id %d exceeds 100%% (%d%%)", r.lvl, typ, id, percentageTotal) + } + + // Sort partition allocations. We want to resolve smallest allocations + // first in order to try to ensure that all allocations can be satisfied + // because small percentages might need to be rounded up + sort.Slice(reqs, func(i, j int) bool { + return reqs[i].req < reqs[j].req + }) + + // Calculate number of bits granted to each partition. + grants := make(map[string]uint64, len(r.partitions)) + bitsTotal := percentageTotal * uint64(r.bitsTotal) / 100 + bitsAvailable := bitsTotal + for i, req := range reqs { + percentageAvailable := bitsAvailable * percentageTotal / bitsTotal + + // This might happen e.g. if number of partitions would be greater + // than the total number of bits + if bitsAvailable < r.minBits { + return fmt.Errorf("unable to resolve %s allocation for cache id %d, not enough exlusive bits available", r.lvl, id) + } + + // Use integer arithmetics, effectively always rounding down + // fractional allocations i.e. trying to avoid over-allocation + numBits := req.req * bitsAvailable / percentageAvailable + + // Guarantee a non-zero allocation + if numBits < r.minBits { + numBits = r.minBits + } + // Don't overflow, allocate all remaining bits to the last partition + if numBits > bitsAvailable || i == len(reqs)-1 { + numBits = bitsAvailable + } + + grants[req.name] = numBits + bitsAvailable -= numBits + } + + // Construct the actual bitmasks for each partition + lsbID := uint64(0) + for _, partition := range r.partitions { + // Compose the actual bitmask + v := r.grants[partition].Alloc[id].set(typ, catAbsoluteAllocation(bitmask(((1< 0 { + return fmt.Errorf("overlapping %s partition allocation requests for cache id %d", r.lvl, id) + } + mask |= bitmask(a) + + r.grants[partition].Alloc[id] = r.grants[partition].Alloc[id].set(typ, a) + } + + return nil +} + +// resolveMBPartitions tries to resolve requested MB allocations between partitions +func (c *Config) resolveMBPartitions(conf partitionSet) error { + // We use percentage values directly from the user conf + for name, partition := range c.Partitions { + allocations, err := partition.MBAllocation.toSchema() + if err != nil { + return fmt.Errorf("failed to resolve MB allocation for partition %q: %v", name, err) + } + for id, allocation := range allocations { + conf[name].MB[id] = allocation + // Check that we don't go under the minimum allowed bandwidth setting + if !info.mb.mbpsEnabled && allocation < info.mb.minBandwidth { + conf[name].MB[id] = info.mb.minBandwidth + } + } + } + + return nil +} + +// resolveClasses tries to resolve class allocations of all partitions +func (c *Config) resolveClasses() (classSet, error) { + classes := make(classSet) + + for bname, partition := range c.Partitions { + for gname, class := range partition.Classes { + gname = unaliasClassName(gname) + + if !IsQualifiedClassName(gname) { + return classes, fmt.Errorf("unqualified class name %q (must not be '.' or '..' and must not contain '/' or newline)", gname) + } + if _, ok := classes[gname]; ok { + return classes, fmt.Errorf("class names must be unique, %q defined multiple times", gname) + } + + var err error + gc := &classConfig{Partition: bname, + CATSchema: make(map[cacheLevel]catSchema), + Kubernetes: class.Kubernetes} + + gc.CATSchema[L2], err = class.L2Allocation.toSchema(L2) + if err != nil { + return classes, fmt.Errorf("failed to resolve L2 allocation for class %q: %v", gname, err) + } + if gc.CATSchema[L2].Alloc != nil && partition.L2Allocation == nil { + return classes, fmt.Errorf("L2 allocation missing from partition %q but class %q specifies L2 schema", bname, gname) + } + + gc.CATSchema[L3], err = class.L3Allocation.toSchema(L3) + if err != nil { + return classes, fmt.Errorf("failed to resolve L3 allocation for class %q: %v", gname, err) + } + if gc.CATSchema[L3].Alloc != nil && partition.L3Allocation == nil { + return classes, fmt.Errorf("L3 allocation missing from partition %q but class %q specifies L3 schema", bname, gname) + } + + gc.MBSchema, err = class.MBAllocation.toSchema() + if err != nil { + return classes, fmt.Errorf("failed to resolve MB allocation for class %q: %v", gname, err) + } + if gc.MBSchema != nil && partition.MBAllocation == nil { + return classes, fmt.Errorf("MB allocation missing from partition %q but class %q specifies MB schema", bname, gname) + } + + classes[gname] = gc + } + } + + return classes, nil +} + +// toSchema converts a cache allocation config to effective allocation schema covering all cache IDs +func (c CatConfig) toSchema(lvl cacheLevel) (catSchema, error) { + if c == nil { + return catSchema{Lvl: lvl}, nil + } + + allocations := newCatSchema(lvl) + minBits := info.cat[lvl].minCbmBits() + + d, ok := c[CacheIdAll] + if !ok { + d = CacheIdCatConfig{Unified: "100%"} + } + defaultVal, err := d.parse(minBits) + if err != nil { + return allocations, err + } + + // Pre-fill with defaults + for _, i := range info.cat[lvl].cacheIds { + allocations.Alloc[i] = defaultVal + } + + for key, val := range c { + if key == CacheIdAll { + continue + } + + ids, err := listStrToArray(key) + if err != nil { + return allocations, err + } + + schemaVal, err := val.parse(minBits) + if err != nil { + return allocations, err + } + + for _, id := range ids { + if _, ok := allocations.Alloc[uint64(id)]; ok { + allocations.Alloc[uint64(id)] = schemaVal + } + } + } + + return allocations, nil +} + +// catConfig is a helper for unmarshalling CatConfig +type catConfig CatConfig + +// UnmarshalJSON implements the Unmarshaler interface of "encoding/json" +func (c *CatConfig) UnmarshalJSON(data []byte) error { + raw := new(interface{}) + + err := json.Unmarshal(data, raw) + if err != nil { + return err + } + + conf := CatConfig{} + switch v := (*raw).(type) { + case string: + conf[CacheIdAll] = CacheIdCatConfig{Unified: CacheProportion(v)} + default: + // Use the helper type to avoid infinite recursion + helper := catConfig{} + if err := json.Unmarshal(data, &helper); err != nil { + return err + } + for k, v := range helper { + conf[k] = v + } + } + *c = conf + return nil +} + +// toSchema converts an MB allocation config to effective allocation schema covering all cache IDs +func (c MbaConfig) toSchema() (mbSchema, error) { + if c == nil { + return nil, nil + } + + d, ok := c[CacheIdAll] + if !ok { + d = CacheIdMbaConfig{"100" + mbSuffixPct, "4294967295" + mbSuffixMbps} + } + defaultVal, err := d.parse() + if err != nil { + return nil, err + } + + allocations := make(mbSchema, len(info.mb.cacheIds)) + // Pre-fill with defaults + for _, i := range info.mb.cacheIds { + allocations[i] = defaultVal + } + + for key, val := range c { + if key == CacheIdAll { + continue + } + + ids, err := listStrToArray(key) + if err != nil { + return nil, err + } + + schemaVal, err := val.parse() + if err != nil { + return nil, err + } + + for _, id := range ids { + if _, ok := allocations[uint64(id)]; ok { + allocations[uint64(id)] = schemaVal + } + } + } + + return allocations, nil +} + +// mbaConfig is a helper for unmarshalling MbaConfig +type mbaConfig MbaConfig + +// UnmarshalJSON implements the Unmarshaler interface of "encoding/json" +func (c *MbaConfig) UnmarshalJSON(data []byte) error { + raw := new(interface{}) + + err := json.Unmarshal(data, raw) + if err != nil { + return err + } + + conf := MbaConfig{} + switch (*raw).(type) { + case []interface{}: + helper := CacheIdMbaConfig{} + if err := json.Unmarshal(data, &helper); err != nil { + return err + } + conf[CacheIdAll] = helper + default: + // Use the helper type to avoid infinite recursion + helper := mbaConfig{} + if err := json.Unmarshal(data, &helper); err != nil { + return err + } + for k, v := range helper { + conf[k] = v + } + } + *c = conf + return nil +} + +// parse per cache-id CAT configuration into an effective allocation to be used +// in the CAT schema +func (c *CacheIdCatConfig) parse(minBits uint64) (catAllocation, error) { + var err error + allocation := catAllocation{} + + allocation.Unified, err = c.Unified.parse(minBits) + if err != nil { + return allocation, err + } + allocation.Code, err = c.Code.parse(minBits) + if err != nil { + return allocation, err + } + allocation.Data, err = c.Data.parse(minBits) + if err != nil { + return allocation, err + } + + // Sanity check for the configuration + if allocation.Unified == nil { + return allocation, fmt.Errorf("'unified' not specified in cache schema %s", *c) + } + if allocation.Code != nil && allocation.Data == nil { + return allocation, fmt.Errorf("'code' specified but missing 'data' from cache schema %s", *c) + } + if allocation.Code == nil && allocation.Data != nil { + return allocation, fmt.Errorf("'data' specified but missing 'code' from cache schema %s", *c) + } + + return allocation, nil +} + +// cacheIdCatConfig is a helper for unmarshalling CacheIdCatConfig +type cacheIdCatConfig CacheIdCatConfig + +// UnmarshalJSON implements the Unmarshaler interface of "encoding/json" +func (c *CacheIdCatConfig) UnmarshalJSON(data []byte) error { + raw := new(interface{}) + + err := json.Unmarshal(data, raw) + if err != nil { + return err + } + + conf := CacheIdCatConfig{} + switch v := (*raw).(type) { + case string: + conf.Unified = CacheProportion(v) + default: + // Use the helper type to avoid infinite recursion + helper := cacheIdCatConfig{} + if err := json.Unmarshal(data, &helper); err != nil { + return err + } + conf.Unified = helper.Unified + conf.Code = helper.Code + conf.Data = helper.Data + } + *c = conf + return nil +} + +// parse converts a per cache-id MBA configuration into effective value +// to be used in the MBA schema +func (c *CacheIdMbaConfig) parse() (uint64, error) { + for _, v := range *c { + str := string(v) + if strings.HasSuffix(str, mbSuffixPct) { + if !info.mb.mbpsEnabled { + value, err := strconv.ParseUint(strings.TrimSuffix(str, mbSuffixPct), 10, 7) + if err != nil { + return 0, err + } + return value, nil + } + } else if strings.HasSuffix(str, mbSuffixMbps) { + if info.mb.mbpsEnabled { + value, err := strconv.ParseUint(strings.TrimSuffix(str, mbSuffixMbps), 10, 32) + if err != nil { + return 0, err + } + return value, nil + } + } else { + log.Warn("unrecognized MBA allocation unit", "value", str) + } + } + + // No value for the active mode was specified + if info.mb.mbpsEnabled { + return 0, fmt.Errorf("missing 'MBps' value from mbSchema; required because 'mba_MBps' is enabled in the system") + } + return 0, fmt.Errorf("missing '%%' value from mbSchema; required because percentage-based MBA allocation is enabled in the system") +} + +// parse converts a string value into cacheAllocation type +func (c CacheProportion) parse(minBits uint64) (cacheAllocation, error) { + if c == "" { + return nil, nil + } + + if c[len(c)-1] == '%' { + // Percentages of the max number of bits + split := strings.SplitN(string(c)[0:len(c)-1], "-", 2) + var allocation cacheAllocation + + if len(split) == 1 { + pct, err := strconv.ParseUint(split[0], 10, 7) + if err != nil { + return allocation, err + } + if pct > 100 { + return allocation, fmt.Errorf("invalid percentage value %q", c) + } + allocation = catPctAllocation(pct) + } else { + low, err := strconv.ParseUint(split[0], 10, 7) + if err != nil { + return allocation, err + } + high, err := strconv.ParseUint(split[1], 10, 7) + if err != nil { + return allocation, err + } + if low > high || low > 100 || high > 100 { + return allocation, fmt.Errorf("invalid percentage range %q", c) + } + allocation = catPctRangeAllocation{lowPct: low, highPct: high} + } + + return allocation, nil + } + + // Absolute allocation + var value uint64 + var err error + if strings.HasPrefix(string(c), "0x") { + // Hex value + value, err = strconv.ParseUint(string(c[2:]), 16, 64) + if err != nil { + return nil, err + } + } else { + // Last, try "list" format (i.e. smthg like 0,2,5-9,...) + tmp, err := listStrToBitmask(string(c)) + value = uint64(tmp) + if err != nil { + return nil, err + } + } + + // Sanity check of absolute allocation: bitmask must (only) contain one + // contiguous block of ones wide enough + numOnes := bits.OnesCount64(value) + if numOnes != bits.Len64(value)-bits.TrailingZeros64(value) { + return nil, fmt.Errorf("invalid cache bitmask %q: more than one continuous block of ones", c) + } + if uint64(numOnes) < minBits { + return nil, fmt.Errorf("invalid cache bitmask %q: number of bits less than %d", c, minBits) + } + + return catAbsoluteAllocation(value), nil +} diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/ctrlmongroup.go b/vendor/github.com/intel/goresctrl/pkg/rdt/ctrlmongroup.go new file mode 100644 index 0000000000..aee3cfecfd --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/ctrlmongroup.go @@ -0,0 +1,463 @@ +/* +Copyright 2019 Intel Corporation + +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. +*/ + +package rdt + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "syscall" +) + +type groupAnnotations = map[string]string + +// Functions for creating and removing resctrl groups from the filesystem. These are +// configurable because of unit tests. +var groupCreateFunc func(string, os.FileMode) error = os.Mkdir +var groupRemoveFunc func(string) error = os.Remove + +// CtrlGroup defines the interface of one goresctrl managed RDT class. It maps +// to one CTRL group directory in the goresctrl pseudo-filesystem. +type CtrlGroup interface { + ResctrlGroup + + // CreateMonGroup creates a new monitoring group under this CtrlGroup. + CreateMonGroup(name string, annotations map[string]string) (MonGroup, error) + + // DeleteMonGroup deletes a monitoring group from this CtrlGroup. + DeleteMonGroup(name string) error + + // DeleteMonGroups deletes all monitoring groups from this CtrlGroup. + DeleteMonGroups() error + + // GetMonGroup returns a specific monitoring group under this CtrlGroup. + GetMonGroup(name string) (MonGroup, bool) + + // GetMonGroups returns all monitoring groups under this CtrlGroup. + GetMonGroups() []MonGroup +} + +// ResctrlGroup is the generic interface for resctrl CTRL and MON groups. It +// maps to one CTRL or MON group directory in the goresctrl pseudo-filesystem. +type ResctrlGroup interface { + // Name returns the name of the group. + Name() string + + // GetPids returns the process ids assigned to the group. + GetPids() ([]string, error) + + // AddPids assigns the given process ids to the group. + AddPids(pids ...string) error + + // GetMonData retrieves the monitoring data of the group. + GetMonData() MonData + + // GetAnnotations returns the annotations associated with the group + GetAnnotations() map[string]string + + // Private methods for internal use + dirName() string +} + +// MonGroup represents the interface to a RDT monitoring group. It maps to one +// MON group in the goresctrl filesystem. +type MonGroup interface { + ResctrlGroup + + // Parent returns the CtrlGroup under which the monitoring group exists. + Parent() CtrlGroup +} + +// MonData contains monitoring stats of one monitoring group. +type MonData struct { + L3 MonL3Data +} + +// MonL3Data contains L3 monitoring stats of one monitoring group. +type MonL3Data map[uint64]MonLeafData + +// MonLeafData represents the raw numerical stats from one RDT monitor data leaf. +type MonLeafData map[string]uint64 + +// MonResource is the type of RDT monitoring resource. +type MonResource string + +const ( + // MonResourceL3 is the RDT L3 cache monitor resource. + MonResourceL3 MonResource = "l3" +) + +type ctrlGroup struct { + resctrlGroup + + monPrefix string +} + +type monGroup struct { + resctrlGroup +} + +type resctrlGroup struct { + prefix string + name string + parent *ctrlGroup // parent for MON groups +} + +func newCtrlGroup(prefix, monPrefix, name string) (*ctrlGroup, error) { + cg := &ctrlGroup{ + resctrlGroup: resctrlGroup{prefix: prefix, name: name}, + monPrefix: monPrefix, + } + + if err := groupCreateFunc(cg.path(""), 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + return cg, nil +} + +func (c *ctrlGroup) CreateMonGroup(name string, annotations map[string]string) (MonGroup, error) { + log.Debug("adding monitoring group", "class", c.Name(), "name", name) + mg, err := newMonGroup(c.monPrefix, name, c) + if err != nil { + return nil, fmt.Errorf("failed to create new monitoring group %q: %v", name, err) + } + + // Store annotations in the reqistry + rdt.setAnnotations(&mg.resctrlGroup, annotations) + + return mg, err +} + +func (c *ctrlGroup) DeleteMonGroup(name string) error { + mg, err := c.monGroupFromResctrlFs(name) + if err != nil { + return err + } + + // Drop annotations from the reqistry + rdt.deleteAnnotations(&mg.resctrlGroup) + + log.Debug("deleting monitoring group", "class", c.Name(), "name", mg.Name(), "path", mg.relPath("")) + if err := mg.rmdir(); err != nil { + return fmt.Errorf("failed to remove monitoring group %q (%s): %v", name, mg.relPath(""), err) + } + + return nil +} + +func (c *ctrlGroup) DeleteMonGroups() error { + grps, err := c.monGroupsFromResctrlFs() + if err != nil { + return err + } + for _, mg := range grps { + log.Debug("deleting monitoring group", "class", c.Name(), "name", mg.Name(), "path", mg.relPath("")) + if err := mg.rmdir(); err != nil { + return err + } + } + return nil +} + +func (c *ctrlGroup) GetMonGroup(name string) (MonGroup, bool) { + mg, err := c.monGroupFromResctrlFs(name) + if err != nil { + log.Error("failed to get monitoring group from resctrl filesystem", "className", c.Name(), "error", err) + return nil, false + } + return mg, true +} + +func (c *ctrlGroup) GetMonGroups() []MonGroup { + grps, err := c.monGroupsFromResctrlFs() + if err != nil { + log.Error("failed to get monitoring groups from resctrl filesystem", "className", c.Name(), "error", err) + return nil + } + ret := make([]MonGroup, len(grps)) + for i, mg := range grps { + ret[i] = mg + } + return ret +} + +func (c *ctrlGroup) configure(name string, class *classConfig, + partition *partitionConfig, options Options) error { + schemata := "" + + // Handle cache allocation + for _, lvl := range []cacheLevel{L2, L3} { + switch { + case info.cat[lvl].unified.Supported(): + schema, err := class.CATSchema[lvl].toStr(catSchemaTypeUnified, partition.CAT[lvl]) + if err != nil { + return err + } + schemata += schema + case info.cat[lvl].data.Supported() || info.cat[lvl].code.Supported(): + schema, err := class.CATSchema[lvl].toStr(catSchemaTypeCode, partition.CAT[lvl]) + if err != nil { + return err + } + schemata += schema + + schema, err = class.CATSchema[lvl].toStr(catSchemaTypeData, partition.CAT[lvl]) + if err != nil { + return err + } + schemata += schema + default: + if class.CATSchema[lvl].Alloc != nil && !options.cat(lvl).Optional { + return fmt.Errorf("%s cache allocation for %q specified in configuration but not supported by system", lvl, name) + } + } + } + + // Handle memory bandwidth allocation + switch { + case info.mb.Supported(): + schemata += class.MBSchema.toStr(partition.MB) + default: + if class.MBSchema != nil && !options.MB.Optional { + return fmt.Errorf("memory bandwidth allocation for %q specified in configuration but not supported by system", name) + } + } + + if len(schemata) > 0 { + log.Debug("writing schemata", "schemata", schemata, "path", c.path("")) + if err := rdt.writeRdtFile(c.relPath("schemata"), []byte(schemata)); err != nil { + return err + } + } else { + log.Debug("empty schemata") + } + + return nil +} + +func (c *ctrlGroup) monGroupsFromResctrlFs() ([]*monGroup, error) { + names, err := resctrlGroupsFromFs(c.monPrefix, c.path("mon_groups")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + + grps := make([]*monGroup, 0, len(names)) + for _, name := range names { + name = name[len(c.monPrefix):] + mg, err := newMonGroup(c.monPrefix, name, c) + if err != nil { + return nil, err + } + grps = append(grps, mg) + } + + sort.Slice(grps, func(i, j int) bool { return grps[i].Name() < grps[j].Name() }) + + return grps, nil +} + +func (c *ctrlGroup) monGroupFromResctrlFs(name string) (*monGroup, error) { + path := filepath.Join(c.path("mon_groups"), c.monPrefix+name) + if !isResctrlGroup(path) { + return nil, fmt.Errorf("resctrl group not found for monitoring group %q (%q)", name, path) + } + + mg, err := newMonGroup(c.monPrefix, name, c) + if err != nil { + return nil, err + } + + return mg, nil +} + +// Remove empty monitoring groups +func (c *ctrlGroup) pruneMonGroups() error { + grps, err := c.monGroupsFromResctrlFs() + if err != nil { + return err + } + for _, mg := range grps { + pids, err := mg.GetPids() + if err != nil { + return fmt.Errorf("failed to get pids for monitoring group %q: %v", mg.relPath(""), err) + } + if len(pids) == 0 { + if err := mg.rmdir(); err != nil { + return fmt.Errorf("failed to remove monitoring group %q: %v", mg.relPath(""), err) + } + } + } + return nil +} + +func (r *resctrlGroup) Name() string { + return r.name +} + +func (r *resctrlGroup) GetPids() ([]string, error) { + data, err := rdt.readRdtFile(r.relPath("tasks")) + if err != nil { + return []string{}, err + } + split := strings.Split(strings.TrimSpace(string(data)), "\n") + if len(split[0]) > 0 { + return split, nil + } + return []string{}, nil +} + +func (r *resctrlGroup) AddPids(pids ...string) (err error) { + f, err := os.OpenFile(r.path("tasks"), os.O_WRONLY, 0644) + if err != nil { + return err + } + defer func() { + if cerr := f.Close(); cerr != nil && err == nil { + err = cerr + } + }() + + for _, pid := range pids { + if _, err := f.WriteString(pid + "\n"); err != nil { + if errors.Is(err, syscall.ESRCH) { + log.Debug("no task", "pid", pid) + } else { + return fmt.Errorf("failed to assign processes %v to class %q: %v", pids, r.name, rdt.cmdError(err)) + } + } + } + return +} + +func (r *resctrlGroup) GetMonData() MonData { + m := MonData{} + + if info.l3mon.Supported() { + l3, err := r.getMonL3Data() + if err != nil { + log.Error("failed to retrieve L3 monitoring data", "error", err) + } else { + m.L3 = l3 + } + } + + return m +} + +func (r *resctrlGroup) GetAnnotations() map[string]string { + return rdt.getAnnotations(r) +} + +func (r *resctrlGroup) getMonL3Data() (MonL3Data, error) { + files, err := os.ReadDir(r.path("mon_data")) + if err != nil { + return nil, err + } + + m := MonL3Data{} + for _, file := range files { + name := file.Name() + if strings.HasPrefix(name, "mon_L3_") { + // Parse cache id from the dirname + id, err := strconv.ParseUint(strings.TrimPrefix(name, "mon_L3_"), 10, 32) + if err != nil { + // Just log an error and continue, we try to retrieve as much info as possible + log.Error("failed to parse L3 monitor data directory name", "fileName", name, "error", err) + continue + } + + data, err := r.getMonLeafData(filepath.Join("mon_data", name)) + if err != nil { + log.Error("failed to read monitor data", "error", err) + continue + } + + m[id] = data + } + } + + return m, nil +} + +func (r *resctrlGroup) getMonLeafData(path string) (MonLeafData, error) { + files, err := os.ReadDir(r.path(path)) + if err != nil { + return nil, err + } + + m := make(MonLeafData, len(files)) + + for _, file := range files { + name := file.Name() + + // We expect that all the files in the dir are regular files + val, err := readFileUint64(r.path(path, name)) + if err != nil { + // Just log an error and continue, we want to retrieve as much info as possible + log.Error("failed to read data file", "error", err) + continue + } + + m[name] = val + } + return m, nil +} + +func (r *resctrlGroup) relPath(elem ...string) string { + if r.parent == nil { + return filepath.Join(append([]string{r.dirName()}, elem...)...) + } + // Parent is only intended for MON groups - non-root CTRL groups are considered + // as peers to the root CTRL group (as they are in HW) and do not have a parent + return r.parent.relPath(append([]string{"mon_groups", r.dirName()}, elem...)...) +} + +func (r *resctrlGroup) path(elem ...string) string { + return filepath.Join(info.resctrlPath, r.relPath(elem...)) +} + +func (r *resctrlGroup) rmdir() error { + return groupRemoveFunc(r.path("")) +} + +func (r *resctrlGroup) dirName() string { + if r.name == RootClassName { + return "" + } + return r.prefix + r.name +} + +func newMonGroup(prefix string, name string, parent *ctrlGroup) (*monGroup, error) { + mg := &monGroup{ + resctrlGroup: resctrlGroup{prefix: prefix, name: name, parent: parent}, + } + + if err := groupCreateFunc(mg.path(""), 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + return mg, nil +} + +func (m *monGroup) Parent() CtrlGroup { + return m.parent +} diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/info.go b/vendor/github.com/intel/goresctrl/pkg/rdt/info.go new file mode 100644 index 0000000000..dc5595f6b1 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/info.go @@ -0,0 +1,351 @@ +/* +Copyright 2019-2021 Intel Corporation + +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. +*/ + +package rdt + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +// resctrlInfo contains information about the RDT support in the system +type resctrlInfo struct { + resctrlPath string + resctrlMountOpts map[string]struct{} + numClosids uint64 + cat map[cacheLevel]catInfoAll + l3mon l3MonInfo + mb mbInfo +} + +type cacheLevel string + +const ( + L2 cacheLevel = "L2" + L3 cacheLevel = "L3" +) + +type catInfoAll struct { + cacheIds []uint64 + unified catInfo + code catInfo + data catInfo +} + +type catInfo struct { + numClosids uint64 + cbmMask bitmask + minCbmBits uint64 + shareableBits bitmask +} + +type l3MonInfo struct { + numRmids uint64 + monFeatures []string +} + +type mbInfo struct { + cacheIds []uint64 + numClosids uint64 + bandwidthGran uint64 + delayLinear uint64 + minBandwidth uint64 + mbpsEnabled bool // true if MBA_MBps is enabled +} + +var mountInfoPath string = "/proc/mounts" + +// getInfo is a helper method for a "unified API" for getting L3 information +func (i catInfoAll) getInfo() catInfo { + switch { + case i.code.Supported(): + return i.code + case i.data.Supported(): + return i.data + } + return i.unified +} + +func (i catInfoAll) cbmMask() bitmask { + mask := i.getInfo().cbmMask + if mask != 0 { + return mask + } + return bitmask(^uint64(0)) +} + +func (i catInfoAll) minCbmBits() uint64 { + return i.getInfo().minCbmBits +} + +func getRdtInfo() (*resctrlInfo, error) { + var err error + info := &resctrlInfo{cat: make(map[cacheLevel]catInfoAll)} + + info.resctrlPath, info.resctrlMountOpts, err = getResctrlMountInfo() + if err != nil { + return info, fmt.Errorf("failed to detect resctrl mount point: %v", err) + } + log.Info("detected resctrl filesystem", "path", info.resctrlPath) + + // Check that RDT is available + infopath := filepath.Join(info.resctrlPath, "info") + if _, err := os.Stat(infopath); err != nil { + return info, fmt.Errorf("failed to read RDT info from %q: %v", infopath, err) + } + + // Check CAT feature available + for _, cl := range []cacheLevel{L2, L3} { + cat := catInfoAll{} + catFeatures := map[string]*catInfo{ + "": &cat.unified, + "CODE": &cat.code, + "DATA": &cat.data, + } + for suffix, i := range catFeatures { + dir := string(cl) + suffix + subpath := filepath.Join(infopath, dir) + if _, err = os.Stat(subpath); err == nil { + *i, err = getCatInfo(subpath) + if err != nil { + return info, fmt.Errorf("failed to get %s info from %q: %v", dir, subpath, err) + } + // Overall number of closids is the minimum across all cache levels/features + if info.numClosids == 0 || i.numClosids < info.numClosids { + info.numClosids = i.numClosids + } + } + } + if cat.getInfo().Supported() { + cat.cacheIds, err = getCacheIds(info.resctrlPath, string(cl)) + if err != nil { + return info, fmt.Errorf("failed to get %s CAT cache IDs: %v", cl, err) + } + } + info.cat[cl] = cat + } + + // Check MON features available + subpath := filepath.Join(infopath, "L3_MON") + if _, err = os.Stat(subpath); err == nil { + info.l3mon, err = getL3MonInfo(subpath) + if err != nil { + return info, fmt.Errorf("failed to get L3_MON info from %q: %v", subpath, err) + } + } + + // Check MBA feature available + subpath = filepath.Join(infopath, "MB") + if _, err = os.Stat(subpath); err == nil { + info.mb, err = getMBInfo(subpath) + if err != nil { + return info, fmt.Errorf("failed to get MBA info from %q: %v", subpath, err) + } + + info.mb.cacheIds, err = getCacheIds(info.resctrlPath, "MB") + if err != nil { + return info, fmt.Errorf("failed to get MBA cache IDs: %v", err) + } + // Overall number of closids is the minimum across all cache levels/features + if info.numClosids == 0 || info.mb.numClosids < info.numClosids { + info.numClosids = info.mb.numClosids + } + } + + return info, nil +} + +func getCatInfo(basepath string) (catInfo, error) { + var err error + info := catInfo{} + + info.cbmMask, err = readFileBitmask(filepath.Join(basepath, "cbm_mask")) + if err != nil { + return info, err + } + info.minCbmBits, err = readFileUint64(filepath.Join(basepath, "min_cbm_bits")) + if err != nil { + return info, err + } + info.shareableBits, err = readFileBitmask(filepath.Join(basepath, "shareable_bits")) + if err != nil { + return info, err + } + info.numClosids, err = readFileUint64(filepath.Join(basepath, "num_closids")) + if err != nil { + return info, err + } + + return info, nil +} + +// Supported returns true if L3 cache allocation has is supported and enabled in the system +func (i catInfo) Supported() bool { + return i.cbmMask != 0 +} + +func getL3MonInfo(basepath string) (l3MonInfo, error) { + var err error + info := l3MonInfo{} + + info.numRmids, err = readFileUint64(filepath.Join(basepath, "num_rmids")) + if err != nil { + return info, err + } + + lines, err := readFileString(filepath.Join(basepath, "mon_features")) + if err != nil { + return info, err + } + info.monFeatures = strings.Split(lines, "\n") + sort.Strings(info.monFeatures) + + return info, nil +} + +// Supported returns true if L3 monitoring is supported and enabled in the system +func (i l3MonInfo) Supported() bool { + return i.numRmids != 0 && len(i.monFeatures) > 0 +} + +func getMBInfo(basepath string) (mbInfo, error) { + var err error + info := mbInfo{} + + info.bandwidthGran, err = readFileUint64(filepath.Join(basepath, "bandwidth_gran")) + if err != nil { + return info, err + } + info.delayLinear, err = readFileUint64(filepath.Join(basepath, "delay_linear")) + if err != nil { + return info, err + } + info.minBandwidth, err = readFileUint64(filepath.Join(basepath, "min_bandwidth")) + if err != nil { + return info, err + } + info.numClosids, err = readFileUint64(filepath.Join(basepath, "num_closids")) + if err != nil { + return info, err + } + + // Detect MBps mode directly from mount options as it's not visible in MB + // info directory + _, mountOpts, err := getResctrlMountInfo() + if err != nil { + return info, fmt.Errorf("failed to get resctrl mount options: %v", err) + } + if _, ok := mountOpts["mba_MBps"]; ok { + info.mbpsEnabled = true + } + + return info, nil +} + +// Supported returns true if memory bandwidth allocation has is supported and enabled in the system +func (i mbInfo) Supported() bool { + return i.minBandwidth != 0 +} + +func getCacheIds(basepath string, prefix string) ([]uint64, error) { + var ids []uint64 + + // Parse cache IDs from the root schemata + data, err := readFileString(filepath.Join(basepath, "schemata")) + if err != nil { + return ids, fmt.Errorf("failed to read root schemata: %v", err) + } + + for _, line := range strings.Split(data, "\n") { + trimmed := strings.TrimSpace(line) + lineSplit := strings.SplitN(trimmed, ":", 2) + + // Find line with given resource prefix + if len(lineSplit) == 2 && strings.HasPrefix(lineSplit[0], prefix) { + schema := strings.Split(lineSplit[1], ";") + ids = make([]uint64, len(schema)) + + // Get individual cache configurations from the schema + for idx, definition := range schema { + split := strings.Split(definition, "=") + if len(split) != 2 { + return ids, fmt.Errorf("looks like an invalid schema %q", trimmed) + } + ids[idx], err = strconv.ParseUint(split[0], 10, 64) + if err != nil { + return ids, fmt.Errorf("failed to parse cache id in %q: %v", trimmed, err) + } + } + return ids, nil + } + } + return ids, fmt.Errorf("no %s resources in root schemata", prefix) +} + +func getResctrlMountInfo() (string, map[string]struct{}, error) { + mountOptions := map[string]struct{}{} + + f, err := os.Open(mountInfoPath) + if err != nil { + return "", mountOptions, err + } + defer f.Close() // nolint:errcheck + + s := bufio.NewScanner(f) + for s.Scan() { + split := strings.Split(s.Text(), " ") + if len(split) > 3 && split[2] == "resctrl" { + opts := strings.Split(split[3], ",") + for _, opt := range opts { + if opt != "" { + mountOptions[opt] = struct{}{} + } + } + return split[1], mountOptions, nil + } + } + return "", mountOptions, errors.New("resctrl not found in " + mountInfoPath) +} + +func readFileUint64(path string) (uint64, error) { + data, err := readFileString(path) + if err != nil { + return 0, err + } + + return strconv.ParseUint(data, 10, 64) +} + +func readFileBitmask(path string) (bitmask, error) { + data, err := readFileString(path) + if err != nil { + return 0, err + } + + value, err := strconv.ParseUint(data, 16, 64) + return bitmask(value), err +} + +func readFileString(path string) (string, error) { + data, err := os.ReadFile(path) + return strings.TrimSpace(string(data)), err +} diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/kubernetes.go b/vendor/github.com/intel/goresctrl/pkg/rdt/kubernetes.go new file mode 100644 index 0000000000..1d33b36f9c --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/kubernetes.go @@ -0,0 +1,75 @@ +/* +Copyright 2021 Intel Corporation + +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. +*/ + +package rdt + +import ( + "fmt" + + "github.com/intel/goresctrl/pkg/kubernetes" +) + +const ( + // RdtContainerAnnotation is the CRI level container annotation for setting + // the RDT class (CLOS) of a container + RdtContainerAnnotation = "io.kubernetes.cri.rdt-class" + + // RdtPodAnnotation is a Pod annotation for setting the RDT class (CLOS) of + // all containers of the pod + RdtPodAnnotation = "rdt.resources.beta.kubernetes.io/pod" + + // RdtPodAnnotationContainerPrefix is prefix for per-container Pod annotation + // for setting the RDT class (CLOS) of one container of the pod + RdtPodAnnotationContainerPrefix = "rdt.resources.beta.kubernetes.io/container." +) + +// ContainerClassFromAnnotations determines the effective RDT class of a +// container from the Pod annotations and CRI level container annotations of a +// container. Verifies that the class exists in goresctrl configuration and that +// it is allowed to be used. +func ContainerClassFromAnnotations(containerName string, containerAnnotations, podAnnotations map[string]string) (string, error) { + clsName, clsOrigin := kubernetes.ContainerClassFromAnnotations( + RdtContainerAnnotation, RdtPodAnnotation, RdtPodAnnotationContainerPrefix, + containerName, containerAnnotations, podAnnotations) + + if clsOrigin != kubernetes.ClassOriginNotFound { + if rdt == nil { + return "", fmt.Errorf("RDT not initialized, class %q not available", clsName) + } + + // Verify validity of class name + if !IsQualifiedClassName(clsName) { + return "", fmt.Errorf("unqualified RDT class name %q", clsName) + } + + // If RDT has been initialized we check that the class exists + if _, err := rdt.getClass(clsName); err != nil { + return "", fmt.Errorf("RDT class not found: %w", err) + } + + // If classes have been configured by goresctrl + if clsConf, ok := rdt.conf.Classes[unaliasClassName(clsName)]; ok { + // Check that the class is allowed + if clsOrigin == kubernetes.ClassOriginPodAnnotation && clsConf.Kubernetes.DenyPodAnnotation { + return "", fmt.Errorf("RDT class %q not allowed from Pod annotations", clsName) + } else if clsOrigin == kubernetes.ClassOriginContainerAnnotation && clsConf.Kubernetes.DenyContainerAnnotation { + return "", fmt.Errorf("RDT class %q not allowed from Container annotation", clsName) + } + } + } + + return clsName, nil +} diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/otel.go b/vendor/github.com/intel/goresctrl/pkg/rdt/otel.go new file mode 100644 index 0000000000..c29d2865ef --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/otel.go @@ -0,0 +1,194 @@ +/* +Copyright 2025 Intel Corporation + +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. +*/ + +package rdt + +import ( + "context" + "fmt" + "strings" + "sync" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +func RegisterOpenTelemetryInstruments(meter metric.Meter) error { + for resource, features := range GetMonFeatures() { + switch resource { + case MonResourceL3: + for _, f := range features { + if err := registerInstrument(meter, f); err != nil { + return err + } + } + } + } + + return nil +} + +func registerInstrument(meter metric.Meter, feature string) error { + instr, err := createInstrument(meter, feature) + if err != nil { + return err + } + + _, err = meter.RegisterCallback( + func(ctx context.Context, o metric.Observer) error { + select { + case <-ctx.Done(): + return fmt.Errorf("rdt metric collection cancelled: %w", ctx.Err()) + default: + observeInstrument(o, instr, feature) + } + return nil + }, + instr, + ) + if err != nil { + return err + } + + return nil +} + +func createInstrument(meter metric.Meter, feature string) (metric.Int64Observable, error) { + switch feature { + case "llc_occupancy": + name := "l3.llc.occupancy" + help := "L3 (LLC) occupancy" + return meter.Int64ObservableGauge( + name, + metric.WithDescription(help), + ) + + case "mbm_local_bytes": + name := "l3.mbm.local" + help := "bytes transferred to/from local memory through LLC" + unit := "bytes" + return meter.Int64ObservableCounter( + name, + metric.WithDescription(help), + metric.WithUnit(unit), + ) + + case "mbm_total_bytes": + name := "l3.mbm.total" + help := "total bytes transferred to/from memory through LLC" + unit := "bytes" + return meter.Int64ObservableCounter( + name, + metric.WithDescription(help), + metric.WithUnit(unit), + ) + } + + // an unknown feature, counter for bytes, gauge otherwise + name := "" + help := "" + unit := "" + if strings.HasSuffix(feature, "_bytes") { + name = strings.TrimSuffix(feature, "_bytes") + unit = "bytes" + } + name = "l3." + strings.ReplaceAll(name, "_", ".") + help = "L3 " + feature + + if unit == "bytes" { + return meter.Int64ObservableUpDownCounter( + name, + metric.WithDescription(help), + metric.WithUnit(unit), + ) + } + + return meter.Int64ObservableGauge( + name, + metric.WithDescription(help), + metric.WithUnit(unit), + ) +} + +func observeInstrument(o metric.Observer, m metric.Int64Observable, feature string) { + var wg sync.WaitGroup + + for _, cls := range GetClasses() { + observeGroup(o, m, feature, cls) + for _, monGrp := range cls.GetMonGroups() { + wg.Add(1) + go func(mg MonGroup) { + defer wg.Done() + observeGroup(o, m, feature, mg, getMonGroupAttributes(mg)...) + }(monGrp) + } + } + + wg.Wait() +} + +func observeGroup(o metric.Observer, m metric.Int64Observable, feature string, g ResctrlGroup, customAttributes ...attribute.KeyValue) { + var ( + allData = g.GetMonData() + cgName string + mgName string + ) + + if mg, ok := g.(MonGroup); ok { + cgName = mg.Parent().Name() + mgName = mg.Name() + } else { + cgName = g.Name() + } + + attributes := attribute.NewSet( + attribute.String("rdt.class", cgName), + attribute.String("rdt.mon.group", mgName), + ) + + for cacheID, data := range allData.L3 { + if value, ok := data[feature]; ok { + o.ObserveInt64( + m, + int64(value), + metric.WithAttributeSet(attributes), + metric.WithAttributes( + append( + []attribute.KeyValue{ + attribute.String("cache.id", fmt.Sprint(cacheID)), + }, + customAttributes..., + )..., + ), + ) + } + } +} + +func getMonGroupAttributes(mg MonGroup) []attribute.KeyValue { + var ( + annotations = mg.GetAnnotations() + attributes = []attribute.KeyValue{} + ) + + for _, name := range customLabels { + if value, ok := annotations[name]; ok { + attributes = append(attributes, attribute.String(name, value)) + } + } + + return attributes +} diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/prometheus.go b/vendor/github.com/intel/goresctrl/pkg/rdt/prometheus.go new file mode 100644 index 0000000000..8300bf5a42 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/prometheus.go @@ -0,0 +1,148 @@ +/* +Copyright 2020 Intel Corporation + +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. +*/ + +package rdt + +import ( + "fmt" + "sync" + + "github.com/prometheus/client_golang/prometheus" +) + +var customLabels []string = []string{} + +// collector implements prometheus.Collector interface +type collector struct { + descriptors map[string]*prometheus.Desc +} + +// NewCollector creates new Prometheus collector of RDT metrics +func NewCollector() prometheus.Collector { + c := &collector{descriptors: make(map[string]*prometheus.Desc)} + return c +} + +// RegisterCustomPrometheusLabels registers monitor group annotations to be +// exported as Prometheus metrics labels +func RegisterCustomPrometheusLabels(names ...string) { +Names: + for _, n := range names { + for _, c := range customLabels { + if n == c { + break Names + } + } + customLabels = append(customLabels, n) + } +} + +// Describe method of the prometheus.Collector interface +func (c *collector) Describe(ch chan<- *prometheus.Desc) { + for resource, features := range GetMonFeatures() { + switch resource { + case MonResourceL3: + for _, f := range features { + ch <- c.describeL3(f) + } + } + } +} + +// Collect method of the prometheus.Collector interface +func (c collector) Collect(ch chan<- prometheus.Metric) { + var wg sync.WaitGroup + + for _, cls := range GetClasses() { + c.collectGroupMetrics(ch, cls) + for _, monGrp := range cls.GetMonGroups() { + wg.Add(1) + g := monGrp + go func() { + defer wg.Done() + c.collectMonGroupMetrics(ch, g) + }() + } + } + wg.Wait() +} + +func (c *collector) describeL3(feature string) *prometheus.Desc { + d, ok := c.descriptors[feature] + if !ok { + name := "l3_" + feature + help := "L3 " + feature + + switch feature { + case "llc_occupancy": + help = "L3 (LLC) occupancy" + case "mbm_local_bytes": + help = "bytes transferred to/from local memory through LLC" + case "mbm_total_bytes": + help = "total bytes transferred to/from memory through LLC" + } + labels := append([]string{"rdt_class", "rdt_mon_group", "cache_id"}, customLabels...) + d = prometheus.NewDesc(name, help, labels, nil) + c.descriptors[feature] = d + } + return d +} + +func (c *collector) collectMonGroupMetrics(ch chan<- prometheus.Metric, mg MonGroup) { + annotations := mg.GetAnnotations() + customLabelValues := make([]string, len(customLabels)) + for i, name := range customLabels { + customLabelValues[i] = annotations[name] + } + + c.collectGroupMetrics(ch, mg, customLabelValues...) +} + +func (c *collector) collectGroupMetrics(ch chan<- prometheus.Metric, g ResctrlGroup, customLabels ...string) { + allData := g.GetMonData() + + cgName, mgName := "", "" + if mg, ok := g.(MonGroup); ok { + cgName = mg.Parent().Name() + mgName = mg.Name() + } else { + cgName = g.Name() + } + + for cacheID, data := range allData.L3 { + labels := append([]string{cgName, mgName, fmt.Sprint(cacheID)}, customLabels...) + for feature, value := range data { + ch <- prometheus.MustNewConstMetric( + c.describeL3(feature), + promValueTypeL3(feature), + float64(value), + labels..., + ) + } + } +} + +// promValueTypeL3 returns Prometheus value type for given L3 metric. +func promValueTypeL3(feature string) prometheus.ValueType { + switch feature { + case "llc_occupancy": + return prometheus.GaugeValue + case "mbm_local_bytes", "mbm_total_bytes": + return prometheus.CounterValue + default: + return prometheus.GaugeValue + } +} diff --git a/vendor/github.com/intel/goresctrl/pkg/rdt/rdt.go b/vendor/github.com/intel/goresctrl/pkg/rdt/rdt.go new file mode 100644 index 0000000000..5c67894825 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/rdt/rdt.go @@ -0,0 +1,471 @@ +/* +Copyright 2019 Intel Corporation + +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. +*/ + +// Package rdt implements an API for managing Intel® RDT technologies via the +// resctrl pseudo-filesystem of the Linux kernel. It provides flexible +// configuration with a hierarchical approach for easy management of exclusive +// cache allocations. +// +// Goresctrl supports all available RDT technologies, i.e. L2 and L3 Cache +// Allocation (CAT) with Code and Data Prioritization (CDP) and Memory +// Bandwidth Allocation (MBA) plus Cache Monitoring (CMT) and Memory Bandwidth +// Monitoring (MBM). +// +// Basic usage example: +// +// rdt.SetLogger(slog.Default().WithGroup("rdt")) +// +// if err := rdt.Initialize(""); err != nil { +// return fmt.Errorf("RDT not supported: %v", err) +// } +// +// if err := rdt.SetConfigFromFile("/path/to/rdt.conf.yaml", false); err != nil { +// return fmt.Errorf("RDT configuration failed: %v", err) +// } +// +// if cls, ok := rdt.GetClass("my-class"); ok { +// // Set PIDs 12345 and 12346 to class "my-class" +// if err := cls.AddPids("12345", "12346"); err != nil { +// return fmt.Errorf("failed to add PIDs to RDT class: %v", err) +// } +// } +package rdt + +import ( + "fmt" + "log/slog" + "os" + "path/filepath" + "sort" + "strings" + + "sigs.k8s.io/yaml" + + "github.com/intel/goresctrl/pkg/utils" +) + +const ( + // RootClassName is the name we use in our config for the special class + // that configures the "root" resctrl group of the system + RootClassName = "system/default" + // RootClassAlias is an alternative name for the root class + RootClassAlias = "" +) + +type control struct { + *slog.Logger + + resctrlGroupPrefix string + conf config + rawConf Config + + // Registry of ctrl/mon group annotations used for custom prometheus metrics labels. + // We keep them here so that after Initialize() the annotations are cleared. + annotations map[string]groupAnnotations +} + +var log *slog.Logger = slog.Default() + +var info *resctrlInfo + +var rdt *control + +// SetLogger sets the logger instance to be used by the package. This function +// may be called even before Initialize(). +func SetLogger(l *slog.Logger) { + log = l + if rdt != nil { + rdt.setLogger(l) + } +} + +// Initialize detects RDT from the system and initializes control interface of +// the package. +// +// NOTE: Monitoring group annotations (used for custom prometheus metrics +// labels) are lost on (re-)initialization. +func Initialize(resctrlGroupPrefix string) error { + var err error + + info = nil + rdt = nil + + // Get info from the resctrl filesystem + info, err = getRdtInfo() + if err != nil { + return err + } + + r := &control{ + Logger: log, + resctrlGroupPrefix: resctrlGroupPrefix, + annotations: make(map[string]groupAnnotations), + } + + // Sanity check that we're able to read the groups from the resctrl filesystem + if _, err = r.classesFromResctrlFs(); err != nil { + return fmt.Errorf("failed to read classes from resctrl fs: %v", err) + } + + rdt = r + + return nil +} + +// SetResctrlGroupPrefix changes the prefix from the one that was set with +// Initialize(). +func SetResctrlGroupPrefix(resctrlGroupPrefix string) error { + if rdt != nil { + rdt.resctrlGroupPrefix = resctrlGroupPrefix + return nil + } + return fmt.Errorf("rdt not initialized") +} + +// SetConfig (re-)configures the resctrl filesystem according to the specified +// configuration. +func SetConfig(c *Config, force bool) error { + if rdt != nil { + return rdt.setConfig(c, force) + } + return fmt.Errorf("rdt not initialized") +} + +// SetConfigFromData takes configuration as raw data, parses it and +// reconfigures the resctrl filesystem. +func SetConfigFromData(data []byte, force bool) error { + cfg := &Config{} + if err := yaml.UnmarshalStrict(data, cfg); err != nil { + return fmt.Errorf("failed to parse configuration data: %v", err) + } + + return SetConfig(cfg, force) +} + +// SetConfigFromFile reads configuration from the filesystem and reconfigures +// the resctrl filesystem. +func SetConfigFromFile(path string, force bool) error { + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read config file: %v", err) + } + + if err := SetConfigFromData(data, force); err != nil { + return err + } + + log.Info("configuration successfully loaded", "path", path) + return nil +} + +// GetClass returns one RDT class. +func GetClass(name string) (CtrlGroup, bool) { + if rdt != nil { + if cls, err := rdt.getClass(name); err != nil { + log.Error("failed to get RDT class", "name", name, "error", err) + } else { + return cls, true + } + } + return nil, false +} + +// GetClasses returns all available RDT classes. +func GetClasses() []CtrlGroup { + if rdt != nil { + if classes, err := rdt.getClasses(); err != nil { + log.Error("failed to get RDT classes", "error", err) + } else { + ret := make([]CtrlGroup, len(classes)) + for i, v := range classes { + ret[i] = v + } + return ret + } + } + return []CtrlGroup{} +} + +// MonSupported returns true if RDT monitoring features are available. +func MonSupported() bool { + if rdt != nil { + return rdt.monSupported() + } + return false +} + +// GetMonFeatures returns the available monitoring stats of each available +// monitoring technology. +func GetMonFeatures() map[MonResource][]string { + if rdt != nil { + return rdt.getMonFeatures() + } + return map[MonResource][]string{} +} + +// IsQualifiedClassName returns true if given string qualifies as a class name +func IsQualifiedClassName(name string) bool { + // Must be qualified as a file name + return name == RootClassName || (len(name) < 4096 && name != "." && name != ".." && !strings.ContainsAny(name, "/\n")) +} + +func (c *control) getClass(name string) (*ctrlGroup, error) { + cls, err := c.classFromResctrlFs(name) + if err != nil { + return nil, fmt.Errorf("failed to get class %q from resctrl fs: %w", name, err) + } + return cls, nil +} + +func (c *control) getClasses() ([]*ctrlGroup, error) { + classes, err := c.classesFromResctrlFs() + if err != nil { + return []*ctrlGroup{}, fmt.Errorf("failed to get classes from resctrl fs: %w", err) + } + + ret := make([]*ctrlGroup, 0, len(classes)) + for _, v := range classes { + ret = append(ret, v) + } + sort.Slice(ret, func(i, j int) bool { return ret[i].Name() < ret[j].Name() }) + + return ret, nil +} + +func (c *control) monSupported() bool { + return info.l3mon.Supported() +} + +func (c *control) getMonFeatures() map[MonResource][]string { + ret := make(map[MonResource][]string) + if info.l3mon.Supported() { + ret[MonResourceL3] = append([]string{}, info.l3mon.monFeatures...) + } + + return ret +} + +func (c *control) setLogger(l *slog.Logger) { + c.Logger = l +} + +func (c *control) setConfig(newConfig *Config, force bool) error { + c.Info("configuration update") + + conf, err := (*newConfig).resolve() + if err != nil { + return fmt.Errorf("invalid configuration: %v", err) + } + + err = c.configureResctrl(conf, force) + if err != nil { + return fmt.Errorf("resctrl configuration failed: %v", err) + } + + c.conf = conf + // TODO: we'd better create a deep copy + c.rawConf = *newConfig + c.Info("configuration finished") + + return nil +} + +func (c *control) configureResctrl(conf config, force bool) error { + // TODO: Think a better (more structured) way to log this + log.Debug("applying resolved configuration:\n" + utils.DumpJSON(conf)) + + // Remove stale resctrl groups + classesFromFs, err := c.classesFromResctrlFs() + if err != nil { + return err + } + + for _, cls := range classesFromFs { + if _, ok := conf.Classes[cls.name]; !isRootClass(cls.name) && !ok { + if !force { + tasks, err := cls.GetPids() + if err != nil { + return fmt.Errorf("failed to get resctrl group tasks: %v", err) + } + if len(tasks) > 0 { + return fmt.Errorf("refusing to remove non-empty resctrl group %q", cls.relPath("")) + } + } + log.Debug("removing existing resctrl group", "name", cls.Name(), "path", cls.path("")) + err = groupRemoveFunc(cls.path("")) + if err != nil { + return fmt.Errorf("failed to remove resctrl group %q: %v", cls.relPath(""), err) + } + } + } + + // Try to apply given configuration + for name, class := range conf.Classes { + cg, ok := classesFromFs[name] + if !ok { + cg, err = newCtrlGroup(c.resctrlGroupPrefix, c.resctrlGroupPrefix, name) + if err != nil { + return err + } + } + partition := conf.Partitions[class.Partition] + if err := cg.configure(name, class, partition, conf.Options); err != nil { + return err + } + } + + if err := c.pruneMonGroups(); err != nil { + return err + } + + return nil +} + +func (c *control) classesFromResctrlFs() (map[string]*ctrlGroup, error) { + return c.classesFromResctrlFsPrefix(c.resctrlGroupPrefix) +} + +func (c *control) classesFromResctrlFsPrefix(prefix string) (map[string]*ctrlGroup, error) { + names := []string{RootClassName} + if g, err := resctrlGroupsFromFs(prefix, info.resctrlPath); err != nil { + return nil, err + } else { + for _, n := range g { + names = append(names, n[len(prefix):]) + } + } + + classes := make(map[string]*ctrlGroup, len(names)+1) + for _, name := range names { + g, err := newCtrlGroup(prefix, c.resctrlGroupPrefix, name) + if err != nil { + return nil, err + } + classes[name] = g + } + + return classes, nil +} + +func (c *control) classFromResctrlFs(name string) (*ctrlGroup, error) { + return c.classFromResctrlFsPrefix(c.resctrlGroupPrefix, name) +} + +func (c *control) classFromResctrlFsPrefix(prefix, name string) (*ctrlGroup, error) { + path := info.resctrlPath + if !isRootClass(name) { + path = filepath.Join(path, prefix+name) + } + if !isResctrlGroup(path) { + return nil, fmt.Errorf("resctrl group not found for class %q (%q)", name, path) + } + + g, err := newCtrlGroup(prefix, c.resctrlGroupPrefix, name) + if err != nil { + return nil, err + } + + return g, nil +} + +func (c *control) pruneMonGroups() error { + classes, err := c.getClasses() + if err != nil { + return err + } + for name, cls := range classes { + if err := cls.pruneMonGroups(); err != nil { + return fmt.Errorf("failed to prune stale monitoring groups of %q: %v", name, err) + } + } + return nil +} + +func (c *control) readRdtFile(rdtPath string) ([]byte, error) { + return os.ReadFile(filepath.Join(info.resctrlPath, rdtPath)) +} + +func (c *control) writeRdtFile(rdtPath string, data []byte) error { + if err := os.WriteFile(filepath.Join(info.resctrlPath, rdtPath), data, 0644); err != nil { + return c.cmdError(err) + } + return nil +} + +func (c *control) cmdError(origErr error) error { + errData, readErr := c.readRdtFile(filepath.Join("info", "last_cmd_status")) + if readErr != nil { + return origErr + } + cmdStatus := strings.TrimSpace(string(errData)) + if len(cmdStatus) > 0 && cmdStatus != "ok" { + return fmt.Errorf("%s", cmdStatus) + } + return origErr +} + +func (c *control) setAnnotations(g *resctrlGroup, annotations map[string]string) { + c.annotations[annotationKey(g)] = annotations +} + +func (c *control) getAnnotations(g *resctrlGroup) map[string]string { + return c.annotations[annotationKey(g)] +} +func (c *control) deleteAnnotations(g *resctrlGroup) { + delete(c.annotations, annotationKey(g)) +} + +func annotationKey(g *resctrlGroup) string { + // we use the dirnames to avoid ambiguity caused by the group prefix (if changed) + if g.parent != nil { + return g.parent.dirName() + "/" + g.dirName() + } + return g.dirName() +} + +func resctrlGroupsFromFs(prefix string, path string) ([]string, error) { + files, err := os.ReadDir(path) + if err != nil { + return nil, err + } + + grps := make([]string, 0, len(files)) + for _, file := range files { + filename := file.Name() + if strings.HasPrefix(filename, prefix) && isResctrlGroup(filepath.Join(path, filename)) { + grps = append(grps, filename) + } + } + return grps, nil +} + +func isResctrlGroup(path string) bool { + if s, err := os.Stat(filepath.Join(path, "tasks")); err == nil && !s.IsDir() { + return true + } + return false +} + +func isRootClass(name string) bool { + return name == RootClassName || name == RootClassAlias +} + +func unaliasClassName(name string) string { + if isRootClass(name) { + return RootClassName + } + return name +} diff --git a/vendor/github.com/intel/goresctrl/pkg/utils/idset.go b/vendor/github.com/intel/goresctrl/pkg/utils/idset.go new file mode 100644 index 0000000000..41d7fc91fe --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/utils/idset.go @@ -0,0 +1,231 @@ +/* +Copyright 2019-2021 Intel Corporation + +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. +*/ + +package utils + +import ( + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" +) + +const ( + // Unknown represents an unknown id. + Unknown ID = -1 + // maxID sets an upper bound for the size of id sets + // generated from listset strings, e.g. "0-16777215". + maxID ID = (1 << 24) - 1 +) + +// ID is nn integer id, used to identify packages, CPUs, nodes, etc. +type ID = int + +// IDSet is an unordered set of integer ids. +type IDSet map[ID]struct{} + +// NewIDSet creates a new unordered set of (integer) ids. +func NewIDSet(ids ...ID) IDSet { + s := make(map[ID]struct{}) + + for _, id := range ids { + s[id] = struct{}{} + } + + return s +} + +// NewIDSetFromIntSlice creates a new unordered set from an integer slice. +func NewIDSetFromIntSlice(ids ...int) IDSet { + s := make(map[ID]struct{}) + + for _, id := range ids { + s[ID(id)] = struct{}{} + } + + return s +} + +// NewIDSetFromString creates new unordered set from string in listset syntax "0,61-63,2". +func NewIDSetFromString(listSet string) (IDSet, error) { + s := NewIDSet() + minValue := 0 + maxValue := maxID + + parts := strings.Split(listSet, ",") + for _, part := range parts { + switch { + case part == "": + continue + case strings.Contains(part, "-"): + rangeParts := strings.Split(part, "-") + if len(rangeParts) != 2 { + return nil, fmt.Errorf("invalid range: %s", part) + } + start, err := strconv.Atoi(rangeParts[0]) + if err != nil { + return nil, err + } + end, err := strconv.Atoi(rangeParts[1]) + if err != nil { + return nil, err + } + if start > end { + return nil, fmt.Errorf("invalid range %s: start > end", part) + } + if start < minValue || end > maxValue { + return nil, fmt.Errorf("invalid range %s: out of range %d-%d", part, minValue, maxValue) + } + for i := start; i <= end; i++ { + s.Add(ID(i)) + } + default: + num, err := strconv.Atoi(part) + if err != nil { + return nil, err + } + if num < minValue || num > maxValue { + return nil, fmt.Errorf("invalid value %d: out of range %d-%d", num, minValue, maxValue) + } + s.Add(ID(num)) + } + } + + return s, nil +} + +// Clone returns a copy of this IdSet. +func (s IDSet) Clone() IDSet { + return NewIDSet(s.Members()...) +} + +// Add adds the given ids into the set. +func (s IDSet) Add(ids ...ID) { + for _, id := range ids { + s[id] = struct{}{} + } +} + +// Del deletes the given ids from the set. +func (s IDSet) Del(ids ...ID) { + if s != nil { + for _, id := range ids { + delete(s, id) + } + } +} + +// Size returns the number of ids in the set. +func (s IDSet) Size() int { + return len(s) +} + +// Has tests if all the ids are present in the set. +func (s IDSet) Has(ids ...ID) bool { + if s == nil { + return false + } + + for _, id := range ids { + _, ok := s[id] + if !ok { + return false + } + } + + return true +} + +// Members returns all ids in the set as a randomly ordered slice. +func (s IDSet) Members() []ID { + if s == nil { + return []ID{} + } + ids := make([]ID, len(s)) + idx := 0 + for id := range s { + ids[idx] = id + idx++ + } + return ids +} + +// SortedMembers returns all ids in the set as a sorted slice. +func (s IDSet) SortedMembers() []ID { + ids := s.Members() + sort.Slice(ids, func(i, j int) bool { + return ids[i] < ids[j] + }) + return ids +} + +// String returns the set as a string. +func (s IDSet) String() string { + return s.StringWithSeparator(",") +} + +// StringWithSeparator returns the set as a string, separated with the given separator. +func (s IDSet) StringWithSeparator(args ...string) string { + if len(s) == 0 { + return "" + } + + var sep string + + if len(args) == 1 { + sep = args[0] + } else { + sep = "," + } + + str := "" + t := "" + for _, id := range s.SortedMembers() { + str = str + t + strconv.Itoa(int(id)) + t = sep + } + + return str +} + +// MarshalJSON is the JSON marshaller for IDSet. +func (s IDSet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +// UnmarshalJSON is the JSON unmarshaller for IDSet. +func (s *IDSet) UnmarshalJSON(data []byte) error { + str := "" + if err := json.Unmarshal(data, &str); err != nil { + return fmt.Errorf("invalid IDSet entry '%s': %v", string(data), err) + } + + *s = NewIDSet() + if str == "" { + return nil + } + + for _, idstr := range strings.Split(str, ",") { + id, err := strconv.ParseInt(idstr, 10, 0) + if err != nil { + return fmt.Errorf("invalid IDSet entry '%s': %v", idstr, err) + } + s.Add(ID(id)) + } + + return nil +} diff --git a/vendor/github.com/intel/goresctrl/pkg/utils/json.go b/vendor/github.com/intel/goresctrl/pkg/utils/json.go new file mode 100644 index 0000000000..0606422e87 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/utils/json.go @@ -0,0 +1,32 @@ +/* +Copyright 2019 Intel Corporation + +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. +*/ + +package utils + +import ( + "fmt" + + "sigs.k8s.io/yaml" +) + +// DumpJSON dumps a json-compatible struct in human-readable form +func DumpJSON(r interface{}) string { + out, err := yaml.Marshal(r) + if err != nil { + return fmt.Sprintf("!!!!!\nUnable to stringify %T: %v\n!!!!!", r, err) + } + return string(out) +} diff --git a/vendor/github.com/intel/goresctrl/pkg/utils/msr.go b/vendor/github.com/intel/goresctrl/pkg/utils/msr.go new file mode 100644 index 0000000000..64c6240dd8 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/utils/msr.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 Intel Corporation + +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. +*/ + +package utils + +import ( + "encoding/binary" + "fmt" + "os" + + goresctrlpath "github.com/intel/goresctrl/pkg/path" +) + +func ReadMSR(cpu ID, msr int64) (uint64, error) { + path := goresctrlpath.Path("dev/cpu", fmt.Sprintf("%d", cpu), "msr") + file, err := os.Open(path) + if err != nil { + return 0, err + } + + defer file.Close() // nolint:errcheck + + // Whence is the point of reference for offset + // 0 = Beginning of file + // 1 = Current position + // 2 = End of file + whence := int(0) + _, err = file.Seek(msr, whence) + if err != nil { + return 0, err + } + + data := make([]byte, 8) + + _, err = file.Read(data) + if err != nil { + return 0, err + } + + return binary.LittleEndian.Uint64(data), nil +} diff --git a/vendor/github.com/intel/goresctrl/pkg/utils/sort.go b/vendor/github.com/intel/goresctrl/pkg/utils/sort.go new file mode 100644 index 0000000000..964e679b21 --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/utils/sort.go @@ -0,0 +1,36 @@ +// Copyright 2020 Intel Corporation. All Rights Reserved. +// +// 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. + +package utils + +import ( + "sort" +) + +// SortUint64s sorts a slice of uint64 in increasing order. +func SortUint64s(a []uint64) { + sort.Sort(Uint64Slice(a)) +} + +// Uint64Slice implmenents sort.Interface for a slice of uint64. +type Uint64Slice []uint64 + +// Len returns the length of an UintSlice +func (s Uint64Slice) Len() int { return len(s) } + +// Less returns true if element at 'i' is less than the element at 'j' +func (s Uint64Slice) Less(i, j int) bool { return s[i] < s[j] } + +// Swap swaps the values of two elements +func (s Uint64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/vendor/github.com/intel/goresctrl/pkg/utils/sysfs.go b/vendor/github.com/intel/goresctrl/pkg/utils/sysfs.go new file mode 100644 index 0000000000..6e72338e3c --- /dev/null +++ b/vendor/github.com/intel/goresctrl/pkg/utils/sysfs.go @@ -0,0 +1,158 @@ +/* +Copyright 2022 Intel Corporation + +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. +*/ + +package utils + +import ( + "fmt" + "os" + "strconv" + "strings" + + goresctrlpath "github.com/intel/goresctrl/pkg/path" +) + +const ( + SysfsUncoreBasepath = "sys/devices/system/cpu/intel_uncore_frequency" + SysfsCpuBasepath = "sys/devices/system/cpu" +) + +func setCPUFreqValue(cpu ID, setting string, value int) error { + return writeFileInt(cpuFreqPath(cpu, setting), value) +} + +func SetCPUScalingGovernor(cpu ID, governor string) error { + return writeFileStr(cpuFreqPath(cpu, "scaling_governor"), governor) +} + +// GetCPUFreqValue returns information of the currently used CPU frequency +func GetCPUFreqValue(cpu ID, setting string) (int, error) { + raw, err := os.ReadFile(cpuFreqPath(cpu, setting)) + if err != nil { + return 0, err + } + + value, err := strconv.Atoi(strings.TrimSpace(string(raw))) + if err != nil { + return 0, err + } + + return value, nil +} + +func cpuFreqPath(cpu ID, setting string) string { + return goresctrlpath.Path(SysfsCpuBasepath, fmt.Sprintf("cpu%d", cpu), "cpufreq", setting) +} + +// SetCPUScalingMinFreq sets the scaling_min_freq value of a given CPU +func SetCPUScalingMinFreq(cpu ID, freq int) error { + return setCPUFreqValue(cpu, "scaling_min_freq", freq) +} + +// SetCPUScalingMaxFreq sets the scaling_max_freq value of a given CPU +func SetCPUScalingMaxFreq(cpu ID, freq int) error { + return setCPUFreqValue(cpu, "scaling_max_freq", freq) +} + +// SetScalingGovernorForCPUs sets the scaling_governor of a given set of CPUs +func SetScalingGovernorForCPUs(cpus []ID, governor string) error { + for _, cpu := range cpus { + if err := SetCPUScalingGovernor(cpu, governor); err != nil { + return err + } + } + + return nil +} + +// SetCPUsScalingMinFreq sets the scaling_min_freq value of a given set of CPUs +func SetCPUsScalingMinFreq(cpus []ID, freq int) error { + for _, cpu := range cpus { + if err := SetCPUScalingMinFreq(cpu, freq); err != nil { + return err + } + } + + return nil +} + +// SetCPUsScalingMaxFreq sets the scaling_max_freq value of a given set of CPUs +func SetCPUsScalingMaxFreq(cpus []ID, freq int) error { + for _, cpu := range cpus { + if err := SetCPUScalingMaxFreq(cpu, freq); err != nil { + return err + } + } + + return nil +} + +// UncoreFreqAvailable returns true if the uncore frequency control functions are available. +func UncoreFreqAvailable() bool { + _, err := os.Stat(goresctrlpath.Path(SysfsUncoreBasepath)) + return err == nil +} + +// SetUncoreMinFreq sets the minimum uncore frequency of a CPU die. Frequency is specified in kHz. +func SetUncoreMinFreq(pkg, die ID, freqKhz int) error { + return setUncoreFreqValue(pkg, die, "min_freq_khz", freqKhz) +} + +// SetUncoreMaxFreq sets the maximum uncore frequency of a CPU die. Frequency is specified in kHz. +func SetUncoreMaxFreq(pkg, die ID, freqKhz int) error { + return setUncoreFreqValue(pkg, die, "max_freq_khz", freqKhz) +} + +func uncoreFreqPath(pkg, die ID, attribute string) string { + return goresctrlpath.Path(SysfsUncoreBasepath, fmt.Sprintf("package_%02d_die_%02d", pkg, die), attribute) +} + +func getUncoreFreqValue(pkg, die ID, attribute string) (int, error) { + return readFileInt(uncoreFreqPath(pkg, die, attribute)) +} + +func setUncoreFreqValue(pkg, die ID, attribute string, value int) error { + // Bounds checking + if hwMinFreq, err := getUncoreFreqValue(pkg, die, "initial_min_freq_khz"); err != nil { + return err + } else if value < hwMinFreq { + value = hwMinFreq + } + if hwMaxFreq, err := getUncoreFreqValue(pkg, die, "initial_max_freq_khz"); err != nil { + return err + } else if value > hwMaxFreq { + value = hwMaxFreq + } + + return writeFileInt(uncoreFreqPath(pkg, die, attribute), value) +} + +func writeFileInt(path string, value int) error { + return os.WriteFile(path, []byte(strconv.Itoa(value)), 0644) +} + +func writeFileStr(path string, value string) error { + return os.WriteFile(path, []byte(value), 0644) +} + +func readFileInt(path string) (int, error) { + data, err := os.ReadFile(path) + if err != nil { + return 0, err + } + i, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 32) + return int(i), err +} diff --git a/vendor/github.com/mdlayher/socket/CHANGELOG.md b/vendor/github.com/mdlayher/socket/CHANGELOG.md new file mode 100644 index 0000000000..b83418532d --- /dev/null +++ b/vendor/github.com/mdlayher/socket/CHANGELOG.md @@ -0,0 +1,94 @@ +# CHANGELOG + +## v0.5.1 + +- [Improvement]: revert `go.mod` to Go 1.20 to [resolve an issue around Go + module version upgrades](https://github.com/mdlayher/socket/issues/13). + +## v0.5.0 + +**This is the first release of package socket that only supports Go 1.21+. +Users on older versions of Go must use v0.4.1.** + +- [Improvement]: drop support for older versions of Go. +- [New API]: add `socket.Conn` wrappers for various `Getsockopt` and + `Setsockopt` system calls. + +## v0.4.1 + +- [Bug Fix] [commit](https://github.com/mdlayher/socket/commit/2a14ceef4da279de1f957c5761fffcc6c87bbd3b): + ensure `socket.Conn` can be used with non-socket file descriptors by handling + `ENOTSOCK` in the constructor. + +## v0.4.0 + +**This is the first release of package socket that only supports Go 1.18+. +Users on older versions of Go must use v0.3.0.** + +- [Improvement]: drop support for older versions of Go so we can begin using + modern versions of `x/sys` and other dependencies. + +## v0.3.0 + +**This is the last release of package socket that supports Go 1.17 and below.** + +- [New API/API change] [PR](https://github.com/mdlayher/socket/pull/8): + numerous `socket.Conn` methods now support context cancelation. Future + releases will continue adding support as needed. + - New `ReadContext` and `WriteContext` methods. + - `Connect`, `Recvfrom`, `Recvmsg`, `Sendmsg`, and `Sendto` methods now accept + a context. + - `Sendto` parameter order was also fixed to match the underlying syscall. + +## v0.2.3 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/a425d96e0f772c053164f8ce4c9c825380a98086): + `socket.Conn` has new `Pidfd*` methods for wrapping the `pidfd_*(2)` family of + system calls. + +## v0.2.2 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/a2429f1dfe8ec2586df5a09f50ead865276cd027): + `socket.Conn` has new `IoctlKCM*` methods for wrapping `ioctl(2)` for `AF_KCM` + operations. + +## v0.2.1 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/b18ddbe9caa0e34552b4409a3aa311cb460d2f99): + `socket.Conn` has a new `SetsockoptPacketMreq` method for wrapping + `setsockopt(2)` for `AF_PACKET` socket options. + +## v0.2.0 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/6e912a68523c45e5fd899239f4b46c402dd856da): + `socket.FileConn` can be used to create a `socket.Conn` from an existing + `os.File`, which may be provided by systemd socket activation or another + external mechanism. +- [API change] [commit](https://github.com/mdlayher/socket/commit/66d61f565188c23fe02b24099ddc856d538bf1a7): + `socket.Conn.Connect` now returns the `unix.Sockaddr` value provided by + `getpeername(2)`, since we have to invoke that system call anyway to verify + that a connection to a remote peer was successfully established. +- [Bug Fix] [commit](https://github.com/mdlayher/socket/commit/b60b2dbe0ac3caff2338446a150083bde8c5c19c): + check the correct error from `unix.GetsockoptInt` in the `socket.Conn.Connect` + method. Thanks @vcabbage! + +## v0.1.2 + +- [Bug Fix]: `socket.Conn.Connect` now properly checks the `SO_ERROR` socket + option value after calling `connect(2)` to verify whether or not a connection + could successfully be established. This means that `Connect` should now report + an error for an `AF_INET` TCP connection refused or `AF_VSOCK` connection + reset by peer. +- [New API]: add `socket.Conn.Getpeername` for use in `Connect`, but also for + use by external callers. + +## v0.1.1 + +- [New API]: `socket.Conn` now has `CloseRead`, `CloseWrite`, and `Shutdown` + methods. +- [Improvement]: internal rework to more robustly handle various errors. + +## v0.1.0 + +- Initial unstable release. Most functionality has been developed and ported +from package [`netlink`](https://github.com/mdlayher/netlink). diff --git a/vendor/github.com/mdlayher/socket/LICENSE.md b/vendor/github.com/mdlayher/socket/LICENSE.md new file mode 100644 index 0000000000..3ccdb75b26 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/LICENSE.md @@ -0,0 +1,9 @@ +# MIT License + +Copyright (C) 2021 Matt Layher + +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/vendor/github.com/mdlayher/socket/README.md b/vendor/github.com/mdlayher/socket/README.md new file mode 100644 index 0000000000..2aa065cbb7 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/README.md @@ -0,0 +1,23 @@ +# socket [![Test Status](https://github.com/mdlayher/socket/workflows/Test/badge.svg)](https://github.com/mdlayher/socket/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/socket.svg)](https://pkg.go.dev/github.com/mdlayher/socket) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/socket)](https://goreportcard.com/report/github.com/mdlayher/socket) + +Package `socket` provides a low-level network connection type which integrates +with Go's runtime network poller to provide asynchronous I/O and deadline +support. MIT Licensed. + +This package focuses on UNIX-like operating systems which make use of BSD +sockets system call APIs. It is meant to be used as a foundation for the +creation of operating system-specific socket packages, for socket families such +as Linux's `AF_NETLINK`, `AF_PACKET`, or `AF_VSOCK`. This package should not be +used directly in end user applications. + +Any use of package socket should be guarded by build tags, as one would also +use when importing the `syscall` or `golang.org/x/sys` packages. + +## Stability + +See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between +releases. + +This package only supports the two most recent major versions of Go, mirroring +Go's own release policy. Older versions of Go may lack critical features and bug +fixes which are necessary for this package to function correctly. diff --git a/vendor/github.com/mdlayher/socket/accept.go b/vendor/github.com/mdlayher/socket/accept.go new file mode 100644 index 0000000000..47e9d897ef --- /dev/null +++ b/vendor/github.com/mdlayher/socket/accept.go @@ -0,0 +1,23 @@ +//go:build !dragonfly && !freebsd && !illumos && !linux +// +build !dragonfly,!freebsd,!illumos,!linux + +package socket + +import ( + "fmt" + "runtime" + + "golang.org/x/sys/unix" +) + +const sysAccept = "accept" + +// accept wraps accept(2). +func accept(fd, flags int) (int, unix.Sockaddr, error) { + if flags != 0 { + // These operating systems have no support for flags to accept(2). + return 0, nil, fmt.Errorf("socket: Conn.Accept flags are ineffective on %s", runtime.GOOS) + } + + return unix.Accept(fd) +} diff --git a/vendor/github.com/mdlayher/socket/accept4.go b/vendor/github.com/mdlayher/socket/accept4.go new file mode 100644 index 0000000000..e1016b2063 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/accept4.go @@ -0,0 +1,15 @@ +//go:build dragonfly || freebsd || illumos || linux +// +build dragonfly freebsd illumos linux + +package socket + +import ( + "golang.org/x/sys/unix" +) + +const sysAccept = "accept4" + +// accept wraps accept4(2). +func accept(fd, flags int) (int, unix.Sockaddr, error) { + return unix.Accept4(fd, flags) +} diff --git a/vendor/github.com/mdlayher/socket/conn.go b/vendor/github.com/mdlayher/socket/conn.go new file mode 100644 index 0000000000..5be502f5a2 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/conn.go @@ -0,0 +1,894 @@ +package socket + +import ( + "context" + "errors" + "io" + "os" + "sync" + "sync/atomic" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +// Lock in an expected public interface for convenience. +var _ interface { + io.ReadWriteCloser + syscall.Conn + SetDeadline(t time.Time) error + SetReadDeadline(t time.Time) error + SetWriteDeadline(t time.Time) error +} = &Conn{} + +// A Conn is a low-level network connection which integrates with Go's runtime +// network poller to provide asynchronous I/O and deadline support. +// +// Many of a Conn's blocking methods support net.Conn deadlines as well as +// cancelation via context. Note that passing a context with a deadline set will +// override any of the previous deadlines set by calls to the SetDeadline family +// of methods. +type Conn struct { + // Indicates whether or not Conn.Close has been called. Must be accessed + // atomically. Atomics definitions must come first in the Conn struct. + closed uint32 + + // A unique name for the Conn which is also associated with derived file + // descriptors such as those created by accept(2). + name string + + // facts contains information we have determined about Conn to trigger + // alternate behavior in certain functions. + facts facts + + // Provides access to the underlying file registered with the runtime + // network poller, and arbitrary raw I/O calls. + fd *os.File + rc syscall.RawConn +} + +// facts contains facts about a Conn. +type facts struct { + // isStream reports whether this is a streaming descriptor, as opposed to a + // packet-based descriptor like a UDP socket. + isStream bool + + // zeroReadIsEOF reports Whether a zero byte read indicates EOF. This is + // false for a message based socket connection. + zeroReadIsEOF bool +} + +// A Config contains options for a Conn. +type Config struct { + // NetNS specifies the Linux network namespace the Conn will operate in. + // This option is unsupported on other operating systems. + // + // If set (non-zero), Conn will enter the specified network namespace and an + // error will occur in Socket if the operation fails. + // + // If not set (zero), a best-effort attempt will be made to enter the + // network namespace of the calling thread: this means that any changes made + // to the calling thread's network namespace will also be reflected in Conn. + // If this operation fails (due to lack of permissions or because network + // namespaces are disabled by kernel configuration), Socket will not return + // an error, and the Conn will operate in the default network namespace of + // the process. This enables non-privileged use of Conn in applications + // which do not require elevated privileges. + // + // Entering a network namespace is a privileged operation (root or + // CAP_SYS_ADMIN are required), and most applications should leave this set + // to 0. + NetNS int +} + +// High-level methods which provide convenience over raw system calls. + +// Close closes the underlying file descriptor for the Conn, which also causes +// all in-flight I/O operations to immediately unblock and return errors. Any +// subsequent uses of Conn will result in EBADF. +func (c *Conn) Close() error { + // The caller has expressed an intent to close the socket, so immediately + // increment s.closed to force further calls to result in EBADF before also + // closing the file descriptor to unblock any outstanding operations. + // + // Because other operations simply check for s.closed != 0, we will permit + // double Close, which would increment s.closed beyond 1. + if atomic.AddUint32(&c.closed, 1) != 1 { + // Multiple Close calls. + return nil + } + + return os.NewSyscallError("close", c.fd.Close()) +} + +// CloseRead shuts down the reading side of the Conn. Most callers should just +// use Close. +func (c *Conn) CloseRead() error { return c.Shutdown(unix.SHUT_RD) } + +// CloseWrite shuts down the writing side of the Conn. Most callers should just +// use Close. +func (c *Conn) CloseWrite() error { return c.Shutdown(unix.SHUT_WR) } + +// Read reads directly from the underlying file descriptor. +func (c *Conn) Read(b []byte) (int, error) { return c.fd.Read(b) } + +// ReadContext reads from the underlying file descriptor with added support for +// context cancelation. +func (c *Conn) ReadContext(ctx context.Context, b []byte) (int, error) { + if c.facts.isStream && len(b) > maxRW { + b = b[:maxRW] + } + + n, err := readT(c, ctx, "read", func(fd int) (int, error) { + return unix.Read(fd, b) + }) + if n == 0 && err == nil && c.facts.zeroReadIsEOF { + return 0, io.EOF + } + + return n, os.NewSyscallError("read", err) +} + +// Write writes directly to the underlying file descriptor. +func (c *Conn) Write(b []byte) (int, error) { return c.fd.Write(b) } + +// WriteContext writes to the underlying file descriptor with added support for +// context cancelation. +func (c *Conn) WriteContext(ctx context.Context, b []byte) (int, error) { + var ( + n, nn int + err error + ) + + doErr := c.write(ctx, "write", func(fd int) error { + max := len(b) + if c.facts.isStream && max-nn > maxRW { + max = nn + maxRW + } + + n, err = unix.Write(fd, b[nn:max]) + if n > 0 { + nn += n + } + if nn == len(b) { + return err + } + if n == 0 && err == nil { + err = io.ErrUnexpectedEOF + return nil + } + + return err + }) + if doErr != nil { + return 0, doErr + } + + return nn, os.NewSyscallError("write", err) +} + +// SetDeadline sets both the read and write deadlines associated with the Conn. +func (c *Conn) SetDeadline(t time.Time) error { return c.fd.SetDeadline(t) } + +// SetReadDeadline sets the read deadline associated with the Conn. +func (c *Conn) SetReadDeadline(t time.Time) error { return c.fd.SetReadDeadline(t) } + +// SetWriteDeadline sets the write deadline associated with the Conn. +func (c *Conn) SetWriteDeadline(t time.Time) error { return c.fd.SetWriteDeadline(t) } + +// ReadBuffer gets the size of the operating system's receive buffer associated +// with the Conn. +func (c *Conn) ReadBuffer() (int, error) { + return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF) +} + +// WriteBuffer gets the size of the operating system's transmit buffer +// associated with the Conn. +func (c *Conn) WriteBuffer() (int, error) { + return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF) +} + +// SetReadBuffer sets the size of the operating system's receive buffer +// associated with the Conn. +// +// When called with elevated privileges on Linux, the SO_RCVBUFFORCE option will +// be used to override operating system limits. Otherwise SO_RCVBUF is used +// (which obeys operating system limits). +func (c *Conn) SetReadBuffer(bytes int) error { return c.setReadBuffer(bytes) } + +// SetWriteBuffer sets the size of the operating system's transmit buffer +// associated with the Conn. +// +// When called with elevated privileges on Linux, the SO_SNDBUFFORCE option will +// be used to override operating system limits. Otherwise SO_SNDBUF is used +// (which obeys operating system limits). +func (c *Conn) SetWriteBuffer(bytes int) error { return c.setWriteBuffer(bytes) } + +// SyscallConn returns a raw network connection. This implements the +// syscall.Conn interface. +// +// SyscallConn is intended for advanced use cases, such as getting and setting +// arbitrary socket options using the socket's file descriptor. If possible, +// those operations should be performed using methods on Conn instead. +// +// Once invoked, it is the caller's responsibility to ensure that operations +// performed using Conn and the syscall.RawConn do not conflict with each other. +func (c *Conn) SyscallConn() (syscall.RawConn, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, os.NewSyscallError("syscallconn", unix.EBADF) + } + + // TODO(mdlayher): mutex or similar to enforce syscall.RawConn contract of + // FD remaining valid for duration of calls? + return c.rc, nil +} + +// Socket wraps the socket(2) system call to produce a Conn. domain, typ, and +// proto are passed directly to socket(2), and name should be a unique name for +// the socket type such as "netlink" or "vsock". +// +// The cfg parameter specifies optional configuration for the Conn. If nil, no +// additional configuration will be applied. +// +// If the operating system supports SOCK_CLOEXEC and SOCK_NONBLOCK, they are +// automatically applied to typ to mirror the standard library's socket flag +// behaviors. +func Socket(domain, typ, proto int, name string, cfg *Config) (*Conn, error) { + if cfg == nil { + cfg = &Config{} + } + + if cfg.NetNS == 0 { + // Non-Linux or no network namespace. + return socket(domain, typ, proto, name) + } + + // Linux only: create Conn in the specified network namespace. + return withNetNS(cfg.NetNS, func() (*Conn, error) { + return socket(domain, typ, proto, name) + }) +} + +// socket is the internal, cross-platform entry point for socket(2). +func socket(domain, typ, proto int, name string) (*Conn, error) { + var ( + fd int + err error + ) + + for { + fd, err = unix.Socket(domain, typ|socketFlags, proto) + switch { + case err == nil: + // Some OSes already set CLOEXEC with typ. + if !flagCLOEXEC { + unix.CloseOnExec(fd) + } + + // No error, prepare the Conn. + return New(fd, name) + case !ready(err): + // System call interrupted or not ready, try again. + continue + case err == unix.EINVAL, err == unix.EPROTONOSUPPORT: + // On Linux, SOCK_NONBLOCK and SOCK_CLOEXEC were introduced in + // 2.6.27. On FreeBSD, both flags were introduced in FreeBSD 10. + // EINVAL and EPROTONOSUPPORT check for earlier versions of these + // OSes respectively. + // + // Mirror what the standard library does when creating file + // descriptors: avoid racing a fork/exec with the creation of new + // file descriptors, so that child processes do not inherit socket + // file descriptors unexpectedly. + // + // For a more thorough explanation, see similar work in the Go tree: + // func sysSocket in net/sock_cloexec.go, as well as the detailed + // comment in syscall/exec_unix.go. + syscall.ForkLock.RLock() + fd, err = unix.Socket(domain, typ, proto) + if err != nil { + syscall.ForkLock.RUnlock() + return nil, os.NewSyscallError("socket", err) + } + unix.CloseOnExec(fd) + syscall.ForkLock.RUnlock() + + return New(fd, name) + default: + // Unhandled error. + return nil, os.NewSyscallError("socket", err) + } + } +} + +// FileConn returns a copy of the network connection corresponding to the open +// file. It is the caller's responsibility to close the file when finished. +// Closing the Conn does not affect the File, and closing the File does not +// affect the Conn. +func FileConn(f *os.File, name string) (*Conn, error) { + // First we'll try to do fctnl(2) with F_DUPFD_CLOEXEC because we can dup + // the file descriptor and set the flag in one syscall. + fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0) + switch err { + case nil: + // OK, ready to set up non-blocking I/O. + return New(fd, name) + case unix.EINVAL: + // The kernel rejected our fcntl(2), fall back to separate dup(2) and + // setting close on exec. + // + // Mirror what the standard library does when creating file descriptors: + // avoid racing a fork/exec with the creation of new file descriptors, + // so that child processes do not inherit socket file descriptors + // unexpectedly. + syscall.ForkLock.RLock() + fd, err := unix.Dup(fd) + if err != nil { + syscall.ForkLock.RUnlock() + return nil, os.NewSyscallError("dup", err) + } + unix.CloseOnExec(fd) + syscall.ForkLock.RUnlock() + + return New(fd, name) + default: + // Any other errors. + return nil, os.NewSyscallError("fcntl", err) + } +} + +// New wraps an existing file descriptor to create a Conn. name should be a +// unique name for the socket type such as "netlink" or "vsock". +// +// Most callers should use Socket or FileConn to construct a Conn. New is +// intended for integrating with specific system calls which provide a file +// descriptor that supports asynchronous I/O. The file descriptor is immediately +// set to nonblocking mode and registered with Go's runtime network poller for +// future I/O operations. +// +// Unlike FileConn, New does not duplicate the existing file descriptor in any +// way. The returned Conn takes ownership of the underlying file descriptor. +func New(fd int, name string) (*Conn, error) { + // All Conn I/O is nonblocking for integration with Go's runtime network + // poller. Depending on the OS this might already be set but it can't hurt + // to set it again. + if err := unix.SetNonblock(fd, true); err != nil { + return nil, os.NewSyscallError("setnonblock", err) + } + + // os.NewFile registers the non-blocking file descriptor with the runtime + // poller, which is then used for most subsequent operations except those + // that require raw I/O via SyscallConn. + // + // See also: https://golang.org/pkg/os/#NewFile + f := os.NewFile(uintptr(fd), name) + rc, err := f.SyscallConn() + if err != nil { + return nil, err + } + + c := &Conn{ + name: name, + fd: f, + rc: rc, + } + + // Probe the file descriptor for socket settings. + sotype, err := c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_TYPE) + switch { + case err == nil: + // File is a socket, check its properties. + c.facts = facts{ + isStream: sotype == unix.SOCK_STREAM, + zeroReadIsEOF: sotype != unix.SOCK_DGRAM && sotype != unix.SOCK_RAW, + } + case errors.Is(err, unix.ENOTSOCK): + // File is not a socket, treat it as a regular file. + c.facts = facts{ + isStream: true, + zeroReadIsEOF: true, + } + default: + return nil, err + } + + return c, nil +} + +// Low-level methods which provide raw system call access. + +// Accept wraps accept(2) or accept4(2) depending on the operating system, but +// returns a Conn for the accepted connection rather than a raw file descriptor. +// +// If the operating system supports accept4(2) (which allows flags), +// SOCK_CLOEXEC and SOCK_NONBLOCK are automatically applied to flags to mirror +// the standard library's socket flag behaviors. +// +// If the operating system only supports accept(2) (which does not allow flags) +// and flags is not zero, an error will be returned. +// +// Accept obeys context cancelation and uses the deadline set on the context to +// cancel accepting the next connection. If a deadline is set on ctx, this +// deadline will override any previous deadlines set using SetDeadline or +// SetReadDeadline. Upon return, the read deadline is cleared. +func (c *Conn) Accept(ctx context.Context, flags int) (*Conn, unix.Sockaddr, error) { + type ret struct { + nfd int + sa unix.Sockaddr + } + + r, err := readT(c, ctx, sysAccept, func(fd int) (ret, error) { + // Either accept(2) or accept4(2) depending on the OS. + nfd, sa, err := accept(fd, flags|socketFlags) + return ret{nfd, sa}, err + }) + if err != nil { + // internal/poll, context error, or user function error. + return nil, nil, err + } + + // Successfully accepted a connection, wrap it in a Conn for use by the + // caller. + ac, err := New(r.nfd, c.name) + if err != nil { + return nil, nil, err + } + + return ac, r.sa, nil +} + +// Bind wraps bind(2). +func (c *Conn) Bind(sa unix.Sockaddr) error { + return c.control("bind", func(fd int) error { return unix.Bind(fd, sa) }) +} + +// Connect wraps connect(2). In order to verify that the underlying socket is +// connected to a remote peer, Connect calls getpeername(2) and returns the +// unix.Sockaddr from that call. +// +// Connect obeys context cancelation and uses the deadline set on the context to +// cancel connecting to a remote peer. If a deadline is set on ctx, this +// deadline will override any previous deadlines set using SetDeadline or +// SetWriteDeadline. Upon return, the write deadline is cleared. +func (c *Conn) Connect(ctx context.Context, sa unix.Sockaddr) (unix.Sockaddr, error) { + const op = "connect" + + // TODO(mdlayher): it would seem that trying to connect to unbound vsock + // listeners by calling Connect multiple times results in ECONNRESET for the + // first and nil error for subsequent calls. Do we need to memoize the + // error? Check what the stdlib behavior is. + + var ( + // Track progress between invocations of the write closure. We don't + // have an explicit WaitWrite call like internal/poll does, so we have + // to wait until the runtime calls the closure again to indicate we can + // write. + progress uint32 + + // Capture closure sockaddr and error. + rsa unix.Sockaddr + err error + ) + + doErr := c.write(ctx, op, func(fd int) error { + if atomic.AddUint32(&progress, 1) == 1 { + // First call: initiate connect. + return unix.Connect(fd, sa) + } + + // Subsequent calls: the runtime network poller indicates fd is + // writable. Check for errno. + errno, gerr := c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_ERROR) + if gerr != nil { + return gerr + } + if errno != 0 { + // Connection is still not ready or failed. If errno indicates + // the socket is not ready, we will wait for the next write + // event. Otherwise we propagate this errno back to the as a + // permanent error. + uerr := unix.Errno(errno) + err = uerr + return uerr + } + + // According to internal/poll, it's possible for the runtime network + // poller to spuriously wake us and return errno 0 for SO_ERROR. + // Make sure we are actually connected to a peer. + peer, err := c.Getpeername() + if err != nil { + // internal/poll unconditionally goes back to WaitWrite. + // Synthesize an error that will do the same for us. + return unix.EAGAIN + } + + // Connection complete. + rsa = peer + return nil + }) + if doErr != nil { + // internal/poll or context error. + return nil, doErr + } + + if err == unix.EISCONN { + // TODO(mdlayher): is this block obsolete with the addition of the + // getsockopt SO_ERROR check above? + // + // EISCONN is reported if the socket is already established and should + // not be treated as an error. + // - Darwin reports this for at least TCP sockets + // - Linux reports this for at least AF_VSOCK sockets + return rsa, nil + } + + return rsa, os.NewSyscallError(op, err) +} + +// Getsockname wraps getsockname(2). +func (c *Conn) Getsockname() (unix.Sockaddr, error) { + return controlT(c, "getsockname", unix.Getsockname) +} + +// Getpeername wraps getpeername(2). +func (c *Conn) Getpeername() (unix.Sockaddr, error) { + return controlT(c, "getpeername", unix.Getpeername) +} + +// GetsockoptICMPv6Filter wraps getsockopt(2) for *unix.ICMPv6Filter values. +func (c *Conn) GetsockoptICMPv6Filter(level, opt int) (*unix.ICMPv6Filter, error) { + return controlT(c, "getsockopt", func(fd int) (*unix.ICMPv6Filter, error) { + return unix.GetsockoptICMPv6Filter(fd, level, opt) + }) +} + +// GetsockoptInt wraps getsockopt(2) for integer values. +func (c *Conn) GetsockoptInt(level, opt int) (int, error) { + return controlT(c, "getsockopt", func(fd int) (int, error) { + return unix.GetsockoptInt(fd, level, opt) + }) +} + +// GetsockoptString wraps getsockopt(2) for string values. +func (c *Conn) GetsockoptString(level, opt int) (string, error) { + return controlT(c, "getsockopt", func(fd int) (string, error) { + return unix.GetsockoptString(fd, level, opt) + }) +} + +// Listen wraps listen(2). +func (c *Conn) Listen(n int) error { + return c.control("listen", func(fd int) error { return unix.Listen(fd, n) }) +} + +// Recvmsg wraps recvmsg(2). +func (c *Conn) Recvmsg(ctx context.Context, p, oob []byte, flags int) (int, int, int, unix.Sockaddr, error) { + type ret struct { + n, oobn, recvflags int + from unix.Sockaddr + } + + r, err := readT(c, ctx, "recvmsg", func(fd int) (ret, error) { + n, oobn, recvflags, from, err := unix.Recvmsg(fd, p, oob, flags) + return ret{n, oobn, recvflags, from}, err + }) + if r.n == 0 && err == nil && c.facts.zeroReadIsEOF { + return 0, 0, 0, nil, io.EOF + } + + return r.n, r.oobn, r.recvflags, r.from, err +} + +// Recvfrom wraps recvfrom(2). +func (c *Conn) Recvfrom(ctx context.Context, p []byte, flags int) (int, unix.Sockaddr, error) { + type ret struct { + n int + addr unix.Sockaddr + } + + out, err := readT(c, ctx, "recvfrom", func(fd int) (ret, error) { + n, addr, err := unix.Recvfrom(fd, p, flags) + return ret{n, addr}, err + }) + if out.n == 0 && err == nil && c.facts.zeroReadIsEOF { + return 0, nil, io.EOF + } + + return out.n, out.addr, err +} + +// Sendmsg wraps sendmsg(2). +func (c *Conn) Sendmsg(ctx context.Context, p, oob []byte, to unix.Sockaddr, flags int) (int, error) { + return writeT(c, ctx, "sendmsg", func(fd int) (int, error) { + return unix.SendmsgN(fd, p, oob, to, flags) + }) +} + +// Sendto wraps sendto(2). +func (c *Conn) Sendto(ctx context.Context, p []byte, flags int, to unix.Sockaddr) error { + return c.write(ctx, "sendto", func(fd int) error { + return unix.Sendto(fd, p, flags, to) + }) +} + +// SetsockoptICMPv6Filter wraps setsockopt(2) for *unix.ICMPv6Filter values. +func (c *Conn) SetsockoptICMPv6Filter(level, opt int, filter *unix.ICMPv6Filter) error { + return c.control("setsockopt", func(fd int) error { + return unix.SetsockoptICMPv6Filter(fd, level, opt, filter) + }) +} + +// SetsockoptInt wraps setsockopt(2) for integer values. +func (c *Conn) SetsockoptInt(level, opt, value int) error { + return c.control("setsockopt", func(fd int) error { + return unix.SetsockoptInt(fd, level, opt, value) + }) +} + +// SetsockoptString wraps setsockopt(2) for string values. +func (c *Conn) SetsockoptString(level, opt int, value string) error { + return c.control("setsockopt", func(fd int) error { + return unix.SetsockoptString(fd, level, opt, value) + }) +} + +// Shutdown wraps shutdown(2). +func (c *Conn) Shutdown(how int) error { + return c.control("shutdown", func(fd int) error { return unix.Shutdown(fd, how) }) +} + +// Conn low-level read/write/control functions. These functions mirror the +// syscall.RawConn APIs but the input closures return errors rather than +// booleans. + +// read wraps readT to execute a function and capture its error result. This is +// a convenience wrapper for functions which don't return any extra values. +func (c *Conn) read(ctx context.Context, op string, f func(fd int) error) error { + _, err := readT(c, ctx, op, func(fd int) (struct{}, error) { + return struct{}{}, f(fd) + }) + return err +} + +// write executes f, a write function, against the associated file descriptor. +// op is used to create an *os.SyscallError if the file descriptor is closed. +func (c *Conn) write(ctx context.Context, op string, f func(fd int) error) error { + _, err := writeT(c, ctx, op, func(fd int) (struct{}, error) { + return struct{}{}, f(fd) + }) + return err +} + +// readT executes c.rc.Read for op using the input function, returning a newly +// allocated result T. +func readT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) { + return rwT(c, rwContext[T]{ + Context: ctx, + Type: read, + Op: op, + Do: f, + }) +} + +// writeT executes c.rc.Write for op using the input function, returning a newly +// allocated result T. +func writeT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) { + return rwT(c, rwContext[T]{ + Context: ctx, + Type: write, + Op: op, + Do: f, + }) +} + +// readWrite indicates if an operation intends to read or write. +type readWrite bool + +// Possible readWrite values. +const ( + read readWrite = false + write readWrite = true +) + +// An rwContext provides arguments to rwT. +type rwContext[T any] struct { + // The caller's context passed for cancelation. + Context context.Context + + // The type of an operation: read or write. + Type readWrite + + // The name of the operation used in errors. + Op string + + // The actual function to perform. + Do func(fd int) (T, error) +} + +// rwT executes c.rc.Read or c.rc.Write (depending on the value of rw.Type) for +// rw.Op using the input function, returning a newly allocated result T. +// +// It obeys context cancelation and the rw.Context must not be nil. +func rwT[T any](c *Conn, rw rwContext[T]) (T, error) { + if atomic.LoadUint32(&c.closed) != 0 { + // If the file descriptor is already closed, do nothing. + return *new(T), os.NewSyscallError(rw.Op, unix.EBADF) + } + + if err := rw.Context.Err(); err != nil { + // Early exit due to context cancel. + return *new(T), os.NewSyscallError(rw.Op, err) + } + + var ( + // The read or write function used to access the runtime network poller. + poll func(func(uintptr) bool) error + + // The read or write function used to set the matching deadline. + deadline func(time.Time) error + ) + + if rw.Type == write { + poll = c.rc.Write + deadline = c.SetWriteDeadline + } else { + poll = c.rc.Read + deadline = c.SetReadDeadline + } + + var ( + // Whether or not the context carried a deadline we are actively using + // for cancelation. + setDeadline bool + + // Signals for the cancelation watcher goroutine. + wg sync.WaitGroup + doneC = make(chan struct{}) + + // Atomic: reports whether we have to disarm the deadline. + needDisarm atomic.Bool + ) + + // On cancel, clean up the watcher. + defer func() { + close(doneC) + wg.Wait() + }() + + if d, ok := rw.Context.Deadline(); ok { + // The context has an explicit deadline. We will use it for cancelation + // but disarm it after poll for the next call. + if err := deadline(d); err != nil { + return *new(T), err + } + setDeadline = true + needDisarm.Store(true) + } else { + // The context does not have an explicit deadline. We have to watch for + // cancelation so we can propagate that signal to immediately unblock + // the runtime network poller. + // + // TODO(mdlayher): is it possible to detect a background context vs a + // context with possible future cancel? + wg.Add(1) + go func() { + defer wg.Done() + + select { + case <-rw.Context.Done(): + // Cancel the operation. Make the caller disarm after poll + // returns. + needDisarm.Store(true) + _ = deadline(time.Unix(0, 1)) + case <-doneC: + // Nothing to do. + } + }() + } + + var ( + t T + err error + ) + + pollErr := poll(func(fd uintptr) bool { + t, err = rw.Do(int(fd)) + return ready(err) + }) + + if needDisarm.Load() { + _ = deadline(time.Time{}) + } + + if pollErr != nil { + if rw.Context.Err() != nil || (setDeadline && errors.Is(pollErr, os.ErrDeadlineExceeded)) { + // The caller canceled the operation or we set a deadline internally + // and it was reached. + // + // Unpack a plain context error. We wait for the context to be done + // to synchronize state externally. Otherwise we have noticed I/O + // timeout wakeups when we set a deadline but the context was not + // yet marked done. + <-rw.Context.Done() + return *new(T), os.NewSyscallError(rw.Op, rw.Context.Err()) + } + + // Error from syscall.RawConn methods. Conventionally the standard + // library does not wrap internal/poll errors in os.NewSyscallError. + return *new(T), pollErr + } + + // Result from user function. + return t, os.NewSyscallError(rw.Op, err) +} + +// control executes Conn.control for op using the input function. +func (c *Conn) control(op string, f func(fd int) error) error { + _, err := controlT(c, op, func(fd int) (struct{}, error) { + return struct{}{}, f(fd) + }) + return err +} + +// controlT executes c.rc.Control for op using the input function, returning a +// newly allocated result T. +func controlT[T any](c *Conn, op string, f func(fd int) (T, error)) (T, error) { + if atomic.LoadUint32(&c.closed) != 0 { + // If the file descriptor is already closed, do nothing. + return *new(T), os.NewSyscallError(op, unix.EBADF) + } + + var ( + t T + err error + ) + + doErr := c.rc.Control(func(fd uintptr) { + // Repeatedly attempt the syscall(s) invoked by f until completion is + // indicated by the return value of ready or the context is canceled. + // + // The last values for t and err are captured outside of the closure for + // use when the loop breaks. + for { + t, err = f(int(fd)) + if ready(err) { + return + } + } + }) + if doErr != nil { + // Error from syscall.RawConn methods. Conventionally the standard + // library does not wrap internal/poll errors in os.NewSyscallError. + return *new(T), doErr + } + + // Result from user function. + return t, os.NewSyscallError(op, err) +} + +// ready indicates readiness based on the value of err. +func ready(err error) bool { + switch err { + case unix.EAGAIN, unix.EINPROGRESS, unix.EINTR: + // When a socket is in non-blocking mode, we might see a variety of errors: + // - EAGAIN: most common case for a socket read not being ready + // - EINPROGRESS: reported by some sockets when first calling connect + // - EINTR: system call interrupted, more frequently occurs in Go 1.14+ + // because goroutines can be asynchronously preempted + // + // Return false to let the poller wait for readiness. See the source code + // for internal/poll.FD.RawRead for more details. + return false + default: + // Ready regardless of whether there was an error or no error. + return true + } +} + +// Darwin and FreeBSD can't read or write 2GB+ files at a time, +// even on 64-bit systems. +// The same is true of socket implementations on many systems. +// See golang.org/issue/7812 and golang.org/issue/16266. +// Use 1GB instead of, say, 2GB-1, to keep subsequent reads aligned. +const maxRW = 1 << 30 diff --git a/vendor/github.com/mdlayher/socket/conn_linux.go b/vendor/github.com/mdlayher/socket/conn_linux.go new file mode 100644 index 0000000000..081194f327 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/conn_linux.go @@ -0,0 +1,118 @@ +//go:build linux +// +build linux + +package socket + +import ( + "context" + "os" + "unsafe" + + "golang.org/x/net/bpf" + "golang.org/x/sys/unix" +) + +// IoctlKCMClone wraps ioctl(2) for unix.KCMClone values, but returns a Conn +// rather than a raw file descriptor. +func (c *Conn) IoctlKCMClone() (*Conn, error) { + info, err := controlT(c, "ioctl", unix.IoctlKCMClone) + if err != nil { + return nil, err + } + + // Successful clone, wrap in a Conn for use by the caller. + return New(int(info.Fd), c.name) +} + +// IoctlKCMAttach wraps ioctl(2) for unix.KCMAttach values. +func (c *Conn) IoctlKCMAttach(info unix.KCMAttach) error { + return c.control("ioctl", func(fd int) error { + return unix.IoctlKCMAttach(fd, info) + }) +} + +// IoctlKCMUnattach wraps ioctl(2) for unix.KCMUnattach values. +func (c *Conn) IoctlKCMUnattach(info unix.KCMUnattach) error { + return c.control("ioctl", func(fd int) error { + return unix.IoctlKCMUnattach(fd, info) + }) +} + +// PidfdGetfd wraps pidfd_getfd(2) for a Conn which wraps a pidfd, but returns a +// Conn rather than a raw file descriptor. +func (c *Conn) PidfdGetfd(targetFD, flags int) (*Conn, error) { + outFD, err := controlT(c, "pidfd_getfd", func(fd int) (int, error) { + return unix.PidfdGetfd(fd, targetFD, flags) + }) + if err != nil { + return nil, err + } + + // Successful getfd, wrap in a Conn for use by the caller. + return New(outFD, c.name) +} + +// PidfdSendSignal wraps pidfd_send_signal(2) for a Conn which wraps a Linux +// pidfd. +func (c *Conn) PidfdSendSignal(sig unix.Signal, info *unix.Siginfo, flags int) error { + return c.control("pidfd_send_signal", func(fd int) error { + return unix.PidfdSendSignal(fd, sig, info, flags) + }) +} + +// SetBPF attaches an assembled BPF program to a Conn. +func (c *Conn) SetBPF(filter []bpf.RawInstruction) error { + // We can't point to the first instruction in the array if no instructions + // are present. + if len(filter) == 0 { + return os.NewSyscallError("setsockopt", unix.EINVAL) + } + + prog := unix.SockFprog{ + Len: uint16(len(filter)), + Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])), + } + + return c.SetsockoptSockFprog(unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, &prog) +} + +// RemoveBPF removes a BPF filter from a Conn. +func (c *Conn) RemoveBPF() error { + // 0 argument is ignored. + return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0) +} + +// SetsockoptPacketMreq wraps setsockopt(2) for unix.PacketMreq values. +func (c *Conn) SetsockoptPacketMreq(level, opt int, mreq *unix.PacketMreq) error { + return c.control("setsockopt", func(fd int) error { + return unix.SetsockoptPacketMreq(fd, level, opt, mreq) + }) +} + +// SetsockoptSockFprog wraps setsockopt(2) for unix.SockFprog values. +func (c *Conn) SetsockoptSockFprog(level, opt int, fprog *unix.SockFprog) error { + return c.control("setsockopt", func(fd int) error { + return unix.SetsockoptSockFprog(fd, level, opt, fprog) + }) +} + +// GetsockoptTpacketStats wraps getsockopt(2) for unix.TpacketStats values. +func (c *Conn) GetsockoptTpacketStats(level, name int) (*unix.TpacketStats, error) { + return controlT(c, "getsockopt", func(fd int) (*unix.TpacketStats, error) { + return unix.GetsockoptTpacketStats(fd, level, name) + }) +} + +// GetsockoptTpacketStatsV3 wraps getsockopt(2) for unix.TpacketStatsV3 values. +func (c *Conn) GetsockoptTpacketStatsV3(level, name int) (*unix.TpacketStatsV3, error) { + return controlT(c, "getsockopt", func(fd int) (*unix.TpacketStatsV3, error) { + return unix.GetsockoptTpacketStatsV3(fd, level, name) + }) +} + +// Waitid wraps waitid(2). +func (c *Conn) Waitid(idType int, info *unix.Siginfo, options int, rusage *unix.Rusage) error { + return c.read(context.Background(), "waitid", func(fd int) error { + return unix.Waitid(idType, fd, info, options, rusage) + }) +} diff --git a/vendor/github.com/mdlayher/socket/doc.go b/vendor/github.com/mdlayher/socket/doc.go new file mode 100644 index 0000000000..7d4566c90b --- /dev/null +++ b/vendor/github.com/mdlayher/socket/doc.go @@ -0,0 +1,13 @@ +// Package socket provides a low-level network connection type which integrates +// with Go's runtime network poller to provide asynchronous I/O and deadline +// support. +// +// This package focuses on UNIX-like operating systems which make use of BSD +// sockets system call APIs. It is meant to be used as a foundation for the +// creation of operating system-specific socket packages, for socket families +// such as Linux's AF_NETLINK, AF_PACKET, or AF_VSOCK. This package should not +// be used directly in end user applications. +// +// Any use of package socket should be guarded by build tags, as one would also +// use when importing the syscall or golang.org/x/sys packages. +package socket diff --git a/vendor/github.com/mdlayher/socket/netns_linux.go b/vendor/github.com/mdlayher/socket/netns_linux.go new file mode 100644 index 0000000000..b29115ad1c --- /dev/null +++ b/vendor/github.com/mdlayher/socket/netns_linux.go @@ -0,0 +1,150 @@ +//go:build linux +// +build linux + +package socket + +import ( + "errors" + "fmt" + "os" + "runtime" + + "golang.org/x/sync/errgroup" + "golang.org/x/sys/unix" +) + +// errNetNSDisabled is returned when network namespaces are unavailable on +// a given system. +var errNetNSDisabled = errors.New("socket: Linux network namespaces are not enabled on this system") + +// withNetNS invokes fn within the context of the network namespace specified by +// fd, while also managing the logic required to safely do so by manipulating +// thread-local state. +func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) { + var ( + eg errgroup.Group + conn *Conn + ) + + eg.Go(func() error { + // Retrieve and store the calling OS thread's network namespace so the + // thread can be reassigned to it after creating a socket in another network + // namespace. + runtime.LockOSThread() + + ns, err := threadNetNS() + if err != nil { + // No thread-local manipulation, unlock. + runtime.UnlockOSThread() + return err + } + defer ns.Close() + + // Beyond this point, the thread's network namespace is poisoned. Do not + // unlock the OS thread until all network namespace manipulation completes + // to avoid returning to the caller with altered thread-local state. + + // Assign the current OS thread the goroutine is locked to to the given + // network namespace. + if err := ns.Set(fd); err != nil { + return err + } + + // Attempt Conn creation and unconditionally restore the original namespace. + c, err := fn() + if nerr := ns.Restore(); nerr != nil { + // Failed to restore original namespace. Return an error and allow the + // runtime to terminate the thread. + if err == nil { + _ = c.Close() + } + + return nerr + } + + // No more thread-local state manipulation; return the new Conn. + runtime.UnlockOSThread() + conn = c + return nil + }) + + if err := eg.Wait(); err != nil { + return nil, err + } + + return conn, nil +} + +// A netNS is a handle that can manipulate network namespaces. +// +// Operations performed on a netNS must use runtime.LockOSThread before +// manipulating any network namespaces. +type netNS struct { + // The handle to a network namespace. + f *os.File + + // Indicates if network namespaces are disabled on this system, and thus + // operations should become a no-op or return errors. + disabled bool +} + +// threadNetNS constructs a netNS using the network namespace of the calling +// thread. If the namespace is not the default namespace, runtime.LockOSThread +// should be invoked first. +func threadNetNS() (*netNS, error) { + return fileNetNS(fmt.Sprintf("/proc/self/task/%d/ns/net", unix.Gettid())) +} + +// fileNetNS opens file and creates a netNS. fileNetNS should only be called +// directly in tests. +func fileNetNS(file string) (*netNS, error) { + f, err := os.Open(file) + switch { + case err == nil: + return &netNS{f: f}, nil + case os.IsNotExist(err): + // Network namespaces are not enabled on this system. Use this signal + // to return errors elsewhere if the caller explicitly asks for a + // network namespace to be set. + return &netNS{disabled: true}, nil + default: + return nil, err + } +} + +// Close releases the handle to a network namespace. +func (n *netNS) Close() error { + return n.do(func() error { return n.f.Close() }) +} + +// FD returns a file descriptor which represents the network namespace. +func (n *netNS) FD() int { + if n.disabled { + // No reasonable file descriptor value in this case, so specify a + // non-existent one. + return -1 + } + + return int(n.f.Fd()) +} + +// Restore restores the original network namespace for the calling thread. +func (n *netNS) Restore() error { + return n.do(func() error { return n.Set(n.FD()) }) +} + +// Set sets a new network namespace for the current thread using fd. +func (n *netNS) Set(fd int) error { + return n.do(func() error { + return os.NewSyscallError("setns", unix.Setns(fd, unix.CLONE_NEWNET)) + }) +} + +// do runs fn if network namespaces are enabled on this system. +func (n *netNS) do(fn func() error) error { + if n.disabled { + return errNetNSDisabled + } + + return fn() +} diff --git a/vendor/github.com/mdlayher/socket/netns_others.go b/vendor/github.com/mdlayher/socket/netns_others.go new file mode 100644 index 0000000000..4cceb3d047 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/netns_others.go @@ -0,0 +1,14 @@ +//go:build !linux +// +build !linux + +package socket + +import ( + "fmt" + "runtime" +) + +// withNetNS returns an error on non-Linux systems. +func withNetNS(_ int, _ func() (*Conn, error)) (*Conn, error) { + return nil, fmt.Errorf("socket: Linux network namespace support is not available on %s", runtime.GOOS) +} diff --git a/vendor/github.com/mdlayher/socket/setbuffer_linux.go b/vendor/github.com/mdlayher/socket/setbuffer_linux.go new file mode 100644 index 0000000000..0d4aa4417c --- /dev/null +++ b/vendor/github.com/mdlayher/socket/setbuffer_linux.go @@ -0,0 +1,24 @@ +//go:build linux +// +build linux + +package socket + +import "golang.org/x/sys/unix" + +// setReadBuffer wraps the SO_RCVBUF{,FORCE} setsockopt(2) options. +func (c *Conn) setReadBuffer(bytes int) error { + err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes) + if err != nil { + err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes) + } + return err +} + +// setWriteBuffer wraps the SO_SNDBUF{,FORCE} setsockopt(2) options. +func (c *Conn) setWriteBuffer(bytes int) error { + err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes) + if err != nil { + err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes) + } + return err +} diff --git a/vendor/github.com/mdlayher/socket/setbuffer_others.go b/vendor/github.com/mdlayher/socket/setbuffer_others.go new file mode 100644 index 0000000000..72b36dbe31 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/setbuffer_others.go @@ -0,0 +1,16 @@ +//go:build !linux +// +build !linux + +package socket + +import "golang.org/x/sys/unix" + +// setReadBuffer wraps the SO_RCVBUF setsockopt(2) option. +func (c *Conn) setReadBuffer(bytes int) error { + return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes) +} + +// setWriteBuffer wraps the SO_SNDBUF setsockopt(2) option. +func (c *Conn) setWriteBuffer(bytes int) error { + return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes) +} diff --git a/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go b/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go new file mode 100644 index 0000000000..40e834310b --- /dev/null +++ b/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go @@ -0,0 +1,12 @@ +//go:build !darwin +// +build !darwin + +package socket + +import "golang.org/x/sys/unix" + +const ( + // These operating systems support CLOEXEC and NONBLOCK socket options. + flagCLOEXEC = true + socketFlags = unix.SOCK_CLOEXEC | unix.SOCK_NONBLOCK +) diff --git a/vendor/github.com/mdlayher/socket/typ_none.go b/vendor/github.com/mdlayher/socket/typ_none.go new file mode 100644 index 0000000000..9bbb1aab5f --- /dev/null +++ b/vendor/github.com/mdlayher/socket/typ_none.go @@ -0,0 +1,11 @@ +//go:build darwin +// +build darwin + +package socket + +const ( + // These operating systems do not support CLOEXEC and NONBLOCK socket + // options. + flagCLOEXEC = false + socketFlags = 0 +) diff --git a/vendor/github.com/mdlayher/vsock/.gitignore b/vendor/github.com/mdlayher/vsock/.gitignore new file mode 100644 index 0000000000..8130d4158a --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/.gitignore @@ -0,0 +1,4 @@ +cover.out +vsock.test +cmd/vscp/vscp +cmd/vsockhttp/vsockhttp diff --git a/vendor/github.com/mdlayher/vsock/CHANGELOG.md b/vendor/github.com/mdlayher/vsock/CHANGELOG.md new file mode 100644 index 0000000000..c64a797bc2 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/CHANGELOG.md @@ -0,0 +1,53 @@ +# CHANGELOG + +# v1.2.1 + +- [Improvement]: updated dependencies, test with Go 1.20. + +# v1.2.0 + +**This is the first release of package vsock that only supports Go 1.18+. Users +on older versions of Go must use v1.1.1.** + +- [Improvement]: drop support for older versions of Go so we can begin using + modern versions of `x/sys` and other dependencies. + +## v1.1.1 + +**This is the last release of package vsock that supports Go 1.17 and below.** + +- [Bug Fix] [commit](https://github.com/mdlayher/vsock/commit/ead86435c244d5d6baad549a6df0557ada3f4401): + fix build on non-UNIX platforms such as Windows. This is a no-op change on + Linux but provides a friendlier experience for non-Linux users. + +## v1.1.0 + +- [New API] [commit](https://github.com/mdlayher/vsock/commit/44cd82dc5f7de644436f22236b111ab97fa9a14f): + `vsock.FileListener` can be used to create a `vsock.Listener` from an existing + `os.File`, which may be provided by systemd socket activation or another + external mechanism. + +## v1.0.1 + +- [Bug Fix] [commit](https://github.com/mdlayher/vsock/commit/99a6dccdebad21d1fa5f757d228d677ccb1412dc): + upgrade `github.com/mdlayher/socket` to handle non-blocking `connect(2)` + errors (called in `vsock.Dial`) properly by checking the `SO_ERROR` socket + option. Lock in this behavior with a new test. +- [Improvement] [commit](https://github.com/mdlayher/vsock/commit/375f3bbcc363500daf367ec511638a4655471719): + downgrade the version of `golang.org/x/net` in use to support Go 1.12. We + don't need the latest version for this package. + +## v1.0.0 + +**This is the first release of package vsock that only supports Go 1.12+. +Users on older versions of Go must use an unstable release.** + +- Initial stable commit! +- [API change]: the `vsock.Dial` and `vsock.Listen` constructors now accept an + optional `*vsock.Config` parameter to enable future expansion in v1.x.x + without prompting further breaking API changes. Because `vsock.Config` has no + options as of this release, `nil` may be passed in all call sites to fix + existing code upon upgrading to v1.0.0. +- [New API]: the `vsock.ListenContextID` function can be used to create a + `*vsock.Listener` which is bound to an explicit context ID address, rather + than inferring one automatically as `vsock.Listen` does. diff --git a/vendor/github.com/mdlayher/vsock/LICENSE.md b/vendor/github.com/mdlayher/vsock/LICENSE.md new file mode 100644 index 0000000000..9fa6774b14 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/LICENSE.md @@ -0,0 +1,9 @@ +# MIT License + +Copyright (C) 2017-2022 Matt Layher + +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/vendor/github.com/mdlayher/vsock/README.md b/vendor/github.com/mdlayher/vsock/README.md new file mode 100644 index 0000000000..b1ec4cfbe1 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/README.md @@ -0,0 +1,21 @@ +# vsock [![Test Status](https://github.com/mdlayher/vsock/workflows/Linux%20Test/badge.svg)](https://github.com/mdlayher/vsock/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/vsock.svg)](https://pkg.go.dev/github.com/mdlayher/vsock) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/vsock)](https://goreportcard.com/report/github.com/mdlayher/vsock) + +Package `vsock` provides access to Linux VM sockets (`AF_VSOCK`) for +communication between a hypervisor and its virtual machines. MIT Licensed. + +For more information about VM sockets, see my blog about +[Linux VM sockets in Go](https://mdlayher.com/blog/linux-vm-sockets-in-go/) or +the [QEMU wiki page on virtio-vsock](http://wiki.qemu-project.org/Features/VirtioVsock). + +## Stability + +See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between +releases. + +This package has a stable v1 API and any future breaking changes will prompt +the release of a new major version. Features and bug fixes will continue to +occur in the v1.x.x series. + +This package only supports the two most recent major versions of Go, mirroring +Go's own release policy. Older versions of Go may lack critical features and bug +fixes which are necessary for this package to function correctly. diff --git a/vendor/github.com/mdlayher/vsock/conn_linux.go b/vendor/github.com/mdlayher/vsock/conn_linux.go new file mode 100644 index 0000000000..6029d547e5 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/conn_linux.go @@ -0,0 +1,62 @@ +//go:build linux +// +build linux + +package vsock + +import ( + "context" + + "github.com/mdlayher/socket" + "golang.org/x/sys/unix" +) + +// A conn is the net.Conn implementation for connection-oriented VM sockets. +// We can use socket.Conn directly on Linux to implement all of the necessary +// methods. +type conn = socket.Conn + +// dial is the entry point for Dial on Linux. +func dial(cid, port uint32, _ *Config) (*Conn, error) { + // TODO(mdlayher): Config default nil check and initialize. Pass options to + // socket.Config where necessary. + + c, err := socket.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0, "vsock", nil) + if err != nil { + return nil, err + } + + sa := &unix.SockaddrVM{CID: cid, Port: port} + rsa, err := c.Connect(context.Background(), sa) + if err != nil { + _ = c.Close() + return nil, err + } + + // TODO(mdlayher): getpeername(2) appears to return nil in the GitHub CI + // environment, so in the event of a nil sockaddr, fall back to the previous + // method of synthesizing the remote address. + if rsa == nil { + rsa = sa + } + + lsa, err := c.Getsockname() + if err != nil { + _ = c.Close() + return nil, err + } + + lsavm := lsa.(*unix.SockaddrVM) + rsavm := rsa.(*unix.SockaddrVM) + + return &Conn{ + c: c, + local: &Addr{ + ContextID: lsavm.CID, + Port: lsavm.Port, + }, + remote: &Addr{ + ContextID: rsavm.CID, + Port: rsavm.Port, + }, + }, nil +} diff --git a/vendor/github.com/mdlayher/vsock/doc.go b/vendor/github.com/mdlayher/vsock/doc.go new file mode 100644 index 0000000000..e158b18361 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/doc.go @@ -0,0 +1,10 @@ +// Package vsock provides access to Linux VM sockets (AF_VSOCK) for +// communication between a hypervisor and its virtual machines. +// +// The types in this package implement interfaces provided by package net and +// may be used in applications that expect a net.Listener or net.Conn. +// +// - *Addr implements net.Addr +// - *Conn implements net.Conn +// - *Listener implements net.Listener +package vsock diff --git a/vendor/github.com/mdlayher/vsock/fd_linux.go b/vendor/github.com/mdlayher/vsock/fd_linux.go new file mode 100644 index 0000000000..531e53f928 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/fd_linux.go @@ -0,0 +1,36 @@ +package vsock + +import ( + "fmt" + "os" + + "golang.org/x/sys/unix" +) + +// contextID retrieves the local context ID for this system. +func contextID() (uint32, error) { + f, err := os.Open(devVsock) + if err != nil { + return 0, err + } + defer f.Close() + + return unix.IoctlGetUint32(int(f.Fd()), unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID) +} + +// isErrno determines if an error a matches UNIX error number. +func isErrno(err error, errno int) bool { + switch errno { + case ebadf: + return err == unix.EBADF + case enotconn: + return err == unix.ENOTCONN + default: + panicf("vsock: isErrno called with unhandled error number parameter: %d", errno) + return false + } +} + +func panicf(format string, a ...interface{}) { + panic(fmt.Sprintf(format, a...)) +} diff --git a/vendor/github.com/mdlayher/vsock/listener_linux.go b/vendor/github.com/mdlayher/vsock/listener_linux.go new file mode 100644 index 0000000000..50fa1b7a49 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/listener_linux.go @@ -0,0 +1,133 @@ +//go:build linux +// +build linux + +package vsock + +import ( + "context" + "net" + "os" + "time" + + "github.com/mdlayher/socket" + "golang.org/x/sys/unix" +) + +var _ net.Listener = &listener{} + +// A listener is the net.Listener implementation for connection-oriented +// VM sockets. +type listener struct { + c *socket.Conn + addr *Addr +} + +// Addr and Close implement the net.Listener interface for listener. +func (l *listener) Addr() net.Addr { return l.addr } +func (l *listener) Close() error { return l.c.Close() } +func (l *listener) SetDeadline(t time.Time) error { return l.c.SetDeadline(t) } + +// Accept accepts a single connection from the listener, and sets up +// a net.Conn backed by conn. +func (l *listener) Accept() (net.Conn, error) { + c, rsa, err := l.c.Accept(context.Background(), 0) + if err != nil { + return nil, err + } + + savm := rsa.(*unix.SockaddrVM) + remote := &Addr{ + ContextID: savm.CID, + Port: savm.Port, + } + + return &Conn{ + c: c, + local: l.addr, + remote: remote, + }, nil +} + +// name is the socket name passed to package socket. +const name = "vsock" + +// listen is the entry point for Listen on Linux. +func listen(cid, port uint32, _ *Config) (*Listener, error) { + // TODO(mdlayher): Config default nil check and initialize. Pass options to + // socket.Config where necessary. + + c, err := socket.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0, name, nil) + if err != nil { + return nil, err + } + + // Be sure to close the Conn if any of the system calls fail before we + // return the Conn to the caller. + + if port == 0 { + port = unix.VMADDR_PORT_ANY + } + + if err := c.Bind(&unix.SockaddrVM{CID: cid, Port: port}); err != nil { + _ = c.Close() + return nil, err + } + + if err := c.Listen(unix.SOMAXCONN); err != nil { + _ = c.Close() + return nil, err + } + + l, err := newListener(c) + if err != nil { + _ = c.Close() + return nil, err + } + + return l, nil +} + +// fileListener is the entry point for FileListener on Linux. +func fileListener(f *os.File) (*Listener, error) { + c, err := socket.FileConn(f, name) + if err != nil { + return nil, err + } + + l, err := newListener(c) + if err != nil { + _ = c.Close() + return nil, err + } + + return l, nil +} + +// newListener creates a Listener from a raw socket.Conn. +func newListener(c *socket.Conn) (*Listener, error) { + lsa, err := c.Getsockname() + if err != nil { + return nil, err + } + + // Now that the library can also accept arbitrary os.Files, we have to + // verify the address family so we don't accidentally create a + // *vsock.Listener backed by TCP or some other socket type. + lsavm, ok := lsa.(*unix.SockaddrVM) + if !ok { + // All errors should wrapped with os.SyscallError. + return nil, os.NewSyscallError("listen", unix.EINVAL) + } + + addr := &Addr{ + ContextID: lsavm.CID, + Port: lsavm.Port, + } + + return &Listener{ + l: &listener{ + c: c, + addr: addr, + }, + }, nil +} diff --git a/vendor/github.com/mdlayher/vsock/vsock.go b/vendor/github.com/mdlayher/vsock/vsock.go new file mode 100644 index 0000000000..78763936ae --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/vsock.go @@ -0,0 +1,435 @@ +package vsock + +import ( + "errors" + "fmt" + "io" + "net" + "os" + "strings" + "syscall" + "time" +) + +const ( + // Hypervisor specifies that a socket should communicate with the hypervisor + // process. Note that this is _not_ the same as a socket owned by a process + // running on the hypervisor. Most users should probably use Host instead. + Hypervisor = 0x0 + + // Local specifies that a socket should communicate with a matching socket + // on the same machine. This provides an alternative to UNIX sockets or + // similar and may be useful in testing VM sockets applications. + Local = 0x1 + + // Host specifies that a socket should communicate with processes other than + // the hypervisor on the host machine. This is the correct choice to + // communicate with a process running on a hypervisor using a socket dialed + // from a guest. + Host = 0x2 + + // Error numbers we recognize, copied here to avoid importing x/sys/unix in + // cross-platform code. + ebadf = 9 + enotconn = 107 + + // devVsock is the location of /dev/vsock. It is exposed on both the + // hypervisor and on virtual machines. + devVsock = "/dev/vsock" + + // network is the vsock network reported in net.OpError. + network = "vsock" + + // Operation names which may be returned in net.OpError. + opAccept = "accept" + opClose = "close" + opDial = "dial" + opListen = "listen" + opRawControl = "raw-control" + opRawRead = "raw-read" + opRawWrite = "raw-write" + opRead = "read" + opSet = "set" + opSyscallConn = "syscall-conn" + opWrite = "write" +) + +// TODO(mdlayher): plumb through socket.Config.NetNS if it makes sense. + +// Config contains options for a Conn or Listener. +type Config struct{} + +// Listen opens a connection-oriented net.Listener for incoming VM sockets +// connections. The port parameter specifies the port for the Listener. Config +// specifies optional configuration for the Listener. If config is nil, a +// default configuration will be used. +// +// To allow the server to assign a port automatically, specify 0 for port. The +// address of the server can be retrieved using the Addr method. +// +// Listen automatically infers the appropriate context ID for this machine by +// calling ContextID and passing that value to ListenContextID. Callers with +// advanced use cases (such as using the Local context ID) may wish to use +// ListenContextID directly. +// +// When the Listener is no longer needed, Close must be called to free +// resources. +func Listen(port uint32, cfg *Config) (*Listener, error) { + cid, err := ContextID() + if err != nil { + // No addresses available. + return nil, opError(opListen, err, nil, nil) + } + + return ListenContextID(cid, port, cfg) +} + +// ListenContextID is the same as Listen, but also accepts an explicit context +// ID parameter. This function is intended for advanced use cases and most +// callers should use Listen instead. +// +// See the documentation of Listen for more details. +func ListenContextID(contextID, port uint32, cfg *Config) (*Listener, error) { + l, err := listen(contextID, port, cfg) + if err != nil { + // No remote address available. + return nil, opError(opListen, err, &Addr{ + ContextID: contextID, + Port: port, + }, nil) + } + + return l, nil +} + +// FileListener returns a copy of the network listener corresponding to an open +// os.File. It is the caller's responsibility to close the Listener when +// finished. Closing the Listener does not affect the os.File, and closing the +// os.File does not affect the Listener. +// +// This function is intended for advanced use cases and most callers should use +// Listen instead. +func FileListener(f *os.File) (*Listener, error) { + l, err := fileListener(f) + if err != nil { + // No addresses available. + return nil, opError(opListen, err, nil, nil) + } + + return l, nil +} + +var _ net.Listener = &Listener{} + +// A Listener is a VM sockets implementation of a net.Listener. +type Listener struct { + l *listener +} + +// Accept implements the Accept method in the net.Listener interface; it waits +// for the next call and returns a generic net.Conn. The returned net.Conn will +// always be of type *Conn. +func (l *Listener) Accept() (net.Conn, error) { + c, err := l.l.Accept() + if err != nil { + return nil, l.opError(opAccept, err) + } + + return c, nil +} + +// Addr returns the listener's network address, a *Addr. The Addr returned is +// shared by all invocations of Addr, so do not modify it. +func (l *Listener) Addr() net.Addr { return l.l.Addr() } + +// Close stops listening on the VM sockets address. Already Accepted connections +// are not closed. +func (l *Listener) Close() error { + return l.opError(opClose, l.l.Close()) +} + +// SetDeadline sets the deadline associated with the listener. A zero time value +// disables the deadline. +func (l *Listener) SetDeadline(t time.Time) error { + return l.opError(opSet, l.l.SetDeadline(t)) +} + +// opError is a convenience for the function opError that also passes the local +// address of the Listener. +func (l *Listener) opError(op string, err error) error { + // No remote address for a Listener. + return opError(op, err, l.Addr(), nil) +} + +// Dial dials a connection-oriented net.Conn to a VM sockets listener. The +// context ID and port parameters specify the address of the listener. Config +// specifies optional configuration for the Conn. If config is nil, a default +// configuration will be used. +// +// If dialing a connection from the hypervisor to a virtual machine, the VM's +// context ID should be specified. +// +// If dialing from a VM to the hypervisor, Hypervisor should be used to +// communicate with the hypervisor process, or Host should be used to +// communicate with other processes on the host machine. +// +// When the connection is no longer needed, Close must be called to free +// resources. +func Dial(contextID, port uint32, cfg *Config) (*Conn, error) { + c, err := dial(contextID, port, cfg) + if err != nil { + // No local address, but we have a remote address we can return. + return nil, opError(opDial, err, nil, &Addr{ + ContextID: contextID, + Port: port, + }) + } + + return c, nil +} + +var ( + _ net.Conn = &Conn{} + _ syscall.Conn = &Conn{} +) + +// A Conn is a VM sockets implementation of a net.Conn. +type Conn struct { + c *conn + local *Addr + remote *Addr +} + +// Close closes the connection. +func (c *Conn) Close() error { + return c.opError(opClose, c.c.Close()) +} + +// CloseRead shuts down the reading side of the VM sockets connection. Most +// callers should just use Close. +func (c *Conn) CloseRead() error { + return c.opError(opClose, c.c.CloseRead()) +} + +// CloseWrite shuts down the writing side of the VM sockets connection. Most +// callers should just use Close. +func (c *Conn) CloseWrite() error { + return c.opError(opClose, c.c.CloseWrite()) +} + +// LocalAddr returns the local network address. The Addr returned is shared by +// all invocations of LocalAddr, so do not modify it. +func (c *Conn) LocalAddr() net.Addr { return c.local } + +// RemoteAddr returns the remote network address. The Addr returned is shared by +// all invocations of RemoteAddr, so do not modify it. +func (c *Conn) RemoteAddr() net.Addr { return c.remote } + +// Read implements the net.Conn Read method. +func (c *Conn) Read(b []byte) (int, error) { + n, err := c.c.Read(b) + if err != nil { + return n, c.opError(opRead, err) + } + + return n, nil +} + +// Write implements the net.Conn Write method. +func (c *Conn) Write(b []byte) (int, error) { + n, err := c.c.Write(b) + if err != nil { + return n, c.opError(opWrite, err) + } + + return n, nil +} + +// SetDeadline implements the net.Conn SetDeadline method. +func (c *Conn) SetDeadline(t time.Time) error { + return c.opError(opSet, c.c.SetDeadline(t)) +} + +// SetReadDeadline implements the net.Conn SetReadDeadline method. +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.opError(opSet, c.c.SetReadDeadline(t)) +} + +// SetWriteDeadline implements the net.Conn SetWriteDeadline method. +func (c *Conn) SetWriteDeadline(t time.Time) error { + return c.opError(opSet, c.c.SetWriteDeadline(t)) +} + +// SyscallConn returns a raw network connection. This implements the +// syscall.Conn interface. +func (c *Conn) SyscallConn() (syscall.RawConn, error) { + rc, err := c.c.SyscallConn() + if err != nil { + return nil, c.opError(opSyscallConn, err) + } + + return &rawConn{ + rc: rc, + local: c.local, + remote: c.remote, + }, nil +} + +// opError is a convenience for the function opError that also passes the local +// and remote addresses of the Conn. +func (c *Conn) opError(op string, err error) error { + return opError(op, err, c.local, c.remote) +} + +// TODO(mdlayher): see if we can port smarter net.OpError with local/remote +// address error logic into socket.Conn's SyscallConn type to avoid the need for +// this wrapper. + +var _ syscall.RawConn = &rawConn{} + +// A rawConn is a syscall.RawConn that wraps an internal syscall.RawConn in order +// to produce net.OpError error values. +type rawConn struct { + rc syscall.RawConn + local, remote *Addr +} + +// Control implements the syscall.RawConn Control method. +func (rc *rawConn) Control(fn func(fd uintptr)) error { + return rc.opError(opRawControl, rc.rc.Control(fn)) +} + +// Control implements the syscall.RawConn Read method. +func (rc *rawConn) Read(fn func(fd uintptr) (done bool)) error { + return rc.opError(opRawRead, rc.rc.Read(fn)) +} + +// Control implements the syscall.RawConn Write method. +func (rc *rawConn) Write(fn func(fd uintptr) (done bool)) error { + return rc.opError(opRawWrite, rc.rc.Write(fn)) +} + +// opError is a convenience for the function opError that also passes the local +// and remote addresses of the rawConn. +func (rc *rawConn) opError(op string, err error) error { + return opError(op, err, rc.local, rc.remote) +} + +var _ net.Addr = &Addr{} + +// An Addr is the address of a VM sockets endpoint. +type Addr struct { + ContextID, Port uint32 +} + +// Network returns the address's network name, "vsock". +func (a *Addr) Network() string { return network } + +// String returns a human-readable representation of Addr, and indicates if +// ContextID is meant to be used for a hypervisor, host, VM, etc. +func (a *Addr) String() string { + var host string + + switch a.ContextID { + case Hypervisor: + host = fmt.Sprintf("hypervisor(%d)", a.ContextID) + case Local: + host = fmt.Sprintf("local(%d)", a.ContextID) + case Host: + host = fmt.Sprintf("host(%d)", a.ContextID) + default: + host = fmt.Sprintf("vm(%d)", a.ContextID) + } + + return fmt.Sprintf("%s:%d", host, a.Port) +} + +// fileName returns a file name for use with os.NewFile for Addr. +func (a *Addr) fileName() string { + return fmt.Sprintf("%s:%s", a.Network(), a.String()) +} + +// ContextID retrieves the local VM sockets context ID for this system. +// ContextID can be used to directly determine if a system is capable of using +// VM sockets. +// +// If the kernel module is unavailable, access to the kernel module is denied, +// or VM sockets are unsupported on this system, it returns an error. +func ContextID() (uint32, error) { + return contextID() +} + +// opError unpacks err if possible, producing a net.OpError with the input +// parameters in order to implement net.Conn. As a convenience, opError returns +// nil if the input error is nil. +func opError(op string, err error, local, remote net.Addr) error { + if err == nil { + return nil + } + + // TODO(mdlayher): this entire function is suspect and should probably be + // looked at carefully, especially with Go 1.13+ error wrapping. + // + // Eventually this *net.OpError logic should probably be ported into + // mdlayher/socket because similar checks are necessary to comply with + // nettest.TestConn. + + // Unwrap inner errors from error types. + // + // TODO(mdlayher): errors.Cause or similar in Go 1.13. + switch xerr := err.(type) { + // os.PathError produced by os.File method calls. + case *os.PathError: + // Although we could make use of xerr.Op here, we're passing it manually + // for consistency, since some of the Conn calls we are making don't + // wrap an os.File, which would return an Op for us. + // + // As a special case, if the error is related to access to the /dev/vsock + // device, we don't unwrap it, so the caller has more context as to why + // their operation actually failed than "permission denied" or similar. + if xerr.Path != devVsock { + err = xerr.Err + } + } + + switch { + case err == io.EOF, isErrno(err, enotconn): + // We may see a literal io.EOF as happens with x/net/nettest, but + // "transport not connected" also means io.EOF in Go. + return io.EOF + case err == os.ErrClosed, isErrno(err, ebadf), strings.Contains(err.Error(), "use of closed"): + // Different operations may return different errors that all effectively + // indicate a closed file. + // + // To rectify the differences, net.TCPConn uses an error with this text + // from internal/poll for the backing file already being closed. + err = errors.New("use of closed network connection") + default: + // Nothing to do, return this directly. + } + + // Determine source and addr using the rules defined by net.OpError's + // documentation: https://golang.org/pkg/net/#OpError. + var source, addr net.Addr + switch op { + case opClose, opDial, opRawRead, opRawWrite, opRead, opWrite: + if local != nil { + source = local + } + if remote != nil { + addr = remote + } + case opAccept, opListen, opRawControl, opSet, opSyscallConn: + if local != nil { + addr = local + } + } + + return &net.OpError{ + Op: op, + Net: network, + Source: source, + Addr: addr, + Err: err, + } +} diff --git a/vendor/github.com/mdlayher/vsock/vsock_others.go b/vendor/github.com/mdlayher/vsock/vsock_others.go new file mode 100644 index 0000000000..5c1e88e398 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/vsock_others.go @@ -0,0 +1,45 @@ +//go:build !linux +// +build !linux + +package vsock + +import ( + "fmt" + "net" + "os" + "runtime" + "syscall" + "time" +) + +// errUnimplemented is returned by all functions on platforms that +// cannot make use of VM sockets. +var errUnimplemented = fmt.Errorf("vsock: not implemented on %s", runtime.GOOS) + +func fileListener(_ *os.File) (*Listener, error) { return nil, errUnimplemented } +func listen(_, _ uint32, _ *Config) (*Listener, error) { return nil, errUnimplemented } + +type listener struct{} + +func (*listener) Accept() (net.Conn, error) { return nil, errUnimplemented } +func (*listener) Addr() net.Addr { return nil } +func (*listener) Close() error { return errUnimplemented } +func (*listener) SetDeadline(_ time.Time) error { return errUnimplemented } + +func dial(_, _ uint32, _ *Config) (*Conn, error) { return nil, errUnimplemented } + +type conn struct{} + +func (*conn) Close() error { return errUnimplemented } +func (*conn) CloseRead() error { return errUnimplemented } +func (*conn) CloseWrite() error { return errUnimplemented } +func (*conn) Read(_ []byte) (int, error) { return 0, errUnimplemented } +func (*conn) Write(_ []byte) (int, error) { return 0, errUnimplemented } +func (*conn) SetDeadline(_ time.Time) error { return errUnimplemented } +func (*conn) SetReadDeadline(_ time.Time) error { return errUnimplemented } +func (*conn) SetWriteDeadline(_ time.Time) error { return errUnimplemented } +func (*conn) SyscallConn() (syscall.RawConn, error) { return nil, errUnimplemented } + +func contextID() (uint32, error) { return 0, errUnimplemented } + +func isErrno(_ error, _ int) bool { return false } diff --git a/vendor/github.com/x448/float16/.travis.yml b/vendor/github.com/x448/float16/.travis.yml new file mode 100644 index 0000000000..8902bdaaff --- /dev/null +++ b/vendor/github.com/x448/float16/.travis.yml @@ -0,0 +1,13 @@ +language: go + +go: + - 1.11.x + +env: + - GO111MODULE=on + +script: + - go test -short -coverprofile=coverage.txt -covermode=count ./... + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/x448/float16/LICENSE b/vendor/github.com/x448/float16/LICENSE new file mode 100644 index 0000000000..bf6e357854 --- /dev/null +++ b/vendor/github.com/x448/float16/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker + +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/vendor/github.com/x448/float16/README.md b/vendor/github.com/x448/float16/README.md new file mode 100644 index 0000000000..b524b8135d --- /dev/null +++ b/vendor/github.com/x448/float16/README.md @@ -0,0 +1,133 @@ +# Float16 (Binary16) in Go/Golang +[![Build Status](https://travis-ci.org/x448/float16.svg?branch=master)](https://travis-ci.org/x448/float16) +[![codecov](https://codecov.io/gh/x448/float16/branch/master/graph/badge.svg?v=4)](https://codecov.io/gh/x448/float16) +[![Go Report Card](https://goreportcard.com/badge/github.com/x448/float16)](https://goreportcard.com/report/github.com/x448/float16) +[![Release](https://img.shields.io/github/release/x448/float16.svg?style=flat-square)](https://github.com/x448/float16/releases) +[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/x448/float16/master/LICENSE) + +`float16` package provides [IEEE 754 half-precision floating-point format (binary16)](https://en.wikipedia.org/wiki/Half-precision_floating-point_format) with IEEE 754 default rounding for conversions. IEEE 754-2008 refers to this 16-bit floating-point format as binary16. + +IEEE 754 default rounding ("Round-to-Nearest RoundTiesToEven") is considered the most accurate and statistically unbiased estimate of the true result. + +All possible 4+ billion floating-point conversions with this library are verified to be correct. + +Lowercase "float16" refers to IEEE 754 binary16. And capitalized "Float16" refers to exported Go data type provided by this library. + +## Features +Current features include: + +* float16 to float32 conversions use lossless conversion. +* float32 to float16 conversions use IEEE 754-2008 "Round-to-Nearest RoundTiesToEven". +* conversions using pure Go take about 2.65 ns/op on a desktop amd64. +* unit tests provide 100% code coverage and check all possible 4+ billion conversions. +* other functions include: IsInf(), IsNaN(), IsNormal(), PrecisionFromfloat32(), String(), etc. +* all functions in this library use zero allocs except String(). + +## Status +This library is used by [fxamacker/cbor](https://github.com/fxamacker/cbor) and is ready for production use on supported platforms. The version number < 1.0 indicates more functions and options are planned but not yet published. + +Current status: + +* core API is done and breaking API changes are unlikely. +* 100% of unit tests pass: + * short mode (`go test -short`) tests around 65765 conversions in 0.005s. + * normal mode (`go test`) tests all possible 4+ billion conversions in about 95s. +* 100% code coverage with both short mode and normal mode. +* tested on amd64 but it should work on all little-endian platforms supported by Go. + +Roadmap: + +* add functions for fast batch conversions leveraging SIMD when supported by hardware. +* speed up unit test when verifying all possible 4+ billion conversions. +* test on additional platforms. + +## Float16 to Float32 Conversion +Conversions from float16 to float32 are lossless conversions. All 65536 possible float16 to float32 conversions (in pure Go) are confirmed to be correct. + +Unit tests take a fraction of a second to check all 65536 expected values for float16 to float32 conversions. + +## Float32 to Float16 Conversion +Conversions from float32 to float16 use IEEE 754 default rounding ("Round-to-Nearest RoundTiesToEven"). All 4294967296 possible float32 to float16 conversions (in pure Go) are confirmed to be correct. + +Unit tests in normal mode take about 1-2 minutes to check all 4+ billion float32 input values and results for Fromfloat32(), FromNaN32ps(), and PrecisionFromfloat32(). + +Unit tests in short mode use a small subset (around 229 float32 inputs) and finish in under 0.01 second while still reaching 100% code coverage. + +## Usage +Install with `go get github.com/x448/float16`. +``` +// Convert float32 to float16 +pi := float32(math.Pi) +pi16 := float16.Fromfloat32(pi) + +// Convert float16 to float32 +pi32 := pi16.Float32() + +// PrecisionFromfloat32() is faster than the overhead of calling a function. +// This example only converts if there's no data loss and input is not a subnormal. +if float16.PrecisionFromfloat32(pi) == float16.PrecisionExact { + pi16 := float16.Fromfloat32(pi) +} +``` + +## Float16 Type and API +Float16 (capitalized) is a Go type with uint16 as the underlying state. There are 6 exported functions and 9 exported methods. +``` +package float16 // import "github.com/x448/float16" + +// Exported types and consts +type Float16 uint16 +const ErrInvalidNaNValue = float16Error("float16: invalid NaN value, expected IEEE 754 NaN") + +// Exported functions +Fromfloat32(f32 float32) Float16 // Float16 number converted from f32 using IEEE 754 default rounding + with identical results to AMD and Intel F16C hardware. NaN inputs + are converted with quiet bit always set on, to be like F16C. + +FromNaN32ps(nan float32) (Float16, error) // Float16 NaN without modifying quiet bit. + // The "ps" suffix means "preserve signaling". + // Returns sNaN and ErrInvalidNaNValue if nan isn't a NaN. + +Frombits(b16 uint16) Float16 // Float16 number corresponding to b16 (IEEE 754 binary16 rep.) +NaN() Float16 // Float16 of IEEE 754 binary16 not-a-number +Inf(sign int) Float16 // Float16 of IEEE 754 binary16 infinity according to sign + +PrecisionFromfloat32(f32 float32) Precision // quickly indicates exact, ..., overflow, underflow + // (inline and < 1 ns/op) +// Exported methods +(f Float16) Float32() float32 // float32 number converted from f16 using lossless conversion +(f Float16) Bits() uint16 // the IEEE 754 binary16 representation of f +(f Float16) IsNaN() bool // true if f is not-a-number (NaN) +(f Float16) IsQuietNaN() bool // true if f is a quiet not-a-number (NaN) +(f Float16) IsInf(sign int) bool // true if f is infinite based on sign (-1=NegInf, 0=any, 1=PosInf) +(f Float16) IsFinite() bool // true if f is not infinite or NaN +(f Float16) IsNormal() bool // true if f is not zero, infinite, subnormal, or NaN. +(f Float16) Signbit() bool // true if f is negative or negative zero +(f Float16) String() string // string representation of f to satisfy fmt.Stringer interface +``` +See [API](https://godoc.org/github.com/x448/float16) at godoc.org for more info. + +## Benchmarks +Conversions (in pure Go) are around 2.65 ns/op for float16 -> float32 and float32 -> float16 on amd64. Speeds can vary depending on input value. + +``` +All functions have zero allocations except float16.String(). + +FromFloat32pi-2 2.59ns ± 0% // speed using Fromfloat32() to convert a float32 of math.Pi to Float16 +ToFloat32pi-2 2.69ns ± 0% // speed using Float32() to convert a float16 of math.Pi to float32 +Frombits-2 0.29ns ± 5% // speed using Frombits() to cast a uint16 to Float16 + +PrecisionFromFloat32-2 0.29ns ± 1% // speed using PrecisionFromfloat32() to check for overflows, etc. +``` + +## System Requirements +* Tested on Go 1.11, 1.12, and 1.13 but it should also work with older versions. +* Tested on amd64 but it should also work on all little-endian platforms supported by Go. + +## Special Thanks +Special thanks to Kathryn Long (starkat99) for creating [half-rs](https://github.com/starkat99/half-rs), a very nice rust implementation of float16. + +## License +Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker + +Licensed under [MIT License](LICENSE) diff --git a/vendor/github.com/x448/float16/float16.go b/vendor/github.com/x448/float16/float16.go new file mode 100644 index 0000000000..1a0e6dad00 --- /dev/null +++ b/vendor/github.com/x448/float16/float16.go @@ -0,0 +1,302 @@ +// Copyright 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker +// +// Special thanks to Kathryn Long for her Rust implementation +// of float16 at github.com/starkat99/half-rs (MIT license) + +package float16 + +import ( + "math" + "strconv" +) + +// Float16 represents IEEE 754 half-precision floating-point numbers (binary16). +type Float16 uint16 + +// Precision indicates whether the conversion to Float16 is +// exact, subnormal without dropped bits, inexact, underflow, or overflow. +type Precision int + +const ( + + // PrecisionExact is for non-subnormals that don't drop bits during conversion. + // All of these can round-trip. Should always convert to float16. + PrecisionExact Precision = iota + + // PrecisionUnknown is for subnormals that don't drop bits during conversion but + // not all of these can round-trip so precision is unknown without more effort. + // Only 2046 of these can round-trip and the rest cannot round-trip. + PrecisionUnknown + + // PrecisionInexact is for dropped significand bits and cannot round-trip. + // Some of these are subnormals. Cannot round-trip float32->float16->float32. + PrecisionInexact + + // PrecisionUnderflow is for Underflows. Cannot round-trip float32->float16->float32. + PrecisionUnderflow + + // PrecisionOverflow is for Overflows. Cannot round-trip float32->float16->float32. + PrecisionOverflow +) + +// PrecisionFromfloat32 returns Precision without performing +// the conversion. Conversions from both Infinity and NaN +// values will always report PrecisionExact even if NaN payload +// or NaN-Quiet-Bit is lost. This function is kept simple to +// allow inlining and run < 0.5 ns/op, to serve as a fast filter. +func PrecisionFromfloat32(f32 float32) Precision { + u32 := math.Float32bits(f32) + + if u32 == 0 || u32 == 0x80000000 { + // +- zero will always be exact conversion + return PrecisionExact + } + + const COEFMASK uint32 = 0x7fffff // 23 least significant bits + const EXPSHIFT uint32 = 23 + const EXPBIAS uint32 = 127 + const EXPMASK uint32 = uint32(0xff) << EXPSHIFT + const DROPMASK uint32 = COEFMASK >> 10 + + exp := int32(((u32 & EXPMASK) >> EXPSHIFT) - EXPBIAS) + coef := u32 & COEFMASK + + if exp == 128 { + // +- infinity or NaN + // apps may want to do extra checks for NaN separately + return PrecisionExact + } + + // https://en.wikipedia.org/wiki/Half-precision_floating-point_format says, + // "Decimals between 2^−24 (minimum positive subnormal) and 2^−14 (maximum subnormal): fixed interval 2^−24" + if exp < -24 { + return PrecisionUnderflow + } + if exp > 15 { + return PrecisionOverflow + } + if (coef & DROPMASK) != uint32(0) { + // these include subnormals and non-subnormals that dropped bits + return PrecisionInexact + } + + if exp < -14 { + // Subnormals. Caller may want to test these further. + // There are 2046 subnormals that can successfully round-trip f32->f16->f32 + // and 20 of those 2046 have 32-bit input coef == 0. + // RFC 7049 and 7049bis Draft 12 don't precisely define "preserves value" + // so some protocols and libraries will choose to handle subnormals differently + // when deciding to encode them to CBOR float32 vs float16. + return PrecisionUnknown + } + + return PrecisionExact +} + +// Frombits returns the float16 number corresponding to the IEEE 754 binary16 +// representation u16, with the sign bit of u16 and the result in the same bit +// position. Frombits(Bits(x)) == x. +func Frombits(u16 uint16) Float16 { + return Float16(u16) +} + +// Fromfloat32 returns a Float16 value converted from f32. Conversion uses +// IEEE default rounding (nearest int, with ties to even). +func Fromfloat32(f32 float32) Float16 { + return Float16(f32bitsToF16bits(math.Float32bits(f32))) +} + +// ErrInvalidNaNValue indicates a NaN was not received. +const ErrInvalidNaNValue = float16Error("float16: invalid NaN value, expected IEEE 754 NaN") + +type float16Error string + +func (e float16Error) Error() string { return string(e) } + +// FromNaN32ps converts nan to IEEE binary16 NaN while preserving both +// signaling and payload. Unlike Fromfloat32(), which can only return +// qNaN because it sets quiet bit = 1, this can return both sNaN and qNaN. +// If the result is infinity (sNaN with empty payload), then the +// lowest bit of payload is set to make the result a NaN. +// Returns ErrInvalidNaNValue and 0x7c01 (sNaN) if nan isn't IEEE 754 NaN. +// This function was kept simple to be able to inline. +func FromNaN32ps(nan float32) (Float16, error) { + const SNAN = Float16(uint16(0x7c01)) // signalling NaN + + u32 := math.Float32bits(nan) + sign := u32 & 0x80000000 + exp := u32 & 0x7f800000 + coef := u32 & 0x007fffff + + if (exp != 0x7f800000) || (coef == 0) { + return SNAN, ErrInvalidNaNValue + } + + u16 := uint16((sign >> 16) | uint32(0x7c00) | (coef >> 13)) + + if (u16 & 0x03ff) == 0 { + // result became infinity, make it NaN by setting lowest bit in payload + u16 = u16 | 0x0001 + } + + return Float16(u16), nil +} + +// NaN returns a Float16 of IEEE 754 binary16 not-a-number (NaN). +// Returned NaN value 0x7e01 has all exponent bits = 1 with the +// first and last bits = 1 in the significand. This is consistent +// with Go's 64-bit math.NaN(). Canonical CBOR in RFC 7049 uses 0x7e00. +func NaN() Float16 { + return Float16(0x7e01) +} + +// Inf returns a Float16 with an infinity value with the specified sign. +// A sign >= returns positive infinity. +// A sign < 0 returns negative infinity. +func Inf(sign int) Float16 { + if sign >= 0 { + return Float16(0x7c00) + } + return Float16(0x8000 | 0x7c00) +} + +// Float32 returns a float32 converted from f (Float16). +// This is a lossless conversion. +func (f Float16) Float32() float32 { + u32 := f16bitsToF32bits(uint16(f)) + return math.Float32frombits(u32) +} + +// Bits returns the IEEE 754 binary16 representation of f, with the sign bit +// of f and the result in the same bit position. Bits(Frombits(x)) == x. +func (f Float16) Bits() uint16 { + return uint16(f) +} + +// IsNaN reports whether f is an IEEE 754 binary16 “not-a-number” value. +func (f Float16) IsNaN() bool { + return (f&0x7c00 == 0x7c00) && (f&0x03ff != 0) +} + +// IsQuietNaN reports whether f is a quiet (non-signaling) IEEE 754 binary16 +// “not-a-number” value. +func (f Float16) IsQuietNaN() bool { + return (f&0x7c00 == 0x7c00) && (f&0x03ff != 0) && (f&0x0200 != 0) +} + +// IsInf reports whether f is an infinity (inf). +// A sign > 0 reports whether f is positive inf. +// A sign < 0 reports whether f is negative inf. +// A sign == 0 reports whether f is either inf. +func (f Float16) IsInf(sign int) bool { + return ((f == 0x7c00) && sign >= 0) || + (f == 0xfc00 && sign <= 0) +} + +// IsFinite returns true if f is neither infinite nor NaN. +func (f Float16) IsFinite() bool { + return (uint16(f) & uint16(0x7c00)) != uint16(0x7c00) +} + +// IsNormal returns true if f is neither zero, infinite, subnormal, or NaN. +func (f Float16) IsNormal() bool { + exp := uint16(f) & uint16(0x7c00) + return (exp != uint16(0x7c00)) && (exp != 0) +} + +// Signbit reports whether f is negative or negative zero. +func (f Float16) Signbit() bool { + return (uint16(f) & uint16(0x8000)) != 0 +} + +// String satisfies the fmt.Stringer interface. +func (f Float16) String() string { + return strconv.FormatFloat(float64(f.Float32()), 'f', -1, 32) +} + +// f16bitsToF32bits returns uint32 (float32 bits) converted from specified uint16. +func f16bitsToF32bits(in uint16) uint32 { + // All 65536 conversions with this were confirmed to be correct + // by Montgomery Edwards⁴⁴⁸ (github.com/x448). + + sign := uint32(in&0x8000) << 16 // sign for 32-bit + exp := uint32(in&0x7c00) >> 10 // exponenent for 16-bit + coef := uint32(in&0x03ff) << 13 // significand for 32-bit + + if exp == 0x1f { + if coef == 0 { + // infinity + return sign | 0x7f800000 | coef + } + // NaN + return sign | 0x7fc00000 | coef + } + + if exp == 0 { + if coef == 0 { + // zero + return sign + } + + // normalize subnormal numbers + exp++ + for coef&0x7f800000 == 0 { + coef <<= 1 + exp-- + } + coef &= 0x007fffff + } + + return sign | ((exp + (0x7f - 0xf)) << 23) | coef +} + +// f32bitsToF16bits returns uint16 (Float16 bits) converted from the specified float32. +// Conversion rounds to nearest integer with ties to even. +func f32bitsToF16bits(u32 uint32) uint16 { + // Translated from Rust to Go by Montgomery Edwards⁴⁴⁸ (github.com/x448). + // All 4294967296 conversions with this were confirmed to be correct by x448. + // Original Rust implementation is by Kathryn Long (github.com/starkat99) with MIT license. + + sign := u32 & 0x80000000 + exp := u32 & 0x7f800000 + coef := u32 & 0x007fffff + + if exp == 0x7f800000 { + // NaN or Infinity + nanBit := uint32(0) + if coef != 0 { + nanBit = uint32(0x0200) + } + return uint16((sign >> 16) | uint32(0x7c00) | nanBit | (coef >> 13)) + } + + halfSign := sign >> 16 + + unbiasedExp := int32(exp>>23) - 127 + halfExp := unbiasedExp + 15 + + if halfExp >= 0x1f { + return uint16(halfSign | uint32(0x7c00)) + } + + if halfExp <= 0 { + if 14-halfExp > 24 { + return uint16(halfSign) + } + coef := coef | uint32(0x00800000) + halfCoef := coef >> uint32(14-halfExp) + roundBit := uint32(1) << uint32(13-halfExp) + if (coef&roundBit) != 0 && (coef&(3*roundBit-1)) != 0 { + halfCoef++ + } + return uint16(halfSign | halfCoef) + } + + uHalfExp := uint32(halfExp) << 10 + halfCoef := coef >> 13 + roundBit := uint32(0x00001000) + if (coef&roundBit) != 0 && (coef&(3*roundBit-1)) != 0 { + return uint16((halfSign | uHalfExp | halfCoef) + 1) + } + return uint16(halfSign | uHalfExp | halfCoef) +} diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/README.md b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/README.md new file mode 100644 index 0000000000..87b842c5d1 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/README.md @@ -0,0 +1,3 @@ +# Semconv v1.17.0 + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/semconv/v1.17.0)](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.17.0) diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/doc.go b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/doc.go new file mode 100644 index 0000000000..e087c9c04d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/doc.go @@ -0,0 +1,9 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package semconv implements OpenTelemetry semantic conventions. +// +// OpenTelemetry semantic conventions are agreed standardized naming +// patterns for OpenTelemetry things. This package represents the conventions +// as of the v1.17.0 version of the OpenTelemetry specification. +package semconv // import "go.opentelemetry.io/otel/semconv/v1.17.0" diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/event.go b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/event.go new file mode 100644 index 0000000000..c7b804bbe2 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/event.go @@ -0,0 +1,188 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.17.0" + +import "go.opentelemetry.io/otel/attribute" + +// This semantic convention defines the attributes used to represent a feature +// flag evaluation as an event. +const ( + // FeatureFlagKeyKey is the attribute Key conforming to the + // "feature_flag.key" semantic conventions. It represents the unique + // identifier of the feature flag. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'logo-color' + FeatureFlagKeyKey = attribute.Key("feature_flag.key") + + // FeatureFlagProviderNameKey is the attribute Key conforming to the + // "feature_flag.provider_name" semantic conventions. It represents the + // name of the service provider that performs the flag evaluation. + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'Flag Manager' + FeatureFlagProviderNameKey = attribute.Key("feature_flag.provider_name") + + // FeatureFlagVariantKey is the attribute Key conforming to the + // "feature_flag.variant" semantic conventions. It represents the sHOULD be + // a semantic identifier for a value. If one is unavailable, a stringified + // version of the value can be used. + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'red', 'true', 'on' + // Note: A semantic identifier, commonly referred to as a variant, provides + // a means + // for referring to a value without including the value itself. This can + // provide additional context for understanding the meaning behind a value. + // For example, the variant `red` maybe be used for the value `#c05543`. + // + // A stringified version of the value can be used in situations where a + // semantic identifier is unavailable. String representation of the value + // should be determined by the implementer. + FeatureFlagVariantKey = attribute.Key("feature_flag.variant") +) + +// FeatureFlagKey returns an attribute KeyValue conforming to the +// "feature_flag.key" semantic conventions. It represents the unique identifier +// of the feature flag. +func FeatureFlagKey(val string) attribute.KeyValue { + return FeatureFlagKeyKey.String(val) +} + +// FeatureFlagProviderName returns an attribute KeyValue conforming to the +// "feature_flag.provider_name" semantic conventions. It represents the name of +// the service provider that performs the flag evaluation. +func FeatureFlagProviderName(val string) attribute.KeyValue { + return FeatureFlagProviderNameKey.String(val) +} + +// FeatureFlagVariant returns an attribute KeyValue conforming to the +// "feature_flag.variant" semantic conventions. It represents the sHOULD be a +// semantic identifier for a value. If one is unavailable, a stringified +// version of the value can be used. +func FeatureFlagVariant(val string) attribute.KeyValue { + return FeatureFlagVariantKey.String(val) +} + +// RPC received/sent message. +const ( + // MessageTypeKey is the attribute Key conforming to the "message.type" + // semantic conventions. It represents the whether this is a received or + // sent message. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessageTypeKey = attribute.Key("message.type") + + // MessageIDKey is the attribute Key conforming to the "message.id" + // semantic conventions. It represents the mUST be calculated as two + // different counters starting from `1` one for sent messages and one for + // received message. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Note: This way we guarantee that the values will be consistent between + // different implementations. + MessageIDKey = attribute.Key("message.id") + + // MessageCompressedSizeKey is the attribute Key conforming to the + // "message.compressed_size" semantic conventions. It represents the + // compressed size of the message in bytes. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + MessageCompressedSizeKey = attribute.Key("message.compressed_size") + + // MessageUncompressedSizeKey is the attribute Key conforming to the + // "message.uncompressed_size" semantic conventions. It represents the + // uncompressed size of the message in bytes. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + MessageUncompressedSizeKey = attribute.Key("message.uncompressed_size") +) + +var ( + // sent + MessageTypeSent = MessageTypeKey.String("SENT") + // received + MessageTypeReceived = MessageTypeKey.String("RECEIVED") +) + +// MessageID returns an attribute KeyValue conforming to the "message.id" +// semantic conventions. It represents the mUST be calculated as two different +// counters starting from `1` one for sent messages and one for received +// message. +func MessageID(val int) attribute.KeyValue { + return MessageIDKey.Int(val) +} + +// MessageCompressedSize returns an attribute KeyValue conforming to the +// "message.compressed_size" semantic conventions. It represents the compressed +// size of the message in bytes. +func MessageCompressedSize(val int) attribute.KeyValue { + return MessageCompressedSizeKey.Int(val) +} + +// MessageUncompressedSize returns an attribute KeyValue conforming to the +// "message.uncompressed_size" semantic conventions. It represents the +// uncompressed size of the message in bytes. +func MessageUncompressedSize(val int) attribute.KeyValue { + return MessageUncompressedSizeKey.Int(val) +} + +// The attributes used to report a single exception associated with a span. +const ( + // ExceptionEscapedKey is the attribute Key conforming to the + // "exception.escaped" semantic conventions. It represents the sHOULD be + // set to true if the exception event is recorded at a point where it is + // known that the exception is escaping the scope of the span. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + // Note: An exception is considered to have escaped (or left) the scope of + // a span, + // if that span is ended while the exception is still logically "in + // flight". + // This may be actually "in flight" in some languages (e.g. if the + // exception + // is passed to a Context manager's `__exit__` method in Python) but will + // usually be caught at the point of recording the exception in most + // languages. + // + // It is usually not possible to determine at the point where an exception + // is thrown + // whether it will escape the scope of a span. + // However, it is trivial to know that an exception + // will escape, if one checks for an active exception just before ending + // the span, + // as done in the [example above](#recording-an-exception). + // + // It follows that an exception may still escape the scope of the span + // even if the `exception.escaped` attribute was not set or set to false, + // since the event might have been recorded at a time where it was not + // clear whether the exception will escape. + ExceptionEscapedKey = attribute.Key("exception.escaped") +) + +// ExceptionEscaped returns an attribute KeyValue conforming to the +// "exception.escaped" semantic conventions. It represents the sHOULD be set to +// true if the exception event is recorded at a point where it is known that +// the exception is escaping the scope of the span. +func ExceptionEscaped(val bool) attribute.KeyValue { + return ExceptionEscapedKey.Bool(val) +} diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/exception.go b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/exception.go new file mode 100644 index 0000000000..137acc67de --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/exception.go @@ -0,0 +1,9 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.17.0" + +const ( + // ExceptionEventName is the name of the Span event representing an exception. + ExceptionEventName = "exception" +) diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/http.go b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/http.go new file mode 100644 index 0000000000..d318221e59 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/http.go @@ -0,0 +1,10 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.17.0" + +// HTTP scheme attributes. +var ( + HTTPSchemeHTTP = HTTPSchemeKey.String("http") + HTTPSchemeHTTPS = HTTPSchemeKey.String("https") +) diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/resource.go b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/resource.go new file mode 100644 index 0000000000..7e365e82ce --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/resource.go @@ -0,0 +1,1999 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.17.0" + +import "go.opentelemetry.io/otel/attribute" + +// The web browser in which the application represented by the resource is +// running. The `browser.*` attributes MUST be used only for resources that +// represent applications running in a web browser (regardless of whether +// running on a mobile or desktop device). +const ( + // BrowserBrandsKey is the attribute Key conforming to the "browser.brands" + // semantic conventions. It represents the array of brand name and version + // separated by a space + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: ' Not A;Brand 99', 'Chromium 99', 'Chrome 99' + // Note: This value is intended to be taken from the [UA client hints + // API](https://wicg.github.io/ua-client-hints/#interface) + // (`navigator.userAgentData.brands`). + BrowserBrandsKey = attribute.Key("browser.brands") + + // BrowserPlatformKey is the attribute Key conforming to the + // "browser.platform" semantic conventions. It represents the platform on + // which the browser is running + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Windows', 'macOS', 'Android' + // Note: This value is intended to be taken from the [UA client hints + // API](https://wicg.github.io/ua-client-hints/#interface) + // (`navigator.userAgentData.platform`). If unavailable, the legacy + // `navigator.platform` API SHOULD NOT be used instead and this attribute + // SHOULD be left unset in order for the values to be consistent. + // The list of possible values is defined in the [W3C User-Agent Client + // Hints + // specification](https://wicg.github.io/ua-client-hints/#sec-ch-ua-platform). + // Note that some (but not all) of these values can overlap with values in + // the [`os.type` and `os.name` attributes](./os.md). However, for + // consistency, the values in the `browser.platform` attribute should + // capture the exact value that the user agent provides. + BrowserPlatformKey = attribute.Key("browser.platform") + + // BrowserMobileKey is the attribute Key conforming to the "browser.mobile" + // semantic conventions. It represents a boolean that is true if the + // browser is running on a mobile device + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + // Note: This value is intended to be taken from the [UA client hints + // API](https://wicg.github.io/ua-client-hints/#interface) + // (`navigator.userAgentData.mobile`). If unavailable, this attribute + // SHOULD be left unset. + BrowserMobileKey = attribute.Key("browser.mobile") + + // BrowserUserAgentKey is the attribute Key conforming to the + // "browser.user_agent" semantic conventions. It represents the full + // user-agent string provided by the browser + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) + // AppleWebKit/537.36 (KHTML, ' + // 'like Gecko) Chrome/95.0.4638.54 Safari/537.36' + // Note: The user-agent value SHOULD be provided only from browsers that do + // not have a mechanism to retrieve brands and platform individually from + // the User-Agent Client Hints API. To retrieve the value, the legacy + // `navigator.userAgent` API can be used. + BrowserUserAgentKey = attribute.Key("browser.user_agent") + + // BrowserLanguageKey is the attribute Key conforming to the + // "browser.language" semantic conventions. It represents the preferred + // language of the user using the browser + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'en', 'en-US', 'fr', 'fr-FR' + // Note: This value is intended to be taken from the Navigator API + // `navigator.language`. + BrowserLanguageKey = attribute.Key("browser.language") +) + +// BrowserBrands returns an attribute KeyValue conforming to the +// "browser.brands" semantic conventions. It represents the array of brand name +// and version separated by a space +func BrowserBrands(val ...string) attribute.KeyValue { + return BrowserBrandsKey.StringSlice(val) +} + +// BrowserPlatform returns an attribute KeyValue conforming to the +// "browser.platform" semantic conventions. It represents the platform on which +// the browser is running +func BrowserPlatform(val string) attribute.KeyValue { + return BrowserPlatformKey.String(val) +} + +// BrowserMobile returns an attribute KeyValue conforming to the +// "browser.mobile" semantic conventions. It represents a boolean that is true +// if the browser is running on a mobile device +func BrowserMobile(val bool) attribute.KeyValue { + return BrowserMobileKey.Bool(val) +} + +// BrowserUserAgent returns an attribute KeyValue conforming to the +// "browser.user_agent" semantic conventions. It represents the full user-agent +// string provided by the browser +func BrowserUserAgent(val string) attribute.KeyValue { + return BrowserUserAgentKey.String(val) +} + +// BrowserLanguage returns an attribute KeyValue conforming to the +// "browser.language" semantic conventions. It represents the preferred +// language of the user using the browser +func BrowserLanguage(val string) attribute.KeyValue { + return BrowserLanguageKey.String(val) +} + +// A cloud environment (e.g. GCP, Azure, AWS) +const ( + // CloudProviderKey is the attribute Key conforming to the "cloud.provider" + // semantic conventions. It represents the name of the cloud provider. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + CloudProviderKey = attribute.Key("cloud.provider") + + // CloudAccountIDKey is the attribute Key conforming to the + // "cloud.account.id" semantic conventions. It represents the cloud account + // ID the resource is assigned to. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '111111111111', 'opentelemetry' + CloudAccountIDKey = attribute.Key("cloud.account.id") + + // CloudRegionKey is the attribute Key conforming to the "cloud.region" + // semantic conventions. It represents the geographical region the resource + // is running. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'us-central1', 'us-east-1' + // Note: Refer to your provider's docs to see the available regions, for + // example [Alibaba Cloud + // regions](https://www.alibabacloud.com/help/doc-detail/40654.htm), [AWS + // regions](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/), + // [Azure + // regions](https://azure.microsoft.com/en-us/global-infrastructure/geographies/), + // [Google Cloud regions](https://cloud.google.com/about/locations), or + // [Tencent Cloud + // regions](https://intl.cloud.tencent.com/document/product/213/6091). + CloudRegionKey = attribute.Key("cloud.region") + + // CloudAvailabilityZoneKey is the attribute Key conforming to the + // "cloud.availability_zone" semantic conventions. It represents the cloud + // regions often have multiple, isolated locations known as zones to + // increase availability. Availability zone represents the zone where the + // resource is running. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'us-east-1c' + // Note: Availability zones are called "zones" on Alibaba Cloud and Google + // Cloud. + CloudAvailabilityZoneKey = attribute.Key("cloud.availability_zone") + + // CloudPlatformKey is the attribute Key conforming to the "cloud.platform" + // semantic conventions. It represents the cloud platform in use. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: The prefix of the service SHOULD match the one specified in + // `cloud.provider`. + CloudPlatformKey = attribute.Key("cloud.platform") +) + +var ( + // Alibaba Cloud + CloudProviderAlibabaCloud = CloudProviderKey.String("alibaba_cloud") + // Amazon Web Services + CloudProviderAWS = CloudProviderKey.String("aws") + // Microsoft Azure + CloudProviderAzure = CloudProviderKey.String("azure") + // Google Cloud Platform + CloudProviderGCP = CloudProviderKey.String("gcp") + // IBM Cloud + CloudProviderIbmCloud = CloudProviderKey.String("ibm_cloud") + // Tencent Cloud + CloudProviderTencentCloud = CloudProviderKey.String("tencent_cloud") +) + +var ( + // Alibaba Cloud Elastic Compute Service + CloudPlatformAlibabaCloudECS = CloudPlatformKey.String("alibaba_cloud_ecs") + // Alibaba Cloud Function Compute + CloudPlatformAlibabaCloudFc = CloudPlatformKey.String("alibaba_cloud_fc") + // Red Hat OpenShift on Alibaba Cloud + CloudPlatformAlibabaCloudOpenshift = CloudPlatformKey.String("alibaba_cloud_openshift") + // AWS Elastic Compute Cloud + CloudPlatformAWSEC2 = CloudPlatformKey.String("aws_ec2") + // AWS Elastic Container Service + CloudPlatformAWSECS = CloudPlatformKey.String("aws_ecs") + // AWS Elastic Kubernetes Service + CloudPlatformAWSEKS = CloudPlatformKey.String("aws_eks") + // AWS Lambda + CloudPlatformAWSLambda = CloudPlatformKey.String("aws_lambda") + // AWS Elastic Beanstalk + CloudPlatformAWSElasticBeanstalk = CloudPlatformKey.String("aws_elastic_beanstalk") + // AWS App Runner + CloudPlatformAWSAppRunner = CloudPlatformKey.String("aws_app_runner") + // Red Hat OpenShift on AWS (ROSA) + CloudPlatformAWSOpenshift = CloudPlatformKey.String("aws_openshift") + // Azure Virtual Machines + CloudPlatformAzureVM = CloudPlatformKey.String("azure_vm") + // Azure Container Instances + CloudPlatformAzureContainerInstances = CloudPlatformKey.String("azure_container_instances") + // Azure Kubernetes Service + CloudPlatformAzureAKS = CloudPlatformKey.String("azure_aks") + // Azure Functions + CloudPlatformAzureFunctions = CloudPlatformKey.String("azure_functions") + // Azure App Service + CloudPlatformAzureAppService = CloudPlatformKey.String("azure_app_service") + // Azure Red Hat OpenShift + CloudPlatformAzureOpenshift = CloudPlatformKey.String("azure_openshift") + // Google Cloud Compute Engine (GCE) + CloudPlatformGCPComputeEngine = CloudPlatformKey.String("gcp_compute_engine") + // Google Cloud Run + CloudPlatformGCPCloudRun = CloudPlatformKey.String("gcp_cloud_run") + // Google Cloud Kubernetes Engine (GKE) + CloudPlatformGCPKubernetesEngine = CloudPlatformKey.String("gcp_kubernetes_engine") + // Google Cloud Functions (GCF) + CloudPlatformGCPCloudFunctions = CloudPlatformKey.String("gcp_cloud_functions") + // Google Cloud App Engine (GAE) + CloudPlatformGCPAppEngine = CloudPlatformKey.String("gcp_app_engine") + // Red Hat OpenShift on Google Cloud + CloudPlatformGoogleCloudOpenshift = CloudPlatformKey.String("google_cloud_openshift") + // Red Hat OpenShift on IBM Cloud + CloudPlatformIbmCloudOpenshift = CloudPlatformKey.String("ibm_cloud_openshift") + // Tencent Cloud Cloud Virtual Machine (CVM) + CloudPlatformTencentCloudCvm = CloudPlatformKey.String("tencent_cloud_cvm") + // Tencent Cloud Elastic Kubernetes Service (EKS) + CloudPlatformTencentCloudEKS = CloudPlatformKey.String("tencent_cloud_eks") + // Tencent Cloud Serverless Cloud Function (SCF) + CloudPlatformTencentCloudScf = CloudPlatformKey.String("tencent_cloud_scf") +) + +// CloudAccountID returns an attribute KeyValue conforming to the +// "cloud.account.id" semantic conventions. It represents the cloud account ID +// the resource is assigned to. +func CloudAccountID(val string) attribute.KeyValue { + return CloudAccountIDKey.String(val) +} + +// CloudRegion returns an attribute KeyValue conforming to the +// "cloud.region" semantic conventions. It represents the geographical region +// the resource is running. +func CloudRegion(val string) attribute.KeyValue { + return CloudRegionKey.String(val) +} + +// CloudAvailabilityZone returns an attribute KeyValue conforming to the +// "cloud.availability_zone" semantic conventions. It represents the cloud +// regions often have multiple, isolated locations known as zones to increase +// availability. Availability zone represents the zone where the resource is +// running. +func CloudAvailabilityZone(val string) attribute.KeyValue { + return CloudAvailabilityZoneKey.String(val) +} + +// Resources used by AWS Elastic Container Service (ECS). +const ( + // AWSECSContainerARNKey is the attribute Key conforming to the + // "aws.ecs.container.arn" semantic conventions. It represents the Amazon + // Resource Name (ARN) of an [ECS container + // instance](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_instances.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: + // 'arn:aws:ecs:us-west-1:123456789123:container/32624152-9086-4f0e-acae-1a75b14fe4d9' + AWSECSContainerARNKey = attribute.Key("aws.ecs.container.arn") + + // AWSECSClusterARNKey is the attribute Key conforming to the + // "aws.ecs.cluster.arn" semantic conventions. It represents the ARN of an + // [ECS + // cluster](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/clusters.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster' + AWSECSClusterARNKey = attribute.Key("aws.ecs.cluster.arn") + + // AWSECSLaunchtypeKey is the attribute Key conforming to the + // "aws.ecs.launchtype" semantic conventions. It represents the [launch + // type](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/launch_types.html) + // for an ECS task. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + AWSECSLaunchtypeKey = attribute.Key("aws.ecs.launchtype") + + // AWSECSTaskARNKey is the attribute Key conforming to the + // "aws.ecs.task.arn" semantic conventions. It represents the ARN of an + // [ECS task + // definition](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: + // 'arn:aws:ecs:us-west-1:123456789123:task/10838bed-421f-43ef-870a-f43feacbbb5b' + AWSECSTaskARNKey = attribute.Key("aws.ecs.task.arn") + + // AWSECSTaskFamilyKey is the attribute Key conforming to the + // "aws.ecs.task.family" semantic conventions. It represents the task + // definition family this task definition is a member of. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-family' + AWSECSTaskFamilyKey = attribute.Key("aws.ecs.task.family") + + // AWSECSTaskRevisionKey is the attribute Key conforming to the + // "aws.ecs.task.revision" semantic conventions. It represents the revision + // for this task definition. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '8', '26' + AWSECSTaskRevisionKey = attribute.Key("aws.ecs.task.revision") +) + +var ( + // ec2 + AWSECSLaunchtypeEC2 = AWSECSLaunchtypeKey.String("ec2") + // fargate + AWSECSLaunchtypeFargate = AWSECSLaunchtypeKey.String("fargate") +) + +// AWSECSContainerARN returns an attribute KeyValue conforming to the +// "aws.ecs.container.arn" semantic conventions. It represents the Amazon +// Resource Name (ARN) of an [ECS container +// instance](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_instances.html). +func AWSECSContainerARN(val string) attribute.KeyValue { + return AWSECSContainerARNKey.String(val) +} + +// AWSECSClusterARN returns an attribute KeyValue conforming to the +// "aws.ecs.cluster.arn" semantic conventions. It represents the ARN of an [ECS +// cluster](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/clusters.html). +func AWSECSClusterARN(val string) attribute.KeyValue { + return AWSECSClusterARNKey.String(val) +} + +// AWSECSTaskARN returns an attribute KeyValue conforming to the +// "aws.ecs.task.arn" semantic conventions. It represents the ARN of an [ECS +// task +// definition](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html). +func AWSECSTaskARN(val string) attribute.KeyValue { + return AWSECSTaskARNKey.String(val) +} + +// AWSECSTaskFamily returns an attribute KeyValue conforming to the +// "aws.ecs.task.family" semantic conventions. It represents the task +// definition family this task definition is a member of. +func AWSECSTaskFamily(val string) attribute.KeyValue { + return AWSECSTaskFamilyKey.String(val) +} + +// AWSECSTaskRevision returns an attribute KeyValue conforming to the +// "aws.ecs.task.revision" semantic conventions. It represents the revision for +// this task definition. +func AWSECSTaskRevision(val string) attribute.KeyValue { + return AWSECSTaskRevisionKey.String(val) +} + +// Resources used by AWS Elastic Kubernetes Service (EKS). +const ( + // AWSEKSClusterARNKey is the attribute Key conforming to the + // "aws.eks.cluster.arn" semantic conventions. It represents the ARN of an + // EKS cluster. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster' + AWSEKSClusterARNKey = attribute.Key("aws.eks.cluster.arn") +) + +// AWSEKSClusterARN returns an attribute KeyValue conforming to the +// "aws.eks.cluster.arn" semantic conventions. It represents the ARN of an EKS +// cluster. +func AWSEKSClusterARN(val string) attribute.KeyValue { + return AWSEKSClusterARNKey.String(val) +} + +// Resources specific to Amazon Web Services. +const ( + // AWSLogGroupNamesKey is the attribute Key conforming to the + // "aws.log.group.names" semantic conventions. It represents the name(s) of + // the AWS log group(s) an application is writing to. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '/aws/lambda/my-function', 'opentelemetry-service' + // Note: Multiple log groups must be supported for cases like + // multi-container applications, where a single application has sidecar + // containers, and each write to their own log group. + AWSLogGroupNamesKey = attribute.Key("aws.log.group.names") + + // AWSLogGroupARNsKey is the attribute Key conforming to the + // "aws.log.group.arns" semantic conventions. It represents the Amazon + // Resource Name(s) (ARN) of the AWS log group(s). + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: + // 'arn:aws:logs:us-west-1:123456789012:log-group:/aws/my/group:*' + // Note: See the [log group ARN format + // documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-access-control-overview-cwl.html#CWL_ARN_Format). + AWSLogGroupARNsKey = attribute.Key("aws.log.group.arns") + + // AWSLogStreamNamesKey is the attribute Key conforming to the + // "aws.log.stream.names" semantic conventions. It represents the name(s) + // of the AWS log stream(s) an application is writing to. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'logs/main/10838bed-421f-43ef-870a-f43feacbbb5b' + AWSLogStreamNamesKey = attribute.Key("aws.log.stream.names") + + // AWSLogStreamARNsKey is the attribute Key conforming to the + // "aws.log.stream.arns" semantic conventions. It represents the ARN(s) of + // the AWS log stream(s). + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: + // 'arn:aws:logs:us-west-1:123456789012:log-group:/aws/my/group:log-stream:logs/main/10838bed-421f-43ef-870a-f43feacbbb5b' + // Note: See the [log stream ARN format + // documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-access-control-overview-cwl.html#CWL_ARN_Format). + // One log group can contain several log streams, so these ARNs necessarily + // identify both a log group and a log stream. + AWSLogStreamARNsKey = attribute.Key("aws.log.stream.arns") +) + +// AWSLogGroupNames returns an attribute KeyValue conforming to the +// "aws.log.group.names" semantic conventions. It represents the name(s) of the +// AWS log group(s) an application is writing to. +func AWSLogGroupNames(val ...string) attribute.KeyValue { + return AWSLogGroupNamesKey.StringSlice(val) +} + +// AWSLogGroupARNs returns an attribute KeyValue conforming to the +// "aws.log.group.arns" semantic conventions. It represents the Amazon Resource +// Name(s) (ARN) of the AWS log group(s). +func AWSLogGroupARNs(val ...string) attribute.KeyValue { + return AWSLogGroupARNsKey.StringSlice(val) +} + +// AWSLogStreamNames returns an attribute KeyValue conforming to the +// "aws.log.stream.names" semantic conventions. It represents the name(s) of +// the AWS log stream(s) an application is writing to. +func AWSLogStreamNames(val ...string) attribute.KeyValue { + return AWSLogStreamNamesKey.StringSlice(val) +} + +// AWSLogStreamARNs returns an attribute KeyValue conforming to the +// "aws.log.stream.arns" semantic conventions. It represents the ARN(s) of the +// AWS log stream(s). +func AWSLogStreamARNs(val ...string) attribute.KeyValue { + return AWSLogStreamARNsKey.StringSlice(val) +} + +// A container instance. +const ( + // ContainerNameKey is the attribute Key conforming to the "container.name" + // semantic conventions. It represents the container name used by container + // runtime. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-autoconf' + ContainerNameKey = attribute.Key("container.name") + + // ContainerIDKey is the attribute Key conforming to the "container.id" + // semantic conventions. It represents the container ID. Usually a UUID, as + // for example used to [identify Docker + // containers](https://docs.docker.com/engine/reference/run/#container-identification). + // The UUID might be abbreviated. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'a3bf90e006b2' + ContainerIDKey = attribute.Key("container.id") + + // ContainerRuntimeKey is the attribute Key conforming to the + // "container.runtime" semantic conventions. It represents the container + // runtime managing this container. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'docker', 'containerd', 'rkt' + ContainerRuntimeKey = attribute.Key("container.runtime") + + // ContainerImageNameKey is the attribute Key conforming to the + // "container.image.name" semantic conventions. It represents the name of + // the image the container was built on. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'gcr.io/opentelemetry/operator' + ContainerImageNameKey = attribute.Key("container.image.name") + + // ContainerImageTagKey is the attribute Key conforming to the + // "container.image.tag" semantic conventions. It represents the container + // image tag. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '0.1' + ContainerImageTagKey = attribute.Key("container.image.tag") +) + +// ContainerName returns an attribute KeyValue conforming to the +// "container.name" semantic conventions. It represents the container name used +// by container runtime. +func ContainerName(val string) attribute.KeyValue { + return ContainerNameKey.String(val) +} + +// ContainerID returns an attribute KeyValue conforming to the +// "container.id" semantic conventions. It represents the container ID. Usually +// a UUID, as for example used to [identify Docker +// containers](https://docs.docker.com/engine/reference/run/#container-identification). +// The UUID might be abbreviated. +func ContainerID(val string) attribute.KeyValue { + return ContainerIDKey.String(val) +} + +// ContainerRuntime returns an attribute KeyValue conforming to the +// "container.runtime" semantic conventions. It represents the container +// runtime managing this container. +func ContainerRuntime(val string) attribute.KeyValue { + return ContainerRuntimeKey.String(val) +} + +// ContainerImageName returns an attribute KeyValue conforming to the +// "container.image.name" semantic conventions. It represents the name of the +// image the container was built on. +func ContainerImageName(val string) attribute.KeyValue { + return ContainerImageNameKey.String(val) +} + +// ContainerImageTag returns an attribute KeyValue conforming to the +// "container.image.tag" semantic conventions. It represents the container +// image tag. +func ContainerImageTag(val string) attribute.KeyValue { + return ContainerImageTagKey.String(val) +} + +// The software deployment. +const ( + // DeploymentEnvironmentKey is the attribute Key conforming to the + // "deployment.environment" semantic conventions. It represents the name of + // the [deployment + // environment](https://en.wikipedia.org/wiki/Deployment_environment) (aka + // deployment tier). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'staging', 'production' + DeploymentEnvironmentKey = attribute.Key("deployment.environment") +) + +// DeploymentEnvironment returns an attribute KeyValue conforming to the +// "deployment.environment" semantic conventions. It represents the name of the +// [deployment +// environment](https://en.wikipedia.org/wiki/Deployment_environment) (aka +// deployment tier). +func DeploymentEnvironment(val string) attribute.KeyValue { + return DeploymentEnvironmentKey.String(val) +} + +// The device on which the process represented by this resource is running. +const ( + // DeviceIDKey is the attribute Key conforming to the "device.id" semantic + // conventions. It represents a unique identifier representing the device + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2ab2916d-a51f-4ac8-80ee-45ac31a28092' + // Note: The device identifier MUST only be defined using the values + // outlined below. This value is not an advertising identifier and MUST NOT + // be used as such. On iOS (Swift or Objective-C), this value MUST be equal + // to the [vendor + // identifier](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor). + // On Android (Java or Kotlin), this value MUST be equal to the Firebase + // Installation ID or a globally unique UUID which is persisted across + // sessions in your application. More information can be found + // [here](https://developer.android.com/training/articles/user-data-ids) on + // best practices and exact implementation details. Caution should be taken + // when storing personal data or anything which can identify a user. GDPR + // and data protection laws may apply, ensure you do your own due + // diligence. + DeviceIDKey = attribute.Key("device.id") + + // DeviceModelIdentifierKey is the attribute Key conforming to the + // "device.model.identifier" semantic conventions. It represents the model + // identifier for the device + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'iPhone3,4', 'SM-G920F' + // Note: It's recommended this value represents a machine readable version + // of the model identifier rather than the market or consumer-friendly name + // of the device. + DeviceModelIdentifierKey = attribute.Key("device.model.identifier") + + // DeviceModelNameKey is the attribute Key conforming to the + // "device.model.name" semantic conventions. It represents the marketing + // name for the device model + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'iPhone 6s Plus', 'Samsung Galaxy S6' + // Note: It's recommended this value represents a human readable version of + // the device model rather than a machine readable alternative. + DeviceModelNameKey = attribute.Key("device.model.name") + + // DeviceManufacturerKey is the attribute Key conforming to the + // "device.manufacturer" semantic conventions. It represents the name of + // the device manufacturer + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Apple', 'Samsung' + // Note: The Android OS provides this field via + // [Build](https://developer.android.com/reference/android/os/Build#MANUFACTURER). + // iOS apps SHOULD hardcode the value `Apple`. + DeviceManufacturerKey = attribute.Key("device.manufacturer") +) + +// DeviceID returns an attribute KeyValue conforming to the "device.id" +// semantic conventions. It represents a unique identifier representing the +// device +func DeviceID(val string) attribute.KeyValue { + return DeviceIDKey.String(val) +} + +// DeviceModelIdentifier returns an attribute KeyValue conforming to the +// "device.model.identifier" semantic conventions. It represents the model +// identifier for the device +func DeviceModelIdentifier(val string) attribute.KeyValue { + return DeviceModelIdentifierKey.String(val) +} + +// DeviceModelName returns an attribute KeyValue conforming to the +// "device.model.name" semantic conventions. It represents the marketing name +// for the device model +func DeviceModelName(val string) attribute.KeyValue { + return DeviceModelNameKey.String(val) +} + +// DeviceManufacturer returns an attribute KeyValue conforming to the +// "device.manufacturer" semantic conventions. It represents the name of the +// device manufacturer +func DeviceManufacturer(val string) attribute.KeyValue { + return DeviceManufacturerKey.String(val) +} + +// A serverless instance. +const ( + // FaaSNameKey is the attribute Key conforming to the "faas.name" semantic + // conventions. It represents the name of the single function that this + // runtime instance executes. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'my-function', 'myazurefunctionapp/some-function-name' + // Note: This is the name of the function as configured/deployed on the + // FaaS + // platform and is usually different from the name of the callback + // function (which may be stored in the + // [`code.namespace`/`code.function`](../../trace/semantic_conventions/span-general.md#source-code-attributes) + // span attributes). + // + // For some cloud providers, the above definition is ambiguous. The + // following + // definition of function name MUST be used for this attribute + // (and consequently the span name) for the listed cloud + // providers/products: + // + // * **Azure:** The full name `/`, i.e., function app name + // followed by a forward slash followed by the function name (this form + // can also be seen in the resource JSON for the function). + // This means that a span attribute MUST be used, as an Azure function + // app can host multiple functions that would usually share + // a TracerProvider (see also the `faas.id` attribute). + FaaSNameKey = attribute.Key("faas.name") + + // FaaSIDKey is the attribute Key conforming to the "faas.id" semantic + // conventions. It represents the unique ID of the single function that + // this runtime instance executes. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:lambda:us-west-2:123456789012:function:my-function' + // Note: On some cloud providers, it may not be possible to determine the + // full ID at startup, + // so consider setting `faas.id` as a span attribute instead. + // + // The exact value to use for `faas.id` depends on the cloud provider: + // + // * **AWS Lambda:** The function + // [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). + // Take care not to use the "invoked ARN" directly but replace any + // [alias + // suffix](https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html) + // with the resolved function version, as the same runtime instance may + // be invokable with + // multiple different aliases. + // * **GCP:** The [URI of the + // resource](https://cloud.google.com/iam/docs/full-resource-names) + // * **Azure:** The [Fully Qualified Resource + // ID](https://docs.microsoft.com/en-us/rest/api/resources/resources/get-by-id) + // of the invoked function, + // *not* the function app, having the form + // `/subscriptions//resourceGroups//providers/Microsoft.Web/sites//functions/`. + // This means that a span attribute MUST be used, as an Azure function + // app can host multiple functions that would usually share + // a TracerProvider. + FaaSIDKey = attribute.Key("faas.id") + + // FaaSVersionKey is the attribute Key conforming to the "faas.version" + // semantic conventions. It represents the immutable version of the + // function being executed. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '26', 'pinkfroid-00002' + // Note: Depending on the cloud provider and platform, use: + // + // * **AWS Lambda:** The [function + // version](https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html) + // (an integer represented as a decimal string). + // * **Google Cloud Run:** The + // [revision](https://cloud.google.com/run/docs/managing/revisions) + // (i.e., the function name plus the revision suffix). + // * **Google Cloud Functions:** The value of the + // [`K_REVISION` environment + // variable](https://cloud.google.com/functions/docs/env-var#runtime_environment_variables_set_automatically). + // * **Azure Functions:** Not applicable. Do not set this attribute. + FaaSVersionKey = attribute.Key("faas.version") + + // FaaSInstanceKey is the attribute Key conforming to the "faas.instance" + // semantic conventions. It represents the execution environment ID as a + // string, that will be potentially reused for other invocations to the + // same function/function version. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2021/06/28/[$LATEST]2f399eb14537447da05ab2a2e39309de' + // Note: * **AWS Lambda:** Use the (full) log stream name. + FaaSInstanceKey = attribute.Key("faas.instance") + + // FaaSMaxMemoryKey is the attribute Key conforming to the + // "faas.max_memory" semantic conventions. It represents the amount of + // memory available to the serverless function in MiB. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 128 + // Note: It's recommended to set this attribute since e.g. too little + // memory can easily stop a Java AWS Lambda function from working + // correctly. On AWS Lambda, the environment variable + // `AWS_LAMBDA_FUNCTION_MEMORY_SIZE` provides this information. + FaaSMaxMemoryKey = attribute.Key("faas.max_memory") +) + +// FaaSName returns an attribute KeyValue conforming to the "faas.name" +// semantic conventions. It represents the name of the single function that +// this runtime instance executes. +func FaaSName(val string) attribute.KeyValue { + return FaaSNameKey.String(val) +} + +// FaaSID returns an attribute KeyValue conforming to the "faas.id" semantic +// conventions. It represents the unique ID of the single function that this +// runtime instance executes. +func FaaSID(val string) attribute.KeyValue { + return FaaSIDKey.String(val) +} + +// FaaSVersion returns an attribute KeyValue conforming to the +// "faas.version" semantic conventions. It represents the immutable version of +// the function being executed. +func FaaSVersion(val string) attribute.KeyValue { + return FaaSVersionKey.String(val) +} + +// FaaSInstance returns an attribute KeyValue conforming to the +// "faas.instance" semantic conventions. It represents the execution +// environment ID as a string, that will be potentially reused for other +// invocations to the same function/function version. +func FaaSInstance(val string) attribute.KeyValue { + return FaaSInstanceKey.String(val) +} + +// FaaSMaxMemory returns an attribute KeyValue conforming to the +// "faas.max_memory" semantic conventions. It represents the amount of memory +// available to the serverless function in MiB. +func FaaSMaxMemory(val int) attribute.KeyValue { + return FaaSMaxMemoryKey.Int(val) +} + +// A host is defined as a general computing instance. +const ( + // HostIDKey is the attribute Key conforming to the "host.id" semantic + // conventions. It represents the unique host ID. For Cloud, this must be + // the instance_id assigned by the cloud provider. For non-containerized + // Linux systems, the `machine-id` located in `/etc/machine-id` or + // `/var/lib/dbus/machine-id` may be used. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'fdbf79e8af94cb7f9e8df36789187052' + HostIDKey = attribute.Key("host.id") + + // HostNameKey is the attribute Key conforming to the "host.name" semantic + // conventions. It represents the name of the host. On Unix systems, it may + // contain what the hostname command returns, or the fully qualified + // hostname, or another name specified by the user. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-test' + HostNameKey = attribute.Key("host.name") + + // HostTypeKey is the attribute Key conforming to the "host.type" semantic + // conventions. It represents the type of host. For Cloud, this must be the + // machine type. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'n1-standard-1' + HostTypeKey = attribute.Key("host.type") + + // HostArchKey is the attribute Key conforming to the "host.arch" semantic + // conventions. It represents the CPU architecture the host system is + // running on. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + HostArchKey = attribute.Key("host.arch") + + // HostImageNameKey is the attribute Key conforming to the + // "host.image.name" semantic conventions. It represents the name of the VM + // image or OS install the host was instantiated from. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'infra-ami-eks-worker-node-7d4ec78312', 'CentOS-8-x86_64-1905' + HostImageNameKey = attribute.Key("host.image.name") + + // HostImageIDKey is the attribute Key conforming to the "host.image.id" + // semantic conventions. It represents the vM image ID. For Cloud, this + // value is from the provider. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'ami-07b06b442921831e5' + HostImageIDKey = attribute.Key("host.image.id") + + // HostImageVersionKey is the attribute Key conforming to the + // "host.image.version" semantic conventions. It represents the version + // string of the VM image as defined in [Version + // Attributes](README.md#version-attributes). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '0.1' + HostImageVersionKey = attribute.Key("host.image.version") +) + +var ( + // AMD64 + HostArchAMD64 = HostArchKey.String("amd64") + // ARM32 + HostArchARM32 = HostArchKey.String("arm32") + // ARM64 + HostArchARM64 = HostArchKey.String("arm64") + // Itanium + HostArchIA64 = HostArchKey.String("ia64") + // 32-bit PowerPC + HostArchPPC32 = HostArchKey.String("ppc32") + // 64-bit PowerPC + HostArchPPC64 = HostArchKey.String("ppc64") + // IBM z/Architecture + HostArchS390x = HostArchKey.String("s390x") + // 32-bit x86 + HostArchX86 = HostArchKey.String("x86") +) + +// HostID returns an attribute KeyValue conforming to the "host.id" semantic +// conventions. It represents the unique host ID. For Cloud, this must be the +// instance_id assigned by the cloud provider. For non-containerized Linux +// systems, the `machine-id` located in `/etc/machine-id` or +// `/var/lib/dbus/machine-id` may be used. +func HostID(val string) attribute.KeyValue { + return HostIDKey.String(val) +} + +// HostName returns an attribute KeyValue conforming to the "host.name" +// semantic conventions. It represents the name of the host. On Unix systems, +// it may contain what the hostname command returns, or the fully qualified +// hostname, or another name specified by the user. +func HostName(val string) attribute.KeyValue { + return HostNameKey.String(val) +} + +// HostType returns an attribute KeyValue conforming to the "host.type" +// semantic conventions. It represents the type of host. For Cloud, this must +// be the machine type. +func HostType(val string) attribute.KeyValue { + return HostTypeKey.String(val) +} + +// HostImageName returns an attribute KeyValue conforming to the +// "host.image.name" semantic conventions. It represents the name of the VM +// image or OS install the host was instantiated from. +func HostImageName(val string) attribute.KeyValue { + return HostImageNameKey.String(val) +} + +// HostImageID returns an attribute KeyValue conforming to the +// "host.image.id" semantic conventions. It represents the vM image ID. For +// Cloud, this value is from the provider. +func HostImageID(val string) attribute.KeyValue { + return HostImageIDKey.String(val) +} + +// HostImageVersion returns an attribute KeyValue conforming to the +// "host.image.version" semantic conventions. It represents the version string +// of the VM image as defined in [Version +// Attributes](README.md#version-attributes). +func HostImageVersion(val string) attribute.KeyValue { + return HostImageVersionKey.String(val) +} + +// A Kubernetes Cluster. +const ( + // K8SClusterNameKey is the attribute Key conforming to the + // "k8s.cluster.name" semantic conventions. It represents the name of the + // cluster. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-cluster' + K8SClusterNameKey = attribute.Key("k8s.cluster.name") +) + +// K8SClusterName returns an attribute KeyValue conforming to the +// "k8s.cluster.name" semantic conventions. It represents the name of the +// cluster. +func K8SClusterName(val string) attribute.KeyValue { + return K8SClusterNameKey.String(val) +} + +// A Kubernetes Node object. +const ( + // K8SNodeNameKey is the attribute Key conforming to the "k8s.node.name" + // semantic conventions. It represents the name of the Node. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'node-1' + K8SNodeNameKey = attribute.Key("k8s.node.name") + + // K8SNodeUIDKey is the attribute Key conforming to the "k8s.node.uid" + // semantic conventions. It represents the UID of the Node. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1eb3a0c6-0477-4080-a9cb-0cb7db65c6a2' + K8SNodeUIDKey = attribute.Key("k8s.node.uid") +) + +// K8SNodeName returns an attribute KeyValue conforming to the +// "k8s.node.name" semantic conventions. It represents the name of the Node. +func K8SNodeName(val string) attribute.KeyValue { + return K8SNodeNameKey.String(val) +} + +// K8SNodeUID returns an attribute KeyValue conforming to the "k8s.node.uid" +// semantic conventions. It represents the UID of the Node. +func K8SNodeUID(val string) attribute.KeyValue { + return K8SNodeUIDKey.String(val) +} + +// A Kubernetes Namespace. +const ( + // K8SNamespaceNameKey is the attribute Key conforming to the + // "k8s.namespace.name" semantic conventions. It represents the name of the + // namespace that the pod is running in. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'default' + K8SNamespaceNameKey = attribute.Key("k8s.namespace.name") +) + +// K8SNamespaceName returns an attribute KeyValue conforming to the +// "k8s.namespace.name" semantic conventions. It represents the name of the +// namespace that the pod is running in. +func K8SNamespaceName(val string) attribute.KeyValue { + return K8SNamespaceNameKey.String(val) +} + +// A Kubernetes Pod object. +const ( + // K8SPodUIDKey is the attribute Key conforming to the "k8s.pod.uid" + // semantic conventions. It represents the UID of the Pod. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SPodUIDKey = attribute.Key("k8s.pod.uid") + + // K8SPodNameKey is the attribute Key conforming to the "k8s.pod.name" + // semantic conventions. It represents the name of the Pod. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-pod-autoconf' + K8SPodNameKey = attribute.Key("k8s.pod.name") +) + +// K8SPodUID returns an attribute KeyValue conforming to the "k8s.pod.uid" +// semantic conventions. It represents the UID of the Pod. +func K8SPodUID(val string) attribute.KeyValue { + return K8SPodUIDKey.String(val) +} + +// K8SPodName returns an attribute KeyValue conforming to the "k8s.pod.name" +// semantic conventions. It represents the name of the Pod. +func K8SPodName(val string) attribute.KeyValue { + return K8SPodNameKey.String(val) +} + +// A container in a +// [PodTemplate](https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates). +const ( + // K8SContainerNameKey is the attribute Key conforming to the + // "k8s.container.name" semantic conventions. It represents the name of the + // Container from Pod specification, must be unique within a Pod. Container + // runtime usually uses different globally unique name (`container.name`). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'redis' + K8SContainerNameKey = attribute.Key("k8s.container.name") + + // K8SContainerRestartCountKey is the attribute Key conforming to the + // "k8s.container.restart_count" semantic conventions. It represents the + // number of times the container was restarted. This attribute can be used + // to identify a particular container (running or stopped) within a + // container spec. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 0, 2 + K8SContainerRestartCountKey = attribute.Key("k8s.container.restart_count") +) + +// K8SContainerName returns an attribute KeyValue conforming to the +// "k8s.container.name" semantic conventions. It represents the name of the +// Container from Pod specification, must be unique within a Pod. Container +// runtime usually uses different globally unique name (`container.name`). +func K8SContainerName(val string) attribute.KeyValue { + return K8SContainerNameKey.String(val) +} + +// K8SContainerRestartCount returns an attribute KeyValue conforming to the +// "k8s.container.restart_count" semantic conventions. It represents the number +// of times the container was restarted. This attribute can be used to identify +// a particular container (running or stopped) within a container spec. +func K8SContainerRestartCount(val int) attribute.KeyValue { + return K8SContainerRestartCountKey.Int(val) +} + +// A Kubernetes ReplicaSet object. +const ( + // K8SReplicaSetUIDKey is the attribute Key conforming to the + // "k8s.replicaset.uid" semantic conventions. It represents the UID of the + // ReplicaSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SReplicaSetUIDKey = attribute.Key("k8s.replicaset.uid") + + // K8SReplicaSetNameKey is the attribute Key conforming to the + // "k8s.replicaset.name" semantic conventions. It represents the name of + // the ReplicaSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SReplicaSetNameKey = attribute.Key("k8s.replicaset.name") +) + +// K8SReplicaSetUID returns an attribute KeyValue conforming to the +// "k8s.replicaset.uid" semantic conventions. It represents the UID of the +// ReplicaSet. +func K8SReplicaSetUID(val string) attribute.KeyValue { + return K8SReplicaSetUIDKey.String(val) +} + +// K8SReplicaSetName returns an attribute KeyValue conforming to the +// "k8s.replicaset.name" semantic conventions. It represents the name of the +// ReplicaSet. +func K8SReplicaSetName(val string) attribute.KeyValue { + return K8SReplicaSetNameKey.String(val) +} + +// A Kubernetes Deployment object. +const ( + // K8SDeploymentUIDKey is the attribute Key conforming to the + // "k8s.deployment.uid" semantic conventions. It represents the UID of the + // Deployment. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SDeploymentUIDKey = attribute.Key("k8s.deployment.uid") + + // K8SDeploymentNameKey is the attribute Key conforming to the + // "k8s.deployment.name" semantic conventions. It represents the name of + // the Deployment. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SDeploymentNameKey = attribute.Key("k8s.deployment.name") +) + +// K8SDeploymentUID returns an attribute KeyValue conforming to the +// "k8s.deployment.uid" semantic conventions. It represents the UID of the +// Deployment. +func K8SDeploymentUID(val string) attribute.KeyValue { + return K8SDeploymentUIDKey.String(val) +} + +// K8SDeploymentName returns an attribute KeyValue conforming to the +// "k8s.deployment.name" semantic conventions. It represents the name of the +// Deployment. +func K8SDeploymentName(val string) attribute.KeyValue { + return K8SDeploymentNameKey.String(val) +} + +// A Kubernetes StatefulSet object. +const ( + // K8SStatefulSetUIDKey is the attribute Key conforming to the + // "k8s.statefulset.uid" semantic conventions. It represents the UID of the + // StatefulSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SStatefulSetUIDKey = attribute.Key("k8s.statefulset.uid") + + // K8SStatefulSetNameKey is the attribute Key conforming to the + // "k8s.statefulset.name" semantic conventions. It represents the name of + // the StatefulSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SStatefulSetNameKey = attribute.Key("k8s.statefulset.name") +) + +// K8SStatefulSetUID returns an attribute KeyValue conforming to the +// "k8s.statefulset.uid" semantic conventions. It represents the UID of the +// StatefulSet. +func K8SStatefulSetUID(val string) attribute.KeyValue { + return K8SStatefulSetUIDKey.String(val) +} + +// K8SStatefulSetName returns an attribute KeyValue conforming to the +// "k8s.statefulset.name" semantic conventions. It represents the name of the +// StatefulSet. +func K8SStatefulSetName(val string) attribute.KeyValue { + return K8SStatefulSetNameKey.String(val) +} + +// A Kubernetes DaemonSet object. +const ( + // K8SDaemonSetUIDKey is the attribute Key conforming to the + // "k8s.daemonset.uid" semantic conventions. It represents the UID of the + // DaemonSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SDaemonSetUIDKey = attribute.Key("k8s.daemonset.uid") + + // K8SDaemonSetNameKey is the attribute Key conforming to the + // "k8s.daemonset.name" semantic conventions. It represents the name of the + // DaemonSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SDaemonSetNameKey = attribute.Key("k8s.daemonset.name") +) + +// K8SDaemonSetUID returns an attribute KeyValue conforming to the +// "k8s.daemonset.uid" semantic conventions. It represents the UID of the +// DaemonSet. +func K8SDaemonSetUID(val string) attribute.KeyValue { + return K8SDaemonSetUIDKey.String(val) +} + +// K8SDaemonSetName returns an attribute KeyValue conforming to the +// "k8s.daemonset.name" semantic conventions. It represents the name of the +// DaemonSet. +func K8SDaemonSetName(val string) attribute.KeyValue { + return K8SDaemonSetNameKey.String(val) +} + +// A Kubernetes Job object. +const ( + // K8SJobUIDKey is the attribute Key conforming to the "k8s.job.uid" + // semantic conventions. It represents the UID of the Job. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SJobUIDKey = attribute.Key("k8s.job.uid") + + // K8SJobNameKey is the attribute Key conforming to the "k8s.job.name" + // semantic conventions. It represents the name of the Job. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SJobNameKey = attribute.Key("k8s.job.name") +) + +// K8SJobUID returns an attribute KeyValue conforming to the "k8s.job.uid" +// semantic conventions. It represents the UID of the Job. +func K8SJobUID(val string) attribute.KeyValue { + return K8SJobUIDKey.String(val) +} + +// K8SJobName returns an attribute KeyValue conforming to the "k8s.job.name" +// semantic conventions. It represents the name of the Job. +func K8SJobName(val string) attribute.KeyValue { + return K8SJobNameKey.String(val) +} + +// A Kubernetes CronJob object. +const ( + // K8SCronJobUIDKey is the attribute Key conforming to the + // "k8s.cronjob.uid" semantic conventions. It represents the UID of the + // CronJob. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SCronJobUIDKey = attribute.Key("k8s.cronjob.uid") + + // K8SCronJobNameKey is the attribute Key conforming to the + // "k8s.cronjob.name" semantic conventions. It represents the name of the + // CronJob. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SCronJobNameKey = attribute.Key("k8s.cronjob.name") +) + +// K8SCronJobUID returns an attribute KeyValue conforming to the +// "k8s.cronjob.uid" semantic conventions. It represents the UID of the +// CronJob. +func K8SCronJobUID(val string) attribute.KeyValue { + return K8SCronJobUIDKey.String(val) +} + +// K8SCronJobName returns an attribute KeyValue conforming to the +// "k8s.cronjob.name" semantic conventions. It represents the name of the +// CronJob. +func K8SCronJobName(val string) attribute.KeyValue { + return K8SCronJobNameKey.String(val) +} + +// The operating system (OS) on which the process represented by this resource +// is running. +const ( + // OSTypeKey is the attribute Key conforming to the "os.type" semantic + // conventions. It represents the operating system type. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + OSTypeKey = attribute.Key("os.type") + + // OSDescriptionKey is the attribute Key conforming to the "os.description" + // semantic conventions. It represents the human readable (not intended to + // be parsed) OS version information, like e.g. reported by `ver` or + // `lsb_release -a` commands. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Microsoft Windows [Version 10.0.18363.778]', 'Ubuntu 18.04.1 + // LTS' + OSDescriptionKey = attribute.Key("os.description") + + // OSNameKey is the attribute Key conforming to the "os.name" semantic + // conventions. It represents the human readable operating system name. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'iOS', 'Android', 'Ubuntu' + OSNameKey = attribute.Key("os.name") + + // OSVersionKey is the attribute Key conforming to the "os.version" + // semantic conventions. It represents the version string of the operating + // system as defined in [Version + // Attributes](../../resource/semantic_conventions/README.md#version-attributes). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '14.2.1', '18.04.1' + OSVersionKey = attribute.Key("os.version") +) + +var ( + // Microsoft Windows + OSTypeWindows = OSTypeKey.String("windows") + // Linux + OSTypeLinux = OSTypeKey.String("linux") + // Apple Darwin + OSTypeDarwin = OSTypeKey.String("darwin") + // FreeBSD + OSTypeFreeBSD = OSTypeKey.String("freebsd") + // NetBSD + OSTypeNetBSD = OSTypeKey.String("netbsd") + // OpenBSD + OSTypeOpenBSD = OSTypeKey.String("openbsd") + // DragonFly BSD + OSTypeDragonflyBSD = OSTypeKey.String("dragonflybsd") + // HP-UX (Hewlett Packard Unix) + OSTypeHPUX = OSTypeKey.String("hpux") + // AIX (Advanced Interactive eXecutive) + OSTypeAIX = OSTypeKey.String("aix") + // SunOS, Oracle Solaris + OSTypeSolaris = OSTypeKey.String("solaris") + // IBM z/OS + OSTypeZOS = OSTypeKey.String("z_os") +) + +// OSDescription returns an attribute KeyValue conforming to the +// "os.description" semantic conventions. It represents the human readable (not +// intended to be parsed) OS version information, like e.g. reported by `ver` +// or `lsb_release -a` commands. +func OSDescription(val string) attribute.KeyValue { + return OSDescriptionKey.String(val) +} + +// OSName returns an attribute KeyValue conforming to the "os.name" semantic +// conventions. It represents the human readable operating system name. +func OSName(val string) attribute.KeyValue { + return OSNameKey.String(val) +} + +// OSVersion returns an attribute KeyValue conforming to the "os.version" +// semantic conventions. It represents the version string of the operating +// system as defined in [Version +// Attributes](../../resource/semantic_conventions/README.md#version-attributes). +func OSVersion(val string) attribute.KeyValue { + return OSVersionKey.String(val) +} + +// An operating system process. +const ( + // ProcessPIDKey is the attribute Key conforming to the "process.pid" + // semantic conventions. It represents the process identifier (PID). + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 1234 + ProcessPIDKey = attribute.Key("process.pid") + + // ProcessParentPIDKey is the attribute Key conforming to the + // "process.parent_pid" semantic conventions. It represents the parent + // Process identifier (PID). + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 111 + ProcessParentPIDKey = attribute.Key("process.parent_pid") + + // ProcessExecutableNameKey is the attribute Key conforming to the + // "process.executable.name" semantic conventions. It represents the name + // of the process executable. On Linux based systems, can be set to the + // `Name` in `proc/[pid]/status`. On Windows, can be set to the base name + // of `GetProcessImageFileNameW`. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes + // below.) + // Stability: stable + // Examples: 'otelcol' + ProcessExecutableNameKey = attribute.Key("process.executable.name") + + // ProcessExecutablePathKey is the attribute Key conforming to the + // "process.executable.path" semantic conventions. It represents the full + // path to the process executable. On Linux based systems, can be set to + // the target of `proc/[pid]/exe`. On Windows, can be set to the result of + // `GetProcessImageFileNameW`. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes + // below.) + // Stability: stable + // Examples: '/usr/bin/cmd/otelcol' + ProcessExecutablePathKey = attribute.Key("process.executable.path") + + // ProcessCommandKey is the attribute Key conforming to the + // "process.command" semantic conventions. It represents the command used + // to launch the process (i.e. the command name). On Linux based systems, + // can be set to the zeroth string in `proc/[pid]/cmdline`. On Windows, can + // be set to the first parameter extracted from `GetCommandLineW`. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes + // below.) + // Stability: stable + // Examples: 'cmd/otelcol' + ProcessCommandKey = attribute.Key("process.command") + + // ProcessCommandLineKey is the attribute Key conforming to the + // "process.command_line" semantic conventions. It represents the full + // command used to launch the process as a single string representing the + // full command. On Windows, can be set to the result of `GetCommandLineW`. + // Do not set this if you have to assemble it just for monitoring; use + // `process.command_args` instead. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes + // below.) + // Stability: stable + // Examples: 'C:\\cmd\\otecol --config="my directory\\config.yaml"' + ProcessCommandLineKey = attribute.Key("process.command_line") + + // ProcessCommandArgsKey is the attribute Key conforming to the + // "process.command_args" semantic conventions. It represents the all the + // command arguments (including the command/executable itself) as received + // by the process. On Linux-based systems (and some other Unixoid systems + // supporting procfs), can be set according to the list of null-delimited + // strings extracted from `proc/[pid]/cmdline`. For libc-based executables, + // this would be the full argv vector passed to `main`. + // + // Type: string[] + // RequirementLevel: ConditionallyRequired (See alternative attributes + // below.) + // Stability: stable + // Examples: 'cmd/otecol', '--config=config.yaml' + ProcessCommandArgsKey = attribute.Key("process.command_args") + + // ProcessOwnerKey is the attribute Key conforming to the "process.owner" + // semantic conventions. It represents the username of the user that owns + // the process. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'root' + ProcessOwnerKey = attribute.Key("process.owner") +) + +// ProcessPID returns an attribute KeyValue conforming to the "process.pid" +// semantic conventions. It represents the process identifier (PID). +func ProcessPID(val int) attribute.KeyValue { + return ProcessPIDKey.Int(val) +} + +// ProcessParentPID returns an attribute KeyValue conforming to the +// "process.parent_pid" semantic conventions. It represents the parent Process +// identifier (PID). +func ProcessParentPID(val int) attribute.KeyValue { + return ProcessParentPIDKey.Int(val) +} + +// ProcessExecutableName returns an attribute KeyValue conforming to the +// "process.executable.name" semantic conventions. It represents the name of +// the process executable. On Linux based systems, can be set to the `Name` in +// `proc/[pid]/status`. On Windows, can be set to the base name of +// `GetProcessImageFileNameW`. +func ProcessExecutableName(val string) attribute.KeyValue { + return ProcessExecutableNameKey.String(val) +} + +// ProcessExecutablePath returns an attribute KeyValue conforming to the +// "process.executable.path" semantic conventions. It represents the full path +// to the process executable. On Linux based systems, can be set to the target +// of `proc/[pid]/exe`. On Windows, can be set to the result of +// `GetProcessImageFileNameW`. +func ProcessExecutablePath(val string) attribute.KeyValue { + return ProcessExecutablePathKey.String(val) +} + +// ProcessCommand returns an attribute KeyValue conforming to the +// "process.command" semantic conventions. It represents the command used to +// launch the process (i.e. the command name). On Linux based systems, can be +// set to the zeroth string in `proc/[pid]/cmdline`. On Windows, can be set to +// the first parameter extracted from `GetCommandLineW`. +func ProcessCommand(val string) attribute.KeyValue { + return ProcessCommandKey.String(val) +} + +// ProcessCommandLine returns an attribute KeyValue conforming to the +// "process.command_line" semantic conventions. It represents the full command +// used to launch the process as a single string representing the full command. +// On Windows, can be set to the result of `GetCommandLineW`. Do not set this +// if you have to assemble it just for monitoring; use `process.command_args` +// instead. +func ProcessCommandLine(val string) attribute.KeyValue { + return ProcessCommandLineKey.String(val) +} + +// ProcessCommandArgs returns an attribute KeyValue conforming to the +// "process.command_args" semantic conventions. It represents the all the +// command arguments (including the command/executable itself) as received by +// the process. On Linux-based systems (and some other Unixoid systems +// supporting procfs), can be set according to the list of null-delimited +// strings extracted from `proc/[pid]/cmdline`. For libc-based executables, +// this would be the full argv vector passed to `main`. +func ProcessCommandArgs(val ...string) attribute.KeyValue { + return ProcessCommandArgsKey.StringSlice(val) +} + +// ProcessOwner returns an attribute KeyValue conforming to the +// "process.owner" semantic conventions. It represents the username of the user +// that owns the process. +func ProcessOwner(val string) attribute.KeyValue { + return ProcessOwnerKey.String(val) +} + +// The single (language) runtime instance which is monitored. +const ( + // ProcessRuntimeNameKey is the attribute Key conforming to the + // "process.runtime.name" semantic conventions. It represents the name of + // the runtime of this process. For compiled native binaries, this SHOULD + // be the name of the compiler. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'OpenJDK Runtime Environment' + ProcessRuntimeNameKey = attribute.Key("process.runtime.name") + + // ProcessRuntimeVersionKey is the attribute Key conforming to the + // "process.runtime.version" semantic conventions. It represents the + // version of the runtime of this process, as returned by the runtime + // without modification. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '14.0.2' + ProcessRuntimeVersionKey = attribute.Key("process.runtime.version") + + // ProcessRuntimeDescriptionKey is the attribute Key conforming to the + // "process.runtime.description" semantic conventions. It represents an + // additional description about the runtime of the process, for example a + // specific vendor customization of the runtime environment. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Eclipse OpenJ9 Eclipse OpenJ9 VM openj9-0.21.0' + ProcessRuntimeDescriptionKey = attribute.Key("process.runtime.description") +) + +// ProcessRuntimeName returns an attribute KeyValue conforming to the +// "process.runtime.name" semantic conventions. It represents the name of the +// runtime of this process. For compiled native binaries, this SHOULD be the +// name of the compiler. +func ProcessRuntimeName(val string) attribute.KeyValue { + return ProcessRuntimeNameKey.String(val) +} + +// ProcessRuntimeVersion returns an attribute KeyValue conforming to the +// "process.runtime.version" semantic conventions. It represents the version of +// the runtime of this process, as returned by the runtime without +// modification. +func ProcessRuntimeVersion(val string) attribute.KeyValue { + return ProcessRuntimeVersionKey.String(val) +} + +// ProcessRuntimeDescription returns an attribute KeyValue conforming to the +// "process.runtime.description" semantic conventions. It represents an +// additional description about the runtime of the process, for example a +// specific vendor customization of the runtime environment. +func ProcessRuntimeDescription(val string) attribute.KeyValue { + return ProcessRuntimeDescriptionKey.String(val) +} + +// A service instance. +const ( + // ServiceNameKey is the attribute Key conforming to the "service.name" + // semantic conventions. It represents the logical name of the service. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'shoppingcart' + // Note: MUST be the same for all instances of horizontally scaled + // services. If the value was not specified, SDKs MUST fallback to + // `unknown_service:` concatenated with + // [`process.executable.name`](process.md#process), e.g. + // `unknown_service:bash`. If `process.executable.name` is not available, + // the value MUST be set to `unknown_service`. + ServiceNameKey = attribute.Key("service.name") + + // ServiceNamespaceKey is the attribute Key conforming to the + // "service.namespace" semantic conventions. It represents a namespace for + // `service.name`. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Shop' + // Note: A string value having a meaning that helps to distinguish a group + // of services, for example the team name that owns a group of services. + // `service.name` is expected to be unique within the same namespace. If + // `service.namespace` is not specified in the Resource then `service.name` + // is expected to be unique for all services that have no explicit + // namespace defined (so the empty/unspecified namespace is simply one more + // valid namespace). Zero-length namespace string is assumed equal to + // unspecified namespace. + ServiceNamespaceKey = attribute.Key("service.namespace") + + // ServiceInstanceIDKey is the attribute Key conforming to the + // "service.instance.id" semantic conventions. It represents the string ID + // of the service instance. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '627cc493-f310-47de-96bd-71410b7dec09' + // Note: MUST be unique for each instance of the same + // `service.namespace,service.name` pair (in other words + // `service.namespace,service.name,service.instance.id` triplet MUST be + // globally unique). The ID helps to distinguish instances of the same + // service that exist at the same time (e.g. instances of a horizontally + // scaled service). It is preferable for the ID to be persistent and stay + // the same for the lifetime of the service instance, however it is + // acceptable that the ID is ephemeral and changes during important + // lifetime events for the service (e.g. service restarts). If the service + // has no inherent unique ID that can be used as the value of this + // attribute it is recommended to generate a random Version 1 or Version 4 + // RFC 4122 UUID (services aiming for reproducible UUIDs may also use + // Version 5, see RFC 4122 for more recommendations). + ServiceInstanceIDKey = attribute.Key("service.instance.id") + + // ServiceVersionKey is the attribute Key conforming to the + // "service.version" semantic conventions. It represents the version string + // of the service API or implementation. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2.0.0' + ServiceVersionKey = attribute.Key("service.version") +) + +// ServiceName returns an attribute KeyValue conforming to the +// "service.name" semantic conventions. It represents the logical name of the +// service. +func ServiceName(val string) attribute.KeyValue { + return ServiceNameKey.String(val) +} + +// ServiceNamespace returns an attribute KeyValue conforming to the +// "service.namespace" semantic conventions. It represents a namespace for +// `service.name`. +func ServiceNamespace(val string) attribute.KeyValue { + return ServiceNamespaceKey.String(val) +} + +// ServiceInstanceID returns an attribute KeyValue conforming to the +// "service.instance.id" semantic conventions. It represents the string ID of +// the service instance. +func ServiceInstanceID(val string) attribute.KeyValue { + return ServiceInstanceIDKey.String(val) +} + +// ServiceVersion returns an attribute KeyValue conforming to the +// "service.version" semantic conventions. It represents the version string of +// the service API or implementation. +func ServiceVersion(val string) attribute.KeyValue { + return ServiceVersionKey.String(val) +} + +// The telemetry SDK used to capture data recorded by the instrumentation +// libraries. +const ( + // TelemetrySDKNameKey is the attribute Key conforming to the + // "telemetry.sdk.name" semantic conventions. It represents the name of the + // telemetry SDK as defined above. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + TelemetrySDKNameKey = attribute.Key("telemetry.sdk.name") + + // TelemetrySDKLanguageKey is the attribute Key conforming to the + // "telemetry.sdk.language" semantic conventions. It represents the + // language of the telemetry SDK. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + TelemetrySDKLanguageKey = attribute.Key("telemetry.sdk.language") + + // TelemetrySDKVersionKey is the attribute Key conforming to the + // "telemetry.sdk.version" semantic conventions. It represents the version + // string of the telemetry SDK. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1.2.3' + TelemetrySDKVersionKey = attribute.Key("telemetry.sdk.version") + + // TelemetryAutoVersionKey is the attribute Key conforming to the + // "telemetry.auto.version" semantic conventions. It represents the version + // string of the auto instrumentation agent, if used. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1.2.3' + TelemetryAutoVersionKey = attribute.Key("telemetry.auto.version") +) + +var ( + // cpp + TelemetrySDKLanguageCPP = TelemetrySDKLanguageKey.String("cpp") + // dotnet + TelemetrySDKLanguageDotnet = TelemetrySDKLanguageKey.String("dotnet") + // erlang + TelemetrySDKLanguageErlang = TelemetrySDKLanguageKey.String("erlang") + // go + TelemetrySDKLanguageGo = TelemetrySDKLanguageKey.String("go") + // java + TelemetrySDKLanguageJava = TelemetrySDKLanguageKey.String("java") + // nodejs + TelemetrySDKLanguageNodejs = TelemetrySDKLanguageKey.String("nodejs") + // php + TelemetrySDKLanguagePHP = TelemetrySDKLanguageKey.String("php") + // python + TelemetrySDKLanguagePython = TelemetrySDKLanguageKey.String("python") + // ruby + TelemetrySDKLanguageRuby = TelemetrySDKLanguageKey.String("ruby") + // webjs + TelemetrySDKLanguageWebjs = TelemetrySDKLanguageKey.String("webjs") + // swift + TelemetrySDKLanguageSwift = TelemetrySDKLanguageKey.String("swift") +) + +// TelemetrySDKName returns an attribute KeyValue conforming to the +// "telemetry.sdk.name" semantic conventions. It represents the name of the +// telemetry SDK as defined above. +func TelemetrySDKName(val string) attribute.KeyValue { + return TelemetrySDKNameKey.String(val) +} + +// TelemetrySDKVersion returns an attribute KeyValue conforming to the +// "telemetry.sdk.version" semantic conventions. It represents the version +// string of the telemetry SDK. +func TelemetrySDKVersion(val string) attribute.KeyValue { + return TelemetrySDKVersionKey.String(val) +} + +// TelemetryAutoVersion returns an attribute KeyValue conforming to the +// "telemetry.auto.version" semantic conventions. It represents the version +// string of the auto instrumentation agent, if used. +func TelemetryAutoVersion(val string) attribute.KeyValue { + return TelemetryAutoVersionKey.String(val) +} + +// Resource describing the packaged software running the application code. Web +// engines are typically executed using process.runtime. +const ( + // WebEngineNameKey is the attribute Key conforming to the "webengine.name" + // semantic conventions. It represents the name of the web engine. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'WildFly' + WebEngineNameKey = attribute.Key("webengine.name") + + // WebEngineVersionKey is the attribute Key conforming to the + // "webengine.version" semantic conventions. It represents the version of + // the web engine. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '21.0.0' + WebEngineVersionKey = attribute.Key("webengine.version") + + // WebEngineDescriptionKey is the attribute Key conforming to the + // "webengine.description" semantic conventions. It represents the + // additional description of the web engine (e.g. detailed version and + // edition information). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'WildFly Full 21.0.0.Final (WildFly Core 13.0.1.Final) - + // 2.2.2.Final' + WebEngineDescriptionKey = attribute.Key("webengine.description") +) + +// WebEngineName returns an attribute KeyValue conforming to the +// "webengine.name" semantic conventions. It represents the name of the web +// engine. +func WebEngineName(val string) attribute.KeyValue { + return WebEngineNameKey.String(val) +} + +// WebEngineVersion returns an attribute KeyValue conforming to the +// "webengine.version" semantic conventions. It represents the version of the +// web engine. +func WebEngineVersion(val string) attribute.KeyValue { + return WebEngineVersionKey.String(val) +} + +// WebEngineDescription returns an attribute KeyValue conforming to the +// "webengine.description" semantic conventions. It represents the additional +// description of the web engine (e.g. detailed version and edition +// information). +func WebEngineDescription(val string) attribute.KeyValue { + return WebEngineDescriptionKey.String(val) +} + +// Attributes used by non-OTLP exporters to represent OpenTelemetry Scope's +// concepts. +const ( + // OtelScopeNameKey is the attribute Key conforming to the + // "otel.scope.name" semantic conventions. It represents the name of the + // instrumentation scope - (`InstrumentationScope.Name` in OTLP). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'io.opentelemetry.contrib.mongodb' + OtelScopeNameKey = attribute.Key("otel.scope.name") + + // OtelScopeVersionKey is the attribute Key conforming to the + // "otel.scope.version" semantic conventions. It represents the version of + // the instrumentation scope - (`InstrumentationScope.Version` in OTLP). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1.0.0' + OtelScopeVersionKey = attribute.Key("otel.scope.version") +) + +// OtelScopeName returns an attribute KeyValue conforming to the +// "otel.scope.name" semantic conventions. It represents the name of the +// instrumentation scope - (`InstrumentationScope.Name` in OTLP). +func OtelScopeName(val string) attribute.KeyValue { + return OtelScopeNameKey.String(val) +} + +// OtelScopeVersion returns an attribute KeyValue conforming to the +// "otel.scope.version" semantic conventions. It represents the version of the +// instrumentation scope - (`InstrumentationScope.Version` in OTLP). +func OtelScopeVersion(val string) attribute.KeyValue { + return OtelScopeVersionKey.String(val) +} + +// Span attributes used by non-OTLP exporters to represent OpenTelemetry +// Scope's concepts. +const ( + // OtelLibraryNameKey is the attribute Key conforming to the + // "otel.library.name" semantic conventions. It represents the deprecated, + // use the `otel.scope.name` attribute. + // + // Type: string + // RequirementLevel: Optional + // Stability: deprecated + // Examples: 'io.opentelemetry.contrib.mongodb' + OtelLibraryNameKey = attribute.Key("otel.library.name") + + // OtelLibraryVersionKey is the attribute Key conforming to the + // "otel.library.version" semantic conventions. It represents the + // deprecated, use the `otel.scope.version` attribute. + // + // Type: string + // RequirementLevel: Optional + // Stability: deprecated + // Examples: '1.0.0' + OtelLibraryVersionKey = attribute.Key("otel.library.version") +) + +// OtelLibraryName returns an attribute KeyValue conforming to the +// "otel.library.name" semantic conventions. It represents the deprecated, use +// the `otel.scope.name` attribute. +func OtelLibraryName(val string) attribute.KeyValue { + return OtelLibraryNameKey.String(val) +} + +// OtelLibraryVersion returns an attribute KeyValue conforming to the +// "otel.library.version" semantic conventions. It represents the deprecated, +// use the `otel.scope.version` attribute. +func OtelLibraryVersion(val string) attribute.KeyValue { + return OtelLibraryVersionKey.String(val) +} diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/schema.go b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/schema.go new file mode 100644 index 0000000000..634a1dce07 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/schema.go @@ -0,0 +1,9 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.17.0" + +// SchemaURL is the schema URL that matches the version of the semantic conventions +// that this package defines. Semconv packages starting from v1.4.0 must declare +// non-empty schema URL in the form https://opentelemetry.io/schemas/ +const SchemaURL = "https://opentelemetry.io/schemas/1.17.0" diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/trace.go b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/trace.go new file mode 100644 index 0000000000..21497bb6bc --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.17.0/trace.go @@ -0,0 +1,3364 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.17.0" + +import "go.opentelemetry.io/otel/attribute" + +// The shared attributes used to report a single exception associated with a +// span or log. +const ( + // ExceptionTypeKey is the attribute Key conforming to the "exception.type" + // semantic conventions. It represents the type of the exception (its + // fully-qualified class name, if applicable). The dynamic type of the + // exception should be preferred over the static type in languages that + // support it. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'java.net.ConnectException', 'OSError' + ExceptionTypeKey = attribute.Key("exception.type") + + // ExceptionMessageKey is the attribute Key conforming to the + // "exception.message" semantic conventions. It represents the exception + // message. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Division by zero', "Can't convert 'int' object to str + // implicitly" + ExceptionMessageKey = attribute.Key("exception.message") + + // ExceptionStacktraceKey is the attribute Key conforming to the + // "exception.stacktrace" semantic conventions. It represents a stacktrace + // as a string in the natural representation for the language runtime. The + // representation is to be determined and documented by each language SIG. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Exception in thread "main" java.lang.RuntimeException: Test + // exception\\n at ' + // 'com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at ' + // 'com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at ' + // 'com.example.GenerateTrace.main(GenerateTrace.java:5)' + ExceptionStacktraceKey = attribute.Key("exception.stacktrace") +) + +// ExceptionType returns an attribute KeyValue conforming to the +// "exception.type" semantic conventions. It represents the type of the +// exception (its fully-qualified class name, if applicable). The dynamic type +// of the exception should be preferred over the static type in languages that +// support it. +func ExceptionType(val string) attribute.KeyValue { + return ExceptionTypeKey.String(val) +} + +// ExceptionMessage returns an attribute KeyValue conforming to the +// "exception.message" semantic conventions. It represents the exception +// message. +func ExceptionMessage(val string) attribute.KeyValue { + return ExceptionMessageKey.String(val) +} + +// ExceptionStacktrace returns an attribute KeyValue conforming to the +// "exception.stacktrace" semantic conventions. It represents a stacktrace as a +// string in the natural representation for the language runtime. The +// representation is to be determined and documented by each language SIG. +func ExceptionStacktrace(val string) attribute.KeyValue { + return ExceptionStacktraceKey.String(val) +} + +// Attributes for Events represented using Log Records. +const ( + // EventNameKey is the attribute Key conforming to the "event.name" + // semantic conventions. It represents the name identifies the event. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'click', 'exception' + EventNameKey = attribute.Key("event.name") + + // EventDomainKey is the attribute Key conforming to the "event.domain" + // semantic conventions. It represents the domain identifies the business + // context for the events. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + // Note: Events across different domains may have same `event.name`, yet be + // unrelated events. + EventDomainKey = attribute.Key("event.domain") +) + +var ( + // Events from browser apps + EventDomainBrowser = EventDomainKey.String("browser") + // Events from mobile apps + EventDomainDevice = EventDomainKey.String("device") + // Events from Kubernetes + EventDomainK8S = EventDomainKey.String("k8s") +) + +// EventName returns an attribute KeyValue conforming to the "event.name" +// semantic conventions. It represents the name identifies the event. +func EventName(val string) attribute.KeyValue { + return EventNameKey.String(val) +} + +// Span attributes used by AWS Lambda (in addition to general `faas` +// attributes). +const ( + // AWSLambdaInvokedARNKey is the attribute Key conforming to the + // "aws.lambda.invoked_arn" semantic conventions. It represents the full + // invoked ARN as provided on the `Context` passed to the function + // (`Lambda-Runtime-Invoked-Function-ARN` header on the + // `/runtime/invocation/next` applicable). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:lambda:us-east-1:123456:function:myfunction:myalias' + // Note: This may be different from `faas.id` if an alias is involved. + AWSLambdaInvokedARNKey = attribute.Key("aws.lambda.invoked_arn") +) + +// AWSLambdaInvokedARN returns an attribute KeyValue conforming to the +// "aws.lambda.invoked_arn" semantic conventions. It represents the full +// invoked ARN as provided on the `Context` passed to the function +// (`Lambda-Runtime-Invoked-Function-ARN` header on the +// `/runtime/invocation/next` applicable). +func AWSLambdaInvokedARN(val string) attribute.KeyValue { + return AWSLambdaInvokedARNKey.String(val) +} + +// Attributes for CloudEvents. CloudEvents is a specification on how to define +// event data in a standard way. These attributes can be attached to spans when +// performing operations with CloudEvents, regardless of the protocol being +// used. +const ( + // CloudeventsEventIDKey is the attribute Key conforming to the + // "cloudevents.event_id" semantic conventions. It represents the + // [event_id](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#id) + // uniquely identifies the event. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: '123e4567-e89b-12d3-a456-426614174000', '0001' + CloudeventsEventIDKey = attribute.Key("cloudevents.event_id") + + // CloudeventsEventSourceKey is the attribute Key conforming to the + // "cloudevents.event_source" semantic conventions. It represents the + // [source](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#source-1) + // identifies the context in which an event happened. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'https://github.com/cloudevents', + // '/cloudevents/spec/pull/123', 'my-service' + CloudeventsEventSourceKey = attribute.Key("cloudevents.event_source") + + // CloudeventsEventSpecVersionKey is the attribute Key conforming to the + // "cloudevents.event_spec_version" semantic conventions. It represents the + // [version of the CloudEvents + // specification](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#specversion) + // which the event uses. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1.0' + CloudeventsEventSpecVersionKey = attribute.Key("cloudevents.event_spec_version") + + // CloudeventsEventTypeKey is the attribute Key conforming to the + // "cloudevents.event_type" semantic conventions. It represents the + // [event_type](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#type) + // contains a value describing the type of event related to the originating + // occurrence. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'com.github.pull_request.opened', + // 'com.example.object.deleted.v2' + CloudeventsEventTypeKey = attribute.Key("cloudevents.event_type") + + // CloudeventsEventSubjectKey is the attribute Key conforming to the + // "cloudevents.event_subject" semantic conventions. It represents the + // [subject](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#subject) + // of the event in the context of the event producer (identified by + // source). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'mynewfile.jpg' + CloudeventsEventSubjectKey = attribute.Key("cloudevents.event_subject") +) + +// CloudeventsEventID returns an attribute KeyValue conforming to the +// "cloudevents.event_id" semantic conventions. It represents the +// [event_id](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#id) +// uniquely identifies the event. +func CloudeventsEventID(val string) attribute.KeyValue { + return CloudeventsEventIDKey.String(val) +} + +// CloudeventsEventSource returns an attribute KeyValue conforming to the +// "cloudevents.event_source" semantic conventions. It represents the +// [source](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#source-1) +// identifies the context in which an event happened. +func CloudeventsEventSource(val string) attribute.KeyValue { + return CloudeventsEventSourceKey.String(val) +} + +// CloudeventsEventSpecVersion returns an attribute KeyValue conforming to +// the "cloudevents.event_spec_version" semantic conventions. It represents the +// [version of the CloudEvents +// specification](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#specversion) +// which the event uses. +func CloudeventsEventSpecVersion(val string) attribute.KeyValue { + return CloudeventsEventSpecVersionKey.String(val) +} + +// CloudeventsEventType returns an attribute KeyValue conforming to the +// "cloudevents.event_type" semantic conventions. It represents the +// [event_type](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#type) +// contains a value describing the type of event related to the originating +// occurrence. +func CloudeventsEventType(val string) attribute.KeyValue { + return CloudeventsEventTypeKey.String(val) +} + +// CloudeventsEventSubject returns an attribute KeyValue conforming to the +// "cloudevents.event_subject" semantic conventions. It represents the +// [subject](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#subject) +// of the event in the context of the event producer (identified by source). +func CloudeventsEventSubject(val string) attribute.KeyValue { + return CloudeventsEventSubjectKey.String(val) +} + +// Semantic conventions for the OpenTracing Shim +const ( + // OpentracingRefTypeKey is the attribute Key conforming to the + // "opentracing.ref_type" semantic conventions. It represents the + // parent-child Reference type + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: The causal relationship between a child Span and a parent Span. + OpentracingRefTypeKey = attribute.Key("opentracing.ref_type") +) + +var ( + // The parent Span depends on the child Span in some capacity + OpentracingRefTypeChildOf = OpentracingRefTypeKey.String("child_of") + // The parent Span does not depend in any way on the result of the child Span + OpentracingRefTypeFollowsFrom = OpentracingRefTypeKey.String("follows_from") +) + +// The attributes used to perform database client calls. +const ( + // DBSystemKey is the attribute Key conforming to the "db.system" semantic + // conventions. It represents an identifier for the database management + // system (DBMS) product being used. See below for a list of well-known + // identifiers. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + DBSystemKey = attribute.Key("db.system") + + // DBConnectionStringKey is the attribute Key conforming to the + // "db.connection_string" semantic conventions. It represents the + // connection string used to connect to the database. It is recommended to + // remove embedded credentials. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Server=(localdb)\\v11.0;Integrated Security=true;' + DBConnectionStringKey = attribute.Key("db.connection_string") + + // DBUserKey is the attribute Key conforming to the "db.user" semantic + // conventions. It represents the username for accessing the database. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'readonly_user', 'reporting_user' + DBUserKey = attribute.Key("db.user") + + // DBJDBCDriverClassnameKey is the attribute Key conforming to the + // "db.jdbc.driver_classname" semantic conventions. It represents the + // fully-qualified class name of the [Java Database Connectivity + // (JDBC)](https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/) + // driver used to connect. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'org.postgresql.Driver', + // 'com.microsoft.sqlserver.jdbc.SQLServerDriver' + DBJDBCDriverClassnameKey = attribute.Key("db.jdbc.driver_classname") + + // DBNameKey is the attribute Key conforming to the "db.name" semantic + // conventions. It represents the this attribute is used to report the name + // of the database being accessed. For commands that switch the database, + // this should be set to the target database (even if the command fails). + // + // Type: string + // RequirementLevel: ConditionallyRequired (If applicable.) + // Stability: stable + // Examples: 'customers', 'main' + // Note: In some SQL databases, the database name to be used is called + // "schema name". In case there are multiple layers that could be + // considered for database name (e.g. Oracle instance name and schema + // name), the database name to be used is the more specific layer (e.g. + // Oracle schema name). + DBNameKey = attribute.Key("db.name") + + // DBStatementKey is the attribute Key conforming to the "db.statement" + // semantic conventions. It represents the database statement being + // executed. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If applicable and not + // explicitly disabled via instrumentation configuration.) + // Stability: stable + // Examples: 'SELECT * FROM wuser_table', 'SET mykey "WuValue"' + // Note: The value may be sanitized to exclude sensitive information. + DBStatementKey = attribute.Key("db.statement") + + // DBOperationKey is the attribute Key conforming to the "db.operation" + // semantic conventions. It represents the name of the operation being + // executed, e.g. the [MongoDB command + // name](https://docs.mongodb.com/manual/reference/command/#database-operations) + // such as `findAndModify`, or the SQL keyword. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If `db.statement` is not + // applicable.) + // Stability: stable + // Examples: 'findAndModify', 'HMSET', 'SELECT' + // Note: When setting this to an SQL keyword, it is not recommended to + // attempt any client-side parsing of `db.statement` just to get this + // property, but it should be set if the operation name is provided by the + // library being instrumented. If the SQL statement has an ambiguous + // operation, or performs more than one operation, this value may be + // omitted. + DBOperationKey = attribute.Key("db.operation") +) + +var ( + // Some other SQL database. Fallback only. See notes + DBSystemOtherSQL = DBSystemKey.String("other_sql") + // Microsoft SQL Server + DBSystemMSSQL = DBSystemKey.String("mssql") + // MySQL + DBSystemMySQL = DBSystemKey.String("mysql") + // Oracle Database + DBSystemOracle = DBSystemKey.String("oracle") + // IBM DB2 + DBSystemDB2 = DBSystemKey.String("db2") + // PostgreSQL + DBSystemPostgreSQL = DBSystemKey.String("postgresql") + // Amazon Redshift + DBSystemRedshift = DBSystemKey.String("redshift") + // Apache Hive + DBSystemHive = DBSystemKey.String("hive") + // Cloudscape + DBSystemCloudscape = DBSystemKey.String("cloudscape") + // HyperSQL DataBase + DBSystemHSQLDB = DBSystemKey.String("hsqldb") + // Progress Database + DBSystemProgress = DBSystemKey.String("progress") + // SAP MaxDB + DBSystemMaxDB = DBSystemKey.String("maxdb") + // SAP HANA + DBSystemHanaDB = DBSystemKey.String("hanadb") + // Ingres + DBSystemIngres = DBSystemKey.String("ingres") + // FirstSQL + DBSystemFirstSQL = DBSystemKey.String("firstsql") + // EnterpriseDB + DBSystemEDB = DBSystemKey.String("edb") + // InterSystems Caché + DBSystemCache = DBSystemKey.String("cache") + // Adabas (Adaptable Database System) + DBSystemAdabas = DBSystemKey.String("adabas") + // Firebird + DBSystemFirebird = DBSystemKey.String("firebird") + // Apache Derby + DBSystemDerby = DBSystemKey.String("derby") + // FileMaker + DBSystemFilemaker = DBSystemKey.String("filemaker") + // Informix + DBSystemInformix = DBSystemKey.String("informix") + // InstantDB + DBSystemInstantDB = DBSystemKey.String("instantdb") + // InterBase + DBSystemInterbase = DBSystemKey.String("interbase") + // MariaDB + DBSystemMariaDB = DBSystemKey.String("mariadb") + // Netezza + DBSystemNetezza = DBSystemKey.String("netezza") + // Pervasive PSQL + DBSystemPervasive = DBSystemKey.String("pervasive") + // PointBase + DBSystemPointbase = DBSystemKey.String("pointbase") + // SQLite + DBSystemSqlite = DBSystemKey.String("sqlite") + // Sybase + DBSystemSybase = DBSystemKey.String("sybase") + // Teradata + DBSystemTeradata = DBSystemKey.String("teradata") + // Vertica + DBSystemVertica = DBSystemKey.String("vertica") + // H2 + DBSystemH2 = DBSystemKey.String("h2") + // ColdFusion IMQ + DBSystemColdfusion = DBSystemKey.String("coldfusion") + // Apache Cassandra + DBSystemCassandra = DBSystemKey.String("cassandra") + // Apache HBase + DBSystemHBase = DBSystemKey.String("hbase") + // MongoDB + DBSystemMongoDB = DBSystemKey.String("mongodb") + // Redis + DBSystemRedis = DBSystemKey.String("redis") + // Couchbase + DBSystemCouchbase = DBSystemKey.String("couchbase") + // CouchDB + DBSystemCouchDB = DBSystemKey.String("couchdb") + // Microsoft Azure Cosmos DB + DBSystemCosmosDB = DBSystemKey.String("cosmosdb") + // Amazon DynamoDB + DBSystemDynamoDB = DBSystemKey.String("dynamodb") + // Neo4j + DBSystemNeo4j = DBSystemKey.String("neo4j") + // Apache Geode + DBSystemGeode = DBSystemKey.String("geode") + // Elasticsearch + DBSystemElasticsearch = DBSystemKey.String("elasticsearch") + // Memcached + DBSystemMemcached = DBSystemKey.String("memcached") + // CockroachDB + DBSystemCockroachdb = DBSystemKey.String("cockroachdb") + // OpenSearch + DBSystemOpensearch = DBSystemKey.String("opensearch") + // ClickHouse + DBSystemClickhouse = DBSystemKey.String("clickhouse") +) + +// DBConnectionString returns an attribute KeyValue conforming to the +// "db.connection_string" semantic conventions. It represents the connection +// string used to connect to the database. It is recommended to remove embedded +// credentials. +func DBConnectionString(val string) attribute.KeyValue { + return DBConnectionStringKey.String(val) +} + +// DBUser returns an attribute KeyValue conforming to the "db.user" semantic +// conventions. It represents the username for accessing the database. +func DBUser(val string) attribute.KeyValue { + return DBUserKey.String(val) +} + +// DBJDBCDriverClassname returns an attribute KeyValue conforming to the +// "db.jdbc.driver_classname" semantic conventions. It represents the +// fully-qualified class name of the [Java Database Connectivity +// (JDBC)](https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/) driver +// used to connect. +func DBJDBCDriverClassname(val string) attribute.KeyValue { + return DBJDBCDriverClassnameKey.String(val) +} + +// DBName returns an attribute KeyValue conforming to the "db.name" semantic +// conventions. It represents the this attribute is used to report the name of +// the database being accessed. For commands that switch the database, this +// should be set to the target database (even if the command fails). +func DBName(val string) attribute.KeyValue { + return DBNameKey.String(val) +} + +// DBStatement returns an attribute KeyValue conforming to the +// "db.statement" semantic conventions. It represents the database statement +// being executed. +func DBStatement(val string) attribute.KeyValue { + return DBStatementKey.String(val) +} + +// DBOperation returns an attribute KeyValue conforming to the +// "db.operation" semantic conventions. It represents the name of the operation +// being executed, e.g. the [MongoDB command +// name](https://docs.mongodb.com/manual/reference/command/#database-operations) +// such as `findAndModify`, or the SQL keyword. +func DBOperation(val string) attribute.KeyValue { + return DBOperationKey.String(val) +} + +// Connection-level attributes for Microsoft SQL Server +const ( + // DBMSSQLInstanceNameKey is the attribute Key conforming to the + // "db.mssql.instance_name" semantic conventions. It represents the + // Microsoft SQL Server [instance + // name](https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver15) + // connecting to. This name is used to determine the port of a named + // instance. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'MSSQLSERVER' + // Note: If setting a `db.mssql.instance_name`, `net.peer.port` is no + // longer required (but still recommended if non-standard). + DBMSSQLInstanceNameKey = attribute.Key("db.mssql.instance_name") +) + +// DBMSSQLInstanceName returns an attribute KeyValue conforming to the +// "db.mssql.instance_name" semantic conventions. It represents the Microsoft +// SQL Server [instance +// name](https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver15) +// connecting to. This name is used to determine the port of a named instance. +func DBMSSQLInstanceName(val string) attribute.KeyValue { + return DBMSSQLInstanceNameKey.String(val) +} + +// Call-level attributes for Cassandra +const ( + // DBCassandraPageSizeKey is the attribute Key conforming to the + // "db.cassandra.page_size" semantic conventions. It represents the fetch + // size used for paging, i.e. how many rows will be returned at once. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 5000 + DBCassandraPageSizeKey = attribute.Key("db.cassandra.page_size") + + // DBCassandraConsistencyLevelKey is the attribute Key conforming to the + // "db.cassandra.consistency_level" semantic conventions. It represents the + // consistency level of the query. Based on consistency values from + // [CQL](https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/dml/dmlConfigConsistency.html). + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + DBCassandraConsistencyLevelKey = attribute.Key("db.cassandra.consistency_level") + + // DBCassandraTableKey is the attribute Key conforming to the + // "db.cassandra.table" semantic conventions. It represents the name of the + // primary table that the operation is acting upon, including the keyspace + // name (if applicable). + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'mytable' + // Note: This mirrors the db.sql.table attribute but references cassandra + // rather than sql. It is not recommended to attempt any client-side + // parsing of `db.statement` just to get this property, but it should be + // set if it is provided by the library being instrumented. If the + // operation is acting upon an anonymous table, or more than one table, + // this value MUST NOT be set. + DBCassandraTableKey = attribute.Key("db.cassandra.table") + + // DBCassandraIdempotenceKey is the attribute Key conforming to the + // "db.cassandra.idempotence" semantic conventions. It represents the + // whether or not the query is idempotent. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + DBCassandraIdempotenceKey = attribute.Key("db.cassandra.idempotence") + + // DBCassandraSpeculativeExecutionCountKey is the attribute Key conforming + // to the "db.cassandra.speculative_execution_count" semantic conventions. + // It represents the number of times a query was speculatively executed. + // Not set or `0` if the query was not executed speculatively. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 0, 2 + DBCassandraSpeculativeExecutionCountKey = attribute.Key("db.cassandra.speculative_execution_count") + + // DBCassandraCoordinatorIDKey is the attribute Key conforming to the + // "db.cassandra.coordinator.id" semantic conventions. It represents the ID + // of the coordinating node for a query. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'be13faa2-8574-4d71-926d-27f16cf8a7af' + DBCassandraCoordinatorIDKey = attribute.Key("db.cassandra.coordinator.id") + + // DBCassandraCoordinatorDCKey is the attribute Key conforming to the + // "db.cassandra.coordinator.dc" semantic conventions. It represents the + // data center of the coordinating node for a query. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'us-west-2' + DBCassandraCoordinatorDCKey = attribute.Key("db.cassandra.coordinator.dc") +) + +var ( + // all + DBCassandraConsistencyLevelAll = DBCassandraConsistencyLevelKey.String("all") + // each_quorum + DBCassandraConsistencyLevelEachQuorum = DBCassandraConsistencyLevelKey.String("each_quorum") + // quorum + DBCassandraConsistencyLevelQuorum = DBCassandraConsistencyLevelKey.String("quorum") + // local_quorum + DBCassandraConsistencyLevelLocalQuorum = DBCassandraConsistencyLevelKey.String("local_quorum") + // one + DBCassandraConsistencyLevelOne = DBCassandraConsistencyLevelKey.String("one") + // two + DBCassandraConsistencyLevelTwo = DBCassandraConsistencyLevelKey.String("two") + // three + DBCassandraConsistencyLevelThree = DBCassandraConsistencyLevelKey.String("three") + // local_one + DBCassandraConsistencyLevelLocalOne = DBCassandraConsistencyLevelKey.String("local_one") + // any + DBCassandraConsistencyLevelAny = DBCassandraConsistencyLevelKey.String("any") + // serial + DBCassandraConsistencyLevelSerial = DBCassandraConsistencyLevelKey.String("serial") + // local_serial + DBCassandraConsistencyLevelLocalSerial = DBCassandraConsistencyLevelKey.String("local_serial") +) + +// DBCassandraPageSize returns an attribute KeyValue conforming to the +// "db.cassandra.page_size" semantic conventions. It represents the fetch size +// used for paging, i.e. how many rows will be returned at once. +func DBCassandraPageSize(val int) attribute.KeyValue { + return DBCassandraPageSizeKey.Int(val) +} + +// DBCassandraTable returns an attribute KeyValue conforming to the +// "db.cassandra.table" semantic conventions. It represents the name of the +// primary table that the operation is acting upon, including the keyspace name +// (if applicable). +func DBCassandraTable(val string) attribute.KeyValue { + return DBCassandraTableKey.String(val) +} + +// DBCassandraIdempotence returns an attribute KeyValue conforming to the +// "db.cassandra.idempotence" semantic conventions. It represents the whether +// or not the query is idempotent. +func DBCassandraIdempotence(val bool) attribute.KeyValue { + return DBCassandraIdempotenceKey.Bool(val) +} + +// DBCassandraSpeculativeExecutionCount returns an attribute KeyValue +// conforming to the "db.cassandra.speculative_execution_count" semantic +// conventions. It represents the number of times a query was speculatively +// executed. Not set or `0` if the query was not executed speculatively. +func DBCassandraSpeculativeExecutionCount(val int) attribute.KeyValue { + return DBCassandraSpeculativeExecutionCountKey.Int(val) +} + +// DBCassandraCoordinatorID returns an attribute KeyValue conforming to the +// "db.cassandra.coordinator.id" semantic conventions. It represents the ID of +// the coordinating node for a query. +func DBCassandraCoordinatorID(val string) attribute.KeyValue { + return DBCassandraCoordinatorIDKey.String(val) +} + +// DBCassandraCoordinatorDC returns an attribute KeyValue conforming to the +// "db.cassandra.coordinator.dc" semantic conventions. It represents the data +// center of the coordinating node for a query. +func DBCassandraCoordinatorDC(val string) attribute.KeyValue { + return DBCassandraCoordinatorDCKey.String(val) +} + +// Call-level attributes for Redis +const ( + // DBRedisDBIndexKey is the attribute Key conforming to the + // "db.redis.database_index" semantic conventions. It represents the index + // of the database being accessed as used in the [`SELECT` + // command](https://redis.io/commands/select), provided as an integer. To + // be used instead of the generic `db.name` attribute. + // + // Type: int + // RequirementLevel: ConditionallyRequired (If other than the default + // database (`0`).) + // Stability: stable + // Examples: 0, 1, 15 + DBRedisDBIndexKey = attribute.Key("db.redis.database_index") +) + +// DBRedisDBIndex returns an attribute KeyValue conforming to the +// "db.redis.database_index" semantic conventions. It represents the index of +// the database being accessed as used in the [`SELECT` +// command](https://redis.io/commands/select), provided as an integer. To be +// used instead of the generic `db.name` attribute. +func DBRedisDBIndex(val int) attribute.KeyValue { + return DBRedisDBIndexKey.Int(val) +} + +// Call-level attributes for MongoDB +const ( + // DBMongoDBCollectionKey is the attribute Key conforming to the + // "db.mongodb.collection" semantic conventions. It represents the + // collection being accessed within the database stated in `db.name`. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'customers', 'products' + DBMongoDBCollectionKey = attribute.Key("db.mongodb.collection") +) + +// DBMongoDBCollection returns an attribute KeyValue conforming to the +// "db.mongodb.collection" semantic conventions. It represents the collection +// being accessed within the database stated in `db.name`. +func DBMongoDBCollection(val string) attribute.KeyValue { + return DBMongoDBCollectionKey.String(val) +} + +// Call-level attributes for SQL databases +const ( + // DBSQLTableKey is the attribute Key conforming to the "db.sql.table" + // semantic conventions. It represents the name of the primary table that + // the operation is acting upon, including the database name (if + // applicable). + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'public.users', 'customers' + // Note: It is not recommended to attempt any client-side parsing of + // `db.statement` just to get this property, but it should be set if it is + // provided by the library being instrumented. If the operation is acting + // upon an anonymous table, or more than one table, this value MUST NOT be + // set. + DBSQLTableKey = attribute.Key("db.sql.table") +) + +// DBSQLTable returns an attribute KeyValue conforming to the "db.sql.table" +// semantic conventions. It represents the name of the primary table that the +// operation is acting upon, including the database name (if applicable). +func DBSQLTable(val string) attribute.KeyValue { + return DBSQLTableKey.String(val) +} + +// Span attributes used by non-OTLP exporters to represent OpenTelemetry Span's +// concepts. +const ( + // OtelStatusCodeKey is the attribute Key conforming to the + // "otel.status_code" semantic conventions. It represents the name of the + // code, either "OK" or "ERROR". MUST NOT be set if the status code is + // UNSET. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + OtelStatusCodeKey = attribute.Key("otel.status_code") + + // OtelStatusDescriptionKey is the attribute Key conforming to the + // "otel.status_description" semantic conventions. It represents the + // description of the Status if it has a value, otherwise not set. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'resource not found' + OtelStatusDescriptionKey = attribute.Key("otel.status_description") +) + +var ( + // The operation has been validated by an Application developer or Operator to have completed successfully + OtelStatusCodeOk = OtelStatusCodeKey.String("OK") + // The operation contains an error + OtelStatusCodeError = OtelStatusCodeKey.String("ERROR") +) + +// OtelStatusDescription returns an attribute KeyValue conforming to the +// "otel.status_description" semantic conventions. It represents the +// description of the Status if it has a value, otherwise not set. +func OtelStatusDescription(val string) attribute.KeyValue { + return OtelStatusDescriptionKey.String(val) +} + +// This semantic convention describes an instance of a function that runs +// without provisioning or managing of servers (also known as serverless +// functions or Function as a Service (FaaS)) with spans. +const ( + // FaaSTriggerKey is the attribute Key conforming to the "faas.trigger" + // semantic conventions. It represents the type of the trigger which caused + // this function execution. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: For the server/consumer span on the incoming side, + // `faas.trigger` MUST be set. + // + // Clients invoking FaaS instances usually cannot set `faas.trigger`, + // since they would typically need to look in the payload to determine + // the event type. If clients set it, it should be the same as the + // trigger that corresponding incoming would have (i.e., this has + // nothing to do with the underlying transport used to make the API + // call to invoke the lambda, which is often HTTP). + FaaSTriggerKey = attribute.Key("faas.trigger") + + // FaaSExecutionKey is the attribute Key conforming to the "faas.execution" + // semantic conventions. It represents the execution ID of the current + // function execution. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'af9d5aa4-a685-4c5f-a22b-444f80b3cc28' + FaaSExecutionKey = attribute.Key("faas.execution") +) + +var ( + // A response to some data source operation such as a database or filesystem read/write + FaaSTriggerDatasource = FaaSTriggerKey.String("datasource") + // To provide an answer to an inbound HTTP request + FaaSTriggerHTTP = FaaSTriggerKey.String("http") + // A function is set to be executed when messages are sent to a messaging system + FaaSTriggerPubsub = FaaSTriggerKey.String("pubsub") + // A function is scheduled to be executed regularly + FaaSTriggerTimer = FaaSTriggerKey.String("timer") + // If none of the others apply + FaaSTriggerOther = FaaSTriggerKey.String("other") +) + +// FaaSExecution returns an attribute KeyValue conforming to the +// "faas.execution" semantic conventions. It represents the execution ID of the +// current function execution. +func FaaSExecution(val string) attribute.KeyValue { + return FaaSExecutionKey.String(val) +} + +// Semantic Convention for FaaS triggered as a response to some data source +// operation such as a database or filesystem read/write. +const ( + // FaaSDocumentCollectionKey is the attribute Key conforming to the + // "faas.document.collection" semantic conventions. It represents the name + // of the source on which the triggering operation was performed. For + // example, in Cloud Storage or S3 corresponds to the bucket name, and in + // Cosmos DB to the database name. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myBucketName', 'myDBName' + FaaSDocumentCollectionKey = attribute.Key("faas.document.collection") + + // FaaSDocumentOperationKey is the attribute Key conforming to the + // "faas.document.operation" semantic conventions. It represents the + // describes the type of the operation that was performed on the data. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + FaaSDocumentOperationKey = attribute.Key("faas.document.operation") + + // FaaSDocumentTimeKey is the attribute Key conforming to the + // "faas.document.time" semantic conventions. It represents a string + // containing the time when the data was accessed in the [ISO + // 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format + // expressed in [UTC](https://www.w3.org/TR/NOTE-datetime). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2020-01-23T13:47:06Z' + FaaSDocumentTimeKey = attribute.Key("faas.document.time") + + // FaaSDocumentNameKey is the attribute Key conforming to the + // "faas.document.name" semantic conventions. It represents the document + // name/table subjected to the operation. For example, in Cloud Storage or + // S3 is the name of the file, and in Cosmos DB the table name. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'myFile.txt', 'myTableName' + FaaSDocumentNameKey = attribute.Key("faas.document.name") +) + +var ( + // When a new object is created + FaaSDocumentOperationInsert = FaaSDocumentOperationKey.String("insert") + // When an object is modified + FaaSDocumentOperationEdit = FaaSDocumentOperationKey.String("edit") + // When an object is deleted + FaaSDocumentOperationDelete = FaaSDocumentOperationKey.String("delete") +) + +// FaaSDocumentCollection returns an attribute KeyValue conforming to the +// "faas.document.collection" semantic conventions. It represents the name of +// the source on which the triggering operation was performed. For example, in +// Cloud Storage or S3 corresponds to the bucket name, and in Cosmos DB to the +// database name. +func FaaSDocumentCollection(val string) attribute.KeyValue { + return FaaSDocumentCollectionKey.String(val) +} + +// FaaSDocumentTime returns an attribute KeyValue conforming to the +// "faas.document.time" semantic conventions. It represents a string containing +// the time when the data was accessed in the [ISO +// 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format +// expressed in [UTC](https://www.w3.org/TR/NOTE-datetime). +func FaaSDocumentTime(val string) attribute.KeyValue { + return FaaSDocumentTimeKey.String(val) +} + +// FaaSDocumentName returns an attribute KeyValue conforming to the +// "faas.document.name" semantic conventions. It represents the document +// name/table subjected to the operation. For example, in Cloud Storage or S3 +// is the name of the file, and in Cosmos DB the table name. +func FaaSDocumentName(val string) attribute.KeyValue { + return FaaSDocumentNameKey.String(val) +} + +// Semantic Convention for FaaS scheduled to be executed regularly. +const ( + // FaaSTimeKey is the attribute Key conforming to the "faas.time" semantic + // conventions. It represents a string containing the function invocation + // time in the [ISO + // 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format + // expressed in [UTC](https://www.w3.org/TR/NOTE-datetime). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2020-01-23T13:47:06Z' + FaaSTimeKey = attribute.Key("faas.time") + + // FaaSCronKey is the attribute Key conforming to the "faas.cron" semantic + // conventions. It represents a string containing the schedule period as + // [Cron + // Expression](https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '0/5 * * * ? *' + FaaSCronKey = attribute.Key("faas.cron") +) + +// FaaSTime returns an attribute KeyValue conforming to the "faas.time" +// semantic conventions. It represents a string containing the function +// invocation time in the [ISO +// 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format +// expressed in [UTC](https://www.w3.org/TR/NOTE-datetime). +func FaaSTime(val string) attribute.KeyValue { + return FaaSTimeKey.String(val) +} + +// FaaSCron returns an attribute KeyValue conforming to the "faas.cron" +// semantic conventions. It represents a string containing the schedule period +// as [Cron +// Expression](https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm). +func FaaSCron(val string) attribute.KeyValue { + return FaaSCronKey.String(val) +} + +// Contains additional attributes for incoming FaaS spans. +const ( + // FaaSColdstartKey is the attribute Key conforming to the "faas.coldstart" + // semantic conventions. It represents a boolean that is true if the + // serverless function is executed for the first time (aka cold-start). + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + FaaSColdstartKey = attribute.Key("faas.coldstart") +) + +// FaaSColdstart returns an attribute KeyValue conforming to the +// "faas.coldstart" semantic conventions. It represents a boolean that is true +// if the serverless function is executed for the first time (aka cold-start). +func FaaSColdstart(val bool) attribute.KeyValue { + return FaaSColdstartKey.Bool(val) +} + +// Contains additional attributes for outgoing FaaS spans. +const ( + // FaaSInvokedNameKey is the attribute Key conforming to the + // "faas.invoked_name" semantic conventions. It represents the name of the + // invoked function. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'my-function' + // Note: SHOULD be equal to the `faas.name` resource attribute of the + // invoked function. + FaaSInvokedNameKey = attribute.Key("faas.invoked_name") + + // FaaSInvokedProviderKey is the attribute Key conforming to the + // "faas.invoked_provider" semantic conventions. It represents the cloud + // provider of the invoked function. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + // Note: SHOULD be equal to the `cloud.provider` resource attribute of the + // invoked function. + FaaSInvokedProviderKey = attribute.Key("faas.invoked_provider") + + // FaaSInvokedRegionKey is the attribute Key conforming to the + // "faas.invoked_region" semantic conventions. It represents the cloud + // region of the invoked function. + // + // Type: string + // RequirementLevel: ConditionallyRequired (For some cloud providers, like + // AWS or GCP, the region in which a function is hosted is essential to + // uniquely identify the function and also part of its endpoint. Since it's + // part of the endpoint being called, the region is always known to + // clients. In these cases, `faas.invoked_region` MUST be set accordingly. + // If the region is unknown to the client or not required for identifying + // the invoked function, setting `faas.invoked_region` is optional.) + // Stability: stable + // Examples: 'eu-central-1' + // Note: SHOULD be equal to the `cloud.region` resource attribute of the + // invoked function. + FaaSInvokedRegionKey = attribute.Key("faas.invoked_region") +) + +var ( + // Alibaba Cloud + FaaSInvokedProviderAlibabaCloud = FaaSInvokedProviderKey.String("alibaba_cloud") + // Amazon Web Services + FaaSInvokedProviderAWS = FaaSInvokedProviderKey.String("aws") + // Microsoft Azure + FaaSInvokedProviderAzure = FaaSInvokedProviderKey.String("azure") + // Google Cloud Platform + FaaSInvokedProviderGCP = FaaSInvokedProviderKey.String("gcp") + // Tencent Cloud + FaaSInvokedProviderTencentCloud = FaaSInvokedProviderKey.String("tencent_cloud") +) + +// FaaSInvokedName returns an attribute KeyValue conforming to the +// "faas.invoked_name" semantic conventions. It represents the name of the +// invoked function. +func FaaSInvokedName(val string) attribute.KeyValue { + return FaaSInvokedNameKey.String(val) +} + +// FaaSInvokedRegion returns an attribute KeyValue conforming to the +// "faas.invoked_region" semantic conventions. It represents the cloud region +// of the invoked function. +func FaaSInvokedRegion(val string) attribute.KeyValue { + return FaaSInvokedRegionKey.String(val) +} + +// These attributes may be used for any network related operation. +const ( + // NetTransportKey is the attribute Key conforming to the "net.transport" + // semantic conventions. It represents the transport protocol used. See + // note below. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + NetTransportKey = attribute.Key("net.transport") + + // NetAppProtocolNameKey is the attribute Key conforming to the + // "net.app.protocol.name" semantic conventions. It represents the + // application layer protocol used. The value SHOULD be normalized to + // lowercase. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'amqp', 'http', 'mqtt' + NetAppProtocolNameKey = attribute.Key("net.app.protocol.name") + + // NetAppProtocolVersionKey is the attribute Key conforming to the + // "net.app.protocol.version" semantic conventions. It represents the + // version of the application layer protocol used. See note below. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '3.1.1' + // Note: `net.app.protocol.version` refers to the version of the protocol + // used and might be different from the protocol client's version. If the + // HTTP client used has a version of `0.27.2`, but sends HTTP version + // `1.1`, this attribute should be set to `1.1`. + NetAppProtocolVersionKey = attribute.Key("net.app.protocol.version") + + // NetSockPeerNameKey is the attribute Key conforming to the + // "net.sock.peer.name" semantic conventions. It represents the remote + // socket peer name. + // + // Type: string + // RequirementLevel: Recommended (If available and different from + // `net.peer.name` and if `net.sock.peer.addr` is set.) + // Stability: stable + // Examples: 'proxy.example.com' + NetSockPeerNameKey = attribute.Key("net.sock.peer.name") + + // NetSockPeerAddrKey is the attribute Key conforming to the + // "net.sock.peer.addr" semantic conventions. It represents the remote + // socket peer address: IPv4 or IPv6 for internet protocols, path for local + // communication, + // [etc](https://man7.org/linux/man-pages/man7/address_families.7.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '127.0.0.1', '/tmp/mysql.sock' + NetSockPeerAddrKey = attribute.Key("net.sock.peer.addr") + + // NetSockPeerPortKey is the attribute Key conforming to the + // "net.sock.peer.port" semantic conventions. It represents the remote + // socket peer port. + // + // Type: int + // RequirementLevel: Recommended (If defined for the address family and if + // different than `net.peer.port` and if `net.sock.peer.addr` is set.) + // Stability: stable + // Examples: 16456 + NetSockPeerPortKey = attribute.Key("net.sock.peer.port") + + // NetSockFamilyKey is the attribute Key conforming to the + // "net.sock.family" semantic conventions. It represents the protocol + // [address + // family](https://man7.org/linux/man-pages/man7/address_families.7.html) + // which is used for communication. + // + // Type: Enum + // RequirementLevel: ConditionallyRequired (If different than `inet` and if + // any of `net.sock.peer.addr` or `net.sock.host.addr` are set. Consumers + // of telemetry SHOULD accept both IPv4 and IPv6 formats for the address in + // `net.sock.peer.addr` if `net.sock.family` is not set. This is to support + // instrumentations that follow previous versions of this document.) + // Stability: stable + // Examples: 'inet6', 'bluetooth' + NetSockFamilyKey = attribute.Key("net.sock.family") + + // NetPeerNameKey is the attribute Key conforming to the "net.peer.name" + // semantic conventions. It represents the logical remote hostname, see + // note below. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'example.com' + // Note: `net.peer.name` SHOULD NOT be set if capturing it would require an + // extra DNS lookup. + NetPeerNameKey = attribute.Key("net.peer.name") + + // NetPeerPortKey is the attribute Key conforming to the "net.peer.port" + // semantic conventions. It represents the logical remote port number + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 80, 8080, 443 + NetPeerPortKey = attribute.Key("net.peer.port") + + // NetHostNameKey is the attribute Key conforming to the "net.host.name" + // semantic conventions. It represents the logical local hostname or + // similar, see note below. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'localhost' + NetHostNameKey = attribute.Key("net.host.name") + + // NetHostPortKey is the attribute Key conforming to the "net.host.port" + // semantic conventions. It represents the logical local port number, + // preferably the one that the peer used to connect + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 8080 + NetHostPortKey = attribute.Key("net.host.port") + + // NetSockHostAddrKey is the attribute Key conforming to the + // "net.sock.host.addr" semantic conventions. It represents the local + // socket address. Useful in case of a multi-IP host. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '192.168.0.1' + NetSockHostAddrKey = attribute.Key("net.sock.host.addr") + + // NetSockHostPortKey is the attribute Key conforming to the + // "net.sock.host.port" semantic conventions. It represents the local + // socket port number. + // + // Type: int + // RequirementLevel: Recommended (If defined for the address family and if + // different than `net.host.port` and if `net.sock.host.addr` is set.) + // Stability: stable + // Examples: 35555 + NetSockHostPortKey = attribute.Key("net.sock.host.port") + + // NetHostConnectionTypeKey is the attribute Key conforming to the + // "net.host.connection.type" semantic conventions. It represents the + // internet connection type currently being used by the host. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Examples: 'wifi' + NetHostConnectionTypeKey = attribute.Key("net.host.connection.type") + + // NetHostConnectionSubtypeKey is the attribute Key conforming to the + // "net.host.connection.subtype" semantic conventions. It represents the + // this describes more details regarding the connection.type. It may be the + // type of cell technology connection, but it could be used for describing + // details about a wifi connection. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Examples: 'LTE' + NetHostConnectionSubtypeKey = attribute.Key("net.host.connection.subtype") + + // NetHostCarrierNameKey is the attribute Key conforming to the + // "net.host.carrier.name" semantic conventions. It represents the name of + // the mobile carrier. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'sprint' + NetHostCarrierNameKey = attribute.Key("net.host.carrier.name") + + // NetHostCarrierMccKey is the attribute Key conforming to the + // "net.host.carrier.mcc" semantic conventions. It represents the mobile + // carrier country code. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '310' + NetHostCarrierMccKey = attribute.Key("net.host.carrier.mcc") + + // NetHostCarrierMncKey is the attribute Key conforming to the + // "net.host.carrier.mnc" semantic conventions. It represents the mobile + // carrier network code. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '001' + NetHostCarrierMncKey = attribute.Key("net.host.carrier.mnc") + + // NetHostCarrierIccKey is the attribute Key conforming to the + // "net.host.carrier.icc" semantic conventions. It represents the ISO + // 3166-1 alpha-2 2-character country code associated with the mobile + // carrier network. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'DE' + NetHostCarrierIccKey = attribute.Key("net.host.carrier.icc") +) + +var ( + // ip_tcp + NetTransportTCP = NetTransportKey.String("ip_tcp") + // ip_udp + NetTransportUDP = NetTransportKey.String("ip_udp") + // Named or anonymous pipe. See note below + NetTransportPipe = NetTransportKey.String("pipe") + // In-process communication + NetTransportInProc = NetTransportKey.String("inproc") + // Something else (non IP-based) + NetTransportOther = NetTransportKey.String("other") +) + +var ( + // IPv4 address + NetSockFamilyInet = NetSockFamilyKey.String("inet") + // IPv6 address + NetSockFamilyInet6 = NetSockFamilyKey.String("inet6") + // Unix domain socket path + NetSockFamilyUnix = NetSockFamilyKey.String("unix") +) + +var ( + // wifi + NetHostConnectionTypeWifi = NetHostConnectionTypeKey.String("wifi") + // wired + NetHostConnectionTypeWired = NetHostConnectionTypeKey.String("wired") + // cell + NetHostConnectionTypeCell = NetHostConnectionTypeKey.String("cell") + // unavailable + NetHostConnectionTypeUnavailable = NetHostConnectionTypeKey.String("unavailable") + // unknown + NetHostConnectionTypeUnknown = NetHostConnectionTypeKey.String("unknown") +) + +var ( + // GPRS + NetHostConnectionSubtypeGprs = NetHostConnectionSubtypeKey.String("gprs") + // EDGE + NetHostConnectionSubtypeEdge = NetHostConnectionSubtypeKey.String("edge") + // UMTS + NetHostConnectionSubtypeUmts = NetHostConnectionSubtypeKey.String("umts") + // CDMA + NetHostConnectionSubtypeCdma = NetHostConnectionSubtypeKey.String("cdma") + // EVDO Rel. 0 + NetHostConnectionSubtypeEvdo0 = NetHostConnectionSubtypeKey.String("evdo_0") + // EVDO Rev. A + NetHostConnectionSubtypeEvdoA = NetHostConnectionSubtypeKey.String("evdo_a") + // CDMA2000 1XRTT + NetHostConnectionSubtypeCdma20001xrtt = NetHostConnectionSubtypeKey.String("cdma2000_1xrtt") + // HSDPA + NetHostConnectionSubtypeHsdpa = NetHostConnectionSubtypeKey.String("hsdpa") + // HSUPA + NetHostConnectionSubtypeHsupa = NetHostConnectionSubtypeKey.String("hsupa") + // HSPA + NetHostConnectionSubtypeHspa = NetHostConnectionSubtypeKey.String("hspa") + // IDEN + NetHostConnectionSubtypeIden = NetHostConnectionSubtypeKey.String("iden") + // EVDO Rev. B + NetHostConnectionSubtypeEvdoB = NetHostConnectionSubtypeKey.String("evdo_b") + // LTE + NetHostConnectionSubtypeLte = NetHostConnectionSubtypeKey.String("lte") + // EHRPD + NetHostConnectionSubtypeEhrpd = NetHostConnectionSubtypeKey.String("ehrpd") + // HSPAP + NetHostConnectionSubtypeHspap = NetHostConnectionSubtypeKey.String("hspap") + // GSM + NetHostConnectionSubtypeGsm = NetHostConnectionSubtypeKey.String("gsm") + // TD-SCDMA + NetHostConnectionSubtypeTdScdma = NetHostConnectionSubtypeKey.String("td_scdma") + // IWLAN + NetHostConnectionSubtypeIwlan = NetHostConnectionSubtypeKey.String("iwlan") + // 5G NR (New Radio) + NetHostConnectionSubtypeNr = NetHostConnectionSubtypeKey.String("nr") + // 5G NRNSA (New Radio Non-Standalone) + NetHostConnectionSubtypeNrnsa = NetHostConnectionSubtypeKey.String("nrnsa") + // LTE CA + NetHostConnectionSubtypeLteCa = NetHostConnectionSubtypeKey.String("lte_ca") +) + +// NetAppProtocolName returns an attribute KeyValue conforming to the +// "net.app.protocol.name" semantic conventions. It represents the application +// layer protocol used. The value SHOULD be normalized to lowercase. +func NetAppProtocolName(val string) attribute.KeyValue { + return NetAppProtocolNameKey.String(val) +} + +// NetAppProtocolVersion returns an attribute KeyValue conforming to the +// "net.app.protocol.version" semantic conventions. It represents the version +// of the application layer protocol used. See note below. +func NetAppProtocolVersion(val string) attribute.KeyValue { + return NetAppProtocolVersionKey.String(val) +} + +// NetSockPeerName returns an attribute KeyValue conforming to the +// "net.sock.peer.name" semantic conventions. It represents the remote socket +// peer name. +func NetSockPeerName(val string) attribute.KeyValue { + return NetSockPeerNameKey.String(val) +} + +// NetSockPeerAddr returns an attribute KeyValue conforming to the +// "net.sock.peer.addr" semantic conventions. It represents the remote socket +// peer address: IPv4 or IPv6 for internet protocols, path for local +// communication, +// [etc](https://man7.org/linux/man-pages/man7/address_families.7.html). +func NetSockPeerAddr(val string) attribute.KeyValue { + return NetSockPeerAddrKey.String(val) +} + +// NetSockPeerPort returns an attribute KeyValue conforming to the +// "net.sock.peer.port" semantic conventions. It represents the remote socket +// peer port. +func NetSockPeerPort(val int) attribute.KeyValue { + return NetSockPeerPortKey.Int(val) +} + +// NetPeerName returns an attribute KeyValue conforming to the +// "net.peer.name" semantic conventions. It represents the logical remote +// hostname, see note below. +func NetPeerName(val string) attribute.KeyValue { + return NetPeerNameKey.String(val) +} + +// NetPeerPort returns an attribute KeyValue conforming to the +// "net.peer.port" semantic conventions. It represents the logical remote port +// number +func NetPeerPort(val int) attribute.KeyValue { + return NetPeerPortKey.Int(val) +} + +// NetHostName returns an attribute KeyValue conforming to the +// "net.host.name" semantic conventions. It represents the logical local +// hostname or similar, see note below. +func NetHostName(val string) attribute.KeyValue { + return NetHostNameKey.String(val) +} + +// NetHostPort returns an attribute KeyValue conforming to the +// "net.host.port" semantic conventions. It represents the logical local port +// number, preferably the one that the peer used to connect +func NetHostPort(val int) attribute.KeyValue { + return NetHostPortKey.Int(val) +} + +// NetSockHostAddr returns an attribute KeyValue conforming to the +// "net.sock.host.addr" semantic conventions. It represents the local socket +// address. Useful in case of a multi-IP host. +func NetSockHostAddr(val string) attribute.KeyValue { + return NetSockHostAddrKey.String(val) +} + +// NetSockHostPort returns an attribute KeyValue conforming to the +// "net.sock.host.port" semantic conventions. It represents the local socket +// port number. +func NetSockHostPort(val int) attribute.KeyValue { + return NetSockHostPortKey.Int(val) +} + +// NetHostCarrierName returns an attribute KeyValue conforming to the +// "net.host.carrier.name" semantic conventions. It represents the name of the +// mobile carrier. +func NetHostCarrierName(val string) attribute.KeyValue { + return NetHostCarrierNameKey.String(val) +} + +// NetHostCarrierMcc returns an attribute KeyValue conforming to the +// "net.host.carrier.mcc" semantic conventions. It represents the mobile +// carrier country code. +func NetHostCarrierMcc(val string) attribute.KeyValue { + return NetHostCarrierMccKey.String(val) +} + +// NetHostCarrierMnc returns an attribute KeyValue conforming to the +// "net.host.carrier.mnc" semantic conventions. It represents the mobile +// carrier network code. +func NetHostCarrierMnc(val string) attribute.KeyValue { + return NetHostCarrierMncKey.String(val) +} + +// NetHostCarrierIcc returns an attribute KeyValue conforming to the +// "net.host.carrier.icc" semantic conventions. It represents the ISO 3166-1 +// alpha-2 2-character country code associated with the mobile carrier network. +func NetHostCarrierIcc(val string) attribute.KeyValue { + return NetHostCarrierIccKey.String(val) +} + +// Operations that access some remote service. +const ( + // PeerServiceKey is the attribute Key conforming to the "peer.service" + // semantic conventions. It represents the + // [`service.name`](../../resource/semantic_conventions/README.md#service) + // of the remote service. SHOULD be equal to the actual `service.name` + // resource attribute of the remote service if any. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'AuthTokenCache' + PeerServiceKey = attribute.Key("peer.service") +) + +// PeerService returns an attribute KeyValue conforming to the +// "peer.service" semantic conventions. It represents the +// [`service.name`](../../resource/semantic_conventions/README.md#service) of +// the remote service. SHOULD be equal to the actual `service.name` resource +// attribute of the remote service if any. +func PeerService(val string) attribute.KeyValue { + return PeerServiceKey.String(val) +} + +// These attributes may be used for any operation with an authenticated and/or +// authorized enduser. +const ( + // EnduserIDKey is the attribute Key conforming to the "enduser.id" + // semantic conventions. It represents the username or client_id extracted + // from the access token or + // [Authorization](https://tools.ietf.org/html/rfc7235#section-4.2) header + // in the inbound request from outside the system. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'username' + EnduserIDKey = attribute.Key("enduser.id") + + // EnduserRoleKey is the attribute Key conforming to the "enduser.role" + // semantic conventions. It represents the actual/assumed role the client + // is making the request under extracted from token or application security + // context. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'admin' + EnduserRoleKey = attribute.Key("enduser.role") + + // EnduserScopeKey is the attribute Key conforming to the "enduser.scope" + // semantic conventions. It represents the scopes or granted authorities + // the client currently possesses extracted from token or application + // security context. The value would come from the scope associated with an + // [OAuth 2.0 Access + // Token](https://tools.ietf.org/html/rfc6749#section-3.3) or an attribute + // value in a [SAML 2.0 + // Assertion](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'read:message, write:files' + EnduserScopeKey = attribute.Key("enduser.scope") +) + +// EnduserID returns an attribute KeyValue conforming to the "enduser.id" +// semantic conventions. It represents the username or client_id extracted from +// the access token or +// [Authorization](https://tools.ietf.org/html/rfc7235#section-4.2) header in +// the inbound request from outside the system. +func EnduserID(val string) attribute.KeyValue { + return EnduserIDKey.String(val) +} + +// EnduserRole returns an attribute KeyValue conforming to the +// "enduser.role" semantic conventions. It represents the actual/assumed role +// the client is making the request under extracted from token or application +// security context. +func EnduserRole(val string) attribute.KeyValue { + return EnduserRoleKey.String(val) +} + +// EnduserScope returns an attribute KeyValue conforming to the +// "enduser.scope" semantic conventions. It represents the scopes or granted +// authorities the client currently possesses extracted from token or +// application security context. The value would come from the scope associated +// with an [OAuth 2.0 Access +// Token](https://tools.ietf.org/html/rfc6749#section-3.3) or an attribute +// value in a [SAML 2.0 +// Assertion](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html). +func EnduserScope(val string) attribute.KeyValue { + return EnduserScopeKey.String(val) +} + +// These attributes may be used for any operation to store information about a +// thread that started a span. +const ( + // ThreadIDKey is the attribute Key conforming to the "thread.id" semantic + // conventions. It represents the current "managed" thread ID (as opposed + // to OS thread ID). + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 42 + ThreadIDKey = attribute.Key("thread.id") + + // ThreadNameKey is the attribute Key conforming to the "thread.name" + // semantic conventions. It represents the current thread name. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'main' + ThreadNameKey = attribute.Key("thread.name") +) + +// ThreadID returns an attribute KeyValue conforming to the "thread.id" +// semantic conventions. It represents the current "managed" thread ID (as +// opposed to OS thread ID). +func ThreadID(val int) attribute.KeyValue { + return ThreadIDKey.Int(val) +} + +// ThreadName returns an attribute KeyValue conforming to the "thread.name" +// semantic conventions. It represents the current thread name. +func ThreadName(val string) attribute.KeyValue { + return ThreadNameKey.String(val) +} + +// These attributes allow to report this unit of code and therefore to provide +// more context about the span. +const ( + // CodeFunctionKey is the attribute Key conforming to the "code.function" + // semantic conventions. It represents the method or function name, or + // equivalent (usually rightmost part of the code unit's name). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'serveRequest' + CodeFunctionKey = attribute.Key("code.function") + + // CodeNamespaceKey is the attribute Key conforming to the "code.namespace" + // semantic conventions. It represents the "namespace" within which + // `code.function` is defined. Usually the qualified class or module name, + // such that `code.namespace` + some separator + `code.function` form a + // unique identifier for the code unit. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'com.example.MyHTTPService' + CodeNamespaceKey = attribute.Key("code.namespace") + + // CodeFilepathKey is the attribute Key conforming to the "code.filepath" + // semantic conventions. It represents the source code file name that + // identifies the code unit as uniquely as possible (preferably an absolute + // file path). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '/usr/local/MyApplication/content_root/app/index.php' + CodeFilepathKey = attribute.Key("code.filepath") + + // CodeLineNumberKey is the attribute Key conforming to the "code.lineno" + // semantic conventions. It represents the line number in `code.filepath` + // best representing the operation. It SHOULD point within the code unit + // named in `code.function`. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 42 + CodeLineNumberKey = attribute.Key("code.lineno") + + // CodeColumnKey is the attribute Key conforming to the "code.column" + // semantic conventions. It represents the column number in `code.filepath` + // best representing the operation. It SHOULD point within the code unit + // named in `code.function`. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 16 + CodeColumnKey = attribute.Key("code.column") +) + +// CodeFunction returns an attribute KeyValue conforming to the +// "code.function" semantic conventions. It represents the method or function +// name, or equivalent (usually rightmost part of the code unit's name). +func CodeFunction(val string) attribute.KeyValue { + return CodeFunctionKey.String(val) +} + +// CodeNamespace returns an attribute KeyValue conforming to the +// "code.namespace" semantic conventions. It represents the "namespace" within +// which `code.function` is defined. Usually the qualified class or module +// name, such that `code.namespace` + some separator + `code.function` form a +// unique identifier for the code unit. +func CodeNamespace(val string) attribute.KeyValue { + return CodeNamespaceKey.String(val) +} + +// CodeFilepath returns an attribute KeyValue conforming to the +// "code.filepath" semantic conventions. It represents the source code file +// name that identifies the code unit as uniquely as possible (preferably an +// absolute file path). +func CodeFilepath(val string) attribute.KeyValue { + return CodeFilepathKey.String(val) +} + +// CodeLineNumber returns an attribute KeyValue conforming to the "code.lineno" +// semantic conventions. It represents the line number in `code.filepath` best +// representing the operation. It SHOULD point within the code unit named in +// `code.function`. +func CodeLineNumber(val int) attribute.KeyValue { + return CodeLineNumberKey.Int(val) +} + +// CodeColumn returns an attribute KeyValue conforming to the "code.column" +// semantic conventions. It represents the column number in `code.filepath` +// best representing the operation. It SHOULD point within the code unit named +// in `code.function`. +func CodeColumn(val int) attribute.KeyValue { + return CodeColumnKey.Int(val) +} + +// Semantic conventions for HTTP client and server Spans. +const ( + // HTTPMethodKey is the attribute Key conforming to the "http.method" + // semantic conventions. It represents the hTTP request method. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'GET', 'POST', 'HEAD' + HTTPMethodKey = attribute.Key("http.method") + + // HTTPStatusCodeKey is the attribute Key conforming to the + // "http.status_code" semantic conventions. It represents the [HTTP + // response status code](https://tools.ietf.org/html/rfc7231#section-6). + // + // Type: int + // RequirementLevel: ConditionallyRequired (If and only if one was + // received/sent.) + // Stability: stable + // Examples: 200 + HTTPStatusCodeKey = attribute.Key("http.status_code") + + // HTTPFlavorKey is the attribute Key conforming to the "http.flavor" + // semantic conventions. It represents the kind of HTTP protocol used. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: If `net.transport` is not specified, it can be assumed to be + // `IP.TCP` except if `http.flavor` is `QUIC`, in which case `IP.UDP` is + // assumed. + HTTPFlavorKey = attribute.Key("http.flavor") + + // HTTPUserAgentKey is the attribute Key conforming to the + // "http.user_agent" semantic conventions. It represents the value of the + // [HTTP + // User-Agent](https://www.rfc-editor.org/rfc/rfc9110.html#field.user-agent) + // header sent by the client. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'CERN-LineMode/2.15 libwww/2.17b3' + HTTPUserAgentKey = attribute.Key("http.user_agent") + + // HTTPRequestContentLengthKey is the attribute Key conforming to the + // "http.request_content_length" semantic conventions. It represents the + // size of the request payload body in bytes. This is the number of bytes + // transferred excluding headers and is often, but not always, present as + // the + // [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) + // header. For requests using transport encoding, this should be the + // compressed size. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 3495 + HTTPRequestContentLengthKey = attribute.Key("http.request_content_length") + + // HTTPResponseContentLengthKey is the attribute Key conforming to the + // "http.response_content_length" semantic conventions. It represents the + // size of the response payload body in bytes. This is the number of bytes + // transferred excluding headers and is often, but not always, present as + // the + // [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) + // header. For requests using transport encoding, this should be the + // compressed size. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 3495 + HTTPResponseContentLengthKey = attribute.Key("http.response_content_length") +) + +var ( + // HTTP/1.0 + HTTPFlavorHTTP10 = HTTPFlavorKey.String("1.0") + // HTTP/1.1 + HTTPFlavorHTTP11 = HTTPFlavorKey.String("1.1") + // HTTP/2 + HTTPFlavorHTTP20 = HTTPFlavorKey.String("2.0") + // HTTP/3 + HTTPFlavorHTTP30 = HTTPFlavorKey.String("3.0") + // SPDY protocol + HTTPFlavorSPDY = HTTPFlavorKey.String("SPDY") + // QUIC protocol + HTTPFlavorQUIC = HTTPFlavorKey.String("QUIC") +) + +// HTTPMethod returns an attribute KeyValue conforming to the "http.method" +// semantic conventions. It represents the hTTP request method. +func HTTPMethod(val string) attribute.KeyValue { + return HTTPMethodKey.String(val) +} + +// HTTPStatusCode returns an attribute KeyValue conforming to the +// "http.status_code" semantic conventions. It represents the [HTTP response +// status code](https://tools.ietf.org/html/rfc7231#section-6). +func HTTPStatusCode(val int) attribute.KeyValue { + return HTTPStatusCodeKey.Int(val) +} + +// HTTPUserAgent returns an attribute KeyValue conforming to the +// "http.user_agent" semantic conventions. It represents the value of the [HTTP +// User-Agent](https://www.rfc-editor.org/rfc/rfc9110.html#field.user-agent) +// header sent by the client. +func HTTPUserAgent(val string) attribute.KeyValue { + return HTTPUserAgentKey.String(val) +} + +// HTTPRequestContentLength returns an attribute KeyValue conforming to the +// "http.request_content_length" semantic conventions. It represents the size +// of the request payload body in bytes. This is the number of bytes +// transferred excluding headers and is often, but not always, present as the +// [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) +// header. For requests using transport encoding, this should be the compressed +// size. +func HTTPRequestContentLength(val int) attribute.KeyValue { + return HTTPRequestContentLengthKey.Int(val) +} + +// HTTPResponseContentLength returns an attribute KeyValue conforming to the +// "http.response_content_length" semantic conventions. It represents the size +// of the response payload body in bytes. This is the number of bytes +// transferred excluding headers and is often, but not always, present as the +// [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) +// header. For requests using transport encoding, this should be the compressed +// size. +func HTTPResponseContentLength(val int) attribute.KeyValue { + return HTTPResponseContentLengthKey.Int(val) +} + +// Semantic Convention for HTTP Client +const ( + // HTTPURLKey is the attribute Key conforming to the "http.url" semantic + // conventions. It represents the full HTTP request URL in the form + // `scheme://host[:port]/path?query[#fragment]`. Usually the fragment is + // not transmitted over HTTP, but if it is known, it should be included + // nevertheless. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'https://www.foo.bar/search?q=OpenTelemetry#SemConv' + // Note: `http.url` MUST NOT contain credentials passed via URL in form of + // `https://username:password@www.example.com/`. In such case the + // attribute's value should be `https://www.example.com/`. + HTTPURLKey = attribute.Key("http.url") + + // HTTPResendCountKey is the attribute Key conforming to the + // "http.resend_count" semantic conventions. It represents the ordinal + // number of request resending attempt (for any reason, including + // redirects). + // + // Type: int + // RequirementLevel: Recommended (if and only if request was retried.) + // Stability: stable + // Examples: 3 + // Note: The resend count SHOULD be updated each time an HTTP request gets + // resent by the client, regardless of what was the cause of the resending + // (e.g. redirection, authorization failure, 503 Server Unavailable, + // network issues, or any other). + HTTPResendCountKey = attribute.Key("http.resend_count") +) + +// HTTPURL returns an attribute KeyValue conforming to the "http.url" +// semantic conventions. It represents the full HTTP request URL in the form +// `scheme://host[:port]/path?query[#fragment]`. Usually the fragment is not +// transmitted over HTTP, but if it is known, it should be included +// nevertheless. +func HTTPURL(val string) attribute.KeyValue { + return HTTPURLKey.String(val) +} + +// HTTPResendCount returns an attribute KeyValue conforming to the +// "http.resend_count" semantic conventions. It represents the ordinal number +// of request resending attempt (for any reason, including redirects). +func HTTPResendCount(val int) attribute.KeyValue { + return HTTPResendCountKey.Int(val) +} + +// Semantic Convention for HTTP Server +const ( + // HTTPSchemeKey is the attribute Key conforming to the "http.scheme" + // semantic conventions. It represents the URI scheme identifying the used + // protocol. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'http', 'https' + HTTPSchemeKey = attribute.Key("http.scheme") + + // HTTPTargetKey is the attribute Key conforming to the "http.target" + // semantic conventions. It represents the full request target as passed in + // a HTTP request line or equivalent. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: '/path/12314/?q=ddds' + HTTPTargetKey = attribute.Key("http.target") + + // HTTPRouteKey is the attribute Key conforming to the "http.route" + // semantic conventions. It represents the matched route (path template in + // the format used by the respective server framework). See note below + // + // Type: string + // RequirementLevel: ConditionallyRequired (If and only if it's available) + // Stability: stable + // Examples: '/users/:userID?', '{controller}/{action}/{id?}' + // Note: 'http.route' MUST NOT be populated when this is not supported by + // the HTTP server framework as the route attribute should have + // low-cardinality and the URI path can NOT substitute it. + HTTPRouteKey = attribute.Key("http.route") + + // HTTPClientIPKey is the attribute Key conforming to the "http.client_ip" + // semantic conventions. It represents the IP address of the original + // client behind all proxies, if known (e.g. from + // [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '83.164.160.102' + // Note: This is not necessarily the same as `net.sock.peer.addr`, which + // would + // identify the network-level peer, which may be a proxy. + // + // This attribute should be set when a source of information different + // from the one used for `net.sock.peer.addr`, is available even if that + // other + // source just confirms the same value as `net.sock.peer.addr`. + // Rationale: For `net.sock.peer.addr`, one typically does not know if it + // comes from a proxy, reverse proxy, or the actual client. Setting + // `http.client_ip` when it's the same as `net.sock.peer.addr` means that + // one is at least somewhat confident that the address is not that of + // the closest proxy. + HTTPClientIPKey = attribute.Key("http.client_ip") +) + +// HTTPScheme returns an attribute KeyValue conforming to the "http.scheme" +// semantic conventions. It represents the URI scheme identifying the used +// protocol. +func HTTPScheme(val string) attribute.KeyValue { + return HTTPSchemeKey.String(val) +} + +// HTTPTarget returns an attribute KeyValue conforming to the "http.target" +// semantic conventions. It represents the full request target as passed in a +// HTTP request line or equivalent. +func HTTPTarget(val string) attribute.KeyValue { + return HTTPTargetKey.String(val) +} + +// HTTPRoute returns an attribute KeyValue conforming to the "http.route" +// semantic conventions. It represents the matched route (path template in the +// format used by the respective server framework). See note below +func HTTPRoute(val string) attribute.KeyValue { + return HTTPRouteKey.String(val) +} + +// HTTPClientIP returns an attribute KeyValue conforming to the +// "http.client_ip" semantic conventions. It represents the IP address of the +// original client behind all proxies, if known (e.g. from +// [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)). +func HTTPClientIP(val string) attribute.KeyValue { + return HTTPClientIPKey.String(val) +} + +// Attributes that exist for multiple DynamoDB request types. +const ( + // AWSDynamoDBTableNamesKey is the attribute Key conforming to the + // "aws.dynamodb.table_names" semantic conventions. It represents the keys + // in the `RequestItems` object field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Users', 'Cats' + AWSDynamoDBTableNamesKey = attribute.Key("aws.dynamodb.table_names") + + // AWSDynamoDBConsumedCapacityKey is the attribute Key conforming to the + // "aws.dynamodb.consumed_capacity" semantic conventions. It represents the + // JSON-serialized value of each item in the `ConsumedCapacity` response + // field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "CapacityUnits": number, "GlobalSecondaryIndexes": { + // "string" : { "CapacityUnits": number, "ReadCapacityUnits": number, + // "WriteCapacityUnits": number } }, "LocalSecondaryIndexes": { "string" : + // { "CapacityUnits": number, "ReadCapacityUnits": number, + // "WriteCapacityUnits": number } }, "ReadCapacityUnits": number, "Table": + // { "CapacityUnits": number, "ReadCapacityUnits": number, + // "WriteCapacityUnits": number }, "TableName": "string", + // "WriteCapacityUnits": number }' + AWSDynamoDBConsumedCapacityKey = attribute.Key("aws.dynamodb.consumed_capacity") + + // AWSDynamoDBItemCollectionMetricsKey is the attribute Key conforming to + // the "aws.dynamodb.item_collection_metrics" semantic conventions. It + // represents the JSON-serialized value of the `ItemCollectionMetrics` + // response field. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "string" : [ { "ItemCollectionKey": { "string" : { "B": + // blob, "BOOL": boolean, "BS": [ blob ], "L": [ "AttributeValue" ], "M": { + // "string" : "AttributeValue" }, "N": "string", "NS": [ "string" ], + // "NULL": boolean, "S": "string", "SS": [ "string" ] } }, + // "SizeEstimateRangeGB": [ number ] } ] }' + AWSDynamoDBItemCollectionMetricsKey = attribute.Key("aws.dynamodb.item_collection_metrics") + + // AWSDynamoDBProvisionedReadCapacityKey is the attribute Key conforming to + // the "aws.dynamodb.provisioned_read_capacity" semantic conventions. It + // represents the value of the `ProvisionedThroughput.ReadCapacityUnits` + // request parameter. + // + // Type: double + // RequirementLevel: Optional + // Stability: stable + // Examples: 1.0, 2.0 + AWSDynamoDBProvisionedReadCapacityKey = attribute.Key("aws.dynamodb.provisioned_read_capacity") + + // AWSDynamoDBProvisionedWriteCapacityKey is the attribute Key conforming + // to the "aws.dynamodb.provisioned_write_capacity" semantic conventions. + // It represents the value of the + // `ProvisionedThroughput.WriteCapacityUnits` request parameter. + // + // Type: double + // RequirementLevel: Optional + // Stability: stable + // Examples: 1.0, 2.0 + AWSDynamoDBProvisionedWriteCapacityKey = attribute.Key("aws.dynamodb.provisioned_write_capacity") + + // AWSDynamoDBConsistentReadKey is the attribute Key conforming to the + // "aws.dynamodb.consistent_read" semantic conventions. It represents the + // value of the `ConsistentRead` request parameter. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + AWSDynamoDBConsistentReadKey = attribute.Key("aws.dynamodb.consistent_read") + + // AWSDynamoDBProjectionKey is the attribute Key conforming to the + // "aws.dynamodb.projection" semantic conventions. It represents the value + // of the `ProjectionExpression` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Title', 'Title, Price, Color', 'Title, Description, + // RelatedItems, ProductReviews' + AWSDynamoDBProjectionKey = attribute.Key("aws.dynamodb.projection") + + // AWSDynamoDBLimitKey is the attribute Key conforming to the + // "aws.dynamodb.limit" semantic conventions. It represents the value of + // the `Limit` request parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 10 + AWSDynamoDBLimitKey = attribute.Key("aws.dynamodb.limit") + + // AWSDynamoDBAttributesToGetKey is the attribute Key conforming to the + // "aws.dynamodb.attributes_to_get" semantic conventions. It represents the + // value of the `AttributesToGet` request parameter. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'lives', 'id' + AWSDynamoDBAttributesToGetKey = attribute.Key("aws.dynamodb.attributes_to_get") + + // AWSDynamoDBIndexNameKey is the attribute Key conforming to the + // "aws.dynamodb.index_name" semantic conventions. It represents the value + // of the `IndexName` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'name_to_group' + AWSDynamoDBIndexNameKey = attribute.Key("aws.dynamodb.index_name") + + // AWSDynamoDBSelectKey is the attribute Key conforming to the + // "aws.dynamodb.select" semantic conventions. It represents the value of + // the `Select` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'ALL_ATTRIBUTES', 'COUNT' + AWSDynamoDBSelectKey = attribute.Key("aws.dynamodb.select") +) + +// AWSDynamoDBTableNames returns an attribute KeyValue conforming to the +// "aws.dynamodb.table_names" semantic conventions. It represents the keys in +// the `RequestItems` object field. +func AWSDynamoDBTableNames(val ...string) attribute.KeyValue { + return AWSDynamoDBTableNamesKey.StringSlice(val) +} + +// AWSDynamoDBConsumedCapacity returns an attribute KeyValue conforming to +// the "aws.dynamodb.consumed_capacity" semantic conventions. It represents the +// JSON-serialized value of each item in the `ConsumedCapacity` response field. +func AWSDynamoDBConsumedCapacity(val ...string) attribute.KeyValue { + return AWSDynamoDBConsumedCapacityKey.StringSlice(val) +} + +// AWSDynamoDBItemCollectionMetrics returns an attribute KeyValue conforming +// to the "aws.dynamodb.item_collection_metrics" semantic conventions. It +// represents the JSON-serialized value of the `ItemCollectionMetrics` response +// field. +func AWSDynamoDBItemCollectionMetrics(val string) attribute.KeyValue { + return AWSDynamoDBItemCollectionMetricsKey.String(val) +} + +// AWSDynamoDBProvisionedReadCapacity returns an attribute KeyValue +// conforming to the "aws.dynamodb.provisioned_read_capacity" semantic +// conventions. It represents the value of the +// `ProvisionedThroughput.ReadCapacityUnits` request parameter. +func AWSDynamoDBProvisionedReadCapacity(val float64) attribute.KeyValue { + return AWSDynamoDBProvisionedReadCapacityKey.Float64(val) +} + +// AWSDynamoDBProvisionedWriteCapacity returns an attribute KeyValue +// conforming to the "aws.dynamodb.provisioned_write_capacity" semantic +// conventions. It represents the value of the +// `ProvisionedThroughput.WriteCapacityUnits` request parameter. +func AWSDynamoDBProvisionedWriteCapacity(val float64) attribute.KeyValue { + return AWSDynamoDBProvisionedWriteCapacityKey.Float64(val) +} + +// AWSDynamoDBConsistentRead returns an attribute KeyValue conforming to the +// "aws.dynamodb.consistent_read" semantic conventions. It represents the value +// of the `ConsistentRead` request parameter. +func AWSDynamoDBConsistentRead(val bool) attribute.KeyValue { + return AWSDynamoDBConsistentReadKey.Bool(val) +} + +// AWSDynamoDBProjection returns an attribute KeyValue conforming to the +// "aws.dynamodb.projection" semantic conventions. It represents the value of +// the `ProjectionExpression` request parameter. +func AWSDynamoDBProjection(val string) attribute.KeyValue { + return AWSDynamoDBProjectionKey.String(val) +} + +// AWSDynamoDBLimit returns an attribute KeyValue conforming to the +// "aws.dynamodb.limit" semantic conventions. It represents the value of the +// `Limit` request parameter. +func AWSDynamoDBLimit(val int) attribute.KeyValue { + return AWSDynamoDBLimitKey.Int(val) +} + +// AWSDynamoDBAttributesToGet returns an attribute KeyValue conforming to +// the "aws.dynamodb.attributes_to_get" semantic conventions. It represents the +// value of the `AttributesToGet` request parameter. +func AWSDynamoDBAttributesToGet(val ...string) attribute.KeyValue { + return AWSDynamoDBAttributesToGetKey.StringSlice(val) +} + +// AWSDynamoDBIndexName returns an attribute KeyValue conforming to the +// "aws.dynamodb.index_name" semantic conventions. It represents the value of +// the `IndexName` request parameter. +func AWSDynamoDBIndexName(val string) attribute.KeyValue { + return AWSDynamoDBIndexNameKey.String(val) +} + +// AWSDynamoDBSelect returns an attribute KeyValue conforming to the +// "aws.dynamodb.select" semantic conventions. It represents the value of the +// `Select` request parameter. +func AWSDynamoDBSelect(val string) attribute.KeyValue { + return AWSDynamoDBSelectKey.String(val) +} + +// DynamoDB.CreateTable +const ( + // AWSDynamoDBGlobalSecondaryIndexesKey is the attribute Key conforming to + // the "aws.dynamodb.global_secondary_indexes" semantic conventions. It + // represents the JSON-serialized value of each item of the + // `GlobalSecondaryIndexes` request field + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "IndexName": "string", "KeySchema": [ { "AttributeName": + // "string", "KeyType": "string" } ], "Projection": { "NonKeyAttributes": [ + // "string" ], "ProjectionType": "string" }, "ProvisionedThroughput": { + // "ReadCapacityUnits": number, "WriteCapacityUnits": number } }' + AWSDynamoDBGlobalSecondaryIndexesKey = attribute.Key("aws.dynamodb.global_secondary_indexes") + + // AWSDynamoDBLocalSecondaryIndexesKey is the attribute Key conforming to + // the "aws.dynamodb.local_secondary_indexes" semantic conventions. It + // represents the JSON-serialized value of each item of the + // `LocalSecondaryIndexes` request field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "IndexARN": "string", "IndexName": "string", + // "IndexSizeBytes": number, "ItemCount": number, "KeySchema": [ { + // "AttributeName": "string", "KeyType": "string" } ], "Projection": { + // "NonKeyAttributes": [ "string" ], "ProjectionType": "string" } }' + AWSDynamoDBLocalSecondaryIndexesKey = attribute.Key("aws.dynamodb.local_secondary_indexes") +) + +// AWSDynamoDBGlobalSecondaryIndexes returns an attribute KeyValue +// conforming to the "aws.dynamodb.global_secondary_indexes" semantic +// conventions. It represents the JSON-serialized value of each item of the +// `GlobalSecondaryIndexes` request field +func AWSDynamoDBGlobalSecondaryIndexes(val ...string) attribute.KeyValue { + return AWSDynamoDBGlobalSecondaryIndexesKey.StringSlice(val) +} + +// AWSDynamoDBLocalSecondaryIndexes returns an attribute KeyValue conforming +// to the "aws.dynamodb.local_secondary_indexes" semantic conventions. It +// represents the JSON-serialized value of each item of the +// `LocalSecondaryIndexes` request field. +func AWSDynamoDBLocalSecondaryIndexes(val ...string) attribute.KeyValue { + return AWSDynamoDBLocalSecondaryIndexesKey.StringSlice(val) +} + +// DynamoDB.ListTables +const ( + // AWSDynamoDBExclusiveStartTableKey is the attribute Key conforming to the + // "aws.dynamodb.exclusive_start_table" semantic conventions. It represents + // the value of the `ExclusiveStartTableName` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Users', 'CatsTable' + AWSDynamoDBExclusiveStartTableKey = attribute.Key("aws.dynamodb.exclusive_start_table") + + // AWSDynamoDBTableCountKey is the attribute Key conforming to the + // "aws.dynamodb.table_count" semantic conventions. It represents the the + // number of items in the `TableNames` response parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 20 + AWSDynamoDBTableCountKey = attribute.Key("aws.dynamodb.table_count") +) + +// AWSDynamoDBExclusiveStartTable returns an attribute KeyValue conforming +// to the "aws.dynamodb.exclusive_start_table" semantic conventions. It +// represents the value of the `ExclusiveStartTableName` request parameter. +func AWSDynamoDBExclusiveStartTable(val string) attribute.KeyValue { + return AWSDynamoDBExclusiveStartTableKey.String(val) +} + +// AWSDynamoDBTableCount returns an attribute KeyValue conforming to the +// "aws.dynamodb.table_count" semantic conventions. It represents the the +// number of items in the `TableNames` response parameter. +func AWSDynamoDBTableCount(val int) attribute.KeyValue { + return AWSDynamoDBTableCountKey.Int(val) +} + +// DynamoDB.Query +const ( + // AWSDynamoDBScanForwardKey is the attribute Key conforming to the + // "aws.dynamodb.scan_forward" semantic conventions. It represents the + // value of the `ScanIndexForward` request parameter. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + AWSDynamoDBScanForwardKey = attribute.Key("aws.dynamodb.scan_forward") +) + +// AWSDynamoDBScanForward returns an attribute KeyValue conforming to the +// "aws.dynamodb.scan_forward" semantic conventions. It represents the value of +// the `ScanIndexForward` request parameter. +func AWSDynamoDBScanForward(val bool) attribute.KeyValue { + return AWSDynamoDBScanForwardKey.Bool(val) +} + +// DynamoDB.Scan +const ( + // AWSDynamoDBSegmentKey is the attribute Key conforming to the + // "aws.dynamodb.segment" semantic conventions. It represents the value of + // the `Segment` request parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 10 + AWSDynamoDBSegmentKey = attribute.Key("aws.dynamodb.segment") + + // AWSDynamoDBTotalSegmentsKey is the attribute Key conforming to the + // "aws.dynamodb.total_segments" semantic conventions. It represents the + // value of the `TotalSegments` request parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 100 + AWSDynamoDBTotalSegmentsKey = attribute.Key("aws.dynamodb.total_segments") + + // AWSDynamoDBCountKey is the attribute Key conforming to the + // "aws.dynamodb.count" semantic conventions. It represents the value of + // the `Count` response parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 10 + AWSDynamoDBCountKey = attribute.Key("aws.dynamodb.count") + + // AWSDynamoDBScannedCountKey is the attribute Key conforming to the + // "aws.dynamodb.scanned_count" semantic conventions. It represents the + // value of the `ScannedCount` response parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 50 + AWSDynamoDBScannedCountKey = attribute.Key("aws.dynamodb.scanned_count") +) + +// AWSDynamoDBSegment returns an attribute KeyValue conforming to the +// "aws.dynamodb.segment" semantic conventions. It represents the value of the +// `Segment` request parameter. +func AWSDynamoDBSegment(val int) attribute.KeyValue { + return AWSDynamoDBSegmentKey.Int(val) +} + +// AWSDynamoDBTotalSegments returns an attribute KeyValue conforming to the +// "aws.dynamodb.total_segments" semantic conventions. It represents the value +// of the `TotalSegments` request parameter. +func AWSDynamoDBTotalSegments(val int) attribute.KeyValue { + return AWSDynamoDBTotalSegmentsKey.Int(val) +} + +// AWSDynamoDBCount returns an attribute KeyValue conforming to the +// "aws.dynamodb.count" semantic conventions. It represents the value of the +// `Count` response parameter. +func AWSDynamoDBCount(val int) attribute.KeyValue { + return AWSDynamoDBCountKey.Int(val) +} + +// AWSDynamoDBScannedCount returns an attribute KeyValue conforming to the +// "aws.dynamodb.scanned_count" semantic conventions. It represents the value +// of the `ScannedCount` response parameter. +func AWSDynamoDBScannedCount(val int) attribute.KeyValue { + return AWSDynamoDBScannedCountKey.Int(val) +} + +// DynamoDB.UpdateTable +const ( + // AWSDynamoDBAttributeDefinitionsKey is the attribute Key conforming to + // the "aws.dynamodb.attribute_definitions" semantic conventions. It + // represents the JSON-serialized value of each item in the + // `AttributeDefinitions` request field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "AttributeName": "string", "AttributeType": "string" }' + AWSDynamoDBAttributeDefinitionsKey = attribute.Key("aws.dynamodb.attribute_definitions") + + // AWSDynamoDBGlobalSecondaryIndexUpdatesKey is the attribute Key + // conforming to the "aws.dynamodb.global_secondary_index_updates" semantic + // conventions. It represents the JSON-serialized value of each item in the + // the `GlobalSecondaryIndexUpdates` request field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "Create": { "IndexName": "string", "KeySchema": [ { + // "AttributeName": "string", "KeyType": "string" } ], "Projection": { + // "NonKeyAttributes": [ "string" ], "ProjectionType": "string" }, + // "ProvisionedThroughput": { "ReadCapacityUnits": number, + // "WriteCapacityUnits": number } }' + AWSDynamoDBGlobalSecondaryIndexUpdatesKey = attribute.Key("aws.dynamodb.global_secondary_index_updates") +) + +// AWSDynamoDBAttributeDefinitions returns an attribute KeyValue conforming +// to the "aws.dynamodb.attribute_definitions" semantic conventions. It +// represents the JSON-serialized value of each item in the +// `AttributeDefinitions` request field. +func AWSDynamoDBAttributeDefinitions(val ...string) attribute.KeyValue { + return AWSDynamoDBAttributeDefinitionsKey.StringSlice(val) +} + +// AWSDynamoDBGlobalSecondaryIndexUpdates returns an attribute KeyValue +// conforming to the "aws.dynamodb.global_secondary_index_updates" semantic +// conventions. It represents the JSON-serialized value of each item in the the +// `GlobalSecondaryIndexUpdates` request field. +func AWSDynamoDBGlobalSecondaryIndexUpdates(val ...string) attribute.KeyValue { + return AWSDynamoDBGlobalSecondaryIndexUpdatesKey.StringSlice(val) +} + +// Semantic conventions to apply when instrumenting the GraphQL implementation. +// They map GraphQL operations to attributes on a Span. +const ( + // GraphqlOperationNameKey is the attribute Key conforming to the + // "graphql.operation.name" semantic conventions. It represents the name of + // the operation being executed. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'findBookByID' + GraphqlOperationNameKey = attribute.Key("graphql.operation.name") + + // GraphqlOperationTypeKey is the attribute Key conforming to the + // "graphql.operation.type" semantic conventions. It represents the type of + // the operation being executed. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Examples: 'query', 'mutation', 'subscription' + GraphqlOperationTypeKey = attribute.Key("graphql.operation.type") + + // GraphqlDocumentKey is the attribute Key conforming to the + // "graphql.document" semantic conventions. It represents the GraphQL + // document being executed. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'query findBookByID { bookByID(id: ?) { name } }' + // Note: The value may be sanitized to exclude sensitive information. + GraphqlDocumentKey = attribute.Key("graphql.document") +) + +var ( + // GraphQL query + GraphqlOperationTypeQuery = GraphqlOperationTypeKey.String("query") + // GraphQL mutation + GraphqlOperationTypeMutation = GraphqlOperationTypeKey.String("mutation") + // GraphQL subscription + GraphqlOperationTypeSubscription = GraphqlOperationTypeKey.String("subscription") +) + +// GraphqlOperationName returns an attribute KeyValue conforming to the +// "graphql.operation.name" semantic conventions. It represents the name of the +// operation being executed. +func GraphqlOperationName(val string) attribute.KeyValue { + return GraphqlOperationNameKey.String(val) +} + +// GraphqlDocument returns an attribute KeyValue conforming to the +// "graphql.document" semantic conventions. It represents the GraphQL document +// being executed. +func GraphqlDocument(val string) attribute.KeyValue { + return GraphqlDocumentKey.String(val) +} + +// Semantic convention describing per-message attributes populated on messaging +// spans or links. +const ( + // MessagingMessageIDKey is the attribute Key conforming to the + // "messaging.message.id" semantic conventions. It represents a value used + // by the messaging system as an identifier for the message, represented as + // a string. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '452a7c7c7c7048c2f887f61572b18fc2' + MessagingMessageIDKey = attribute.Key("messaging.message.id") + + // MessagingMessageConversationIDKey is the attribute Key conforming to the + // "messaging.message.conversation_id" semantic conventions. It represents + // the [conversation ID](#conversations) identifying the conversation to + // which the message belongs, represented as a string. Sometimes called + // "Correlation ID". + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'MyConversationID' + MessagingMessageConversationIDKey = attribute.Key("messaging.message.conversation_id") + + // MessagingMessagePayloadSizeBytesKey is the attribute Key conforming to + // the "messaging.message.payload_size_bytes" semantic conventions. It + // represents the (uncompressed) size of the message payload in bytes. Also + // use this attribute if it is unknown whether the compressed or + // uncompressed payload size is reported. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 2738 + MessagingMessagePayloadSizeBytesKey = attribute.Key("messaging.message.payload_size_bytes") + + // MessagingMessagePayloadCompressedSizeBytesKey is the attribute Key + // conforming to the "messaging.message.payload_compressed_size_bytes" + // semantic conventions. It represents the compressed size of the message + // payload in bytes. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 2048 + MessagingMessagePayloadCompressedSizeBytesKey = attribute.Key("messaging.message.payload_compressed_size_bytes") +) + +// MessagingMessageID returns an attribute KeyValue conforming to the +// "messaging.message.id" semantic conventions. It represents a value used by +// the messaging system as an identifier for the message, represented as a +// string. +func MessagingMessageID(val string) attribute.KeyValue { + return MessagingMessageIDKey.String(val) +} + +// MessagingMessageConversationID returns an attribute KeyValue conforming +// to the "messaging.message.conversation_id" semantic conventions. It +// represents the [conversation ID](#conversations) identifying the +// conversation to which the message belongs, represented as a string. +// Sometimes called "Correlation ID". +func MessagingMessageConversationID(val string) attribute.KeyValue { + return MessagingMessageConversationIDKey.String(val) +} + +// MessagingMessagePayloadSizeBytes returns an attribute KeyValue conforming +// to the "messaging.message.payload_size_bytes" semantic conventions. It +// represents the (uncompressed) size of the message payload in bytes. Also use +// this attribute if it is unknown whether the compressed or uncompressed +// payload size is reported. +func MessagingMessagePayloadSizeBytes(val int) attribute.KeyValue { + return MessagingMessagePayloadSizeBytesKey.Int(val) +} + +// MessagingMessagePayloadCompressedSizeBytes returns an attribute KeyValue +// conforming to the "messaging.message.payload_compressed_size_bytes" semantic +// conventions. It represents the compressed size of the message payload in +// bytes. +func MessagingMessagePayloadCompressedSizeBytes(val int) attribute.KeyValue { + return MessagingMessagePayloadCompressedSizeBytesKey.Int(val) +} + +// Semantic convention for attributes that describe messaging destination on +// broker +const ( + // MessagingDestinationNameKey is the attribute Key conforming to the + // "messaging.destination.name" semantic conventions. It represents the + // message destination name + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'MyQueue', 'MyTopic' + // Note: Destination name SHOULD uniquely identify a specific queue, topic + // or other entity within the broker. If + // the broker does not have such notion, the destination name SHOULD + // uniquely identify the broker. + MessagingDestinationNameKey = attribute.Key("messaging.destination.name") + + // MessagingDestinationKindKey is the attribute Key conforming to the + // "messaging.destination.kind" semantic conventions. It represents the + // kind of message destination + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessagingDestinationKindKey = attribute.Key("messaging.destination.kind") + + // MessagingDestinationTemplateKey is the attribute Key conforming to the + // "messaging.destination.template" semantic conventions. It represents the + // low cardinality representation of the messaging destination name + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '/customers/{customerID}' + // Note: Destination names could be constructed from templates. An example + // would be a destination name involving a user name or product id. + // Although the destination name in this case is of high cardinality, the + // underlying template is of low cardinality and can be effectively used + // for grouping and aggregation. + MessagingDestinationTemplateKey = attribute.Key("messaging.destination.template") + + // MessagingDestinationTemporaryKey is the attribute Key conforming to the + // "messaging.destination.temporary" semantic conventions. It represents a + // boolean that is true if the message destination is temporary and might + // not exist anymore after messages are processed. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + MessagingDestinationTemporaryKey = attribute.Key("messaging.destination.temporary") + + // MessagingDestinationAnonymousKey is the attribute Key conforming to the + // "messaging.destination.anonymous" semantic conventions. It represents a + // boolean that is true if the message destination is anonymous (could be + // unnamed or have auto-generated name). + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + MessagingDestinationAnonymousKey = attribute.Key("messaging.destination.anonymous") +) + +var ( + // A message sent to a queue + MessagingDestinationKindQueue = MessagingDestinationKindKey.String("queue") + // A message sent to a topic + MessagingDestinationKindTopic = MessagingDestinationKindKey.String("topic") +) + +// MessagingDestinationName returns an attribute KeyValue conforming to the +// "messaging.destination.name" semantic conventions. It represents the message +// destination name +func MessagingDestinationName(val string) attribute.KeyValue { + return MessagingDestinationNameKey.String(val) +} + +// MessagingDestinationTemplate returns an attribute KeyValue conforming to +// the "messaging.destination.template" semantic conventions. It represents the +// low cardinality representation of the messaging destination name +func MessagingDestinationTemplate(val string) attribute.KeyValue { + return MessagingDestinationTemplateKey.String(val) +} + +// MessagingDestinationTemporary returns an attribute KeyValue conforming to +// the "messaging.destination.temporary" semantic conventions. It represents a +// boolean that is true if the message destination is temporary and might not +// exist anymore after messages are processed. +func MessagingDestinationTemporary(val bool) attribute.KeyValue { + return MessagingDestinationTemporaryKey.Bool(val) +} + +// MessagingDestinationAnonymous returns an attribute KeyValue conforming to +// the "messaging.destination.anonymous" semantic conventions. It represents a +// boolean that is true if the message destination is anonymous (could be +// unnamed or have auto-generated name). +func MessagingDestinationAnonymous(val bool) attribute.KeyValue { + return MessagingDestinationAnonymousKey.Bool(val) +} + +// Semantic convention for attributes that describe messaging source on broker +const ( + // MessagingSourceNameKey is the attribute Key conforming to the + // "messaging.source.name" semantic conventions. It represents the message + // source name + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'MyQueue', 'MyTopic' + // Note: Source name SHOULD uniquely identify a specific queue, topic, or + // other entity within the broker. If + // the broker does not have such notion, the source name SHOULD uniquely + // identify the broker. + MessagingSourceNameKey = attribute.Key("messaging.source.name") + + // MessagingSourceKindKey is the attribute Key conforming to the + // "messaging.source.kind" semantic conventions. It represents the kind of + // message source + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessagingSourceKindKey = attribute.Key("messaging.source.kind") + + // MessagingSourceTemplateKey is the attribute Key conforming to the + // "messaging.source.template" semantic conventions. It represents the low + // cardinality representation of the messaging source name + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '/customers/{customerID}' + // Note: Source names could be constructed from templates. An example would + // be a source name involving a user name or product id. Although the + // source name in this case is of high cardinality, the underlying template + // is of low cardinality and can be effectively used for grouping and + // aggregation. + MessagingSourceTemplateKey = attribute.Key("messaging.source.template") + + // MessagingSourceTemporaryKey is the attribute Key conforming to the + // "messaging.source.temporary" semantic conventions. It represents a + // boolean that is true if the message source is temporary and might not + // exist anymore after messages are processed. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + MessagingSourceTemporaryKey = attribute.Key("messaging.source.temporary") + + // MessagingSourceAnonymousKey is the attribute Key conforming to the + // "messaging.source.anonymous" semantic conventions. It represents a + // boolean that is true if the message source is anonymous (could be + // unnamed or have auto-generated name). + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + MessagingSourceAnonymousKey = attribute.Key("messaging.source.anonymous") +) + +var ( + // A message received from a queue + MessagingSourceKindQueue = MessagingSourceKindKey.String("queue") + // A message received from a topic + MessagingSourceKindTopic = MessagingSourceKindKey.String("topic") +) + +// MessagingSourceName returns an attribute KeyValue conforming to the +// "messaging.source.name" semantic conventions. It represents the message +// source name +func MessagingSourceName(val string) attribute.KeyValue { + return MessagingSourceNameKey.String(val) +} + +// MessagingSourceTemplate returns an attribute KeyValue conforming to the +// "messaging.source.template" semantic conventions. It represents the low +// cardinality representation of the messaging source name +func MessagingSourceTemplate(val string) attribute.KeyValue { + return MessagingSourceTemplateKey.String(val) +} + +// MessagingSourceTemporary returns an attribute KeyValue conforming to the +// "messaging.source.temporary" semantic conventions. It represents a boolean +// that is true if the message source is temporary and might not exist anymore +// after messages are processed. +func MessagingSourceTemporary(val bool) attribute.KeyValue { + return MessagingSourceTemporaryKey.Bool(val) +} + +// MessagingSourceAnonymous returns an attribute KeyValue conforming to the +// "messaging.source.anonymous" semantic conventions. It represents a boolean +// that is true if the message source is anonymous (could be unnamed or have +// auto-generated name). +func MessagingSourceAnonymous(val bool) attribute.KeyValue { + return MessagingSourceAnonymousKey.Bool(val) +} + +// General attributes used in messaging systems. +const ( + // MessagingSystemKey is the attribute Key conforming to the + // "messaging.system" semantic conventions. It represents a string + // identifying the messaging system. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'kafka', 'rabbitmq', 'rocketmq', 'activemq', 'AmazonSQS' + MessagingSystemKey = attribute.Key("messaging.system") + + // MessagingOperationKey is the attribute Key conforming to the + // "messaging.operation" semantic conventions. It represents a string + // identifying the kind of messaging operation as defined in the [Operation + // names](#operation-names) section above. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + // Note: If a custom value is used, it MUST be of low cardinality. + MessagingOperationKey = attribute.Key("messaging.operation") + + // MessagingBatchMessageCountKey is the attribute Key conforming to the + // "messaging.batch.message_count" semantic conventions. It represents the + // number of messages sent, received, or processed in the scope of the + // batching operation. + // + // Type: int + // RequirementLevel: ConditionallyRequired (If the span describes an + // operation on a batch of messages.) + // Stability: stable + // Examples: 0, 1, 2 + // Note: Instrumentations SHOULD NOT set `messaging.batch.message_count` on + // spans that operate with a single message. When a messaging client + // library supports both batch and single-message API for the same + // operation, instrumentations SHOULD use `messaging.batch.message_count` + // for batching APIs and SHOULD NOT use it for single-message APIs. + MessagingBatchMessageCountKey = attribute.Key("messaging.batch.message_count") +) + +var ( + // publish + MessagingOperationPublish = MessagingOperationKey.String("publish") + // receive + MessagingOperationReceive = MessagingOperationKey.String("receive") + // process + MessagingOperationProcess = MessagingOperationKey.String("process") +) + +// MessagingSystem returns an attribute KeyValue conforming to the +// "messaging.system" semantic conventions. It represents a string identifying +// the messaging system. +func MessagingSystem(val string) attribute.KeyValue { + return MessagingSystemKey.String(val) +} + +// MessagingBatchMessageCount returns an attribute KeyValue conforming to +// the "messaging.batch.message_count" semantic conventions. It represents the +// number of messages sent, received, or processed in the scope of the batching +// operation. +func MessagingBatchMessageCount(val int) attribute.KeyValue { + return MessagingBatchMessageCountKey.Int(val) +} + +// Semantic convention for a consumer of messages received from a messaging +// system +const ( + // MessagingConsumerIDKey is the attribute Key conforming to the + // "messaging.consumer.id" semantic conventions. It represents the + // identifier for the consumer receiving a message. For Kafka, set it to + // `{messaging.kafka.consumer.group} - {messaging.kafka.client_id}`, if + // both are present, or only `messaging.kafka.consumer.group`. For brokers, + // such as RabbitMQ and Artemis, set it to the `client_id` of the client + // consuming the message. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'mygroup - client-6' + MessagingConsumerIDKey = attribute.Key("messaging.consumer.id") +) + +// MessagingConsumerID returns an attribute KeyValue conforming to the +// "messaging.consumer.id" semantic conventions. It represents the identifier +// for the consumer receiving a message. For Kafka, set it to +// `{messaging.kafka.consumer.group} - {messaging.kafka.client_id}`, if both +// are present, or only `messaging.kafka.consumer.group`. For brokers, such as +// RabbitMQ and Artemis, set it to the `client_id` of the client consuming the +// message. +func MessagingConsumerID(val string) attribute.KeyValue { + return MessagingConsumerIDKey.String(val) +} + +// Attributes for RabbitMQ +const ( + // MessagingRabbitmqDestinationRoutingKeyKey is the attribute Key + // conforming to the "messaging.rabbitmq.destination.routing_key" semantic + // conventions. It represents the rabbitMQ message routing key. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If not empty.) + // Stability: stable + // Examples: 'myKey' + MessagingRabbitmqDestinationRoutingKeyKey = attribute.Key("messaging.rabbitmq.destination.routing_key") +) + +// MessagingRabbitmqDestinationRoutingKey returns an attribute KeyValue +// conforming to the "messaging.rabbitmq.destination.routing_key" semantic +// conventions. It represents the rabbitMQ message routing key. +func MessagingRabbitmqDestinationRoutingKey(val string) attribute.KeyValue { + return MessagingRabbitmqDestinationRoutingKeyKey.String(val) +} + +// Attributes for Apache Kafka +const ( + // MessagingKafkaMessageKeyKey is the attribute Key conforming to the + // "messaging.kafka.message.key" semantic conventions. It represents the + // message keys in Kafka are used for grouping alike messages to ensure + // they're processed on the same partition. They differ from + // `messaging.message.id` in that they're not unique. If the key is `null`, + // the attribute MUST NOT be set. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'myKey' + // Note: If the key type is not string, it's string representation has to + // be supplied for the attribute. If the key has no unambiguous, canonical + // string form, don't include its value. + MessagingKafkaMessageKeyKey = attribute.Key("messaging.kafka.message.key") + + // MessagingKafkaConsumerGroupKey is the attribute Key conforming to the + // "messaging.kafka.consumer.group" semantic conventions. It represents the + // name of the Kafka Consumer Group that is handling the message. Only + // applies to consumers, not producers. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'my-group' + MessagingKafkaConsumerGroupKey = attribute.Key("messaging.kafka.consumer.group") + + // MessagingKafkaClientIDKey is the attribute Key conforming to the + // "messaging.kafka.client_id" semantic conventions. It represents the + // client ID for the Consumer or Producer that is handling the message. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'client-5' + MessagingKafkaClientIDKey = attribute.Key("messaging.kafka.client_id") + + // MessagingKafkaDestinationPartitionKey is the attribute Key conforming to + // the "messaging.kafka.destination.partition" semantic conventions. It + // represents the partition the message is sent to. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 2 + MessagingKafkaDestinationPartitionKey = attribute.Key("messaging.kafka.destination.partition") + + // MessagingKafkaSourcePartitionKey is the attribute Key conforming to the + // "messaging.kafka.source.partition" semantic conventions. It represents + // the partition the message is received from. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 2 + MessagingKafkaSourcePartitionKey = attribute.Key("messaging.kafka.source.partition") + + // MessagingKafkaMessageOffsetKey is the attribute Key conforming to the + // "messaging.kafka.message.offset" semantic conventions. It represents the + // offset of a record in the corresponding Kafka partition. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 42 + MessagingKafkaMessageOffsetKey = attribute.Key("messaging.kafka.message.offset") + + // MessagingKafkaMessageTombstoneKey is the attribute Key conforming to the + // "messaging.kafka.message.tombstone" semantic conventions. It represents + // a boolean that is true if the message is a tombstone. + // + // Type: boolean + // RequirementLevel: ConditionallyRequired (If value is `true`. When + // missing, the value is assumed to be `false`.) + // Stability: stable + MessagingKafkaMessageTombstoneKey = attribute.Key("messaging.kafka.message.tombstone") +) + +// MessagingKafkaMessageKey returns an attribute KeyValue conforming to the +// "messaging.kafka.message.key" semantic conventions. It represents the +// message keys in Kafka are used for grouping alike messages to ensure they're +// processed on the same partition. They differ from `messaging.message.id` in +// that they're not unique. If the key is `null`, the attribute MUST NOT be +// set. +func MessagingKafkaMessageKey(val string) attribute.KeyValue { + return MessagingKafkaMessageKeyKey.String(val) +} + +// MessagingKafkaConsumerGroup returns an attribute KeyValue conforming to +// the "messaging.kafka.consumer.group" semantic conventions. It represents the +// name of the Kafka Consumer Group that is handling the message. Only applies +// to consumers, not producers. +func MessagingKafkaConsumerGroup(val string) attribute.KeyValue { + return MessagingKafkaConsumerGroupKey.String(val) +} + +// MessagingKafkaClientID returns an attribute KeyValue conforming to the +// "messaging.kafka.client_id" semantic conventions. It represents the client +// ID for the Consumer or Producer that is handling the message. +func MessagingKafkaClientID(val string) attribute.KeyValue { + return MessagingKafkaClientIDKey.String(val) +} + +// MessagingKafkaDestinationPartition returns an attribute KeyValue +// conforming to the "messaging.kafka.destination.partition" semantic +// conventions. It represents the partition the message is sent to. +func MessagingKafkaDestinationPartition(val int) attribute.KeyValue { + return MessagingKafkaDestinationPartitionKey.Int(val) +} + +// MessagingKafkaSourcePartition returns an attribute KeyValue conforming to +// the "messaging.kafka.source.partition" semantic conventions. It represents +// the partition the message is received from. +func MessagingKafkaSourcePartition(val int) attribute.KeyValue { + return MessagingKafkaSourcePartitionKey.Int(val) +} + +// MessagingKafkaMessageOffset returns an attribute KeyValue conforming to +// the "messaging.kafka.message.offset" semantic conventions. It represents the +// offset of a record in the corresponding Kafka partition. +func MessagingKafkaMessageOffset(val int) attribute.KeyValue { + return MessagingKafkaMessageOffsetKey.Int(val) +} + +// MessagingKafkaMessageTombstone returns an attribute KeyValue conforming +// to the "messaging.kafka.message.tombstone" semantic conventions. It +// represents a boolean that is true if the message is a tombstone. +func MessagingKafkaMessageTombstone(val bool) attribute.KeyValue { + return MessagingKafkaMessageTombstoneKey.Bool(val) +} + +// Attributes for Apache RocketMQ +const ( + // MessagingRocketmqNamespaceKey is the attribute Key conforming to the + // "messaging.rocketmq.namespace" semantic conventions. It represents the + // namespace of RocketMQ resources, resources in different namespaces are + // individual. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myNamespace' + MessagingRocketmqNamespaceKey = attribute.Key("messaging.rocketmq.namespace") + + // MessagingRocketmqClientGroupKey is the attribute Key conforming to the + // "messaging.rocketmq.client_group" semantic conventions. It represents + // the name of the RocketMQ producer/consumer group that is handling the + // message. The client type is identified by the SpanKind. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myConsumerGroup' + MessagingRocketmqClientGroupKey = attribute.Key("messaging.rocketmq.client_group") + + // MessagingRocketmqClientIDKey is the attribute Key conforming to the + // "messaging.rocketmq.client_id" semantic conventions. It represents the + // unique identifier for each client. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myhost@8742@s8083jm' + MessagingRocketmqClientIDKey = attribute.Key("messaging.rocketmq.client_id") + + // MessagingRocketmqMessageDeliveryTimestampKey is the attribute Key + // conforming to the "messaging.rocketmq.message.delivery_timestamp" + // semantic conventions. It represents the timestamp in milliseconds that + // the delay message is expected to be delivered to consumer. + // + // Type: int + // RequirementLevel: ConditionallyRequired (If the message type is delay + // and delay time level is not specified.) + // Stability: stable + // Examples: 1665987217045 + MessagingRocketmqMessageDeliveryTimestampKey = attribute.Key("messaging.rocketmq.message.delivery_timestamp") + + // MessagingRocketmqMessageDelayTimeLevelKey is the attribute Key + // conforming to the "messaging.rocketmq.message.delay_time_level" semantic + // conventions. It represents the delay time level for delay message, which + // determines the message delay time. + // + // Type: int + // RequirementLevel: ConditionallyRequired (If the message type is delay + // and delivery timestamp is not specified.) + // Stability: stable + // Examples: 3 + MessagingRocketmqMessageDelayTimeLevelKey = attribute.Key("messaging.rocketmq.message.delay_time_level") + + // MessagingRocketmqMessageGroupKey is the attribute Key conforming to the + // "messaging.rocketmq.message.group" semantic conventions. It represents + // the it is essential for FIFO message. Messages that belong to the same + // message group are always processed one by one within the same consumer + // group. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If the message type is FIFO.) + // Stability: stable + // Examples: 'myMessageGroup' + MessagingRocketmqMessageGroupKey = attribute.Key("messaging.rocketmq.message.group") + + // MessagingRocketmqMessageTypeKey is the attribute Key conforming to the + // "messaging.rocketmq.message.type" semantic conventions. It represents + // the type of message. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessagingRocketmqMessageTypeKey = attribute.Key("messaging.rocketmq.message.type") + + // MessagingRocketmqMessageTagKey is the attribute Key conforming to the + // "messaging.rocketmq.message.tag" semantic conventions. It represents the + // secondary classifier of message besides topic. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'tagA' + MessagingRocketmqMessageTagKey = attribute.Key("messaging.rocketmq.message.tag") + + // MessagingRocketmqMessageKeysKey is the attribute Key conforming to the + // "messaging.rocketmq.message.keys" semantic conventions. It represents + // the key(s) of message, another way to mark message besides message id. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'keyA', 'keyB' + MessagingRocketmqMessageKeysKey = attribute.Key("messaging.rocketmq.message.keys") + + // MessagingRocketmqConsumptionModelKey is the attribute Key conforming to + // the "messaging.rocketmq.consumption_model" semantic conventions. It + // represents the model of message consumption. This only applies to + // consumer spans. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessagingRocketmqConsumptionModelKey = attribute.Key("messaging.rocketmq.consumption_model") +) + +var ( + // Normal message + MessagingRocketmqMessageTypeNormal = MessagingRocketmqMessageTypeKey.String("normal") + // FIFO message + MessagingRocketmqMessageTypeFifo = MessagingRocketmqMessageTypeKey.String("fifo") + // Delay message + MessagingRocketmqMessageTypeDelay = MessagingRocketmqMessageTypeKey.String("delay") + // Transaction message + MessagingRocketmqMessageTypeTransaction = MessagingRocketmqMessageTypeKey.String("transaction") +) + +var ( + // Clustering consumption model + MessagingRocketmqConsumptionModelClustering = MessagingRocketmqConsumptionModelKey.String("clustering") + // Broadcasting consumption model + MessagingRocketmqConsumptionModelBroadcasting = MessagingRocketmqConsumptionModelKey.String("broadcasting") +) + +// MessagingRocketmqNamespace returns an attribute KeyValue conforming to +// the "messaging.rocketmq.namespace" semantic conventions. It represents the +// namespace of RocketMQ resources, resources in different namespaces are +// individual. +func MessagingRocketmqNamespace(val string) attribute.KeyValue { + return MessagingRocketmqNamespaceKey.String(val) +} + +// MessagingRocketmqClientGroup returns an attribute KeyValue conforming to +// the "messaging.rocketmq.client_group" semantic conventions. It represents +// the name of the RocketMQ producer/consumer group that is handling the +// message. The client type is identified by the SpanKind. +func MessagingRocketmqClientGroup(val string) attribute.KeyValue { + return MessagingRocketmqClientGroupKey.String(val) +} + +// MessagingRocketmqClientID returns an attribute KeyValue conforming to the +// "messaging.rocketmq.client_id" semantic conventions. It represents the +// unique identifier for each client. +func MessagingRocketmqClientID(val string) attribute.KeyValue { + return MessagingRocketmqClientIDKey.String(val) +} + +// MessagingRocketmqMessageDeliveryTimestamp returns an attribute KeyValue +// conforming to the "messaging.rocketmq.message.delivery_timestamp" semantic +// conventions. It represents the timestamp in milliseconds that the delay +// message is expected to be delivered to consumer. +func MessagingRocketmqMessageDeliveryTimestamp(val int) attribute.KeyValue { + return MessagingRocketmqMessageDeliveryTimestampKey.Int(val) +} + +// MessagingRocketmqMessageDelayTimeLevel returns an attribute KeyValue +// conforming to the "messaging.rocketmq.message.delay_time_level" semantic +// conventions. It represents the delay time level for delay message, which +// determines the message delay time. +func MessagingRocketmqMessageDelayTimeLevel(val int) attribute.KeyValue { + return MessagingRocketmqMessageDelayTimeLevelKey.Int(val) +} + +// MessagingRocketmqMessageGroup returns an attribute KeyValue conforming to +// the "messaging.rocketmq.message.group" semantic conventions. It represents +// the it is essential for FIFO message. Messages that belong to the same +// message group are always processed one by one within the same consumer +// group. +func MessagingRocketmqMessageGroup(val string) attribute.KeyValue { + return MessagingRocketmqMessageGroupKey.String(val) +} + +// MessagingRocketmqMessageTag returns an attribute KeyValue conforming to +// the "messaging.rocketmq.message.tag" semantic conventions. It represents the +// secondary classifier of message besides topic. +func MessagingRocketmqMessageTag(val string) attribute.KeyValue { + return MessagingRocketmqMessageTagKey.String(val) +} + +// MessagingRocketmqMessageKeys returns an attribute KeyValue conforming to +// the "messaging.rocketmq.message.keys" semantic conventions. It represents +// the key(s) of message, another way to mark message besides message id. +func MessagingRocketmqMessageKeys(val ...string) attribute.KeyValue { + return MessagingRocketmqMessageKeysKey.StringSlice(val) +} + +// Semantic conventions for remote procedure calls. +const ( + // RPCSystemKey is the attribute Key conforming to the "rpc.system" + // semantic conventions. It represents a string identifying the remoting + // system. See below for a list of well-known identifiers. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + RPCSystemKey = attribute.Key("rpc.system") + + // RPCServiceKey is the attribute Key conforming to the "rpc.service" + // semantic conventions. It represents the full (logical) name of the + // service being called, including its package name, if applicable. + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'myservice.EchoService' + // Note: This is the logical name of the service from the RPC interface + // perspective, which can be different from the name of any implementing + // class. The `code.namespace` attribute may be used to store the latter + // (despite the attribute name, it may include a class name; e.g., class + // with method actually executing the call on the server side, RPC client + // stub class on the client side). + RPCServiceKey = attribute.Key("rpc.service") + + // RPCMethodKey is the attribute Key conforming to the "rpc.method" + // semantic conventions. It represents the name of the (logical) method + // being called, must be equal to the $method part in the span name. + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'exampleMethod' + // Note: This is the logical name of the method from the RPC interface + // perspective, which can be different from the name of any implementing + // method/function. The `code.function` attribute may be used to store the + // latter (e.g., method actually executing the call on the server side, RPC + // client stub method on the client side). + RPCMethodKey = attribute.Key("rpc.method") +) + +var ( + // gRPC + RPCSystemGRPC = RPCSystemKey.String("grpc") + // Java RMI + RPCSystemJavaRmi = RPCSystemKey.String("java_rmi") + // .NET WCF + RPCSystemDotnetWcf = RPCSystemKey.String("dotnet_wcf") + // Apache Dubbo + RPCSystemApacheDubbo = RPCSystemKey.String("apache_dubbo") +) + +// RPCService returns an attribute KeyValue conforming to the "rpc.service" +// semantic conventions. It represents the full (logical) name of the service +// being called, including its package name, if applicable. +func RPCService(val string) attribute.KeyValue { + return RPCServiceKey.String(val) +} + +// RPCMethod returns an attribute KeyValue conforming to the "rpc.method" +// semantic conventions. It represents the name of the (logical) method being +// called, must be equal to the $method part in the span name. +func RPCMethod(val string) attribute.KeyValue { + return RPCMethodKey.String(val) +} + +// Tech-specific attributes for gRPC. +const ( + // RPCGRPCStatusCodeKey is the attribute Key conforming to the + // "rpc.grpc.status_code" semantic conventions. It represents the [numeric + // status + // code](https://github.com/grpc/grpc/blob/v1.33.2/doc/statuscodes.md) of + // the gRPC request. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + RPCGRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code") +) + +var ( + // OK + RPCGRPCStatusCodeOk = RPCGRPCStatusCodeKey.Int(0) + // CANCELLED + RPCGRPCStatusCodeCancelled = RPCGRPCStatusCodeKey.Int(1) + // UNKNOWN + RPCGRPCStatusCodeUnknown = RPCGRPCStatusCodeKey.Int(2) + // INVALID_ARGUMENT + RPCGRPCStatusCodeInvalidArgument = RPCGRPCStatusCodeKey.Int(3) + // DEADLINE_EXCEEDED + RPCGRPCStatusCodeDeadlineExceeded = RPCGRPCStatusCodeKey.Int(4) + // NOT_FOUND + RPCGRPCStatusCodeNotFound = RPCGRPCStatusCodeKey.Int(5) + // ALREADY_EXISTS + RPCGRPCStatusCodeAlreadyExists = RPCGRPCStatusCodeKey.Int(6) + // PERMISSION_DENIED + RPCGRPCStatusCodePermissionDenied = RPCGRPCStatusCodeKey.Int(7) + // RESOURCE_EXHAUSTED + RPCGRPCStatusCodeResourceExhausted = RPCGRPCStatusCodeKey.Int(8) + // FAILED_PRECONDITION + RPCGRPCStatusCodeFailedPrecondition = RPCGRPCStatusCodeKey.Int(9) + // ABORTED + RPCGRPCStatusCodeAborted = RPCGRPCStatusCodeKey.Int(10) + // OUT_OF_RANGE + RPCGRPCStatusCodeOutOfRange = RPCGRPCStatusCodeKey.Int(11) + // UNIMPLEMENTED + RPCGRPCStatusCodeUnimplemented = RPCGRPCStatusCodeKey.Int(12) + // INTERNAL + RPCGRPCStatusCodeInternal = RPCGRPCStatusCodeKey.Int(13) + // UNAVAILABLE + RPCGRPCStatusCodeUnavailable = RPCGRPCStatusCodeKey.Int(14) + // DATA_LOSS + RPCGRPCStatusCodeDataLoss = RPCGRPCStatusCodeKey.Int(15) + // UNAUTHENTICATED + RPCGRPCStatusCodeUnauthenticated = RPCGRPCStatusCodeKey.Int(16) +) + +// Tech-specific attributes for [JSON RPC](https://www.jsonrpc.org/). +const ( + // RPCJsonrpcVersionKey is the attribute Key conforming to the + // "rpc.jsonrpc.version" semantic conventions. It represents the protocol + // version as in `jsonrpc` property of request/response. Since JSON-RPC 1.0 + // does not specify this, the value can be omitted. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If other than the default + // version (`1.0`)) + // Stability: stable + // Examples: '2.0', '1.0' + RPCJsonrpcVersionKey = attribute.Key("rpc.jsonrpc.version") + + // RPCJsonrpcRequestIDKey is the attribute Key conforming to the + // "rpc.jsonrpc.request_id" semantic conventions. It represents the `id` + // property of request or response. Since protocol allows id to be int, + // string, `null` or missing (for notifications), value is expected to be + // cast to string for simplicity. Use empty string in case of `null` value. + // Omit entirely if this is a notification. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '10', 'request-7', '' + RPCJsonrpcRequestIDKey = attribute.Key("rpc.jsonrpc.request_id") + + // RPCJsonrpcErrorCodeKey is the attribute Key conforming to the + // "rpc.jsonrpc.error_code" semantic conventions. It represents the + // `error.code` property of response if it is an error response. + // + // Type: int + // RequirementLevel: ConditionallyRequired (If response is not successful.) + // Stability: stable + // Examples: -32700, 100 + RPCJsonrpcErrorCodeKey = attribute.Key("rpc.jsonrpc.error_code") + + // RPCJsonrpcErrorMessageKey is the attribute Key conforming to the + // "rpc.jsonrpc.error_message" semantic conventions. It represents the + // `error.message` property of response if it is an error response. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Parse error', 'User already exists' + RPCJsonrpcErrorMessageKey = attribute.Key("rpc.jsonrpc.error_message") +) + +// RPCJsonrpcVersion returns an attribute KeyValue conforming to the +// "rpc.jsonrpc.version" semantic conventions. It represents the protocol +// version as in `jsonrpc` property of request/response. Since JSON-RPC 1.0 +// does not specify this, the value can be omitted. +func RPCJsonrpcVersion(val string) attribute.KeyValue { + return RPCJsonrpcVersionKey.String(val) +} + +// RPCJsonrpcRequestID returns an attribute KeyValue conforming to the +// "rpc.jsonrpc.request_id" semantic conventions. It represents the `id` +// property of request or response. Since protocol allows id to be int, string, +// `null` or missing (for notifications), value is expected to be cast to +// string for simplicity. Use empty string in case of `null` value. Omit +// entirely if this is a notification. +func RPCJsonrpcRequestID(val string) attribute.KeyValue { + return RPCJsonrpcRequestIDKey.String(val) +} + +// RPCJsonrpcErrorCode returns an attribute KeyValue conforming to the +// "rpc.jsonrpc.error_code" semantic conventions. It represents the +// `error.code` property of response if it is an error response. +func RPCJsonrpcErrorCode(val int) attribute.KeyValue { + return RPCJsonrpcErrorCodeKey.Int(val) +} + +// RPCJsonrpcErrorMessage returns an attribute KeyValue conforming to the +// "rpc.jsonrpc.error_message" semantic conventions. It represents the +// `error.message` property of response if it is an error response. +func RPCJsonrpcErrorMessage(val string) attribute.KeyValue { + return RPCJsonrpcErrorMessageKey.String(val) +} diff --git a/vendor/gopkg.in/inf.v0/LICENSE b/vendor/gopkg.in/inf.v0/LICENSE new file mode 100644 index 0000000000..87a5cede33 --- /dev/null +++ b/vendor/gopkg.in/inf.v0/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go +Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/gopkg.in/inf.v0/dec.go b/vendor/gopkg.in/inf.v0/dec.go new file mode 100644 index 0000000000..26548b63ce --- /dev/null +++ b/vendor/gopkg.in/inf.v0/dec.go @@ -0,0 +1,615 @@ +// Package inf (type inf.Dec) implements "infinite-precision" decimal +// arithmetic. +// "Infinite precision" describes two characteristics: practically unlimited +// precision for decimal number representation and no support for calculating +// with any specific fixed precision. +// (Although there is no practical limit on precision, inf.Dec can only +// represent finite decimals.) +// +// This package is currently in experimental stage and the API may change. +// +// This package does NOT support: +// - rounding to specific precisions (as opposed to specific decimal positions) +// - the notion of context (each rounding must be explicit) +// - NaN and Inf values, and distinguishing between positive and negative zero +// - conversions to and from float32/64 types +// +// Features considered for possible addition: +// + formatting options +// + Exp method +// + combined operations such as AddRound/MulAdd etc +// + exchanging data in decimal32/64/128 formats +// +package inf // import "gopkg.in/inf.v0" + +// TODO: +// - avoid excessive deep copying (quo and rounders) + +import ( + "fmt" + "io" + "math/big" + "strings" +) + +// A Dec represents a signed arbitrary-precision decimal. +// It is a combination of a sign, an arbitrary-precision integer coefficient +// value, and a signed fixed-precision exponent value. +// The sign and the coefficient value are handled together as a signed value +// and referred to as the unscaled value. +// (Positive and negative zero values are not distinguished.) +// Since the exponent is most commonly non-positive, it is handled in negated +// form and referred to as scale. +// +// The mathematical value of a Dec equals: +// +// unscaled * 10**(-scale) +// +// Note that different Dec representations may have equal mathematical values. +// +// unscaled scale String() +// ------------------------- +// 0 0 "0" +// 0 2 "0.00" +// 0 -2 "0" +// 1 0 "1" +// 100 2 "1.00" +// 10 0 "10" +// 1 -1 "10" +// +// The zero value for a Dec represents the value 0 with scale 0. +// +// Operations are typically performed through the *Dec type. +// The semantics of the assignment operation "=" for "bare" Dec values is +// undefined and should not be relied on. +// +// Methods are typically of the form: +// +// func (z *Dec) Op(x, y *Dec) *Dec +// +// and implement operations z = x Op y with the result as receiver; if it +// is one of the operands it may be overwritten (and its memory reused). +// To enable chaining of operations, the result is also returned. Methods +// returning a result other than *Dec take one of the operands as the receiver. +// +// A "bare" Quo method (quotient / division operation) is not provided, as the +// result is not always a finite decimal and thus in general cannot be +// represented as a Dec. +// Instead, in the common case when rounding is (potentially) necessary, +// QuoRound should be used with a Scale and a Rounder. +// QuoExact or QuoRound with RoundExact can be used in the special cases when it +// is known that the result is always a finite decimal. +// +type Dec struct { + unscaled big.Int + scale Scale +} + +// Scale represents the type used for the scale of a Dec. +type Scale int32 + +const scaleSize = 4 // bytes in a Scale value + +// Scaler represents a method for obtaining the scale to use for the result of +// an operation on x and y. +type scaler interface { + Scale(x *Dec, y *Dec) Scale +} + +var bigInt = [...]*big.Int{ + big.NewInt(0), big.NewInt(1), big.NewInt(2), big.NewInt(3), big.NewInt(4), + big.NewInt(5), big.NewInt(6), big.NewInt(7), big.NewInt(8), big.NewInt(9), + big.NewInt(10), +} + +var exp10cache [64]big.Int = func() [64]big.Int { + e10, e10i := [64]big.Int{}, bigInt[1] + for i := range e10 { + e10[i].Set(e10i) + e10i = new(big.Int).Mul(e10i, bigInt[10]) + } + return e10 +}() + +// NewDec allocates and returns a new Dec set to the given int64 unscaled value +// and scale. +func NewDec(unscaled int64, scale Scale) *Dec { + return new(Dec).SetUnscaled(unscaled).SetScale(scale) +} + +// NewDecBig allocates and returns a new Dec set to the given *big.Int unscaled +// value and scale. +func NewDecBig(unscaled *big.Int, scale Scale) *Dec { + return new(Dec).SetUnscaledBig(unscaled).SetScale(scale) +} + +// Scale returns the scale of x. +func (x *Dec) Scale() Scale { + return x.scale +} + +// Unscaled returns the unscaled value of x for u and true for ok when the +// unscaled value can be represented as int64; otherwise it returns an undefined +// int64 value for u and false for ok. Use x.UnscaledBig().Int64() to avoid +// checking the validity of the value when the check is known to be redundant. +func (x *Dec) Unscaled() (u int64, ok bool) { + u = x.unscaled.Int64() + var i big.Int + ok = i.SetInt64(u).Cmp(&x.unscaled) == 0 + return +} + +// UnscaledBig returns the unscaled value of x as *big.Int. +func (x *Dec) UnscaledBig() *big.Int { + return &x.unscaled +} + +// SetScale sets the scale of z, with the unscaled value unchanged, and returns +// z. +// The mathematical value of the Dec changes as if it was multiplied by +// 10**(oldscale-scale). +func (z *Dec) SetScale(scale Scale) *Dec { + z.scale = scale + return z +} + +// SetUnscaled sets the unscaled value of z, with the scale unchanged, and +// returns z. +func (z *Dec) SetUnscaled(unscaled int64) *Dec { + z.unscaled.SetInt64(unscaled) + return z +} + +// SetUnscaledBig sets the unscaled value of z, with the scale unchanged, and +// returns z. +func (z *Dec) SetUnscaledBig(unscaled *big.Int) *Dec { + z.unscaled.Set(unscaled) + return z +} + +// Set sets z to the value of x and returns z. +// It does nothing if z == x. +func (z *Dec) Set(x *Dec) *Dec { + if z != x { + z.SetUnscaledBig(x.UnscaledBig()) + z.SetScale(x.Scale()) + } + return z +} + +// Sign returns: +// +// -1 if x < 0 +// 0 if x == 0 +// +1 if x > 0 +// +func (x *Dec) Sign() int { + return x.UnscaledBig().Sign() +} + +// Neg sets z to -x and returns z. +func (z *Dec) Neg(x *Dec) *Dec { + z.SetScale(x.Scale()) + z.UnscaledBig().Neg(x.UnscaledBig()) + return z +} + +// Cmp compares x and y and returns: +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +// +func (x *Dec) Cmp(y *Dec) int { + xx, yy := upscale(x, y) + return xx.UnscaledBig().Cmp(yy.UnscaledBig()) +} + +// Abs sets z to |x| (the absolute value of x) and returns z. +func (z *Dec) Abs(x *Dec) *Dec { + z.SetScale(x.Scale()) + z.UnscaledBig().Abs(x.UnscaledBig()) + return z +} + +// Add sets z to the sum x+y and returns z. +// The scale of z is the greater of the scales of x and y. +func (z *Dec) Add(x, y *Dec) *Dec { + xx, yy := upscale(x, y) + z.SetScale(xx.Scale()) + z.UnscaledBig().Add(xx.UnscaledBig(), yy.UnscaledBig()) + return z +} + +// Sub sets z to the difference x-y and returns z. +// The scale of z is the greater of the scales of x and y. +func (z *Dec) Sub(x, y *Dec) *Dec { + xx, yy := upscale(x, y) + z.SetScale(xx.Scale()) + z.UnscaledBig().Sub(xx.UnscaledBig(), yy.UnscaledBig()) + return z +} + +// Mul sets z to the product x*y and returns z. +// The scale of z is the sum of the scales of x and y. +func (z *Dec) Mul(x, y *Dec) *Dec { + z.SetScale(x.Scale() + y.Scale()) + z.UnscaledBig().Mul(x.UnscaledBig(), y.UnscaledBig()) + return z +} + +// Round sets z to the value of x rounded to Scale s using Rounder r, and +// returns z. +func (z *Dec) Round(x *Dec, s Scale, r Rounder) *Dec { + return z.QuoRound(x, NewDec(1, 0), s, r) +} + +// QuoRound sets z to the quotient x/y, rounded using the given Rounder to the +// specified scale. +// +// If the rounder is RoundExact but the result can not be expressed exactly at +// the specified scale, QuoRound returns nil, and the value of z is undefined. +// +// There is no corresponding Div method; the equivalent can be achieved through +// the choice of Rounder used. +// +func (z *Dec) QuoRound(x, y *Dec, s Scale, r Rounder) *Dec { + return z.quo(x, y, sclr{s}, r) +} + +func (z *Dec) quo(x, y *Dec, s scaler, r Rounder) *Dec { + scl := s.Scale(x, y) + var zzz *Dec + if r.UseRemainder() { + zz, rA, rB := new(Dec).quoRem(x, y, scl, true, new(big.Int), new(big.Int)) + zzz = r.Round(new(Dec), zz, rA, rB) + } else { + zz, _, _ := new(Dec).quoRem(x, y, scl, false, nil, nil) + zzz = r.Round(new(Dec), zz, nil, nil) + } + if zzz == nil { + return nil + } + return z.Set(zzz) +} + +// QuoExact sets z to the quotient x/y and returns z when x/y is a finite +// decimal. Otherwise it returns nil and the value of z is undefined. +// +// The scale of a non-nil result is "x.Scale() - y.Scale()" or greater; it is +// calculated so that the remainder will be zero whenever x/y is a finite +// decimal. +func (z *Dec) QuoExact(x, y *Dec) *Dec { + return z.quo(x, y, scaleQuoExact{}, RoundExact) +} + +// quoRem sets z to the quotient x/y with the scale s, and if useRem is true, +// it sets remNum and remDen to the numerator and denominator of the remainder. +// It returns z, remNum and remDen. +// +// The remainder is normalized to the range -1 < r < 1 to simplify rounding; +// that is, the results satisfy the following equation: +// +// x / y = z + (remNum/remDen) * 10**(-z.Scale()) +// +// See Rounder for more details about rounding. +// +func (z *Dec) quoRem(x, y *Dec, s Scale, useRem bool, + remNum, remDen *big.Int) (*Dec, *big.Int, *big.Int) { + // difference (required adjustment) compared to "canonical" result scale + shift := s - (x.Scale() - y.Scale()) + // pointers to adjusted unscaled dividend and divisor + var ix, iy *big.Int + switch { + case shift > 0: + // increased scale: decimal-shift dividend left + ix = new(big.Int).Mul(x.UnscaledBig(), exp10(shift)) + iy = y.UnscaledBig() + case shift < 0: + // decreased scale: decimal-shift divisor left + ix = x.UnscaledBig() + iy = new(big.Int).Mul(y.UnscaledBig(), exp10(-shift)) + default: + ix = x.UnscaledBig() + iy = y.UnscaledBig() + } + // save a copy of iy in case it to be overwritten with the result + iy2 := iy + if iy == z.UnscaledBig() { + iy2 = new(big.Int).Set(iy) + } + // set scale + z.SetScale(s) + // set unscaled + if useRem { + // Int division + _, intr := z.UnscaledBig().QuoRem(ix, iy, new(big.Int)) + // set remainder + remNum.Set(intr) + remDen.Set(iy2) + } else { + z.UnscaledBig().Quo(ix, iy) + } + return z, remNum, remDen +} + +type sclr struct{ s Scale } + +func (s sclr) Scale(x, y *Dec) Scale { + return s.s +} + +type scaleQuoExact struct{} + +func (sqe scaleQuoExact) Scale(x, y *Dec) Scale { + rem := new(big.Rat).SetFrac(x.UnscaledBig(), y.UnscaledBig()) + f2, f5 := factor2(rem.Denom()), factor(rem.Denom(), bigInt[5]) + var f10 Scale + if f2 > f5 { + f10 = Scale(f2) + } else { + f10 = Scale(f5) + } + return x.Scale() - y.Scale() + f10 +} + +func factor(n *big.Int, p *big.Int) int { + // could be improved for large factors + d, f := n, 0 + for { + dd, dm := new(big.Int).DivMod(d, p, new(big.Int)) + if dm.Sign() == 0 { + f++ + d = dd + } else { + break + } + } + return f +} + +func factor2(n *big.Int) int { + // could be improved for large factors + f := 0 + for ; n.Bit(f) == 0; f++ { + } + return f +} + +func upscale(a, b *Dec) (*Dec, *Dec) { + if a.Scale() == b.Scale() { + return a, b + } + if a.Scale() > b.Scale() { + bb := b.rescale(a.Scale()) + return a, bb + } + aa := a.rescale(b.Scale()) + return aa, b +} + +func exp10(x Scale) *big.Int { + if int(x) < len(exp10cache) { + return &exp10cache[int(x)] + } + return new(big.Int).Exp(bigInt[10], big.NewInt(int64(x)), nil) +} + +func (x *Dec) rescale(newScale Scale) *Dec { + shift := newScale - x.Scale() + switch { + case shift < 0: + e := exp10(-shift) + return NewDecBig(new(big.Int).Quo(x.UnscaledBig(), e), newScale) + case shift > 0: + e := exp10(shift) + return NewDecBig(new(big.Int).Mul(x.UnscaledBig(), e), newScale) + } + return x +} + +var zeros = []byte("00000000000000000000000000000000" + + "00000000000000000000000000000000") +var lzeros = Scale(len(zeros)) + +func appendZeros(s []byte, n Scale) []byte { + for i := Scale(0); i < n; i += lzeros { + if n > i+lzeros { + s = append(s, zeros...) + } else { + s = append(s, zeros[0:n-i]...) + } + } + return s +} + +func (x *Dec) String() string { + if x == nil { + return "" + } + scale := x.Scale() + s := []byte(x.UnscaledBig().String()) + if scale <= 0 { + if scale != 0 && x.unscaled.Sign() != 0 { + s = appendZeros(s, -scale) + } + return string(s) + } + negbit := Scale(-((x.Sign() - 1) / 2)) + // scale > 0 + lens := Scale(len(s)) + if lens-negbit <= scale { + ss := make([]byte, 0, scale+2) + if negbit == 1 { + ss = append(ss, '-') + } + ss = append(ss, '0', '.') + ss = appendZeros(ss, scale-lens+negbit) + ss = append(ss, s[negbit:]...) + return string(ss) + } + // lens > scale + ss := make([]byte, 0, lens+1) + ss = append(ss, s[:lens-scale]...) + ss = append(ss, '.') + ss = append(ss, s[lens-scale:]...) + return string(ss) +} + +// Format is a support routine for fmt.Formatter. It accepts the decimal +// formats 'd' and 'f', and handles both equivalently. +// Width, precision, flags and bases 2, 8, 16 are not supported. +func (x *Dec) Format(s fmt.State, ch rune) { + if ch != 'd' && ch != 'f' && ch != 'v' && ch != 's' { + fmt.Fprintf(s, "%%!%c(dec.Dec=%s)", ch, x.String()) + return + } + fmt.Fprintf(s, x.String()) +} + +func (z *Dec) scan(r io.RuneScanner) (*Dec, error) { + unscaled := make([]byte, 0, 256) // collects chars of unscaled as bytes + dp, dg := -1, -1 // indexes of decimal point, first digit +loop: + for { + ch, _, err := r.ReadRune() + if err == io.EOF { + break loop + } + if err != nil { + return nil, err + } + switch { + case ch == '+' || ch == '-': + if len(unscaled) > 0 || dp >= 0 { // must be first character + r.UnreadRune() + break loop + } + case ch == '.': + if dp >= 0 { + r.UnreadRune() + break loop + } + dp = len(unscaled) + continue // don't add to unscaled + case ch >= '0' && ch <= '9': + if dg == -1 { + dg = len(unscaled) + } + default: + r.UnreadRune() + break loop + } + unscaled = append(unscaled, byte(ch)) + } + if dg == -1 { + return nil, fmt.Errorf("no digits read") + } + if dp >= 0 { + z.SetScale(Scale(len(unscaled) - dp)) + } else { + z.SetScale(0) + } + _, ok := z.UnscaledBig().SetString(string(unscaled), 10) + if !ok { + return nil, fmt.Errorf("invalid decimal: %s", string(unscaled)) + } + return z, nil +} + +// SetString sets z to the value of s, interpreted as a decimal (base 10), +// and returns z and a boolean indicating success. The scale of z is the +// number of digits after the decimal point (including any trailing 0s), +// or 0 if there is no decimal point. If SetString fails, the value of z +// is undefined but the returned value is nil. +func (z *Dec) SetString(s string) (*Dec, bool) { + r := strings.NewReader(s) + _, err := z.scan(r) + if err != nil { + return nil, false + } + _, _, err = r.ReadRune() + if err != io.EOF { + return nil, false + } + // err == io.EOF => scan consumed all of s + return z, true +} + +// Scan is a support routine for fmt.Scanner; it sets z to the value of +// the scanned number. It accepts the decimal formats 'd' and 'f', and +// handles both equivalently. Bases 2, 8, 16 are not supported. +// The scale of z is the number of digits after the decimal point +// (including any trailing 0s), or 0 if there is no decimal point. +func (z *Dec) Scan(s fmt.ScanState, ch rune) error { + if ch != 'd' && ch != 'f' && ch != 's' && ch != 'v' { + return fmt.Errorf("Dec.Scan: invalid verb '%c'", ch) + } + s.SkipSpace() + _, err := z.scan(s) + return err +} + +// Gob encoding version +const decGobVersion byte = 1 + +func scaleBytes(s Scale) []byte { + buf := make([]byte, scaleSize) + i := scaleSize + for j := 0; j < scaleSize; j++ { + i-- + buf[i] = byte(s) + s >>= 8 + } + return buf +} + +func scale(b []byte) (s Scale) { + for j := 0; j < scaleSize; j++ { + s <<= 8 + s |= Scale(b[j]) + } + return +} + +// GobEncode implements the gob.GobEncoder interface. +func (x *Dec) GobEncode() ([]byte, error) { + buf, err := x.UnscaledBig().GobEncode() + if err != nil { + return nil, err + } + buf = append(append(buf, scaleBytes(x.Scale())...), decGobVersion) + return buf, nil +} + +// GobDecode implements the gob.GobDecoder interface. +func (z *Dec) GobDecode(buf []byte) error { + if len(buf) == 0 { + return fmt.Errorf("Dec.GobDecode: no data") + } + b := buf[len(buf)-1] + if b != decGobVersion { + return fmt.Errorf("Dec.GobDecode: encoding version %d not supported", b) + } + l := len(buf) - scaleSize - 1 + err := z.UnscaledBig().GobDecode(buf[:l]) + if err != nil { + return err + } + z.SetScale(scale(buf[l : l+scaleSize])) + return nil +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (x *Dec) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (z *Dec) UnmarshalText(data []byte) error { + _, ok := z.SetString(string(data)) + if !ok { + return fmt.Errorf("invalid inf.Dec") + } + return nil +} diff --git a/vendor/gopkg.in/inf.v0/rounder.go b/vendor/gopkg.in/inf.v0/rounder.go new file mode 100644 index 0000000000..3a97ef529b --- /dev/null +++ b/vendor/gopkg.in/inf.v0/rounder.go @@ -0,0 +1,145 @@ +package inf + +import ( + "math/big" +) + +// Rounder represents a method for rounding the (possibly infinite decimal) +// result of a division to a finite Dec. It is used by Dec.Round() and +// Dec.Quo(). +// +// See the Example for results of using each Rounder with some sample values. +// +type Rounder rounder + +// See http://speleotrove.com/decimal/damodel.html#refround for more detailed +// definitions of these rounding modes. +var ( + RoundDown Rounder // towards 0 + RoundUp Rounder // away from 0 + RoundFloor Rounder // towards -infinity + RoundCeil Rounder // towards +infinity + RoundHalfDown Rounder // to nearest; towards 0 if same distance + RoundHalfUp Rounder // to nearest; away from 0 if same distance + RoundHalfEven Rounder // to nearest; even last digit if same distance +) + +// RoundExact is to be used in the case when rounding is not necessary. +// When used with Quo or Round, it returns the result verbatim when it can be +// expressed exactly with the given precision, and it returns nil otherwise. +// QuoExact is a shorthand for using Quo with RoundExact. +var RoundExact Rounder + +type rounder interface { + + // When UseRemainder() returns true, the Round() method is passed the + // remainder of the division, expressed as the numerator and denominator of + // a rational. + UseRemainder() bool + + // Round sets the rounded value of a quotient to z, and returns z. + // quo is rounded down (truncated towards zero) to the scale obtained from + // the Scaler in Quo(). + // + // When the remainder is not used, remNum and remDen are nil. + // When used, the remainder is normalized between -1 and 1; that is: + // + // -|remDen| < remNum < |remDen| + // + // remDen has the same sign as y, and remNum is zero or has the same sign + // as x. + Round(z, quo *Dec, remNum, remDen *big.Int) *Dec +} + +type rndr struct { + useRem bool + round func(z, quo *Dec, remNum, remDen *big.Int) *Dec +} + +func (r rndr) UseRemainder() bool { + return r.useRem +} + +func (r rndr) Round(z, quo *Dec, remNum, remDen *big.Int) *Dec { + return r.round(z, quo, remNum, remDen) +} + +var intSign = []*big.Int{big.NewInt(-1), big.NewInt(0), big.NewInt(1)} + +func roundHalf(f func(c int, odd uint) (roundUp bool)) func(z, q *Dec, rA, rB *big.Int) *Dec { + return func(z, q *Dec, rA, rB *big.Int) *Dec { + z.Set(q) + brA, brB := rA.BitLen(), rB.BitLen() + if brA < brB-1 { + // brA < brB-1 => |rA| < |rB/2| + return z + } + roundUp := false + srA, srB := rA.Sign(), rB.Sign() + s := srA * srB + if brA == brB-1 { + rA2 := new(big.Int).Lsh(rA, 1) + if s < 0 { + rA2.Neg(rA2) + } + roundUp = f(rA2.Cmp(rB)*srB, z.UnscaledBig().Bit(0)) + } else { + // brA > brB-1 => |rA| > |rB/2| + roundUp = true + } + if roundUp { + z.UnscaledBig().Add(z.UnscaledBig(), intSign[s+1]) + } + return z + } +} + +func init() { + RoundExact = rndr{true, + func(z, q *Dec, rA, rB *big.Int) *Dec { + if rA.Sign() != 0 { + return nil + } + return z.Set(q) + }} + RoundDown = rndr{false, + func(z, q *Dec, rA, rB *big.Int) *Dec { + return z.Set(q) + }} + RoundUp = rndr{true, + func(z, q *Dec, rA, rB *big.Int) *Dec { + z.Set(q) + if rA.Sign() != 0 { + z.UnscaledBig().Add(z.UnscaledBig(), intSign[rA.Sign()*rB.Sign()+1]) + } + return z + }} + RoundFloor = rndr{true, + func(z, q *Dec, rA, rB *big.Int) *Dec { + z.Set(q) + if rA.Sign()*rB.Sign() < 0 { + z.UnscaledBig().Add(z.UnscaledBig(), intSign[0]) + } + return z + }} + RoundCeil = rndr{true, + func(z, q *Dec, rA, rB *big.Int) *Dec { + z.Set(q) + if rA.Sign()*rB.Sign() > 0 { + z.UnscaledBig().Add(z.UnscaledBig(), intSign[2]) + } + return z + }} + RoundHalfDown = rndr{true, roundHalf( + func(c int, odd uint) bool { + return c > 0 + })} + RoundHalfUp = rndr{true, roundHalf( + func(c int, odd uint) bool { + return c >= 0 + })} + RoundHalfEven = rndr{true, roundHalf( + func(c int, odd uint) bool { + return c > 0 || c == 0 && odd == 1 + })} +} diff --git a/vendor/k8s.io/apimachinery/LICENSE b/vendor/k8s.io/apimachinery/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/k8s.io/apimachinery/LICENSE @@ -0,0 +1,202 @@ + + 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/vendor/k8s.io/apimachinery/pkg/api/resource/OWNERS b/vendor/k8s.io/apimachinery/pkg/api/resource/OWNERS new file mode 100644 index 0000000000..063fd285da --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/OWNERS @@ -0,0 +1,10 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: + - thockin + - smarterclayton + - wojtek-t + - derekwaynecarr + - mikedanese + - saad-ali + - janetkuo diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/amount.go b/vendor/k8s.io/apimachinery/pkg/api/resource/amount.go new file mode 100644 index 0000000000..2eebec667d --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/amount.go @@ -0,0 +1,337 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math/big" + "strconv" + + inf "gopkg.in/inf.v0" +) + +// Scale is used for getting and setting the base-10 scaled value. +// Base-2 scales are omitted for mathematical simplicity. +// See Quantity.ScaledValue for more details. +type Scale int32 + +// infScale adapts a Scale value to an inf.Scale value. +func (s Scale) infScale() inf.Scale { + return inf.Scale(-s) // inf.Scale is upside-down +} + +const ( + Nano Scale = -9 + Micro Scale = -6 + Milli Scale = -3 + Kilo Scale = 3 + Mega Scale = 6 + Giga Scale = 9 + Tera Scale = 12 + Peta Scale = 15 + Exa Scale = 18 +) + +var ( + Zero = int64Amount{} + + // Used by quantity strings - treat as read only + zeroBytes = []byte("0") +) + +// int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster +// than operations on inf.Dec for values that can be represented as int64. +// +k8s:openapi-gen=true +type int64Amount struct { + value int64 + scale Scale +} + +// Sign returns 0 if the value is zero, -1 if it is less than 0, or 1 if it is greater than 0. +func (a int64Amount) Sign() int { + switch { + case a.value == 0: + return 0 + case a.value > 0: + return 1 + default: + return -1 + } +} + +// AsInt64 returns the current amount as an int64 at scale 0, or false if the value cannot be +// represented in an int64 OR would result in a loss of precision. This method is intended as +// an optimization to avoid calling AsDec. +func (a int64Amount) AsInt64() (int64, bool) { + if a.scale == 0 { + return a.value, true + } + if a.scale < 0 { + // TODO: attempt to reduce factors, although it is assumed that factors are reduced prior + // to the int64Amount being created. + return 0, false + } + return positiveScaleInt64(a.value, a.scale) +} + +// AsScaledInt64 returns an int64 representing the value of this amount at the specified scale, +// rounding up, or false if that would result in overflow. (1e20).AsScaledInt64(1) would result +// in overflow because 1e19 is not representable as an int64. Note that setting a scale larger +// than the current value may result in loss of precision - i.e. (1e-6).AsScaledInt64(0) would +// return 1, because 0.000001 is rounded up to 1. +func (a int64Amount) AsScaledInt64(scale Scale) (result int64, ok bool) { + if a.scale < scale { + result, _ = negativeScaleInt64(a.value, scale-a.scale) + return result, true + } + return positiveScaleInt64(a.value, a.scale-scale) +} + +// AsDec returns an inf.Dec representation of this value. +func (a int64Amount) AsDec() *inf.Dec { + var base inf.Dec + base.SetUnscaled(a.value) + base.SetScale(inf.Scale(-a.scale)) + return &base +} + +// Cmp returns 0 if a and b are equal, 1 if a is greater than b, or -1 if a is less than b. +func (a int64Amount) Cmp(b int64Amount) int { + switch { + case a.scale == b.scale: + // compare only the unscaled portion + case a.scale > b.scale: + result, remainder, exact := divideByScaleInt64(b.value, a.scale-b.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == a.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return -1 + default: + return 1 + } + } + b.value = result + default: + result, remainder, exact := divideByScaleInt64(a.value, b.scale-a.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == b.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return 1 + default: + return -1 + } + } + a.value = result + } + + switch { + case a.value == b.value: + return 0 + case a.value < b.value: + return -1 + default: + return 1 + } +} + +// Add adds two int64Amounts together, matching scales. It will return false and not mutate +// a if overflow or underflow would result. +func (a *int64Amount) Add(b int64Amount) bool { + switch { + case b.value == 0: + return true + case a.value == 0: + a.value = b.value + a.scale = b.scale + return true + case a.scale == b.scale: + c, ok := int64Add(a.value, b.value) + if !ok { + return false + } + a.value = c + case a.scale > b.scale: + c, ok := positiveScaleInt64(a.value, a.scale-b.scale) + if !ok { + return false + } + c, ok = int64Add(c, b.value) + if !ok { + return false + } + a.scale = b.scale + a.value = c + default: + c, ok := positiveScaleInt64(b.value, b.scale-a.scale) + if !ok { + return false + } + c, ok = int64Add(a.value, c) + if !ok { + return false + } + a.value = c + } + return true +} + +// Sub removes the value of b from the current amount, or returns false if underflow would result. +func (a *int64Amount) Sub(b int64Amount) bool { + return a.Add(int64Amount{value: -b.value, scale: b.scale}) +} + +// Mul multiplies the provided b to the current amount, or +// returns false if overflow or underflow would result. +func (a *int64Amount) Mul(b int64) bool { + switch { + case a.value == 0: + return true + case b == 0: + a.value = 0 + a.scale = 0 + return true + case a.scale == 0: + c, ok := int64Multiply(a.value, b) + if !ok { + return false + } + a.value = c + case a.scale > 0: + c, ok := int64Multiply(a.value, b) + if !ok { + return false + } + if _, ok = positiveScaleInt64(c, a.scale); !ok { + return false + } + a.value = c + default: + c, ok := int64Multiply(a.value, b) + if !ok { + return false + } + if _, ok = negativeScaleInt64(c, -a.scale); !ok { + return false + } + a.value = c + } + return true +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a int64Amount) AsScale(scale Scale) (int64Amount, bool) { + if a.scale >= scale { + return a, true + } + result, exact := negativeScaleInt64(a.value, scale-a.scale) + return int64Amount{value: result, scale: scale}, exact +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a int64Amount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.value + exponent = int32(a.scale) + + amount, times := removeInt64Factors(mantissa, 10) + exponent += int32(times) + + // make sure exponent is a multiple of 3 + var ok bool + switch exponent % 3 { + case 1, -2: + amount, ok = int64MultiplyScale10(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent = exponent - 1 + case 2, -1: + amount, ok = int64MultiplyScale100(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent = exponent - 2 + } + return strconv.AppendInt(out, amount, 10), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a int64Amount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + value, ok := a.AsScaledInt64(0) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBase1024Bytes(out) + } + amount, exponent := removeInt64Factors(value, 1024) + return strconv.AppendInt(out, amount, 10), exponent +} + +// infDecAmount implements common operations over an inf.Dec that are specific to the quantity +// representation. +type infDecAmount struct { + *inf.Dec +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a infDecAmount) AsScale(scale Scale) (infDecAmount, bool) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, scale.infScale(), inf.RoundUp) + return infDecAmount{tmp}, tmp.Cmp(a.Dec) == 0 +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a infDecAmount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.Dec.UnscaledBig() + exponent = int32(-a.Dec.Scale()) + amount := big.NewInt(0).Set(mantissa) + // move all factors of 10 into the exponent for easy reasoning + amount, times := removeBigIntFactors(amount, bigTen) + exponent += times + + // make sure exponent is a multiple of 3 + for exponent%3 != 0 { + amount.Mul(amount, bigTen) + exponent-- + } + + return append(out, amount.String()...), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a infDecAmount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, 0, inf.RoundUp) + amount, exponent := removeBigIntFactors(tmp.UnscaledBig(), big1024) + return append(out, amount.String()...), exponent +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/generated.pb.go b/vendor/k8s.io/apimachinery/pkg/api/resource/generated.pb.go new file mode 100644 index 0000000000..9e1a5c0e1f --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/generated.pb.go @@ -0,0 +1,24 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/api/resource/generated.proto + +package resource + +func (m *Quantity) Reset() { *m = Quantity{} } + +func (m *QuantityValue) Reset() { *m = QuantityValue{} } diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/generated.proto b/vendor/k8s.io/apimachinery/pkg/api/resource/generated.proto new file mode 100644 index 0000000000..875ad8577a --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/generated.proto @@ -0,0 +1,113 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.api.resource; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/api/resource"; + +// Quantity is a fixed-point representation of a number. +// It provides convenient marshaling/unmarshaling in JSON and YAML, +// in addition to String() and AsInt64() accessors. +// +// The serialization format is: +// +// ``` +// ::= +// +// (Note that may be empty, from the "" case in .) +// +// ::= 0 | 1 | ... | 9 +// ::= | +// ::= | . | . | . +// ::= "+" | "-" +// ::= | +// ::= | | +// ::= Ki | Mi | Gi | Ti | Pi | Ei +// +// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) +// +// ::= m | "" | k | M | G | T | P | E +// +// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) +// +// ::= "e" | "E" +// ``` +// +// No matter which of the three exponent forms is used, no quantity may represent +// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal +// places. Numbers larger or more precise will be capped or rounded up. +// (E.g.: 0.1m will rounded up to 1m.) +// This may be extended in the future if we require larger or smaller quantities. +// +// When a Quantity is parsed from a string, it will remember the type of suffix +// it had, and will use the same type again when it is serialized. +// +// Before serializing, Quantity will be put in "canonical form". +// This means that Exponent/suffix will be adjusted up or down (with a +// corresponding increase or decrease in Mantissa) such that: +// +// - No precision is lost +// - No fractional digits will be emitted +// - The exponent (or suffix) is as large as possible. +// +// The sign will be omitted unless the number is negative. +// +// Examples: +// +// - 1.5 will be serialized as "1500m" +// - 1.5Gi will be serialized as "1536Mi" +// +// Note that the quantity will NEVER be internally represented by a +// floating point number. That is the whole point of this exercise. +// +// Non-canonical values will still parse as long as they are well formed, +// but will be re-emitted in their canonical form. (So always use canonical +// form, or don't diff.) +// +// This format is intended to make it difficult to use these numbers without +// writing some sort of special handling code in the hopes that that will +// cause implementors to also use a fixed point implementation. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +message Quantity { + optional string string = 1; +} + +// QuantityValue makes it possible to use a Quantity as value for a command +// line parameter. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +message QuantityValue { + optional string string = 1; +} + diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/math.go b/vendor/k8s.io/apimachinery/pkg/api/resource/math.go new file mode 100644 index 0000000000..8ffcb9f09a --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/math.go @@ -0,0 +1,310 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math/big" + + inf "gopkg.in/inf.v0" +) + +const ( + // maxInt64Factors is the highest value that will be checked when removing factors of 10 from an int64. + // It is also the maximum decimal digits that can be represented with an int64. + maxInt64Factors = 18 +) + +var ( + // Commonly needed big.Int values-- treat as read only! + bigTen = big.NewInt(10) + bigZero = big.NewInt(0) + bigOne = big.NewInt(1) + bigThousand = big.NewInt(1000) + big1024 = big.NewInt(1024) + + // Commonly needed inf.Dec values-- treat as read only! + decZero = inf.NewDec(0, 0) + decOne = inf.NewDec(1, 0) + + // Largest (in magnitude) number allowed. + maxAllowed = infDecAmount{inf.NewDec((1<<63)-1, 0)} // == max int64 + + // The maximum value we can represent milli-units for. + // Compare with the return value of Quantity.Value() to + // see if it's safe to use Quantity.MilliValue(). + MaxMilliValue = int64(((1 << 63) - 1) / 1000) +) + +const mostNegative = -(mostPositive + 1) +const mostPositive = 1<<63 - 1 + +// int64Add returns a+b, or false if that would overflow int64. +func int64Add(a, b int64) (int64, bool) { + c := a + b + switch { + case a > 0 && b > 0: + if c < 0 { + return 0, false + } + case a < 0 && b < 0: + if c > 0 { + return 0, false + } + if a == mostNegative && b == mostNegative { + return 0, false + } + } + return c, true +} + +// int64Multiply returns a*b, or false if that would overflow or underflow int64. +func int64Multiply(a, b int64) (int64, bool) { + if a == 0 || b == 0 || a == 1 || b == 1 { + return a * b, true + } + if a == mostNegative || b == mostNegative { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale returns a*b, assuming b is greater than one, or false if that would overflow or underflow int64. +// Use when b is known to be greater than one. +func int64MultiplyScale(a int64, b int64) (int64, bool) { + if a == 0 || a == 1 { + return a * b, true + } + if a == mostNegative && b != 1 { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale10 multiplies a by 10, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 10) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale10(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 10, true + } + if a == mostNegative { + return 0, false + } + c := a * 10 + return c, c/10 == a +} + +// int64MultiplyScale100 multiplies a by 100, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 100) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale100(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 100, true + } + if a == mostNegative { + return 0, false + } + c := a * 100 + return c, c/100 == a +} + +// int64MultiplyScale1000 multiplies a by 1000, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 1000) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale1000(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 1000, true + } + if a == mostNegative { + return 0, false + } + c := a * 1000 + return c, c/1000 == a +} + +// positiveScaleInt64 multiplies base by 10^scale, returning false if the +// value overflows. Passing a negative scale is undefined. +func positiveScaleInt64(base int64, scale Scale) (int64, bool) { + switch scale { + case 0: + return base, true + case 1: + return int64MultiplyScale10(base) + case 2: + return int64MultiplyScale100(base) + case 3: + return int64MultiplyScale1000(base) + case 6: + return int64MultiplyScale(base, 1000000) + case 9: + return int64MultiplyScale(base, 1000000000) + default: + value := base + var ok bool + for i := Scale(0); i < scale; i++ { + if value, ok = int64MultiplyScale(value, 10); !ok { + return 0, false + } + } + return value, true + } +} + +// negativeScaleInt64 reduces base by the provided scale, rounding up, until the +// value is zero or the scale is reached. Passing a negative scale is undefined. +// The value returned, if not exact, is rounded away from zero. +func negativeScaleInt64(base int64, scale Scale) (result int64, exact bool) { + if scale == 0 { + return base, true + } + + value := base + var fraction bool + for i := Scale(0); i < scale; i++ { + if !fraction && value%10 != 0 { + fraction = true + } + value = value / 10 + if value == 0 { + if fraction { + if base > 0 { + return 1, false + } + return -1, false + } + return 0, true + } + } + if fraction { + if base > 0 { + value++ + } else { + value-- + } + } + return value, !fraction +} + +func pow10Int64(b int64) int64 { + switch b { + case 0: + return 1 + case 1: + return 10 + case 2: + return 100 + case 3: + return 1000 + case 4: + return 10000 + case 5: + return 100000 + case 6: + return 1000000 + case 7: + return 10000000 + case 8: + return 100000000 + case 9: + return 1000000000 + case 10: + return 10000000000 + case 11: + return 100000000000 + case 12: + return 1000000000000 + case 13: + return 10000000000000 + case 14: + return 100000000000000 + case 15: + return 1000000000000000 + case 16: + return 10000000000000000 + case 17: + return 100000000000000000 + case 18: + return 1000000000000000000 + default: + return 0 + } +} + +// negativeScaleInt64 returns the result of dividing base by scale * 10 and the remainder, or +// false if no such division is possible. Dividing by negative scales is undefined. +func divideByScaleInt64(base int64, scale Scale) (result, remainder int64, exact bool) { + if scale == 0 { + return base, 0, true + } + // the max scale representable in base 10 in an int64 is 18 decimal places + if scale >= 18 { + return 0, base, false + } + divisor := pow10Int64(int64(scale)) + return base / divisor, base % divisor, true +} + +// removeInt64Factors divides in a loop; the return values have the property that +// value == result * base ^ scale +func removeInt64Factors(value int64, base int64) (result int64, times int32) { + times = 0 + result = value + negative := result < 0 + if negative { + result = -result + } + switch base { + // allow the compiler to optimize the common cases + case 10: + for result >= 10 && result%10 == 0 { + times++ + result = result / 10 + } + // allow the compiler to optimize the common cases + case 1024: + for result >= 1024 && result%1024 == 0 { + times++ + result = result / 1024 + } + default: + for result >= base && result%base == 0 { + times++ + result = result / base + } + } + if negative { + result = -result + } + return result, times +} + +// removeBigIntFactors divides in a loop; the return values have the property that +// d == result * factor ^ times +// d may be modified in place. +// If d == 0, then the return values will be (0, 0) +func removeBigIntFactors(d, factor *big.Int) (result *big.Int, times int32) { + q := big.NewInt(0) + m := big.NewInt(0) + for d.Cmp(bigZero) != 0 { + q.DivMod(d, factor, m) + if m.Cmp(bigZero) != 0 { + break + } + times++ + d, q = q, d + } + return d, times +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/quantity.go b/vendor/k8s.io/apimachinery/pkg/api/resource/quantity.go new file mode 100644 index 0000000000..f3cd600604 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/quantity.go @@ -0,0 +1,880 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "bytes" + "errors" + "fmt" + math "math" + "math/big" + "strconv" + "strings" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + + inf "gopkg.in/inf.v0" +) + +// Quantity is a fixed-point representation of a number. +// It provides convenient marshaling/unmarshaling in JSON and YAML, +// in addition to String() and AsInt64() accessors. +// +// The serialization format is: +// +// ``` +// ::= +// +// (Note that may be empty, from the "" case in .) +// +// ::= 0 | 1 | ... | 9 +// ::= | +// ::= | . | . | . +// ::= "+" | "-" +// ::= | +// ::= | | +// ::= Ki | Mi | Gi | Ti | Pi | Ei +// +// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) +// +// ::= m | "" | k | M | G | T | P | E +// +// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) +// +// ::= "e" | "E" +// ``` +// +// No matter which of the three exponent forms is used, no quantity may represent +// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal +// places. Numbers larger or more precise will be capped or rounded up. +// (E.g.: 0.1m will rounded up to 1m.) +// This may be extended in the future if we require larger or smaller quantities. +// +// When a Quantity is parsed from a string, it will remember the type of suffix +// it had, and will use the same type again when it is serialized. +// +// Before serializing, Quantity will be put in "canonical form". +// This means that Exponent/suffix will be adjusted up or down (with a +// corresponding increase or decrease in Mantissa) such that: +// +// - No precision is lost +// - No fractional digits will be emitted +// - The exponent (or suffix) is as large as possible. +// +// The sign will be omitted unless the number is negative. +// +// Examples: +// +// - 1.5 will be serialized as "1500m" +// - 1.5Gi will be serialized as "1536Mi" +// +// Note that the quantity will NEVER be internally represented by a +// floating point number. That is the whole point of this exercise. +// +// Non-canonical values will still parse as long as they are well formed, +// but will be re-emitted in their canonical form. (So always use canonical +// form, or don't diff.) +// +// This format is intended to make it difficult to use these numbers without +// writing some sort of special handling code in the hopes that that will +// cause implementors to also use a fixed point implementation. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +type Quantity struct { + // i is the quantity in int64 scaled form, if d.Dec == nil + i int64Amount + // d is the quantity in inf.Dec form if d.Dec != nil + d infDecAmount + // s is the generated value of this quantity to avoid recalculation + s string + + // Change Format at will. See the comment for Canonicalize for + // more details. + Format +} + +// CanonicalValue allows a quantity amount to be converted to a string. +type CanonicalValue interface { + // AsCanonicalBytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-10. Callers may + // pass a byte slice to the method to avoid allocations. + AsCanonicalBytes(out []byte) ([]byte, int32) + // AsCanonicalBase1024Bytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-1024. Callers + // may pass a byte slice to the method to avoid allocations. + AsCanonicalBase1024Bytes(out []byte) ([]byte, int32) +} + +// Format lists the three possible formattings of a quantity. +type Format string + +const ( + DecimalExponent = Format("DecimalExponent") // e.g., 12e6 + BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20) + DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) +) + +// MustParse turns the given string into a quantity or panics; for tests +// or other cases where you know the string is valid. +func MustParse(str string) Quantity { + q, err := ParseQuantity(str) + if err != nil { + panic(fmt.Errorf("cannot parse '%v': %v", str, err)) + } + return q +} + +const ( + // splitREString is used to separate a number from its suffix; as such, + // this is overly permissive, but that's OK-- it will be checked later. + splitREString = "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$" +) + +var ( + // Errors that could happen while parsing a string. + ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'") + ErrNumeric = errors.New("unable to parse numeric part of quantity") + ErrSuffix = errors.New("unable to parse quantity's suffix") +) + +// parseQuantityString is a fast scanner for quantity values. +func parseQuantityString(str string) (positive bool, value, num, denom, suffix string, err error) { + positive = true + pos := 0 + end := len(str) + + // handle leading sign + if pos < end { + switch str[0] { + case '-': + positive = false + pos++ + case '+': + pos++ + } + } + + // strip leading zeros +Zeroes: + for i := pos; ; i++ { + if i >= end { + num = "0" + value = num + return + } + switch str[i] { + case '0': + pos++ + default: + break Zeroes + } + } + + // extract the numerator +Num: + for i := pos; ; i++ { + if i >= end { + num = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + num = str[pos:i] + pos = i + break Num + } + } + + // if we stripped all numerator positions, always return 0 + if len(num) == 0 { + num = "0" + } + + // handle a denominator + if pos < end && str[pos] == '.' { + pos++ + Denom: + for i := pos; ; i++ { + if i >= end { + denom = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + denom = str[pos:i] + pos = i + break Denom + } + } + // TODO: we currently allow 1.G, but we may not want to in the future. + // if len(denom) == 0 { + // err = ErrFormatWrong + // return + // } + } + value = str[0:pos] + + // grab the elements of the suffix + suffixStart := pos + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + if !strings.ContainsAny(str[i:i+1], "eEinumkKMGTP") { + pos = i + break + } + } + if pos < end { + switch str[pos] { + case '-', '+': + pos++ + } + } +Suffix: + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + break Suffix + } + } + // we encountered a non decimal in the Suffix loop, but the last character + // was not a valid exponent + err = ErrFormatWrong + return +} + +// ParseQuantity turns str into a Quantity, or returns an error. +func ParseQuantity(str string) (Quantity, error) { + if len(str) == 0 { + return Quantity{}, ErrFormatWrong + } + if str == "0" { + return Quantity{Format: DecimalSI, s: str}, nil + } + + positive, value, num, denom, suf, err := parseQuantityString(str) + if err != nil { + return Quantity{}, err + } + + base, exponent, format, ok := quantitySuffixer.interpret(suffix(suf)) + if !ok { + return Quantity{}, ErrSuffix + } + + precision := int32(0) + scale := int32(0) + mantissa := int64(1) + switch format { + case DecimalExponent, DecimalSI: + scale = exponent + precision = maxInt64Factors - int32(len(num)+len(denom)) + case BinarySI: + scale = 0 + switch { + case exponent >= 0 && len(denom) == 0: + // only handle positive binary numbers with the fast path + mantissa = int64(int64(mantissa) << uint64(exponent)) + // 1Mi (2^20) has ~6 digits of decimal precision, so exponent*3/10 -1 is roughly the precision + precision = 15 - int32(len(num)) - int32(float32(exponent)*3/10) - 1 + default: + precision = -1 + } + } + + if precision >= 0 { + // if we have a denominator, shift the entire value to the left by the number of places in the + // denominator + scale -= int32(len(denom)) + if scale >= int32(Nano) { + shifted := num + denom + + var value int64 + value, err := strconv.ParseInt(shifted, 10, 64) + if err != nil { + return Quantity{}, ErrNumeric + } + if result, ok := int64Multiply(value, int64(mantissa)); ok { + if !positive { + result = -result + } + // if the number is in canonical form, reuse the string + switch format { + case BinarySI: + if exponent%10 == 0 && (value&0x07 != 0) { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + default: + if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + } + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil + } + } + } + + amount := new(inf.Dec) + if _, ok := amount.SetString(value); !ok { + return Quantity{}, ErrNumeric + } + + // So that no one but us has to think about suffixes, remove it. + if base == 10 { + amount.SetScale(amount.Scale() + Scale(exponent).infScale()) + } else if base == 2 { + // numericSuffix = 2 ** exponent + numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) + ub := amount.UnscaledBig() + amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) + } + + // Cap at min/max bounds. + sign := amount.Sign() + if sign == -1 { + amount.Neg(amount) + } + + // This rounds non-zero values up to the minimum representable value, under the theory that + // if you want some resources, you should get some resources, even if you asked for way too small + // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have + // the side effect of rounding values < .5n to zero. + if v, ok := amount.Unscaled(); v != int64(0) || !ok { + amount.Round(amount, Nano.infScale(), inf.RoundUp) + } + + // The max is just a simple cap. + // TODO: this prevents accumulating quantities greater than int64, for instance quota across a cluster + if format == BinarySI && amount.Cmp(maxAllowed.Dec) > 0 { + amount.Set(maxAllowed.Dec) + } + + if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } + if sign == -1 { + amount.Neg(amount) + } + + return Quantity{d: infDecAmount{amount}, Format: format}, nil +} + +// DeepCopy returns a deep-copy of the Quantity value. Note that the method +// receiver is a value, so we can mutate it in-place and return it. +func (q Quantity) DeepCopy() Quantity { + if q.d.Dec != nil { + tmp := &inf.Dec{} + q.d.Dec = tmp.Set(q.d.Dec) + } + return q +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ Quantity) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ Quantity) OpenAPISchemaFormat() string { return "" } + +// OpenAPIV3OneOfTypes is used by the kube-openapi generator when constructing +// the OpenAPI v3 spec of this type. +func (Quantity) OpenAPIV3OneOfTypes() []string { return []string{"string", "number"} } + +// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity). +// +// Note about BinarySI: +// - If q.Format is set to BinarySI and q.Amount represents a non-zero value between +// -1 and +1, it will be emitted as if q.Format were DecimalSI. +// - Otherwise, if q.Format is set to BinarySI, fractional parts of q.Amount will be +// rounded up. (1.1i becomes 2i.) +func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) { + if q.IsZero() { + return zeroBytes, nil + } + + var rounded CanonicalValue + format := q.Format + switch format { + case DecimalExponent, DecimalSI: + case BinarySI: + if q.CmpInt64(-1024) > 0 && q.CmpInt64(1024) < 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } else { + var exact bool + if rounded, exact = q.AsScale(0); !exact { + // Don't lose precision-- show as DecimalSI + format = DecimalSI + } + } + default: + format = DecimalExponent + } + + // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to + // one of the other formats. + switch format { + case DecimalExponent, DecimalSI: + number, exponent := q.AsCanonicalBytes(out) + suffix, _ := quantitySuffixer.constructBytes(10, exponent, format) + return number, suffix + default: + // format must be BinarySI + number, exponent := rounded.AsCanonicalBase1024Bytes(out) + suffix, _ := quantitySuffixer.constructBytes(2, exponent*10, format) + return number, suffix + } +} + +// AsApproximateFloat64 returns a float64 representation of the quantity which +// may lose precision. If precision matter more than performance, see +// AsFloat64Slow. If the value of the quantity is outside the range of a +// float64 +Inf/-Inf will be returned. +func (q *Quantity) AsApproximateFloat64() float64 { + var base float64 + var exponent int + if q.d.Dec != nil { + base, _ = big.NewFloat(0).SetInt(q.d.Dec.UnscaledBig()).Float64() + exponent = int(-q.d.Dec.Scale()) + } else { + base = float64(q.i.value) + exponent = int(q.i.scale) + } + if exponent == 0 { + return base + } + + return base * math.Pow10(exponent) +} + +// AsFloat64Slow returns a float64 representation of the quantity. This is +// more precise than AsApproximateFloat64 but significantly slower. If the +// value of the quantity is outside the range of a float64 +Inf/-Inf will be +// returned. +func (q *Quantity) AsFloat64Slow() float64 { + infDec := q.AsDec() + + var absScale int64 + if infDec.Scale() < 0 { + absScale = int64(-infDec.Scale()) + } else { + absScale = int64(infDec.Scale()) + } + pow10AbsScale := big.NewInt(10) + pow10AbsScale = pow10AbsScale.Exp(pow10AbsScale, big.NewInt(absScale), nil) + + var resultBigFloat *big.Float + if infDec.Scale() < 0 { + resultBigInt := new(big.Int).Mul(infDec.UnscaledBig(), pow10AbsScale) + resultBigFloat = new(big.Float).SetInt(resultBigInt) + } else { + pow10AbsScaleFloat := new(big.Float).SetInt(pow10AbsScale) + resultBigFloat = new(big.Float).SetInt(infDec.UnscaledBig()) + resultBigFloat = resultBigFloat.Quo(resultBigFloat, pow10AbsScaleFloat) + } + + result, _ := resultBigFloat.Float64() + return result +} + +// AsInt64 returns a representation of the current value as an int64 if a fast conversion +// is possible. If false is returned, callers must use the inf.Dec form of this quantity. +func (q *Quantity) AsInt64() (int64, bool) { + if q.d.Dec != nil { + return 0, false + } + return q.i.AsInt64() +} + +// ToDec promotes the quantity in place to use an inf.Dec representation and returns itself. +func (q *Quantity) ToDec() *Quantity { + if q.d.Dec == nil { + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + } + return q +} + +// AsDec returns the quantity as represented by a scaled inf.Dec. +func (q *Quantity) AsDec() *inf.Dec { + if q.d.Dec != nil { + return q.d.Dec + } + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + return q.d.Dec +} + +// AsCanonicalBytes returns the canonical byte representation of this quantity as a mantissa +// and base 10 exponent. The out byte slice may be passed to the method to avoid an extra +// allocation. +func (q *Quantity) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + if q.d.Dec != nil { + return q.d.AsCanonicalBytes(out) + } + return q.i.AsCanonicalBytes(out) +} + +// IsZero returns true if the quantity is equal to zero. +func (q *Quantity) IsZero() bool { + if q.d.Dec != nil { + return q.d.Dec.Sign() == 0 + } + return q.i.value == 0 +} + +// Sign returns 0 if the quantity is zero, -1 if the quantity is less than zero, or 1 if the +// quantity is greater than zero. +func (q *Quantity) Sign() int { + if q.d.Dec != nil { + return q.d.Dec.Sign() + } + return q.i.Sign() +} + +// AsScale returns the current value, rounded up to the provided scale, and returns +// false if the scale resulted in a loss of precision. +func (q *Quantity) AsScale(scale Scale) (CanonicalValue, bool) { + if q.d.Dec != nil { + return q.d.AsScale(scale) + } + return q.i.AsScale(scale) +} + +// RoundUp updates the quantity to the provided scale, ensuring that the value is at +// least 1. False is returned if the rounding operation resulted in a loss of precision. +// Negative numbers are rounded away from zero (-9 scale 1 rounds to -10). +func (q *Quantity) RoundUp(scale Scale) bool { + if q.d.Dec != nil { + q.s = "" + d, exact := q.d.AsScale(scale) + q.d = d + return exact + } + // avoid clearing the string value if we have already calculated it + if q.i.scale >= scale { + return true + } + q.s = "" + i, exact := q.i.AsScale(scale) + q.i = i + return exact +} + +// Add adds the provide y quantity to the current value. If the current value is zero, +// the format of the quantity will be updated to the format of y. +func (q *Quantity) Add(y Quantity) { + q.s = "" + if q.d.Dec == nil && y.d.Dec == nil { + if q.i.value == 0 { + q.Format = y.Format + } + if q.i.Add(y.i) { + return + } + } else if q.IsZero() { + q.Format = y.Format + } + q.ToDec().d.Dec.Add(q.d.Dec, y.AsDec()) +} + +// Sub subtracts the provided quantity from the current value in place. If the current +// value is zero, the format of the quantity will be updated to the format of y. +func (q *Quantity) Sub(y Quantity) { + q.s = "" + if q.IsZero() { + q.Format = y.Format + } + if q.d.Dec == nil && y.d.Dec == nil && q.i.Sub(y.i) { + return + } + q.ToDec().d.Dec.Sub(q.d.Dec, y.AsDec()) +} + +// Mul multiplies the provided y to the current value. +// It will return false if the result is inexact. Otherwise, it will return true. +func (q *Quantity) Mul(y int64) bool { + q.s = "" + if q.d.Dec == nil && q.i.Mul(y) { + return true + } + return q.ToDec().d.Dec.Mul(q.d.Dec, inf.NewDec(y, inf.Scale(0))).UnscaledBig().IsInt64() +} + +// Cmp returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) Cmp(y Quantity) int { + if q.d.Dec == nil && y.d.Dec == nil { + return q.i.Cmp(y.i) + } + return q.AsDec().Cmp(y.AsDec()) +} + +// CmpInt64 returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) CmpInt64(y int64) int { + if q.d.Dec != nil { + return q.d.Dec.Cmp(inf.NewDec(y, inf.Scale(0))) + } + return q.i.Cmp(int64Amount{value: y}) +} + +// Neg sets quantity to be the negative value of itself. +func (q *Quantity) Neg() { + q.s = "" + if q.d.Dec == nil { + q.i.value = -q.i.value + return + } + q.d.Dec.Neg(q.d.Dec) +} + +// Equal checks equality of two Quantities. This is useful for testing with +// cmp.Equal. +func (q Quantity) Equal(v Quantity) bool { + return q.Cmp(v) == 0 +} + +// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation +// of most Quantity values. +const int64QuantityExpectedBytes = 18 + +// String formats the Quantity as a string, caching the result if not calculated. +// String is an expensive operation and caching this result significantly reduces the cost of +// normal parse / marshal operations on Quantity. +func (q *Quantity) String() string { + if q == nil { + return "" + } + if len(q.s) == 0 { + result := make([]byte, 0, int64QuantityExpectedBytes) + number, suffix := q.CanonicalizeBytes(result) + number = append(number, suffix...) + q.s = string(number) + } + return q.s +} + +// MarshalJSON implements the json.Marshaller interface. +func (q Quantity) MarshalJSON() ([]byte, error) { + if len(q.s) > 0 { + out := make([]byte, len(q.s)+2) + out[0], out[len(out)-1] = '"', '"' + copy(out[1:], q.s) + return out, nil + } + result := make([]byte, int64QuantityExpectedBytes) + result[0] = '"' + number, suffix := q.CanonicalizeBytes(result[1:1]) + // if the same slice was returned to us that we passed in, avoid another allocation by copying number into + // the source slice and returning that + if len(number) > 0 && &number[0] == &result[1] && (len(number)+len(suffix)+2) <= int64QuantityExpectedBytes { + number = append(number, suffix...) + number = append(number, '"') + return result[:1+len(number)], nil + } + // if CanonicalizeBytes needed more space than our slice provided, we may need to allocate again so use + // append + result = result[:1] + result = append(result, number...) + result = append(result, suffix...) + result = append(result, '"') + return result, nil +} + +func (q Quantity) MarshalCBOR() ([]byte, error) { + // The call to String() should never return the string "" because the receiver's + // address will never be nil. + return cbor.Marshal(q.String()) +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (q Quantity) ToUnstructured() interface{} { + return q.String() +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +// TODO: Remove support for leading/trailing whitespace +func (q *Quantity) UnmarshalJSON(value []byte) error { + l := len(value) + if l == 4 && bytes.Equal(value, []byte("null")) { + q.d.Dec = nil + q.i = int64Amount{} + return nil + } + if l >= 2 && value[0] == '"' && value[l-1] == '"' { + value = value[1 : l-1] + } + + parsed, err := ParseQuantity(strings.TrimSpace(string(value))) + if err != nil { + return err + } + + // This copy is safe because parsed will not be referred to again. + *q = parsed + return nil +} + +func (q *Quantity) UnmarshalCBOR(value []byte) error { + var s *string + if err := cbor.Unmarshal(value, &s); err != nil { + return err + } + + if s == nil { + q.d.Dec = nil + q.i = int64Amount{} + return nil + } + + parsed, err := ParseQuantity(strings.TrimSpace(*s)) + if err != nil { + return err + } + + *q = parsed + return nil +} + +// NewDecimalQuantity returns a new Quantity representing the given +// value in the given format. +func NewDecimalQuantity(b inf.Dec, format Format) *Quantity { + return &Quantity{ + d: infDecAmount{&b}, + Format: format, + } +} + +// NewQuantity returns a new Quantity representing the given +// value in the given format. +func NewQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value}, + Format: format, + } +} + +// NewMilliQuantity returns a new Quantity representing the given +// value * 1/1000 in the given format. Note that BinarySI formatting +// will round fractional values, and will be changed to DecimalSI for +// values x where (-1 < x < 1) && (x != 0). +func NewMilliQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: -3}, + Format: format, + } +} + +// NewScaledQuantity returns a new Quantity representing the given +// value * 10^scale in DecimalSI format. +func NewScaledQuantity(value int64, scale Scale) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: scale}, + Format: DecimalSI, + } +} + +// Value returns the unscaled value of q rounded up to the nearest integer away from 0. +func (q *Quantity) Value() int64 { + return q.ScaledValue(0) +} + +// MilliValue returns the value of ceil(q * 1000); this could overflow an int64; +// if that's a concern, call Value() first to verify the number is small enough. +func (q *Quantity) MilliValue() int64 { + return q.ScaledValue(Milli) +} + +// ScaledValue returns the value of ceil(q / 10^scale). +// For example, NewQuantity(1, DecimalSI).ScaledValue(Milli) returns 1000. +// This could overflow an int64. +// To detect overflow, call Value() first and verify the expected magnitude. +func (q *Quantity) ScaledValue(scale Scale) int64 { + if q.d.Dec == nil { + i, _ := q.i.AsScaledInt64(scale) + return i + } + dec := q.d.Dec + return scaledValue(dec.UnscaledBig(), int(dec.Scale()), int(scale.infScale())) +} + +// Set sets q's value to be value. +func (q *Quantity) Set(value int64) { + q.SetScaled(value, 0) +} + +// SetMilli sets q's value to be value * 1/1000. +func (q *Quantity) SetMilli(value int64) { + q.SetScaled(value, Milli) +} + +// SetScaled sets q's value to be value * 10^scale +func (q *Quantity) SetScaled(value int64, scale Scale) { + q.s = "" + q.d.Dec = nil + q.i = int64Amount{value: value, scale: scale} +} + +// QuantityValue makes it possible to use a Quantity as value for a command +// line parameter. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +type QuantityValue struct { + Quantity +} + +// Set implements pflag.Value.Set and Go flag.Value.Set. +func (q *QuantityValue) Set(s string) error { + quantity, err := ParseQuantity(s) + if err != nil { + return err + } + q.Quantity = quantity + return nil +} + +// Type implements pflag.Value.Type. +func (q QuantityValue) Type() string { + return "quantity" +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/quantity_proto.go b/vendor/k8s.io/apimachinery/pkg/api/resource/quantity_proto.go new file mode 100644 index 0000000000..364ec80da2 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/quantity_proto.go @@ -0,0 +1,284 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "fmt" + "io" + "math/bits" +) + +func (m *Quantity) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalToSizedBuffer(data[:size]) + if err != nil { + return nil, err + } + return data[:n], nil +} + +// MarshalTo is a customized version of the generated Protobuf unmarshaler for a struct +// with a single string field. +func (m *Quantity) MarshalTo(data []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(data[:size]) +} + +// MarshalToSizedBuffer is a customized version of the generated +// Protobuf unmarshaler for a struct with a single string field. +func (m *Quantity) MarshalToSizedBuffer(data []byte) (int, error) { + i := len(data) + _ = i + var l int + _ = l + + // BEGIN CUSTOM MARSHAL + out := m.String() + i -= len(out) + copy(data[i:], out) + i = encodeVarintGenerated(data, i, uint64(len(out))) + // END CUSTOM MARSHAL + i-- + data[i] = 0xa + + return len(data) - i, nil +} + +func encodeVarintGenerated(data []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return base +} + +func (m *Quantity) Size() (n int) { + var l int + _ = l + + // BEGIN CUSTOM SIZE + l = len(m.String()) + // END CUSTOM SIZE + + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + return (bits.Len64(x|1) + 6) / 7 +} + +// Unmarshal is a customized version of the generated Protobuf unmarshaler for a struct +// with a single string field. +func (m *Quantity) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Quantity: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Quantity: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field String_", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(data[iNdEx:postIndex]) + + // BEGIN CUSTOM DECODE + p, err := ParseQuantity(s) + if err != nil { + return err + } + *m = p + // END CUSTOM DECODE + + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(data[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +func skipGenerated(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/scale_int.go b/vendor/k8s.io/apimachinery/pkg/api/resource/scale_int.go new file mode 100644 index 0000000000..55e177b0e9 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/scale_int.go @@ -0,0 +1,95 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math" + "math/big" + "sync" +) + +var ( + // A sync pool to reduce allocation. + intPool sync.Pool + maxInt64 = big.NewInt(math.MaxInt64) +) + +func init() { + intPool.New = func() interface{} { + return &big.Int{} + } +} + +// scaledValue scales given unscaled value from scale to new Scale and returns +// an int64. It ALWAYS rounds up the result when scale down. The final result might +// overflow. +// +// scale, newScale represents the scale of the unscaled decimal. +// The mathematical value of the decimal is unscaled * 10**(-scale). +func scaledValue(unscaled *big.Int, scale, newScale int) int64 { + dif := scale - newScale + if dif == 0 { + return unscaled.Int64() + } + + // Handle scale up + // This is an easy case, we do not need to care about rounding and overflow. + // If any intermediate operation causes overflow, the result will overflow. + if dif < 0 { + return unscaled.Int64() * int64(math.Pow10(-dif)) + } + + // Handle scale down + // We have to be careful about the intermediate operations. + + // fast path when unscaled < max.Int64 and exp(10,dif) < max.Int64 + const log10MaxInt64 = 19 + if unscaled.Cmp(maxInt64) < 0 && dif < log10MaxInt64 { + divide := int64(math.Pow10(dif)) + result := unscaled.Int64() / divide + mod := unscaled.Int64() % divide + if mod != 0 { + return result + 1 + } + return result + } + + // We should only convert back to int64 when getting the result. + divisor := intPool.Get().(*big.Int) + exp := intPool.Get().(*big.Int) + result := intPool.Get().(*big.Int) + defer func() { + intPool.Put(divisor) + intPool.Put(exp) + intPool.Put(result) + }() + + // divisor = 10^(dif) + // TODO: create loop up table if exp costs too much. + divisor.Exp(bigTen, exp.SetInt64(int64(dif)), nil) + // reuse exp + remainder := exp + + // result = unscaled / divisor + // remainder = unscaled % divisor + result.DivMod(unscaled, divisor, remainder) + if remainder.Sign() != 0 { + return result.Int64() + 1 + } + + return result.Int64() +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/suffix.go b/vendor/k8s.io/apimachinery/pkg/api/resource/suffix.go new file mode 100644 index 0000000000..6ec527f9c0 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/suffix.go @@ -0,0 +1,198 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "strconv" +) + +type suffix string + +// suffixer can interpret and construct suffixes. +type suffixer interface { + interpret(suffix) (base, exponent int32, fmt Format, ok bool) + construct(base, exponent int32, fmt Format) (s suffix, ok bool) + constructBytes(base, exponent int32, fmt Format) (s []byte, ok bool) +} + +// quantitySuffixer handles suffixes for all three formats that quantity +// can handle. +var quantitySuffixer = newSuffixer() + +type bePair struct { + base, exponent int32 +} + +type listSuffixer struct { + suffixToBE map[suffix]bePair + beToSuffix map[bePair]suffix + beToSuffixBytes map[bePair][]byte +} + +func (ls *listSuffixer) addSuffix(s suffix, pair bePair) { + if ls.suffixToBE == nil { + ls.suffixToBE = map[suffix]bePair{} + } + if ls.beToSuffix == nil { + ls.beToSuffix = map[bePair]suffix{} + } + if ls.beToSuffixBytes == nil { + ls.beToSuffixBytes = map[bePair][]byte{} + } + ls.suffixToBE[s] = pair + ls.beToSuffix[pair] = s + ls.beToSuffixBytes[pair] = []byte(s) +} + +func (ls *listSuffixer) lookup(s suffix) (base, exponent int32, ok bool) { + pair, ok := ls.suffixToBE[s] + if !ok { + return 0, 0, false + } + return pair.base, pair.exponent, true +} + +func (ls *listSuffixer) construct(base, exponent int32) (s suffix, ok bool) { + s, ok = ls.beToSuffix[bePair{base, exponent}] + return +} + +func (ls *listSuffixer) constructBytes(base, exponent int32) (s []byte, ok bool) { + s, ok = ls.beToSuffixBytes[bePair{base, exponent}] + return +} + +type suffixHandler struct { + decSuffixes listSuffixer + binSuffixes listSuffixer +} + +type fastLookup struct { + *suffixHandler +} + +func (l fastLookup) interpret(s suffix) (base, exponent int32, format Format, ok bool) { + switch s { + case "": + return 10, 0, DecimalSI, true + case "n": + return 10, -9, DecimalSI, true + case "u": + return 10, -6, DecimalSI, true + case "m": + return 10, -3, DecimalSI, true + case "k": + return 10, 3, DecimalSI, true + case "M": + return 10, 6, DecimalSI, true + case "G": + return 10, 9, DecimalSI, true + } + return l.suffixHandler.interpret(s) +} + +func newSuffixer() suffixer { + sh := &suffixHandler{} + + // IMPORTANT: if you change this section you must change fastLookup + + sh.binSuffixes.addSuffix("Ki", bePair{2, 10}) + sh.binSuffixes.addSuffix("Mi", bePair{2, 20}) + sh.binSuffixes.addSuffix("Gi", bePair{2, 30}) + sh.binSuffixes.addSuffix("Ti", bePair{2, 40}) + sh.binSuffixes.addSuffix("Pi", bePair{2, 50}) + sh.binSuffixes.addSuffix("Ei", bePair{2, 60}) + // Don't emit an error when trying to produce + // a suffix for 2^0. + sh.decSuffixes.addSuffix("", bePair{2, 0}) + + sh.decSuffixes.addSuffix("n", bePair{10, -9}) + sh.decSuffixes.addSuffix("u", bePair{10, -6}) + sh.decSuffixes.addSuffix("m", bePair{10, -3}) + sh.decSuffixes.addSuffix("", bePair{10, 0}) + sh.decSuffixes.addSuffix("k", bePair{10, 3}) + sh.decSuffixes.addSuffix("M", bePair{10, 6}) + sh.decSuffixes.addSuffix("G", bePair{10, 9}) + sh.decSuffixes.addSuffix("T", bePair{10, 12}) + sh.decSuffixes.addSuffix("P", bePair{10, 15}) + sh.decSuffixes.addSuffix("E", bePair{10, 18}) + + return fastLookup{sh} +} + +func (sh *suffixHandler) construct(base, exponent int32, fmt Format) (s suffix, ok bool) { + switch fmt { + case DecimalSI: + return sh.decSuffixes.construct(base, exponent) + case BinarySI: + return sh.binSuffixes.construct(base, exponent) + case DecimalExponent: + if base != 10 { + return "", false + } + if exponent == 0 { + return "", true + } + return suffix("e" + strconv.FormatInt(int64(exponent), 10)), true + } + return "", false +} + +func (sh *suffixHandler) constructBytes(base, exponent int32, format Format) (s []byte, ok bool) { + switch format { + case DecimalSI: + return sh.decSuffixes.constructBytes(base, exponent) + case BinarySI: + return sh.binSuffixes.constructBytes(base, exponent) + case DecimalExponent: + if base != 10 { + return nil, false + } + if exponent == 0 { + return nil, true + } + result := make([]byte, 8) + result[0] = 'e' + number := strconv.AppendInt(result[1:1], int64(exponent), 10) + if &result[1] == &number[0] { + return result[:1+len(number)], true + } + result = append(result[:1], number...) + return result, true + } + return nil, false +} + +func (sh *suffixHandler) interpret(suffix suffix) (base, exponent int32, fmt Format, ok bool) { + // Try lookup tables first + if b, e, ok := sh.decSuffixes.lookup(suffix); ok { + return b, e, DecimalSI, true + } + if b, e, ok := sh.binSuffixes.lookup(suffix); ok { + return b, e, BinarySI, true + } + + if len(suffix) > 1 && (suffix[0] == 'E' || suffix[0] == 'e') { + parsed, err := strconv.ParseInt(string(suffix[1:]), 10, 64) + if err != nil { + return 0, 0, DecimalExponent, false + } + return 10, int32(parsed), DecimalExponent, true + } + + return 0, 0, DecimalExponent, false +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go b/vendor/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go new file mode 100644 index 0000000000..abb00f38e2 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go @@ -0,0 +1,45 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package resource + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Quantity) DeepCopyInto(out *Quantity) { + *out = in.DeepCopy() + return +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuantityValue) DeepCopyInto(out *QuantityValue) { + *out = *in + out.Quantity = in.Quantity.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuantityValue. +func (in *QuantityValue) DeepCopy() *QuantityValue { + if in == nil { + return nil + } + out := new(QuantityValue) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/zz_generated.model_name.go b/vendor/k8s.io/apimachinery/pkg/api/resource/zz_generated.model_name.go new file mode 100644 index 0000000000..2575a2e8c8 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/resource/zz_generated.model_name.go @@ -0,0 +1,32 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package resource + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Quantity) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.api.resource.Quantity" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in QuantityValue) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.api.resource.QuantityValue" +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct/direct.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct/direct.go new file mode 100644 index 0000000000..945dc47c14 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct/direct.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package direct provides functions for marshaling and unmarshaling between arbitrary Go values and +// CBOR data, with behavior that is compatible with that of the CBOR serializer. In particular, +// types that implement cbor.Marshaler and cbor.Unmarshaler should use these functions. +package direct + +import ( + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" +) + +// Marshal serializes a value to CBOR. If there is more than one way to encode the value, it will +// make the same choice as the CBOR implementation of runtime.Serializer. +func Marshal(src any) ([]byte, error) { + return modes.Encode.Marshal(src) +} + +// Unmarshal deserializes from CBOR into an addressable value. If there is more than one way to +// unmarshal a value, it will make the same choice as the CBOR implementation of runtime.Serializer. +func Unmarshal(src []byte, dst any) error { + return modes.Decode.Unmarshal(src, dst) +} + +// Diagnose accepts well-formed CBOR bytes and returns a string representing the same data item in +// human-readable diagnostic notation (RFC 8949 Section 8). The diagnostic notation is not meant to +// be parsed. +func Diagnose(src []byte) (string, error) { + return modes.Diagnostic.Diagnose(src) +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers.go new file mode 100644 index 0000000000..f14cbd6b58 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package modes + +import ( + "bytes" + "sync" +) + +var buffers = BufferProvider{p: new(sync.Pool)} + +type buffer struct { + bytes.Buffer +} + +type pool interface { + Get() interface{} + Put(interface{}) +} + +type BufferProvider struct { + p pool +} + +func (b *BufferProvider) Get() *buffer { + if buf, ok := b.p.Get().(*buffer); ok { + return buf + } + return &buffer{} +} + +func (b *BufferProvider) Put(buf *buffer) { + if buf.Cap() > 3*1024*1024 /* Default MaxRequestBodyBytes */ { + // Objects in a sync.Pool are assumed to be fungible. This is not a good assumption + // for pools of *bytes.Buffer because a *bytes.Buffer's underlying array grows as + // needed to accommodate writes. In Kubernetes, apiservers tend to encode "small" + // objects very frequently and much larger objects (especially large lists) only + // occasionally. Under steady load, pooled buffers tend to be borrowed frequently + // enough to prevent them from being released. Over time, each buffer is used to + // encode a large object and its capacity increases accordingly. The result is that + // practically all buffers in the pool retain much more capacity than needed to + // encode most objects. + + // As a basic mitigation for the worst case, buffers with more capacity than the + // default max request body size are never returned to the pool. + // TODO: Optimize for higher buffer utilization. + return + } + buf.Reset() + b.p.Put(buf) +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode.go new file mode 100644 index 0000000000..4a7055c10f --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode.go @@ -0,0 +1,184 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package modes + +import ( + "reflect" + + "github.com/fxamacker/cbor/v2" +) + +var simpleValues *cbor.SimpleValueRegistry = func() *cbor.SimpleValueRegistry { + var opts []func(*cbor.SimpleValueRegistry) error + for sv := 0; sv <= 255; sv++ { + // Reject simple values 0-19, 23, and 32-255. The simple values 24-31 are reserved + // and considered ill-formed by the CBOR specification. We only accept false (20), + // true (21), and null (22). + switch sv { + case 20: // false + case 21: // true + case 22: // null + case 24, 25, 26, 27, 28, 29, 30, 31: // reserved + default: + opts = append(opts, cbor.WithRejectedSimpleValue(cbor.SimpleValue(sv))) + } + } + simpleValues, err := cbor.NewSimpleValueRegistryFromDefaults(opts...) + if err != nil { + panic(err) + } + return simpleValues +}() + +// decode is the basis for the Decode mode, with no JSONUnmarshalerTranscoder +// configured. TranscodeToJSON uses this directly rather than Decode to avoid an initialization +// cycle between the two. Everything else should use one of the exported DecModes. +var decode cbor.DecMode = func() cbor.DecMode { + decode, err := cbor.DecOptions{ + // Maps with duplicate keys are well-formed but invalid according to the CBOR spec + // and never acceptable. Unlike the JSON serializer, inputs containing duplicate map + // keys are rejected outright and not surfaced as a strict decoding error. + DupMapKey: cbor.DupMapKeyEnforcedAPF, + + // For JSON parity, decoding an RFC3339 string into time.Time needs to be accepted + // with or without tagging. If a tag number is present, it must be valid. + TimeTag: cbor.DecTagOptional, + + // MaxNestedLevels is set to the same value used in the JSON implementation. + MaxNestedLevels: 10000, + + // MaxArrayElements is set to the maximum allowed by the cbor library. We rely on + // the library initial wellformedness scan and on the api max request limit to + // prevent preallocating very large slices during decoding. + MaxArrayElements: 2147483647, + + // MaxMapPairs specifies the maximum number of key-value pairs allowed in a map. + // We selected this value as it is large enough so that in practice the API server + // decoder will always hit the request body limit before the limit here is reached. + MaxMapPairs: 2097152, + + // Indefinite-length sequences aren't produced by this serializer, but other + // implementations can. + IndefLength: cbor.IndefLengthAllowed, + + // Accept inputs that contain CBOR tags. + TagsMd: cbor.TagsAllowed, + + // Decode type 0 (unsigned integer) as int64. + // TODO: IntDecConvertSignedOrFail errors on overflow, JSON will try to fall back to float64. + IntDec: cbor.IntDecConvertSignedOrFail, + + // Disable producing map[cbor.ByteString]interface{}, which is not acceptable for + // decodes into interface{}. + MapKeyByteString: cbor.MapKeyByteStringForbidden, + + // Error on map keys that don't map to a field in the destination struct. + ExtraReturnErrors: cbor.ExtraDecErrorUnknownField, + + // Decode maps into concrete type map[string]interface{} when the destination is an + // interface{}. + DefaultMapType: reflect.TypeOf(map[string]interface{}(nil)), + + // A CBOR text string whose content is not a valid UTF-8 sequence is well-formed but + // invalid according to the CBOR spec. Reject invalid inputs. Encoders are + // responsible for ensuring that all text strings they produce contain valid UTF-8 + // sequences and may use the byte string major type to encode strings that have not + // been validated. + UTF8: cbor.UTF8RejectInvalid, + + // Never make a case-insensitive match between a map key and a struct field. + FieldNameMatching: cbor.FieldNameMatchingCaseSensitive, + + // Produce string concrete values when decoding a CBOR byte string into interface{}. + DefaultByteStringType: reflect.TypeOf(""), + + // Allow CBOR byte strings to be decoded into string destination values. If a byte + // string is enclosed in an "expected later encoding" tag + // (https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.5.2), then the text + // encoding indicated by that tag (e.g. base64) will be applied to the contents of + // the byte string. + ByteStringToString: cbor.ByteStringToStringAllowedWithExpectedLaterEncoding, + + // Allow CBOR byte strings to match struct fields when appearing as a map key. + FieldNameByteString: cbor.FieldNameByteStringAllowed, + + // When decoding an unrecognized tag to interface{}, return the decoded tag content + // instead of the default, a cbor.Tag representing a (number, content) pair. + UnrecognizedTagToAny: cbor.UnrecognizedTagContentToAny, + + // Decode time tags to interface{} as strings containing RFC 3339 timestamps. + TimeTagToAny: cbor.TimeTagToRFC3339Nano, + + // For parity with JSON, strings can be decoded into time.Time if they are RFC 3339 + // timestamps. + ByteStringToTime: cbor.ByteStringToTimeAllowed, + + // Reject NaN and infinite floating-point values since they don't have a JSON + // representation (RFC 8259 Section 6). + NaN: cbor.NaNDecodeForbidden, + Inf: cbor.InfDecodeForbidden, + + // When unmarshaling a byte string into a []byte, assume that the byte string + // contains base64-encoded bytes, unless explicitly counterindicated by an "expected + // later encoding" tag. This is consistent with the because of unmarshaling a JSON + // text into a []byte. + ByteStringExpectedFormat: cbor.ByteStringExpectedBase64, + + // Reject the arbitrary-precision integer tags because they can't be faithfully + // roundtripped through the allowable Unstructured types. + BignumTag: cbor.BignumTagForbidden, + + // Reject anything other than the simple values true, false, and null. + SimpleValues: simpleValues, + + // Disable default recognition of types implementing encoding.BinaryUnmarshaler, + // which is not recognized for JSON decoding. + BinaryUnmarshaler: cbor.BinaryUnmarshalerNone, + + // Marshal types that implement encoding.TextMarshaler by calling their MarshalText + // method and encoding the result to a CBOR text string. + TextUnmarshaler: cbor.TextUnmarshalerTextString, + }.DecMode() + if err != nil { + panic(err) + } + return decode +}() + +var Decode cbor.DecMode = func() cbor.DecMode { + opts := decode.DecOptions() + // When decoding into a value of a type that implements json.Unmarshaler (and does not + // implement cbor.Unmarshaler), transcode the input to JSON and pass it to the value's + // UnmarshalJSON method. + opts.JSONUnmarshalerTranscoder = TranscodeFunc(TranscodeToJSON) + dm, err := opts.DecMode() + if err != nil { + panic(err) + } + return dm +}() + +// DecodeLax is derived from Decode, but does not complain about unknown fields in the input. +var DecodeLax cbor.DecMode = func() cbor.DecMode { + opts := Decode.DecOptions() + opts.ExtraReturnErrors &^= cbor.ExtraDecErrorUnknownField // clear bit + dm, err := opts.DecMode() + if err != nil { + panic(err) + } + return dm +}() diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/diagnostic.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/diagnostic.go new file mode 100644 index 0000000000..61f3f145f5 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/diagnostic.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package modes + +import ( + "github.com/fxamacker/cbor/v2" +) + +var Diagnostic cbor.DiagMode = func() cbor.DiagMode { + opts := Decode.DecOptions() + diagnostic, err := cbor.DiagOptions{ + ByteStringText: true, + + MaxNestedLevels: opts.MaxNestedLevels, + MaxArrayElements: opts.MaxArrayElements, + MaxMapPairs: opts.MaxMapPairs, + }.DiagMode() + if err != nil { + panic(err) + } + return diagnostic +}() diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode.go new file mode 100644 index 0000000000..815dbe6660 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode.go @@ -0,0 +1,177 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package modes + +import ( + "io" + + "github.com/fxamacker/cbor/v2" +) + +// encode is the basis for the Encode mode, with no JSONMarshalerTranscoder +// configured. TranscodeFromJSON uses this directly rather than Encode to avoid an initialization +// cycle between the two. Everything else should use one of the exported EncModes. +var encode = EncMode{ + delegate: func() cbor.UserBufferEncMode { + encode, err := cbor.EncOptions{ + // Map keys need to be sorted to have deterministic output, and this is the order + // defined in RFC 8949 4.2.1 "Core Deterministic Encoding Requirements". + Sort: cbor.SortBytewiseLexical, + + // CBOR supports distinct types for IEEE-754 float16, float32, and float64. Store + // floats in the smallest width that preserves value so that equivalent float32 and + // float64 values encode to identical bytes, as they do in a JSON + // encoding. Satisfies one of the "Core Deterministic Encoding Requirements". + ShortestFloat: cbor.ShortestFloat16, + + // Error on attempt to encode NaN and infinite values. This is what the JSON + // serializer does. + NaNConvert: cbor.NaNConvertReject, + InfConvert: cbor.InfConvertReject, + + // Error on attempt to encode math/big.Int values, which can't be faithfully + // roundtripped through Unstructured in general (the dynamic numeric types allowed + // in Unstructured are limited to float64 and int64). + BigIntConvert: cbor.BigIntConvertReject, + + // MarshalJSON for time.Time writes RFC3339 with nanos. + Time: cbor.TimeRFC3339Nano, + + // The decoder must be able to accept RFC3339 strings with or without tag 0 (e.g. by + // the end of time.Time -> JSON -> Unstructured -> CBOR, the CBOR encoder has no + // reliable way of knowing that a particular string originated from serializing a + // time.Time), so producing tag 0 has little use. + TimeTag: cbor.EncTagNone, + + // Indefinite-length items have multiple encodings and aren't being used anyway, so + // disable to avoid an opportunity for nondeterminism. + IndefLength: cbor.IndefLengthForbidden, + + // Preserve distinction between nil and empty for slices and maps. + NilContainers: cbor.NilContainerAsNull, + + // OK to produce tags. + TagsMd: cbor.TagsAllowed, + + // Use the same definition of "empty" as encoding/json. + OmitEmpty: cbor.OmitEmptyGoValue, + + // The CBOR types text string and byte string are structurally equivalent, with the + // semantic difference that a text string whose content is an invalid UTF-8 sequence + // is itself invalid. We reject all invalid text strings at decode time and do not + // validate or sanitize all Go strings at encode time. Encoding Go strings to the + // byte string type is comparable to the existing Protobuf behavior and cheaply + // ensures that the output is valid CBOR. + String: cbor.StringToByteString, + + // Encode struct field names to the byte string type rather than the text string + // type. + FieldName: cbor.FieldNameToByteString, + + // Marshal Go byte arrays to CBOR arrays of integers (as in JSON) instead of byte + // strings. + ByteArray: cbor.ByteArrayToArray, + + // Marshal []byte to CBOR byte string enclosed in tag 22 (expected later base64 + // encoding, https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.5.2), to + // interoperate with the existing JSON behavior. This indicates to the decoder that, + // when decoding into a string (or unstructured), the resulting value should be the + // base64 encoding of the original bytes. No base64 encoding or decoding needs to be + // performed for []byte-to-CBOR-to-[]byte roundtrips. + ByteSliceLaterFormat: cbor.ByteSliceLaterFormatBase64, + + // Disable default recognition of types implementing encoding.BinaryMarshaler, which + // is not recognized for JSON encoding. + BinaryMarshaler: cbor.BinaryMarshalerNone, + + // Unmarshal into types that implement encoding.TextUnmarshaler by passing + // the contents of a CBOR string to their UnmarshalText method. + TextMarshaler: cbor.TextMarshalerTextString, + }.UserBufferEncMode() + if err != nil { + panic(err) + } + return encode + }(), +} + +var Encode = EncMode{ + delegate: func() cbor.UserBufferEncMode { + opts := encode.options() + // To encode a value of a type that implements json.Marshaler (and does not + // implement cbor.Marshaler), transcode the result of calling its MarshalJSON method + // directly to CBOR. + opts.JSONMarshalerTranscoder = TranscodeFunc(TranscodeFromJSON) + em, err := opts.UserBufferEncMode() + if err != nil { + panic(err) + } + return em + }(), +} + +var EncodeNondeterministic = EncMode{ + delegate: func() cbor.UserBufferEncMode { + opts := Encode.options() + opts.Sort = cbor.SortFastShuffle + em, err := opts.UserBufferEncMode() + if err != nil { + panic(err) + } + return em + }(), +} + +type EncMode struct { + delegate cbor.UserBufferEncMode +} + +func (em EncMode) options() cbor.EncOptions { + return em.delegate.EncOptions() +} + +func (em EncMode) MarshalTo(v interface{}, w io.Writer) error { + if buf, ok := w.(*buffer); ok { + return em.delegate.MarshalToBuffer(v, &buf.Buffer) + } + + buf := buffers.Get() + defer buffers.Put(buf) + if err := em.delegate.MarshalToBuffer(v, &buf.Buffer); err != nil { + return err + } + + if _, err := io.Copy(w, buf); err != nil { + return err + } + + return nil +} + +func (em EncMode) Marshal(v interface{}) ([]byte, error) { + buf := buffers.Get() + defer buffers.Put(buf) + + if err := em.MarshalTo(v, &buf.Buffer); err != nil { + return nil, err + } + + clone := make([]byte, buf.Len()) + copy(clone, buf.Bytes()) + + return clone, nil +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding.go new file mode 100644 index 0000000000..5620e9ccc9 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding.go @@ -0,0 +1,108 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package modes + +import ( + "encoding/json" + "errors" + "io" + + kjson "sigs.k8s.io/json" +) + +type TranscodeFunc func(dst io.Writer, src io.Reader) error + +func (f TranscodeFunc) Transcode(dst io.Writer, src io.Reader) error { + return f(dst, src) +} + +func TranscodeFromJSON(dst io.Writer, src io.Reader) error { + var tmp any + dec := kjson.NewDecoderCaseSensitivePreserveInts(src) + if err := dec.Decode(&tmp); err != nil { + return err + } + if err := dec.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + return errors.New("extraneous data") + } + + return encode.MarshalTo(tmp, dst) +} + +func TranscodeToJSON(dst io.Writer, src io.Reader) error { + var tmp any + dec := decode.NewDecoder(src) + if err := dec.Decode(&tmp); err != nil { + return err + } + if err := dec.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + return errors.New("extraneous data") + } + + // Use an Encoder to avoid the extra []byte allocated by Marshal. Encode, unlike Marshal, + // appends a trailing newline to separate consecutive encodings of JSON values that aren't + // self-delimiting, like numbers. Strip the newline to avoid the assumption that every + // json.Unmarshaler implementation will accept trailing whitespace. + enc := json.NewEncoder(&trailingLinefeedSuppressor{delegate: dst}) + enc.SetIndent("", "") + return enc.Encode(tmp) +} + +// trailingLinefeedSuppressor is an io.Writer that wraps another io.Writer, suppressing a single +// trailing linefeed if it is the last byte written by the latest call to Write. +type trailingLinefeedSuppressor struct { + lf bool + delegate io.Writer +} + +func (w *trailingLinefeedSuppressor) Write(p []byte) (int, error) { + if len(p) == 0 { + // Avoid flushing a buffered linefeeds on an empty write. + return 0, nil + } + + if w.lf { + // The previous write had a trailing linefeed that was buffered. That wasn't the + // last Write call, so flush the buffered linefeed before continuing. + n, err := w.delegate.Write([]byte{'\n'}) + if n > 0 { + w.lf = false + } + if err != nil { + return 0, err + } + } + + if p[len(p)-1] != '\n' { + return w.delegate.Write(p) + } + + p = p[:len(p)-1] + + if len(p) == 0 { // []byte{'\n'} + w.lf = true + return 1, nil + } + + n, err := w.delegate.Write(p) + if n == len(p) { + // Everything up to the trailing linefeed has been flushed. Eat the linefeed. + w.lf = true + n++ + } + return n, err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 17a43c2968..b3653c6d75 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -109,6 +109,7 @@ github.com/Microsoft/go-winio/pkg/etw github.com/Microsoft/go-winio/pkg/etwlogrus github.com/Microsoft/go-winio/pkg/fs github.com/Microsoft/go-winio/pkg/guid +github.com/Microsoft/go-winio/pkg/security github.com/Microsoft/go-winio/vhd # github.com/Microsoft/hcsshim v0.15.0-rc.1 ## explicit; go 1.24.0 @@ -116,6 +117,10 @@ github.com/Microsoft/hcsshim github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats github.com/Microsoft/hcsshim/computestorage +github.com/Microsoft/hcsshim/ext4/dmverity +github.com/Microsoft/hcsshim/ext4/internal/compactext4 +github.com/Microsoft/hcsshim/ext4/internal/format +github.com/Microsoft/hcsshim/ext4/tar2ext4 github.com/Microsoft/hcsshim/hcn github.com/Microsoft/hcsshim/internal/cni github.com/Microsoft/hcsshim/internal/cow @@ -141,12 +146,16 @@ github.com/Microsoft/hcsshim/internal/security github.com/Microsoft/hcsshim/internal/timeout github.com/Microsoft/hcsshim/internal/vmcompute github.com/Microsoft/hcsshim/internal/wclayer +github.com/Microsoft/hcsshim/internal/wclayer/cim github.com/Microsoft/hcsshim/internal/winapi github.com/Microsoft/hcsshim/internal/winapi/cimfs github.com/Microsoft/hcsshim/internal/winapi/cimwriter github.com/Microsoft/hcsshim/internal/winapi/types github.com/Microsoft/hcsshim/osversion +github.com/Microsoft/hcsshim/pkg/cimfs +github.com/Microsoft/hcsshim/pkg/cimfs/format github.com/Microsoft/hcsshim/pkg/ociwclayer +github.com/Microsoft/hcsshim/pkg/ociwclayer/cim # github.com/ProtonMail/go-crypto v1.4.1 ## explicit; go 1.23.0 github.com/ProtonMail/go-crypto/bitcurves @@ -322,6 +331,9 @@ github.com/cenkalti/backoff/v5 # github.com/cespare/xxhash/v2 v2.3.0 ## explicit; go 1.11 github.com/cespare/xxhash/v2 +# github.com/checkpoint-restore/checkpointctl v1.5.0 +## explicit; go 1.24.6 +github.com/checkpoint-restore/checkpointctl/lib # github.com/cilium/ebpf v0.17.3 ## explicit; go 1.22 github.com/cilium/ebpf @@ -389,7 +401,10 @@ github.com/containerd/console # github.com/containerd/containerd/api v1.11.1 ## explicit; go 1.24.0 github.com/containerd/containerd/api/events +github.com/containerd/containerd/api/runtime/bootstrap/v1 github.com/containerd/containerd/api/runtime/sandbox/v1 +github.com/containerd/containerd/api/runtime/task/v2 +github.com/containerd/containerd/api/runtime/task/v3 github.com/containerd/containerd/api/services/containers/v1 github.com/containerd/containerd/api/services/content/v1 github.com/containerd/containerd/api/services/diff/v1 @@ -404,6 +419,7 @@ github.com/containerd/containerd/api/services/snapshots/v1 github.com/containerd/containerd/api/services/streaming/v1 github.com/containerd/containerd/api/services/tasks/v1 github.com/containerd/containerd/api/services/transfer/v1 +github.com/containerd/containerd/api/services/ttrpc/events/v1 github.com/containerd/containerd/api/services/version/v1 github.com/containerd/containerd/api/types github.com/containerd/containerd/api/types/runc/options @@ -418,8 +434,10 @@ github.com/containerd/containerd/v2/core/containers github.com/containerd/containerd/v2/core/content github.com/containerd/containerd/v2/core/content/proxy github.com/containerd/containerd/v2/core/diff +github.com/containerd/containerd/v2/core/diff/apply github.com/containerd/containerd/v2/core/diff/proxy github.com/containerd/containerd/v2/core/events +github.com/containerd/containerd/v2/core/events/exchange github.com/containerd/containerd/v2/core/events/proxy github.com/containerd/containerd/v2/core/images github.com/containerd/containerd/v2/core/images/archive @@ -431,17 +449,28 @@ github.com/containerd/containerd/v2/core/leases github.com/containerd/containerd/v2/core/leases/proxy github.com/containerd/containerd/v2/core/metadata github.com/containerd/containerd/v2/core/metadata/boltutil +github.com/containerd/containerd/v2/core/metrics +github.com/containerd/containerd/v2/core/metrics/cgroups +github.com/containerd/containerd/v2/core/metrics/cgroups/common +github.com/containerd/containerd/v2/core/metrics/cgroups/v1 +github.com/containerd/containerd/v2/core/metrics/cgroups/v2 +github.com/containerd/containerd/v2/core/metrics/types/v1 +github.com/containerd/containerd/v2/core/metrics/types/v2 github.com/containerd/containerd/v2/core/mount +github.com/containerd/containerd/v2/core/mount/manager github.com/containerd/containerd/v2/core/mount/proxy github.com/containerd/containerd/v2/core/remotes github.com/containerd/containerd/v2/core/remotes/docker github.com/containerd/containerd/v2/core/remotes/docker/auth github.com/containerd/containerd/v2/core/remotes/docker/config github.com/containerd/containerd/v2/core/remotes/errors +github.com/containerd/containerd/v2/core/runtime +github.com/containerd/containerd/v2/core/runtime/v2 github.com/containerd/containerd/v2/core/sandbox github.com/containerd/containerd/v2/core/sandbox/proxy github.com/containerd/containerd/v2/core/snapshots github.com/containerd/containerd/v2/core/snapshots/proxy +github.com/containerd/containerd/v2/core/snapshots/storage github.com/containerd/containerd/v2/core/streaming github.com/containerd/containerd/v2/core/streaming/proxy github.com/containerd/containerd/v2/core/transfer @@ -455,13 +484,17 @@ github.com/containerd/containerd/v2/internal/fsview github.com/containerd/containerd/v2/internal/kmutex github.com/containerd/containerd/v2/internal/lazyregexp github.com/containerd/containerd/v2/internal/randutil +github.com/containerd/containerd/v2/internal/tomlext github.com/containerd/containerd/v2/internal/userns github.com/containerd/containerd/v2/pkg/apparmor github.com/containerd/containerd/v2/pkg/archive github.com/containerd/containerd/v2/pkg/archive/compression github.com/containerd/containerd/v2/pkg/archive/tarheader +github.com/containerd/containerd/v2/pkg/atomicfile +github.com/containerd/containerd/v2/pkg/blockio github.com/containerd/containerd/v2/pkg/cap github.com/containerd/containerd/v2/pkg/cio +github.com/containerd/containerd/v2/pkg/deprecation github.com/containerd/containerd/v2/pkg/dialer github.com/containerd/containerd/v2/pkg/epoch github.com/containerd/containerd/v2/pkg/filters @@ -474,18 +507,51 @@ github.com/containerd/containerd/v2/pkg/oci github.com/containerd/containerd/v2/pkg/protobuf github.com/containerd/containerd/v2/pkg/protobuf/proto github.com/containerd/containerd/v2/pkg/protobuf/types +github.com/containerd/containerd/v2/pkg/rdt github.com/containerd/containerd/v2/pkg/reference github.com/containerd/containerd/v2/pkg/rootfs github.com/containerd/containerd/v2/pkg/seccomp +github.com/containerd/containerd/v2/pkg/shim +github.com/containerd/containerd/v2/pkg/shutdown github.com/containerd/containerd/v2/pkg/snapshotters github.com/containerd/containerd/v2/pkg/sys +github.com/containerd/containerd/v2/pkg/sys/reaper +github.com/containerd/containerd/v2/pkg/timeout github.com/containerd/containerd/v2/pkg/tracing +github.com/containerd/containerd/v2/pkg/ttrpcutil github.com/containerd/containerd/v2/plugins github.com/containerd/containerd/v2/plugins/content/local +github.com/containerd/containerd/v2/plugins/content/local/plugin +github.com/containerd/containerd/v2/plugins/diff/lcow github.com/containerd/containerd/v2/plugins/diff/walking +github.com/containerd/containerd/v2/plugins/diff/walking/plugin +github.com/containerd/containerd/v2/plugins/diff/windows +github.com/containerd/containerd/v2/plugins/events +github.com/containerd/containerd/v2/plugins/gc +github.com/containerd/containerd/v2/plugins/leases +github.com/containerd/containerd/v2/plugins/metadata +github.com/containerd/containerd/v2/plugins/mount github.com/containerd/containerd/v2/plugins/services +github.com/containerd/containerd/v2/plugins/services/containers +github.com/containerd/containerd/v2/plugins/services/content github.com/containerd/containerd/v2/plugins/services/content/contentserver +github.com/containerd/containerd/v2/plugins/services/diff +github.com/containerd/containerd/v2/plugins/services/events +github.com/containerd/containerd/v2/plugins/services/healthcheck +github.com/containerd/containerd/v2/plugins/services/images +github.com/containerd/containerd/v2/plugins/services/introspection +github.com/containerd/containerd/v2/plugins/services/leases +github.com/containerd/containerd/v2/plugins/services/namespaces +github.com/containerd/containerd/v2/plugins/services/snapshots +github.com/containerd/containerd/v2/plugins/services/tasks +github.com/containerd/containerd/v2/plugins/services/version +github.com/containerd/containerd/v2/plugins/services/warning +github.com/containerd/containerd/v2/plugins/snapshots/native +github.com/containerd/containerd/v2/plugins/snapshots/native/plugin +github.com/containerd/containerd/v2/plugins/snapshots/overlay github.com/containerd/containerd/v2/plugins/snapshots/overlay/overlayutils +github.com/containerd/containerd/v2/plugins/snapshots/overlay/plugin +github.com/containerd/containerd/v2/plugins/snapshots/windows github.com/containerd/containerd/v2/version # github.com/containerd/continuity v0.5.0 ## explicit; go 1.21 @@ -515,7 +581,7 @@ github.com/containerd/go-runc ## explicit; go 1.20 github.com/containerd/log github.com/containerd/log/logtest -# github.com/containerd/nri v0.12.1 +# github.com/containerd/nri v0.12.0 ## explicit; go 1.24.0 github.com/containerd/nri/pkg/adaptation github.com/containerd/nri/pkg/adaptation/builtin @@ -533,12 +599,17 @@ github.com/containerd/nri/plugins/default-validator/builtin github.com/containerd/nydus-snapshotter/pkg/converter github.com/containerd/nydus-snapshotter/pkg/converter/tool github.com/containerd/nydus-snapshotter/pkg/label +# github.com/containerd/otelttrpc v0.1.0 +## explicit; go 1.21 +github.com/containerd/otelttrpc +github.com/containerd/otelttrpc/internal # github.com/containerd/platforms v1.0.0-rc.4 ## explicit; go 1.24 github.com/containerd/platforms # github.com/containerd/plugin v1.1.0 ## explicit; go 1.22 github.com/containerd/plugin +github.com/containerd/plugin/registry # github.com/containerd/stargz-snapshotter/estargz v0.18.2 ## explicit; go 1.24.0 github.com/containerd/stargz-snapshotter/estargz @@ -658,6 +729,9 @@ github.com/fluent/fluent-logger-golang/fluent ## explicit; go 1.17 github.com/fsnotify/fsnotify github.com/fsnotify/fsnotify/internal +# github.com/fxamacker/cbor/v2 v2.9.0 +## explicit; go 1.20 +github.com/fxamacker/cbor/v2 # github.com/go-logr/logr v1.4.3 ## explicit; go 1.18 github.com/go-logr/logr @@ -954,6 +1028,13 @@ github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1 # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap +# github.com/intel/goresctrl v0.12.0 +## explicit; go 1.25.0 +github.com/intel/goresctrl/pkg/blockio +github.com/intel/goresctrl/pkg/kubernetes +github.com/intel/goresctrl/pkg/path +github.com/intel/goresctrl/pkg/rdt +github.com/intel/goresctrl/pkg/utils # github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2 ## explicit; go 1.12 github.com/ishidawataru/sctp @@ -973,6 +1054,12 @@ github.com/klauspost/compress/zstd/internal/xxhash # github.com/knqyf263/go-plugin v0.9.0 ## explicit; go 1.24.0 github.com/knqyf263/go-plugin/wasm +# github.com/mdlayher/socket v0.5.1 +## explicit; go 1.20 +github.com/mdlayher/socket +# github.com/mdlayher/vsock v1.2.1 +## explicit; go 1.20 +github.com/mdlayher/vsock # github.com/miekg/dns v1.1.72 ## explicit; go 1.24.0 github.com/miekg/dns @@ -1637,6 +1724,9 @@ github.com/vishvananda/netns # github.com/weppos/publicsuffix-go v0.30.0 ## explicit; go 1.16 github.com/weppos/publicsuffix-go/publicsuffix +# github.com/x448/float16 v0.8.4 +## explicit; go 1.11 +github.com/x448/float16 # github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 ## explicit; go 1.17 github.com/youmark/pkcs8 @@ -1743,6 +1833,7 @@ go.opentelemetry.io/otel/internal/baggage go.opentelemetry.io/otel/internal/errorhandler go.opentelemetry.io/otel/internal/global go.opentelemetry.io/otel/propagation +go.opentelemetry.io/otel/semconv/v1.17.0 go.opentelemetry.io/otel/semconv/v1.37.0 go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv go.opentelemetry.io/otel/semconv/v1.41.0 @@ -2155,6 +2246,9 @@ google.golang.org/protobuf/types/known/structpb google.golang.org/protobuf/types/known/timestamppb google.golang.org/protobuf/types/known/wrapperspb google.golang.org/protobuf/types/pluginpb +# gopkg.in/inf.v0 v0.9.1 +## explicit +gopkg.in/inf.v0 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 @@ -2173,6 +2267,11 @@ gotest.tools/v3/internal/format gotest.tools/v3/internal/source gotest.tools/v3/poll gotest.tools/v3/skip +# k8s.io/apimachinery v0.36.0 +## explicit; go 1.26.0 +k8s.io/apimachinery/pkg/api/resource +k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct +k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes # k8s.io/klog/v2 v2.140.0 ## explicit; go 1.21 k8s.io/klog/v2 @@ -2188,6 +2287,10 @@ pgregory.net/rapid # resenje.org/singleflight v0.4.3 ## explicit; go 1.18 resenje.org/singleflight +# sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 +## explicit; go 1.23 +sigs.k8s.io/json +sigs.k8s.io/json/internal/golang/encoding/json # sigs.k8s.io/yaml v1.6.0 ## explicit; go 1.22 sigs.k8s.io/yaml diff --git a/vendor/sigs.k8s.io/json/CONTRIBUTING.md b/vendor/sigs.k8s.io/json/CONTRIBUTING.md new file mode 100644 index 0000000000..3bcd26689f --- /dev/null +++ b/vendor/sigs.k8s.io/json/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing Guidelines + +Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: + +_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ + +## Criteria for adding code here + +This library adapts the stdlib `encoding/json` decoder to be compatible with +Kubernetes JSON decoding, and is not expected to actively add new features. + +It may be updated with changes from the stdlib `encoding/json` decoder. + +Any code that is added must: +* Have full unit test and benchmark coverage +* Be backward compatible with the existing exposed go API +* Have zero external dependencies +* Preserve existing benchmark performance +* Preserve compatibility with existing decoding behavior of `UnmarshalCaseSensitivePreserveInts()` or `UnmarshalStrict()` +* Avoid use of `unsafe` + +## Getting Started + +We have full documentation on how to get started contributing here: + + + +- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests +- [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing) +- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers + +## Community, discussion, contribution, and support + +You can reach the maintainers of this project via the +[sig-api-machinery mailing list / channels](https://github.com/kubernetes/community/tree/master/sig-api-machinery#contact). + +## Mentorship + +- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! + diff --git a/vendor/sigs.k8s.io/json/LICENSE b/vendor/sigs.k8s.io/json/LICENSE new file mode 100644 index 0000000000..e5adf7f0c0 --- /dev/null +++ b/vendor/sigs.k8s.io/json/LICENSE @@ -0,0 +1,238 @@ +Files other than internal/golang/* licensed under: + + + 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. + + +------------------ + +internal/golang/* files licensed under: + + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sigs.k8s.io/json/Makefile b/vendor/sigs.k8s.io/json/Makefile new file mode 100644 index 0000000000..fb6cf040f5 --- /dev/null +++ b/vendor/sigs.k8s.io/json/Makefile @@ -0,0 +1,35 @@ +.PHONY: default build test benchmark fmt vet + +default: build + +build: + go build ./... + +test: + go test sigs.k8s.io/json/... + +benchmark: + go test sigs.k8s.io/json -bench . -benchmem + +fmt: + go mod tidy + gofmt -s -w *.go + +vet: + go vet sigs.k8s.io/json + + @echo "checking for external dependencies" + @deps=$$(go list -f '{{ if not (or .Standard .Module.Main) }}{{.ImportPath}}{{ end }}' -deps sigs.k8s.io/json/... || true); \ + if [ -n "$${deps}" ]; then \ + echo "only stdlib dependencies allowed, found:"; \ + echo "$${deps}"; \ + exit 1; \ + fi + + @echo "checking for unsafe use" + @unsafe=$$(go list -f '{{.ImportPath}} depends on {{.Imports}}' sigs.k8s.io/json/... | grep unsafe || true); \ + if [ -n "$${unsafe}" ]; then \ + echo "no dependencies on unsafe allowed, found:"; \ + echo "$${unsafe}"; \ + exit 1; \ + fi diff --git a/vendor/sigs.k8s.io/json/OWNERS b/vendor/sigs.k8s.io/json/OWNERS new file mode 100644 index 0000000000..a08a434e61 --- /dev/null +++ b/vendor/sigs.k8s.io/json/OWNERS @@ -0,0 +1,6 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - deads2k + - jpbetz + - liggitt diff --git a/vendor/sigs.k8s.io/json/README.md b/vendor/sigs.k8s.io/json/README.md new file mode 100644 index 0000000000..0ef8f51c21 --- /dev/null +++ b/vendor/sigs.k8s.io/json/README.md @@ -0,0 +1,40 @@ +# sigs.k8s.io/json + +[![Go Reference](https://pkg.go.dev/badge/sigs.k8s.io/json.svg)](https://pkg.go.dev/sigs.k8s.io/json) + +## Introduction + +This library is a subproject of [sig-api-machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery#json). +It provides case-sensitive, integer-preserving JSON unmarshaling functions based on `encoding/json` `Unmarshal()`. + +## Compatibility + +The `UnmarshalCaseSensitivePreserveInts()` function behaves like `encoding/json#Unmarshal()` with the following differences: + +- JSON object keys are treated case-sensitively. + Object keys must exactly match json tag names (for tagged struct fields) + or struct field names (for untagged struct fields). +- JSON integers are unmarshaled into `interface{}` fields as an `int64` instead of a + `float64` when possible, falling back to `float64` on any parse or overflow error. +- Syntax errors do not return an `encoding/json` `*SyntaxError` error. + Instead, they return an error which can be passed to `SyntaxErrorOffset()` to obtain an offset. + +## Additional capabilities + +The `UnmarshalStrict()` function decodes identically to `UnmarshalCaseSensitivePreserveInts()`, +and also returns non-fatal strict errors encountered while decoding: + +- Duplicate fields encountered +- Unknown fields encountered + +### Community, discussion, contribution, and support + +You can reach the maintainers of this project via the +[sig-api-machinery mailing list / channels](https://github.com/kubernetes/community/tree/master/sig-api-machinery#contact). + +### Code of conduct + +Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). + +[owners]: https://git.k8s.io/community/contributors/guide/owners.md +[Creative Commons 4.0]: https://git.k8s.io/website/LICENSE diff --git a/vendor/sigs.k8s.io/json/SECURITY.md b/vendor/sigs.k8s.io/json/SECURITY.md new file mode 100644 index 0000000000..2083d44cdf --- /dev/null +++ b/vendor/sigs.k8s.io/json/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +## Security Announcements + +Join the [kubernetes-security-announce] group for security and vulnerability announcements. + +You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss]. + +## Reporting a Vulnerability + +Instructions for reporting a vulnerability can be found on the +[Kubernetes Security and Disclosure Information] page. + +## Supported Versions + +Information about supported Kubernetes versions can be found on the +[Kubernetes version and version skew support policy] page on the Kubernetes website. + +[kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce +[kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50 +[Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions +[Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability diff --git a/vendor/sigs.k8s.io/json/SECURITY_CONTACTS b/vendor/sigs.k8s.io/json/SECURITY_CONTACTS new file mode 100644 index 0000000000..6a51db2fe8 --- /dev/null +++ b/vendor/sigs.k8s.io/json/SECURITY_CONTACTS @@ -0,0 +1,15 @@ +# Defined below are the security contacts for this repo. +# +# They are the contact point for the Product Security Committee to reach out +# to for triaging and handling of incoming issues. +# +# The below names agree to abide by the +# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) +# and will be removed and replaced if they violate that agreement. +# +# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE +# INSTRUCTIONS AT https://kubernetes.io/security/ + +deads2k +lavalamp +liggitt diff --git a/vendor/sigs.k8s.io/json/code-of-conduct.md b/vendor/sigs.k8s.io/json/code-of-conduct.md new file mode 100644 index 0000000000..0d15c00cf3 --- /dev/null +++ b/vendor/sigs.k8s.io/json/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/vendor/sigs.k8s.io/json/doc.go b/vendor/sigs.k8s.io/json/doc.go new file mode 100644 index 0000000000..050eebb03e --- /dev/null +++ b/vendor/sigs.k8s.io/json/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package json // import "sigs.k8s.io/json" diff --git a/vendor/sigs.k8s.io/json/internal/golang/encoding/json/decode.go b/vendor/sigs.k8s.io/json/internal/golang/encoding/json/decode.go new file mode 100644 index 0000000000..3fe528bbf3 --- /dev/null +++ b/vendor/sigs.k8s.io/json/internal/golang/encoding/json/decode.go @@ -0,0 +1,1423 @@ +// Copyright 2010 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. + +// Represents JSON data structure using native Go types: booleans, floats, +// strings, arrays, and maps. + +package json + +import ( + "encoding" + "encoding/base64" + "fmt" + "reflect" + "strconv" + "strings" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// Unmarshal parses the JSON-encoded data and stores the result +// in the value pointed to by v. If v is nil or not a pointer, +// Unmarshal returns an [InvalidUnmarshalError]. +// +// Unmarshal uses the inverse of the encodings that +// [Marshal] uses, allocating maps, slices, and pointers as necessary, +// with the following additional rules: +// +// To unmarshal JSON into a pointer, Unmarshal first handles the case of +// the JSON being the JSON literal null. In that case, Unmarshal sets +// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into +// the value pointed at by the pointer. If the pointer is nil, Unmarshal +// allocates a new value for it to point to. +// +// To unmarshal JSON into a value implementing [Unmarshaler], +// Unmarshal calls that value's [Unmarshaler.UnmarshalJSON] method, including +// when the input is a JSON null. +// Otherwise, if the value implements [encoding.TextUnmarshaler] +// and the input is a JSON quoted string, Unmarshal calls +// [encoding.TextUnmarshaler.UnmarshalText] with the unquoted form of the string. +// +// To unmarshal JSON into a struct, Unmarshal matches incoming object +// keys to the keys used by [Marshal] (either the struct field name or its tag), +// preferring an exact match but also accepting a case-insensitive match. By +// default, object keys which don't have a corresponding struct field are +// ignored (see [Decoder.DisallowUnknownFields] for an alternative). +// +// To unmarshal JSON into an interface value, +// Unmarshal stores one of these in the interface value: +// +// - bool, for JSON booleans +// - float64, for JSON numbers +// - string, for JSON strings +// - []any, for JSON arrays +// - map[string]any, for JSON objects +// - nil for JSON null +// +// To unmarshal a JSON array into a slice, Unmarshal resets the slice length +// to zero and then appends each element to the slice. +// As a special case, to unmarshal an empty JSON array into a slice, +// Unmarshal replaces the slice with a new empty slice. +// +// To unmarshal a JSON array into a Go array, Unmarshal decodes +// JSON array elements into corresponding Go array elements. +// If the Go array is smaller than the JSON array, +// the additional JSON array elements are discarded. +// If the JSON array is smaller than the Go array, +// the additional Go array elements are set to zero values. +// +// To unmarshal a JSON object into a map, Unmarshal first establishes a map to +// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal +// reuses the existing map, keeping existing entries. Unmarshal then stores +// key-value pairs from the JSON object into the map. The map's key type must +// either be any string type, an integer, or implement [encoding.TextUnmarshaler]. +// +// If the JSON-encoded data contain a syntax error, Unmarshal returns a [SyntaxError]. +// +// If a JSON value is not appropriate for a given target type, +// or if a JSON number overflows the target type, Unmarshal +// skips that field and completes the unmarshaling as best it can. +// If no more serious errors are encountered, Unmarshal returns +// an [UnmarshalTypeError] describing the earliest such error. In any +// case, it's not guaranteed that all the remaining fields following +// the problematic one will be unmarshaled into the target object. +// +// The JSON null value unmarshals into an interface, map, pointer, or slice +// by setting that Go value to nil. Because null is often used in JSON to mean +// “not present,” unmarshaling a JSON null into any other Go type has no effect +// on the value and produces no error. +// +// When unmarshaling quoted strings, invalid UTF-8 or +// invalid UTF-16 surrogate pairs are not treated as an error. +// Instead, they are replaced by the Unicode replacement +// character U+FFFD. +func Unmarshal(data []byte, v any, opts ...UnmarshalOpt) error { + // Check for well-formedness. + // Avoids filling out half a data structure + // before discovering a JSON syntax error. + var d decodeState + + for _, opt := range opts { + opt(&d) + } + + err := checkValid(data, &d.scan) + if err != nil { + return err + } + + d.init(data) + return d.unmarshal(v) +} + +// Unmarshaler is the interface implemented by types +// that can unmarshal a JSON description of themselves. +// The input can be assumed to be a valid encoding of +// a JSON value. UnmarshalJSON must copy the JSON data +// if it wishes to retain the data after returning. +type Unmarshaler interface { + UnmarshalJSON([]byte) error +} + +/* +// An UnmarshalTypeError describes a JSON value that was +// not appropriate for a value of a specific Go type. +type UnmarshalTypeError struct { + Value string // description of JSON value - "bool", "array", "number -5" + Type reflect.Type // type of Go value it could not be assigned to + Offset int64 // error occurred after reading Offset bytes + Struct string // name of the struct type containing the field + Field string // the full path from root node to the field, include embedded struct +} + +func (e *UnmarshalTypeError) Error() string { + if e.Struct != "" || e.Field != "" { + return "json: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String() + } + return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() +} + +// An UnmarshalFieldError describes a JSON object key that +// led to an unexported (and therefore unwritable) struct field. +// +// Deprecated: No longer used; kept for compatibility. +type UnmarshalFieldError struct { + Key string + Type reflect.Type + Field reflect.StructField +} + +func (e *UnmarshalFieldError) Error() string { + return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() +} + +// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal]. +// (The argument to [Unmarshal] must be a non-nil pointer.) +type InvalidUnmarshalError struct { + Type reflect.Type +} + +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "json: Unmarshal(nil)" + } + + if e.Type.Kind() != reflect.Pointer { + return "json: Unmarshal(non-pointer " + e.Type.String() + ")" + } + return "json: Unmarshal(nil " + e.Type.String() + ")" +} +*/ + +func (d *decodeState) unmarshal(v any) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Pointer || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + + d.scan.reset() + d.scanWhile(scanSkipSpace) + // We decode rv not rv.Elem because the Unmarshaler interface + // test must be applied at the top level of the value. + err := d.value(rv) + if err != nil { + return d.addErrorContext(err) + } + if d.savedError != nil { + return d.savedError + } + if len(d.savedStrictErrors) > 0 { + return &UnmarshalStrictError{Errors: d.savedStrictErrors} + } + return nil +} + +/* +// A Number represents a JSON number literal. +type Number string + +// String returns the literal text of the number. +func (n Number) String() string { return string(n) } + +// Float64 returns the number as a float64. +func (n Number) Float64() (float64, error) { + return strconv.ParseFloat(string(n), 64) +} + +// Int64 returns the number as an int64. +func (n Number) Int64() (int64, error) { + return strconv.ParseInt(string(n), 10, 64) +} +*/ + +// An errorContext provides context for type errors during decoding. +type errorContext struct { + Struct reflect.Type + FieldStack []string +} + +// decodeState represents the state while decoding a JSON value. +type decodeState struct { + data []byte + off int // next read offset in data + opcode int // last read result + scan scanner + errorContext *errorContext + savedError error + useNumber bool + disallowUnknownFields bool + + savedStrictErrors []error + seenStrictErrors map[strictError]struct{} + strictFieldStack []string + + caseSensitive bool + + preserveInts bool + + disallowDuplicateFields bool +} + +// readIndex returns the position of the last byte read. +func (d *decodeState) readIndex() int { + return d.off - 1 +} + +// phasePanicMsg is used as a panic message when we end up with something that +// shouldn't happen. It can indicate a bug in the JSON decoder, or that +// something is editing the data slice while the decoder executes. +const phasePanicMsg = "JSON decoder out of sync - data changing underfoot?" + +func (d *decodeState) init(data []byte) *decodeState { + d.data = data + d.off = 0 + d.savedError = nil + if d.errorContext != nil { + d.errorContext.Struct = nil + // Reuse the allocated space for the FieldStack slice. + d.errorContext.FieldStack = d.errorContext.FieldStack[:0] + } + // Reuse the allocated space for the strict FieldStack slice. + d.strictFieldStack = d.strictFieldStack[:0] + return d +} + +// saveError saves the first err it is called with, +// for reporting at the end of the unmarshal. +func (d *decodeState) saveError(err error) { + if d.savedError == nil { + d.savedError = d.addErrorContext(err) + } +} + +// addErrorContext returns a new error enhanced with information from d.errorContext +func (d *decodeState) addErrorContext(err error) error { + if d.errorContext != nil && (d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0) { + switch err := err.(type) { + case *UnmarshalTypeError: + err.Struct = d.errorContext.Struct.Name() + fieldStack := d.errorContext.FieldStack + if err.Field != "" { + fieldStack = append(fieldStack, err.Field) + } + err.Field = strings.Join(fieldStack, ".") + } + } + return err +} + +// skip scans to the end of what was started. +func (d *decodeState) skip() { + s, data, i := &d.scan, d.data, d.off + depth := len(s.parseState) + for { + op := s.step(s, data[i]) + i++ + if len(s.parseState) < depth { + d.off = i + d.opcode = op + return + } + } +} + +// scanNext processes the byte at d.data[d.off]. +func (d *decodeState) scanNext() { + if d.off < len(d.data) { + d.opcode = d.scan.step(&d.scan, d.data[d.off]) + d.off++ + } else { + d.opcode = d.scan.eof() + d.off = len(d.data) + 1 // mark processed EOF with len+1 + } +} + +// scanWhile processes bytes in d.data[d.off:] until it +// receives a scan code not equal to op. +func (d *decodeState) scanWhile(op int) { + s, data, i := &d.scan, d.data, d.off + for i < len(data) { + newOp := s.step(s, data[i]) + i++ + if newOp != op { + d.opcode = newOp + d.off = i + return + } + } + + d.off = len(data) + 1 // mark processed EOF with len+1 + d.opcode = d.scan.eof() +} + +// rescanLiteral is similar to scanWhile(scanContinue), but it specialises the +// common case where we're decoding a literal. The decoder scans the input +// twice, once for syntax errors and to check the length of the value, and the +// second to perform the decoding. +// +// Only in the second step do we use decodeState to tokenize literals, so we +// know there aren't any syntax errors. We can take advantage of that knowledge, +// and scan a literal's bytes much more quickly. +func (d *decodeState) rescanLiteral() { + data, i := d.data, d.off +Switch: + switch data[i-1] { + case '"': // string + for ; i < len(data); i++ { + switch data[i] { + case '\\': + i++ // escaped char + case '"': + i++ // tokenize the closing quote too + break Switch + } + } + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': // number + for ; i < len(data); i++ { + switch data[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', 'e', 'E', '+', '-': + default: + break Switch + } + } + case 't': // true + i += len("rue") + case 'f': // false + i += len("alse") + case 'n': // null + i += len("ull") + } + if i < len(data) { + d.opcode = stateEndValue(&d.scan, data[i]) + } else { + d.opcode = scanEnd + } + d.off = i + 1 +} + +// value consumes a JSON value from d.data[d.off-1:], decoding into v, and +// reads the following byte ahead. If v is invalid, the value is discarded. +// The first byte of the value has been read already. +func (d *decodeState) value(v reflect.Value) error { + switch d.opcode { + default: + panic(phasePanicMsg) + + case scanBeginArray: + if v.IsValid() { + if err := d.array(v); err != nil { + return err + } + } else { + d.skip() + } + d.scanNext() + + case scanBeginObject: + if v.IsValid() { + if err := d.object(v); err != nil { + return err + } + } else { + d.skip() + } + d.scanNext() + + case scanBeginLiteral: + // All bytes inside literal return scanContinue op code. + start := d.readIndex() + d.rescanLiteral() + + if v.IsValid() { + if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil { + return err + } + } + } + return nil +} + +type unquotedValue struct{} + +// valueQuoted is like value but decodes a +// quoted string literal or literal null into an interface value. +// If it finds anything other than a quoted string literal or null, +// valueQuoted returns unquotedValue{}. +func (d *decodeState) valueQuoted() any { + switch d.opcode { + default: + panic(phasePanicMsg) + + case scanBeginArray, scanBeginObject: + d.skip() + d.scanNext() + + case scanBeginLiteral: + v := d.literalInterface() + switch v.(type) { + case nil, string: + return v + } + } + return unquotedValue{} +} + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// If it encounters an Unmarshaler, indirect stops and returns that. +// If decodingNull is true, indirect stops at the first settable pointer so it +// can be set to nil. +func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // Issue #24153 indicates that it is generally not a guaranteed property + // that you may round-trip a reflect.Value by calling Value.Addr().Elem() + // and expect the value to still be settable for values derived from + // unexported embedded struct fields. + // + // The logic below effectively does this when it first addresses the value + // (to satisfy possible pointer methods) and continues to dereference + // subsequent pointers as necessary. + // + // After the first round-trip, we set v back to the original value to + // preserve the original RW flags contained in reflect.Value. + v0 := v + haveAddr := false + + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() { + haveAddr = true + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Pointer && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Pointer) { + haveAddr = false + v = e + continue + } + } + + if v.Kind() != reflect.Pointer { + break + } + + if decodingNull && v.CanSet() { + break + } + + // Prevent infinite loop if v is an interface pointing to its own address: + // var v any + // v = &v + if v.Elem().Kind() == reflect.Interface && v.Elem().Elem().Equal(v) { + v = v.Elem() + break + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + if v.Type().NumMethod() > 0 && v.CanInterface() { + if u, ok := v.Interface().(Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if !decodingNull { + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + } + + if haveAddr { + v = v0 // restore original value after round-trip Value.Addr().Elem() + haveAddr = false + } else { + v = v.Elem() + } + } + return nil, nil, v +} + +// array consumes an array from d.data[d.off-1:], decoding into v. +// The first byte of the array ('[') has been read already. +func (d *decodeState) array(v reflect.Value) error { + // Check for unmarshaler. + u, ut, pv := indirect(v, false) + if u != nil { + start := d.readIndex() + d.skip() + return u.UnmarshalJSON(d.data[start:d.off]) + } + if ut != nil { + d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + } + v = pv + + // Check type of target. + switch v.Kind() { + case reflect.Interface: + if v.NumMethod() == 0 { + // Decoding into nil interface? Switch to non-reflect code. + ai := d.arrayInterface() + v.Set(reflect.ValueOf(ai)) + return nil + } + // Otherwise it's invalid. + fallthrough + default: + d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + case reflect.Array, reflect.Slice: + break + } + + origStrictFieldStackLen := len(d.strictFieldStack) + defer func() { + // Reset to original length and reuse the allocated space for the strict FieldStack slice. + d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] + }() + + i := 0 + for { + // Look ahead for ] - can only happen on first iteration. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndArray { + break + } + + // Expand slice length, growing the slice if necessary. + if v.Kind() == reflect.Slice { + if i >= v.Cap() { + v.Grow(1) + } + if i >= v.Len() { + v.SetLen(i + 1) + } + } + + d.appendStrictFieldStackIndex(i) + if i < v.Len() { + // Decode into element. + if err := d.value(v.Index(i)); err != nil { + return err + } + } else { + // Ran out of fixed array: skip. + if err := d.value(reflect.Value{}); err != nil { + return err + } + } + // Reset to original length and reuse the allocated space for the strict FieldStack slice. + d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] + i++ + + // Next token must be , or ]. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndArray { + break + } + if d.opcode != scanArrayValue { + panic(phasePanicMsg) + } + } + + if i < v.Len() { + if v.Kind() == reflect.Array { + for ; i < v.Len(); i++ { + v.Index(i).SetZero() // zero remainder of array + } + } else { + v.SetLen(i) // truncate the slice + } + } + if i == 0 && v.Kind() == reflect.Slice { + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + } + return nil +} + +var nullLiteral = []byte("null") +var textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]() + +// object consumes an object from d.data[d.off-1:], decoding into v. +// The first byte ('{') of the object has been read already. +func (d *decodeState) object(v reflect.Value) error { + // Check for unmarshaler. + u, ut, pv := indirect(v, false) + if u != nil { + start := d.readIndex() + d.skip() + return u.UnmarshalJSON(d.data[start:d.off]) + } + if ut != nil { + d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) + d.skip() + return nil + } + v = pv + t := v.Type() + + // Decoding into nil interface? Switch to non-reflect code. + if v.Kind() == reflect.Interface && v.NumMethod() == 0 { + oi := d.objectInterface() + v.Set(reflect.ValueOf(oi)) + return nil + } + + var fields structFields + var checkDuplicateField func(fieldNameIndex int, fieldName string) + + // Check type of target: + // struct or + // map[T1]T2 where T1 is string, an integer type, + // or an encoding.TextUnmarshaler + switch v.Kind() { + case reflect.Map: + // Map key must either have string kind, have an integer kind, + // or be an encoding.TextUnmarshaler. + switch t.Key().Kind() { + case reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + default: + if !reflect.PointerTo(t.Key()).Implements(textUnmarshalerType) { + d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) + d.skip() + return nil + } + } + if v.IsNil() { + v.Set(reflect.MakeMap(t)) + } + + if d.disallowDuplicateFields { + var seenKeys map[string]struct{} + checkDuplicateField = func(fieldNameIndex int, fieldName string) { + if seenKeys == nil { + seenKeys = map[string]struct{}{} + } + if _, seen := seenKeys[fieldName]; seen { + d.saveStrictError(d.newFieldError(duplicateStrictErrType, fieldName)) + } else { + seenKeys[fieldName] = struct{}{} + } + } + } + + case reflect.Struct: + fields = cachedTypeFields(t) + + if d.disallowDuplicateFields { + if len(fields.list) <= 64 { + // bitset by field index for structs with <= 64 fields + var seenKeys uint64 + checkDuplicateField = func(fieldNameIndex int, fieldName string) { + if seenKeys&(1< '9') { + if fromQuoted { + return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) + } + panic(phasePanicMsg) + } + switch v.Kind() { + default: + if v.Kind() == reflect.String && v.Type() == numberType { + // s must be a valid number, because it's + // already been tokenized. + v.SetString(string(item)) + break + } + if fromQuoted { + return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) + } + d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) + case reflect.Interface: + n, err := d.convertNumber(string(item)) + if err != nil { + d.saveError(err) + break + } + if v.NumMethod() != 0 { + d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.Set(reflect.ValueOf(n)) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, err := strconv.ParseInt(string(item), 10, 64) + if err != nil || v.OverflowInt(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + string(item), Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.SetInt(n) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, err := strconv.ParseUint(string(item), 10, 64) + if err != nil || v.OverflowUint(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + string(item), Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.SetUint(n) + + case reflect.Float32, reflect.Float64: + n, err := strconv.ParseFloat(string(item), v.Type().Bits()) + if err != nil || v.OverflowFloat(n) { + d.saveError(&UnmarshalTypeError{Value: "number " + string(item), Type: v.Type(), Offset: int64(d.readIndex())}) + break + } + v.SetFloat(n) + } + } + return nil +} + +// The xxxInterface routines build up a value to be stored +// in an empty interface. They are not strictly necessary, +// but they avoid the weight of reflection in this common case. + +// valueInterface is like value but returns any. +func (d *decodeState) valueInterface() (val any) { + switch d.opcode { + default: + panic(phasePanicMsg) + case scanBeginArray: + val = d.arrayInterface() + d.scanNext() + case scanBeginObject: + val = d.objectInterface() + d.scanNext() + case scanBeginLiteral: + val = d.literalInterface() + } + return +} + +// arrayInterface is like array but returns []any. +func (d *decodeState) arrayInterface() []any { + origStrictFieldStackLen := len(d.strictFieldStack) + defer func() { + // Reset to original length and reuse the allocated space for the strict FieldStack slice. + d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] + }() + + var v = make([]any, 0) + for { + // Look ahead for ] - can only happen on first iteration. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndArray { + break + } + + d.appendStrictFieldStackIndex(len(v)) + v = append(v, d.valueInterface()) + // Reset to original length and reuse the allocated space for the strict FieldStack slice. + d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] + + // Next token must be , or ]. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndArray { + break + } + if d.opcode != scanArrayValue { + panic(phasePanicMsg) + } + } + return v +} + +// objectInterface is like object but returns map[string]any. +func (d *decodeState) objectInterface() map[string]any { + origStrictFieldStackLen := len(d.strictFieldStack) + defer func() { + // Reset to original length and reuse the allocated space for the strict FieldStack slice. + d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] + }() + + m := make(map[string]any) + for { + // Read opening " of string key or closing }. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if d.opcode != scanBeginLiteral { + panic(phasePanicMsg) + } + + // Read string key. + start := d.readIndex() + d.rescanLiteral() + item := d.data[start:d.readIndex()] + key, ok := unquote(item) + if !ok { + panic(phasePanicMsg) + } + + // Read : before value. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode != scanObjectKey { + panic(phasePanicMsg) + } + d.scanWhile(scanSkipSpace) + + if d.disallowDuplicateFields { + if _, exists := m[key]; exists { + d.saveStrictError(d.newFieldError(duplicateStrictErrType, key)) + } + } + + // Read value. + d.appendStrictFieldStackKey(key) + m[key] = d.valueInterface() + // Reset to original length and reuse the allocated space for the strict FieldStack slice. + d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] + + // Next token must be , or }. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndObject { + break + } + if d.opcode != scanObjectValue { + panic(phasePanicMsg) + } + } + return m +} + +// literalInterface consumes and returns a literal from d.data[d.off-1:] and +// it reads the following byte ahead. The first byte of the literal has been +// read already (that's how the caller knows it's a literal). +func (d *decodeState) literalInterface() any { + // All bytes inside literal return scanContinue op code. + start := d.readIndex() + d.rescanLiteral() + + item := d.data[start:d.readIndex()] + + switch c := item[0]; c { + case 'n': // null + return nil + + case 't', 'f': // true, false + return c == 't' + + case '"': // string + s, ok := unquote(item) + if !ok { + panic(phasePanicMsg) + } + return s + + default: // number + if c != '-' && (c < '0' || c > '9') { + panic(phasePanicMsg) + } + n, err := d.convertNumber(string(item)) + if err != nil { + d.saveError(err) + } + return n + } +} + +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + var r rune + for _, c := range s[2:6] { + switch { + case '0' <= c && c <= '9': + c = c - '0' + case 'a' <= c && c <= 'f': + c = c - 'a' + 10 + case 'A' <= c && c <= 'F': + c = c - 'A' + 10 + default: + return -1 + } + r = r*16 + rune(c) + } + return r +} + +// unquote converts a quoted JSON string literal s into an actual string t. +// The rules are different than for Go, so cannot use strconv.Unquote. +func unquote(s []byte) (t string, ok bool) { + s, ok = unquoteBytes(s) + t = string(s) + return +} + +func unquoteBytes(s []byte) (t []byte, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + s = s[1 : len(s)-1] + + // Check for unusual characters. If there are none, + // then no unquoting is needed, so return a slice of the + // original bytes. + r := 0 + for r < len(s) { + c := s[r] + if c == '\\' || c == '"' || c < ' ' { + break + } + if c < utf8.RuneSelf { + r++ + continue + } + rr, size := utf8.DecodeRune(s[r:]) + if rr == utf8.RuneError && size == 1 { + break + } + r += size + } + if r == len(s) { + return s, true + } + + b := make([]byte, len(s)+2*utf8.UTFMax) + w := copy(b, s[0:r]) + for r < len(s) { + // Out of room? Can only happen if s is full of + // malformed UTF-8 and we're replacing each + // byte with RuneError. + if w >= len(b)-2*utf8.UTFMax { + nb := make([]byte, (len(b)+utf8.UTFMax)*2) + copy(nb, b[0:w]) + b = nb + } + switch c := s[r]; { + case c == '\\': + r++ + if r >= len(s) { + return + } + switch s[r] { + default: + return + case '"', '\\', '/', '\'': + b[w] = s[r] + r++ + w++ + case 'b': + b[w] = '\b' + r++ + w++ + case 'f': + b[w] = '\f' + r++ + w++ + case 'n': + b[w] = '\n' + r++ + w++ + case 'r': + b[w] = '\r' + r++ + w++ + case 't': + b[w] = '\t' + r++ + w++ + case 'u': + r-- + rr := getu4(s[r:]) + if rr < 0 { + return + } + r += 6 + if utf16.IsSurrogate(rr) { + rr1 := getu4(s[r:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + // A valid pair; consume. + r += 6 + w += utf8.EncodeRune(b[w:], dec) + break + } + // Invalid surrogate; fall back to replacement rune. + rr = unicode.ReplacementChar + } + w += utf8.EncodeRune(b[w:], rr) + } + + // Quote, control characters are invalid. + case c == '"', c < ' ': + return + + // ASCII + case c < utf8.RuneSelf: + b[w] = c + r++ + w++ + + // Coerce to well-formed UTF-8. + default: + rr, size := utf8.DecodeRune(s[r:]) + r += size + w += utf8.EncodeRune(b[w:], rr) + } + } + return b[0:w], true +} diff --git a/vendor/sigs.k8s.io/json/internal/golang/encoding/json/encode.go b/vendor/sigs.k8s.io/json/internal/golang/encoding/json/encode.go new file mode 100644 index 0000000000..4e3a1a2f10 --- /dev/null +++ b/vendor/sigs.k8s.io/json/internal/golang/encoding/json/encode.go @@ -0,0 +1,1323 @@ +// Copyright 2010 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 json implements encoding and decoding of JSON as defined in +// RFC 7159. The mapping between JSON and Go values is described +// in the documentation for the Marshal and Unmarshal functions. +// +// See "JSON and Go" for an introduction to this package: +// https://golang.org/doc/articles/json_and_go.html +package json + +import ( + "bytes" + "cmp" + "encoding" + "encoding/base64" + "fmt" + "math" + "reflect" + "slices" + "strconv" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// Marshal returns the JSON encoding of v. +// +// Marshal traverses the value v recursively. +// If an encountered value implements [Marshaler] +// and is not a nil pointer, Marshal calls [Marshaler.MarshalJSON] +// to produce JSON. If no [Marshaler.MarshalJSON] method is present but the +// value implements [encoding.TextMarshaler] instead, Marshal calls +// [encoding.TextMarshaler.MarshalText] and encodes the result as a JSON string. +// The nil pointer exception is not strictly necessary +// but mimics a similar, necessary exception in the behavior of +// [Unmarshaler.UnmarshalJSON]. +// +// Otherwise, Marshal uses the following type-dependent default encodings: +// +// Boolean values encode as JSON booleans. +// +// Floating point, integer, and [Number] values encode as JSON numbers. +// NaN and +/-Inf values will return an [UnsupportedValueError]. +// +// String values encode as JSON strings coerced to valid UTF-8, +// replacing invalid bytes with the Unicode replacement rune. +// So that the JSON will be safe to embed inside HTML