mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-11-06 05:17:08 +00:00
Bump opencontainers/cgroups to v0.1.0, moby/sys/userns to v0.2.1, containerd/log to v0.2.0, CDI specs-go to v1.1.1
The only sig-node runtime libraries behind their latest tags. No CVEs. cgroups: hardening (device-filter kernel OOB workaround, O_CLOEXEC, GetAllPids skips removed cgroups). userns: RunningInUserNS compares the /proc/self/ns/user inode instead of parsing uid_map. containerd/log: slog output support (vendors logrus/hooks/slog). specs-go: drops the golang.org/x/mod requirement. Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
20 files changed
+799
-144
No files matched your search
@@ -40,11 +40,11 @@ require (
|
||||
github.com/ishidawataru/sctp v0.0.0-20250521072954-ae8eb7fa7995
|
||||
github.com/lithammer/dedent v1.1.0
|
||||
github.com/moby/ipvs v1.1.0
|
||||
github.com/moby/sys/userns v0.1.0
|
||||
github.com/moby/sys/userns v0.2.1
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
|
||||
github.com/onsi/ginkgo/v2 v2.33.0
|
||||
github.com/onsi/gomega v1.43.1
|
||||
github.com/opencontainers/cgroups v0.0.7
|
||||
github.com/opencontainers/cgroups v0.1.0
|
||||
github.com/opencontainers/selinux v1.15.1
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
@@ -123,7 +123,7 @@ require (
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
sigs.k8s.io/structured-merge-diff/v7 v7.0.0
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.0
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -139,7 +139,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/containerd/containerd/api v1.12.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/log v0.2.0 // indirect
|
||||
github.com/containerd/ttrpc v1.2.10 // indirect
|
||||
github.com/coredns/caddy v1.1.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
|
||||
@@ -51,8 +51,8 @@ github.com/container-storage-interface/spec v1.13.0 h1:6ja3nYoACisewTPAAZkJ1pbf4
|
||||
github.com/container-storage-interface/spec v1.13.0/go.mod h1:fPZ7EFHYJwIwc9CMcoaT/yIhFTdToocYVtyafMG7EDM=
|
||||
github.com/containerd/containerd/api v1.12.0 h1:kuQm82SbDrCuO4n7hf2L8zsBtZLuympyq5X/VotfX2A=
|
||||
github.com/containerd/containerd/api v1.12.0/go.mod h1:EBcSzoi9Vl18cdODaXUCskf3D2NT8lsSXeZJnU5jIUc=
|
||||
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/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc=
|
||||
github.com/containerd/log v0.2.0/go.mod h1:/M7L7CXKcPTfNC74XzaK+5H5KbO5+4lJVpuVI6vRLoM=
|
||||
github.com/containerd/ttrpc v1.2.10 h1:qQvy3mokhoXON7FXiOOc05O7Up6nrNj6amJF8urNvDc=
|
||||
github.com/containerd/ttrpc v1.2.10/go.mod h1:YrmEQKkMbBA4EM26AzVAIwHGIYgptkL4CJ/yVTaDyBE=
|
||||
github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ=
|
||||
@@ -235,8 +235,8 @@ github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y
|
||||
github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/sys/userns v0.2.1 h1:4OvdM7BcPkASbuouHsbW3aeMJSFlYDldBRnXVZhaRk8=
|
||||
github.com/moby/sys/userns v0.2.1/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -252,8 +252,8 @@ github.com/onsi/ginkgo/v2 v2.33.0 h1:C8gBA6Uc2ZEubiV+SXiu5tZnMTwEmXHgkJwGozKtZf8
|
||||
github.com/onsi/ginkgo/v2 v2.33.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
|
||||
github.com/onsi/gomega v1.43.1 h1:vGIPFuYrIO6/0Z09s0I0QQQgFchiX4+tb1re3MScJYo=
|
||||
github.com/onsi/gomega v1.43.1/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA=
|
||||
github.com/opencontainers/cgroups v0.0.7 h1:FqhggFhigAMgKKwy39jweWX3h7Ha6VBF/qNR6Sso3oc=
|
||||
github.com/opencontainers/cgroups v0.0.7/go.mod h1:hPBRvnBhLZueEN0eJyozMeM3HeFGYlZW9KnO//px6G4=
|
||||
github.com/opencontainers/cgroups v0.1.0 h1:6W05KiDvgj1yi/6D13SJCvYx8TAESz67szvHPxhfcrs=
|
||||
github.com/opencontainers/cgroups v0.1.0/go.mod h1:hPBRvnBhLZueEN0eJyozMeM3HeFGYlZW9KnO//px6G4=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -502,5 +502,5 @@ sigs.k8s.io/structured-merge-diff/v7 v7.0.0 h1:LnKTE1vO6tDITDMVOJ5oCsWauLIAPJnV9
|
||||
sigs.k8s.io/structured-merge-diff/v7 v7.0.0/go.mod h1:xKoMSgtnJnkfRR+CTNSQ1dBVhkKJR5OQl5RxX/OqHbI=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.0 h1:QRZVeAceQM+zTZe12eyfuJuuzp524EKYwhmvLd+h+yQ=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.1 h1:3xjaytilFeCBVFJsJTaT9uOFahoqJMVuYG7gYqi2+NY=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.1/go.mod h1:BhJIkjjPh4qpys+qm4DAYtUyryaTDg9zris+AczXyws=
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
k8s.io/kubelet v0.0.0
|
||||
k8s.io/utils v0.0.0-20260626114624-be93311217bd
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.0
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -69,7 +69,6 @@ require (
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect
|
||||
golang.org/x/mod v0.41.0 // indirect
|
||||
golang.org/x/net v0.59.0 // indirect
|
||||
golang.org/x/oauth2 v0.37.0 // indirect
|
||||
golang.org/x/sync v0.23.0 // indirect
|
||||
|
||||
+2
-3
@@ -183,7 +183,6 @@ go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU=
|
||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E=
|
||||
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
@@ -237,5 +236,5 @@ sigs.k8s.io/structured-merge-diff/v7 v7.0.0 h1:LnKTE1vO6tDITDMVOJ5oCsWauLIAPJnV9
|
||||
sigs.k8s.io/structured-merge-diff/v7 v7.0.0/go.mod h1:xKoMSgtnJnkfRR+CTNSQ1dBVhkKJR5OQl5RxX/OqHbI=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.0 h1:QRZVeAceQM+zTZe12eyfuJuuzp524EKYwhmvLd+h+yQ=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.1 h1:3xjaytilFeCBVFJsJTaT9uOFahoqJMVuYG7gYqi2+NY=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.1/go.mod h1:BhJIkjjPh4qpys+qm4DAYtUyryaTDg9zris+AczXyws=
|
||||
Generated
+1
-1
@@ -209,4 +209,4 @@ sigs.k8s.io/structured-merge-diff/v7 v7.0.0 h1:LnKTE1vO6tDITDMVOJ5oCsWauLIAPJnV9
|
||||
sigs.k8s.io/structured-merge-diff/v7 v7.0.0/go.mod h1:xKoMSgtnJnkfRR+CTNSQ1dBVhkKJR5OQl5RxX/OqHbI=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.1.1/go.mod h1:BhJIkjjPh4qpys+qm4DAYtUyryaTDg9zris+AczXyws=
|
||||
+1
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
+35
-15
@@ -1,30 +1,50 @@
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- exportloopref # Checks for pointers to enclosing loop variables
|
||||
- gofmt
|
||||
- goimports
|
||||
- copyloopvar
|
||||
- dupword
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
|
||||
- unconvert
|
||||
- unused
|
||||
- vet
|
||||
- dupword # Checks for duplicate words in the source code
|
||||
disable:
|
||||
- errcheck
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
skip-dirs:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test # e2e scripts
|
||||
+61
-9
@@ -40,8 +40,11 @@ package log
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
lslog "github.com/sirupsen/logrus/hooks/slog"
|
||||
)
|
||||
|
||||
// G is a shorthand for [GetLogger].
|
||||
@@ -108,10 +111,17 @@ const (
|
||||
PanicLevel Level = logrus.PanicLevel
|
||||
)
|
||||
|
||||
// SetLevel sets log level globally. It returns an error if the given
|
||||
// level is not supported.
|
||||
// levelValue is a log level accepted by [SetLevel].
|
||||
type levelValue interface {
|
||||
string | Level | slog.Level | lslog.Level | lslog.SlogLevel
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level.
|
||||
//
|
||||
// level can be one of:
|
||||
// The level may be specified as a string, a [Level], a [slog.Level], an
|
||||
// [lslog.Level], or an [lslog.SlogLevel].
|
||||
//
|
||||
// String levels are parsed using [logrus.ParseLevel] and may be one of:
|
||||
//
|
||||
// - "trace" ([TraceLevel])
|
||||
// - "debug" ([DebugLevel])
|
||||
@@ -120,10 +130,34 @@ const (
|
||||
// - "error" ([ErrorLevel])
|
||||
// - "fatal" ([FatalLevel])
|
||||
// - "panic" ([PanicLevel])
|
||||
func SetLevel(level string) error {
|
||||
lvl, err := logrus.ParseLevel(level)
|
||||
if err != nil {
|
||||
return err
|
||||
//
|
||||
// slog levels are mapped to Logrus levels using the mapping defined by
|
||||
// [lslog.SlogLevel]. [lslog.Level] values are accepted directly as their
|
||||
// underlying Logrus level.
|
||||
//
|
||||
// SetLevel returns an error if a string level is not supported.
|
||||
func SetLevel[T levelValue](level T) error {
|
||||
var lvl Level
|
||||
|
||||
switch l := any(level).(type) {
|
||||
case string:
|
||||
var err error
|
||||
lvl, err = logrus.ParseLevel(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case Level:
|
||||
lvl = l
|
||||
|
||||
case slog.Level:
|
||||
lvl = lslog.SlogLevel(l).Level()
|
||||
|
||||
case lslog.Level:
|
||||
lvl = Level(l)
|
||||
|
||||
case lslog.SlogLevel:
|
||||
lvl = l.Level()
|
||||
}
|
||||
|
||||
L.Logger.SetLevel(lvl)
|
||||
@@ -149,21 +183,39 @@ const (
|
||||
|
||||
// SetFormat sets the log output format ([TextFormat] or [JSONFormat]).
|
||||
func SetFormat(format OutputFormat) error {
|
||||
if slogOut != nil {
|
||||
var handler slog.Handler
|
||||
switch format {
|
||||
case TextFormat:
|
||||
handler = slog.NewTextHandler(slogOut, &slog.HandlerOptions{Level: loggerLevel{}})
|
||||
case JSONFormat:
|
||||
handler = slog.NewJSONHandler(slogOut, &slog.HandlerOptions{Level: loggerLevel{}})
|
||||
default:
|
||||
return fmt.Errorf("unknown log format: %s", format)
|
||||
}
|
||||
slog.SetDefault(slog.New(handler))
|
||||
|
||||
// Keep logrus formatting and output disabled, as both are handled by slog.
|
||||
L.Logger.SetFormatter(discardFormatter{})
|
||||
L.Logger.SetOutput(io.Discard)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case TextFormat:
|
||||
L.Logger.SetFormatter(&logrus.TextFormatter{
|
||||
TimestampFormat: RFC3339NanoFixed,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
return nil
|
||||
case JSONFormat:
|
||||
L.Logger.SetFormatter(&logrus.JSONFormatter{
|
||||
TimestampFormat: RFC3339NanoFixed,
|
||||
})
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown log format: %s", format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithLogger returns a new context with the provided logger. Use in
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
lslog "github.com/sirupsen/logrus/hooks/slog"
|
||||
)
|
||||
|
||||
// slogOut is used to set the slog logger when setting output format.
|
||||
var slogOut io.Writer
|
||||
|
||||
// slogOnce guards UseSlog so repeated calls do not stack up hooks or
|
||||
// reset slogOut to the discard writer installed on the first call.
|
||||
var slogOnce sync.Once
|
||||
|
||||
func UseSlog() {
|
||||
slogOnce.Do(func() {
|
||||
L.Logger.SetNoLock()
|
||||
L.Logger.AddHook(slogHook{})
|
||||
slogOut = L.Logger.Out
|
||||
|
||||
// Disable logrus formatting and output, as both are handled by slog.
|
||||
L.Logger.SetFormatter(discardFormatter{})
|
||||
L.Logger.SetOutput(io.Discard)
|
||||
})
|
||||
}
|
||||
|
||||
type slogHook struct{}
|
||||
|
||||
func (slogHook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
func (slogHook) Fire(entry *logrus.Entry) error {
|
||||
return lslog.NewHook(slog.Default(), nil).Fire(entry)
|
||||
}
|
||||
|
||||
// loggerLevel exposes the current Logrus logger level as a slog.Leveler.
|
||||
type loggerLevel struct{}
|
||||
|
||||
func (loggerLevel) Level() slog.Level {
|
||||
return lslog.Level(L.Logger.GetLevel()).Level()
|
||||
}
|
||||
|
||||
type discardFormatter struct{}
|
||||
|
||||
func (discardFormatter) Format(*logrus.Entry) ([]byte, error) { return nil, nil }
|
||||
+48
@@ -2,11 +2,16 @@ package userns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// See PROC_USER_INIT_INO in https://github.com/torvalds/linux/blob/v7.1/include/uapi/linux/nsfs.h#L50.
|
||||
const procUserInitIno = 0xEFFFFFFD
|
||||
|
||||
var inUserNS = sync.OnceValue(runningInUserNS)
|
||||
|
||||
// runningInUserNS detects whether we are currently running in a user namespace.
|
||||
@@ -17,6 +22,33 @@ var inUserNS = sync.OnceValue(runningInUserNS)
|
||||
// [libcontainer/runc]: https://github.com/opencontainers/runc/blob/3778ae603c706494fd1e2c2faf83b406e38d687d/libcontainer/userns/userns_linux.go#L12-L49
|
||||
// [lcx/incus]: https://github.com/lxc/incus/blob/e45085dd42f826b3c8c3228e9733c0b6f998eafe/shared/util.go#L678-L700
|
||||
func runningInUserNS() bool {
|
||||
var st syscall.Stat_t
|
||||
if err := syscall.Stat("/proc/self/ns/user", &st); err == nil {
|
||||
// The kernel's initial user namespace inode is definitive when it
|
||||
// matches. OpenVZ virtualizes namespace inode numbers, so a mismatch
|
||||
// must fall back to uid_map-based detection there.
|
||||
if st.Ino == procUserInitIno {
|
||||
return false
|
||||
}
|
||||
if runningInOpenVZ() {
|
||||
return runningInUserNSFromUIDMap()
|
||||
}
|
||||
return true
|
||||
} else if !os.IsNotExist(err) {
|
||||
// As long as /proc/self/ns/user exists, we are on a modern kernel.
|
||||
// Other errors indicate an unexpected procfs state, where assuming the
|
||||
// init namespace would be unsafe.
|
||||
return false
|
||||
}
|
||||
|
||||
// Only fall back for older kernels that do not expose the user namespace
|
||||
// through procfs at /proc/self/ns/user.
|
||||
// TODO: Remove this fallback once Linux kernels older than 3.8 are no
|
||||
// longer supported.
|
||||
return runningInUserNSFromUIDMap()
|
||||
}
|
||||
|
||||
func runningInUserNSFromUIDMap() bool {
|
||||
file, err := os.Open("/proc/self/uid_map")
|
||||
if err != nil {
|
||||
// This kernel-provided file only exists if user namespaces are supported.
|
||||
@@ -51,3 +83,19 @@ func uidMapInUserNS(uidMap string) bool {
|
||||
initNS := a == 0 && b == 0 && c == 4294967295
|
||||
return !initNS
|
||||
}
|
||||
|
||||
// runningInOpenVZ reports whether the process is running inside an OpenVZ
|
||||
// container.
|
||||
//
|
||||
// OpenVZ exposes /proc/vz both on the host and inside containers, while
|
||||
// /proc/bc is only exposed on the host. This follows systemd's OpenVZ
|
||||
// detection:
|
||||
// https://github.com/systemd/systemd/blob/v261.2/src/basic/virt.c#L642-L653
|
||||
func runningInOpenVZ() bool {
|
||||
var st syscall.Stat_t
|
||||
if err := syscall.Stat("/proc/vz", &st); err != nil {
|
||||
return false
|
||||
}
|
||||
err := syscall.Stat("/proc/bc", &st)
|
||||
return errors.Is(err, syscall.ENOENT)
|
||||
}
|
||||
+2
@@ -10,6 +10,8 @@ formatters:
|
||||
linters:
|
||||
enable:
|
||||
- errorlint
|
||||
- govet
|
||||
- modernize
|
||||
- nolintlint
|
||||
- unconvert
|
||||
- unparam
|
||||
|
||||
+4
-3
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -153,8 +153,9 @@ func (m *Manager) AddPid(subcgroup string, pid int) (retErr error) {
|
||||
c := m.cgroups
|
||||
|
||||
for _, dir := range m.paths {
|
||||
path := path.Join(dir, subcgroup)
|
||||
if !strings.HasPrefix(path, dir) {
|
||||
path := filepath.Join(dir, subcgroup)
|
||||
// Make sure path is either dir itself or below it.
|
||||
if path != dir && !strings.HasPrefix(path, dir+"/") {
|
||||
return fmt.Errorf("bad sub cgroup path: %s", subcgroup)
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -89,7 +89,8 @@ func (m *Manager) Apply(pid int) error {
|
||||
// a cgroup under under the manager's cgroup.
|
||||
func (m *Manager) AddPid(subcgroup string, pid int) error {
|
||||
path := filepath.Join(m.dirPath, subcgroup)
|
||||
if !strings.HasPrefix(path, m.dirPath) {
|
||||
// Make sure path is either m.dirPath itself or below it.
|
||||
if path != m.dirPath && !strings.HasPrefix(path, m.dirPath+"/") {
|
||||
return fmt.Errorf("bad sub cgroup path: %s", subcgroup)
|
||||
}
|
||||
|
||||
|
||||
+17
@@ -1,8 +1,12 @@
|
||||
package cgroups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// GetAllPids returns all pids from the cgroup identified by path, and all its
|
||||
@@ -11,6 +15,11 @@ func GetAllPids(path string) ([]int, error) {
|
||||
var pids []int
|
||||
err := filepath.WalkDir(path, func(p string, d fs.DirEntry, iErr error) error {
|
||||
if iErr != nil {
|
||||
// A descendant cgroup can be removed while we walk, ignore
|
||||
// any such error unless it's on the root (path) cgroup here
|
||||
if p != path && ignoreCgroupRemoved(iErr) {
|
||||
return nil
|
||||
}
|
||||
return iErr
|
||||
}
|
||||
if !d.IsDir() {
|
||||
@@ -18,6 +27,9 @@ func GetAllPids(path string) ([]int, error) {
|
||||
}
|
||||
cPids, err := readProcsFile(p)
|
||||
if err != nil {
|
||||
if p != path && ignoreCgroupRemoved(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
pids = append(pids, cPids...)
|
||||
@@ -25,3 +37,8 @@ func GetAllPids(path string) ([]int, error) {
|
||||
})
|
||||
return pids, err
|
||||
}
|
||||
|
||||
// ignoreCgroupRemoved reports whether err indicates the cgroup was removed.
|
||||
func ignoreCgroupRemoved(err error) bool {
|
||||
return os.IsNotExist(err) || errors.Is(err, unix.ENODEV)
|
||||
}
|
||||
+27
-8
@@ -20,7 +20,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
cpuIdleSupportedVersion = 252
|
||||
cpuIdleSupportedVersion = 252
|
||||
oomPolicySupportedVersion = 253
|
||||
)
|
||||
|
||||
type UnifiedManager struct {
|
||||
@@ -183,13 +184,8 @@ func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props
|
||||
newProp("TasksMax", num))
|
||||
|
||||
case "memory.oom.group":
|
||||
// Setting this to 1 is roughly equivalent to OOMPolicy=kill
|
||||
// (as per systemd.service(5) and
|
||||
// https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html),
|
||||
// but it's not clear what to do if it is unset or set
|
||||
// to 0 in runc update, as there are two other possible
|
||||
// values for OOMPolicy (continue/stop).
|
||||
fallthrough
|
||||
// This was set before the unit started, so no need to
|
||||
// warn about it here.
|
||||
|
||||
default:
|
||||
// Ignore the unknown resource here -- will still be
|
||||
@@ -338,6 +334,29 @@ func (m *UnifiedManager) Apply(pid int) error {
|
||||
|
||||
properties = append(properties, c.SystemdProps...)
|
||||
|
||||
// OOMPolicy must be set at unit creation time, systemd does not allow
|
||||
// changing it on a running scope for some reason despite the kernel allowing so
|
||||
// so we do this here in Apply() instead of Set()
|
||||
if c.Resources != nil {
|
||||
if v, ok := c.Resources.Unified["memory.oom.group"]; ok {
|
||||
if systemdVersion(m.dbus) >= oomPolicySupportedVersion {
|
||||
value, err := strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unified resource %q value conversion error: %w", "memory.oom.group", err)
|
||||
}
|
||||
|
||||
switch value {
|
||||
case 0:
|
||||
properties = append(properties, newProp("OOMPolicy", "continue"))
|
||||
case 1:
|
||||
properties = append(properties, newProp("OOMPolicy", "kill"))
|
||||
default:
|
||||
logrus.Debugf("don't know how to convert memory.oom.group=%d; skipping (will still be applied to cgroupfs)", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := startUnit(m.dbus, unitName, properties, pid == -1); err != nil {
|
||||
return fmt.Errorf("unable to start unit %q (properties %+v): %w", unitName, properties, err)
|
||||
}
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HandlerOptions are options for a [Handler].
|
||||
// A zero HandlerOptions consists entirely of default values.
|
||||
type HandlerOptions struct {
|
||||
// AddSource causes the handler to include the source code position
|
||||
// of the log statement in the Logrus entry.
|
||||
AddSource bool
|
||||
|
||||
// LevelMapper maps slog levels to Logrus levels. If nil, the default
|
||||
// mapping is used. Set it to customize level mapping, for example to map
|
||||
// custom slog levels to specific Logrus levels.
|
||||
LevelMapper func(slog.Level) logrus.Level
|
||||
}
|
||||
|
||||
// Handler is a [slog.Handler] that writes records to a [logrus.Logger].
|
||||
//
|
||||
// It is intended for bridging libraries or application code that log via slog
|
||||
// into an existing Logrus logger, for example during a gradual migration.
|
||||
//
|
||||
// By default, slog levels are mapped using [SlogLevel].
|
||||
// Set [HandlerOptions.LevelMapper] to customize the mapping.
|
||||
//
|
||||
// Mapping to [logrus.FatalLevel] or [logrus.PanicLevel] preserves the level
|
||||
// only; handling a record does not exit or panic.
|
||||
type Handler struct {
|
||||
logger *logrus.Logger
|
||||
opts HandlerOptions
|
||||
|
||||
// fields holds attributes from prior WithAttrs calls, already resolved
|
||||
// under the group prefix that applied when they were added.
|
||||
fields logrus.Fields
|
||||
|
||||
// groups is the current group name stack for attributes added later.
|
||||
groups []string
|
||||
}
|
||||
|
||||
var _ slog.Handler = (*Handler)(nil)
|
||||
|
||||
// NewHandler creates a [slog.Handler] that writes to the provided
|
||||
// [logrus.Logger].
|
||||
//
|
||||
// The provided logger must not be nil. NewHandler panics if logger is nil.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewHandler(logger *logrus.Logger, opts *HandlerOptions) *Handler {
|
||||
if logger == nil {
|
||||
panic("cannot create handler from nil logger")
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
return &Handler{
|
||||
logger: logger,
|
||||
opts: *opts,
|
||||
}
|
||||
}
|
||||
|
||||
// toLogrusLevel maps a slog level using LevelMapper or the default mapping.
|
||||
func (h *Handler) toLogrusLevel(level slog.Level) logrus.Level {
|
||||
if h.opts.LevelMapper != nil {
|
||||
return h.opts.LevelMapper(level)
|
||||
}
|
||||
return SlogLevel(level).Level()
|
||||
}
|
||||
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// It maps the slog level to a logrus level and consults the underlying logger.
|
||||
func (h *Handler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return h.logger.IsLevelEnabled(h.toLogrusLevel(level))
|
||||
}
|
||||
|
||||
// Handle converts the slog record into a logrus entry and logs it.
|
||||
// Record time, context, message, and attributes (including those from
|
||||
// [Handler.WithAttrs]/[Handler.WithGroup]) are preserved. Attributes are
|
||||
// attached as logrus fields; group names are joined with "." as a key prefix
|
||||
// (similar to [slog.TextHandler]).
|
||||
func (h *Handler) Handle(ctx context.Context, record slog.Record) error {
|
||||
// Stop recursive forwarding before it can cycle back through this logger.
|
||||
if hasLogrusLogger(ctx, h.logger) {
|
||||
return nil
|
||||
}
|
||||
level := h.toLogrusLevel(record.Level)
|
||||
if !h.logger.IsLevelEnabled(level) {
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := &logrus.Entry{
|
||||
Logger: h.logger,
|
||||
Data: h.fields,
|
||||
Time: record.Time,
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
if h.opts.AddSource && record.PC != 0 {
|
||||
// Preserve the caller selected by slog instead of rediscovering it
|
||||
// through Logrus's caller stack filtering.
|
||||
//
|
||||
// Record.PC is a return PC; resolve it with CallersFrames.
|
||||
frame, _ := runtime.CallersFrames([]uintptr{record.PC}).Next()
|
||||
entry.Caller = &frame
|
||||
}
|
||||
|
||||
if n := record.NumAttrs(); n > 0 {
|
||||
// Clone before mutating the handler's fields.
|
||||
entry.Data = maps.Clone(h.fields)
|
||||
if entry.Data == nil {
|
||||
entry.Data = make(logrus.Fields, n)
|
||||
}
|
||||
record.Attrs(func(a slog.Attr) bool {
|
||||
appendAttr(entry.Data, h.groups, a)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
entry.Log(level, record.Message)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithAttrs returns a new Handler whose attributes consist of h's attributes
|
||||
// followed by attrs.
|
||||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if len(attrs) == 0 {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.fields = maps.Clone(h.fields)
|
||||
if h2.fields == nil {
|
||||
h2.fields = make(logrus.Fields, len(attrs))
|
||||
}
|
||||
for _, a := range attrs {
|
||||
appendAttr(h2.fields, h.groups, a)
|
||||
}
|
||||
return h2
|
||||
}
|
||||
|
||||
// WithGroup returns a new Handler with a group appended to the receiver's
|
||||
// existing groups. All attributes added to the returned handler (via WithAttrs
|
||||
// or Handle) are nested under the combined group names.
|
||||
// If name is empty, WithGroup returns h unchanged.
|
||||
func (h *Handler) WithGroup(name string) slog.Handler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.groups = append(slices.Clip(h.groups), name)
|
||||
return h2
|
||||
}
|
||||
|
||||
// clone returns a shallow copy of h. Callers must clone fields or groups
|
||||
// before modifying them.
|
||||
func (h *Handler) clone() *Handler {
|
||||
return &Handler{
|
||||
logger: h.logger,
|
||||
opts: h.opts,
|
||||
fields: h.fields,
|
||||
groups: h.groups,
|
||||
}
|
||||
}
|
||||
|
||||
// appendAttr adds attr to fields, resolving it and applying group prefixes.
|
||||
// Group attributes are flattened with "."-separated keys.
|
||||
func appendAttr(fields logrus.Fields, groups []string, attr slog.Attr) {
|
||||
attr.Value = attr.Value.Resolve()
|
||||
if attr.Equal(slog.Attr{}) {
|
||||
return
|
||||
}
|
||||
if attr.Value.Kind() == slog.KindGroup {
|
||||
gs := attr.Value.Group()
|
||||
if len(gs) == 0 {
|
||||
return
|
||||
}
|
||||
g := groups
|
||||
if attr.Key != "" {
|
||||
g = append(slices.Clip(groups), attr.Key)
|
||||
}
|
||||
for _, a := range gs {
|
||||
appendAttr(fields, g, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
key := attr.Key
|
||||
if len(groups) > 0 {
|
||||
key = strings.Join(groups, ".")
|
||||
if attr.Key != "" {
|
||||
key += "." + attr.Key
|
||||
}
|
||||
}
|
||||
fields[key] = attr.Value.Any()
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package slog
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// slog levels corresponding to Logrus levels.
|
||||
const (
|
||||
slogLevelTrace = slog.LevelDebug - 4
|
||||
slogLevelDebug = slog.LevelDebug
|
||||
slogLevelInfo = slog.LevelInfo
|
||||
slogLevelWarn = slog.LevelWarn
|
||||
slogLevelError = slog.LevelError
|
||||
slogLevelFatal = slog.LevelError + 2
|
||||
slogLevelPanic = slog.LevelError + 4
|
||||
)
|
||||
|
||||
// A Leveler provides a [logrus.Level] value.
|
||||
//
|
||||
// It is the Logrus counterpart of [slog.Leveler].
|
||||
//
|
||||
// As [SlogLevel] itself implements Leveler, clients typically supply
|
||||
// a SlogLevel value wherever a Leveler is needed.
|
||||
// Clients who need to vary the level dynamically can provide a more complex
|
||||
// Leveler implementation.
|
||||
type Leveler interface {
|
||||
Level() logrus.Level
|
||||
}
|
||||
|
||||
// Level adapts a [logrus.Level] to a [slog.Leveler] using the default
|
||||
// Logrus-to-slog level mapping:
|
||||
//
|
||||
// - [logrus.TraceLevel] -> [slog.LevelDebug] - 4
|
||||
// - [logrus.DebugLevel] -> [slog.LevelDebug]
|
||||
// - [logrus.InfoLevel] -> [slog.LevelInfo]
|
||||
// - [logrus.WarnLevel] -> [slog.LevelWarn]
|
||||
// - [logrus.ErrorLevel] -> [slog.LevelError]
|
||||
// - [logrus.FatalLevel] -> [slog.LevelError] + 2
|
||||
// - [logrus.PanicLevel] -> [slog.LevelError] + 4
|
||||
//
|
||||
// Unknown Logrus levels map to [slog.LevelError].
|
||||
type Level logrus.Level
|
||||
|
||||
// Level returns l mapped to the corresponding slog level.
|
||||
func (l Level) Level() slog.Level {
|
||||
return toSlogLevel(logrus.Level(l))
|
||||
}
|
||||
|
||||
var _ slog.Leveler = Level(0)
|
||||
|
||||
// SlogLevel adapts a [slog.Level] to a [Leveler] using the default
|
||||
// slog-to-Logrus level mapping:
|
||||
//
|
||||
// - [slog.LevelDebug] - 4 -> [logrus.TraceLevel]
|
||||
// - [slog.LevelDebug] -> [logrus.DebugLevel]
|
||||
// - [slog.LevelInfo] -> [logrus.InfoLevel]
|
||||
// - [slog.LevelWarn] -> [logrus.WarnLevel]
|
||||
// - [slog.LevelError] -> [logrus.ErrorLevel]
|
||||
// - [slog.LevelError] + 2 -> [logrus.FatalLevel]
|
||||
// - [slog.LevelError] + 4 -> [logrus.PanicLevel]
|
||||
//
|
||||
// Levels between these boundaries map to the next lower Logrus severity.
|
||||
// Levels below [slog.LevelDebug] map to [logrus.TraceLevel], and levels at or
|
||||
// above the Panic boundary map to [logrus.PanicLevel].
|
||||
type SlogLevel slog.Level
|
||||
|
||||
// Level returns l mapped to the corresponding Logrus level.
|
||||
func (l SlogLevel) Level() logrus.Level {
|
||||
return toLogrusLevel(slog.Level(l))
|
||||
}
|
||||
|
||||
var _ Leveler = SlogLevel(0)
|
||||
|
||||
// toLogrusLevel maps a slog level using the default mapping.
|
||||
func toLogrusLevel(level slog.Level) logrus.Level {
|
||||
switch {
|
||||
case level >= slogLevelPanic:
|
||||
return logrus.PanicLevel
|
||||
case level >= slogLevelFatal:
|
||||
return logrus.FatalLevel
|
||||
case level >= slogLevelError:
|
||||
return logrus.ErrorLevel
|
||||
case level >= slogLevelWarn:
|
||||
return logrus.WarnLevel
|
||||
case level >= slogLevelInfo:
|
||||
return logrus.InfoLevel
|
||||
case level >= slogLevelDebug:
|
||||
return logrus.DebugLevel
|
||||
case level >= slogLevelTrace:
|
||||
return logrus.TraceLevel
|
||||
default:
|
||||
return logrus.TraceLevel
|
||||
}
|
||||
}
|
||||
|
||||
// toSlogLevel maps a Logrus level using the default mapping.
|
||||
func toSlogLevel(level logrus.Level) slog.Level {
|
||||
switch level {
|
||||
case logrus.PanicLevel:
|
||||
return slogLevelPanic
|
||||
case logrus.FatalLevel:
|
||||
return slogLevelFatal
|
||||
case logrus.ErrorLevel:
|
||||
return slogLevelError
|
||||
case logrus.WarnLevel:
|
||||
return slogLevelWarn
|
||||
case logrus.InfoLevel:
|
||||
return slogLevelInfo
|
||||
case logrus.DebugLevel:
|
||||
return slogLevelDebug
|
||||
case logrus.TraceLevel:
|
||||
return slogLevelTrace
|
||||
default:
|
||||
// Treat all unknown levels as errors
|
||||
return slogLevelError
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// Package slog provides adapters between Logrus and log/slog.
|
||||
//
|
||||
// [Handler] forwards slog records to a Logrus logger, while [Hook] forwards
|
||||
// Logrus entries to a slog logger. They can be used independently to support
|
||||
// incremental migration between the two logging APIs.
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"maps"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// logrusLoggerContextKey is the context key used to track Logrus loggers a
|
||||
// record has already traversed while being forwarded through slog adapters.
|
||||
type logrusLoggerContextKey struct{}
|
||||
|
||||
// withLogrusLogger returns a context that records logger as having already
|
||||
// handled the current log record.
|
||||
func withLogrusLogger(ctx context.Context, logger *logrus.Logger) context.Context {
|
||||
seen, _ := ctx.Value(logrusLoggerContextKey{}).(map[*logrus.Logger]struct{})
|
||||
seen = maps.Clone(seen)
|
||||
if seen == nil {
|
||||
seen = make(map[*logrus.Logger]struct{}, 1)
|
||||
}
|
||||
seen[logger] = struct{}{}
|
||||
return context.WithValue(ctx, logrusLoggerContextKey{}, seen)
|
||||
}
|
||||
|
||||
// hasLogrusLogger reports whether logger has already handled the current log
|
||||
// record while it was forwarded through slog adapters.
|
||||
func hasLogrusLogger(ctx context.Context, logger *logrus.Logger) bool {
|
||||
seen, _ := ctx.Value(logrusLoggerContextKey{}).(map[*logrus.Logger]struct{})
|
||||
_, ok := seen[logger]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HookOptions are options for a [Hook].
|
||||
// A zero HookOptions consists entirely of default values.
|
||||
type HookOptions struct {
|
||||
// LevelMapper maps Logrus levels to slog levels. If nil, the default
|
||||
// mapping is used. Set it to customize level mapping, for example to map
|
||||
// custom Logrus levels to specific slog levels.
|
||||
LevelMapper func(logrus.Level) slog.Level
|
||||
}
|
||||
|
||||
// Hook sends Logrus entries to slog.
|
||||
//
|
||||
// It is intended for bridging libraries or application code that log via
|
||||
// Logrus into an existing slog logger, for example during a gradual migration.
|
||||
//
|
||||
// By default, Logrus levels are mapped using [Level]. Set
|
||||
// [HookOptions.LevelMapper] to customize the mapping.
|
||||
type Hook struct {
|
||||
logger *slog.Logger
|
||||
opts HookOptions
|
||||
}
|
||||
|
||||
var _ logrus.Hook = (*Hook)(nil)
|
||||
|
||||
// NewHook creates a [logrus.Hook] that sends logs to the provided [slog.Logger].
|
||||
//
|
||||
// This hook is intended to be used during transition from Logrus to slog,
|
||||
// or as a shim between different parts of your application or different
|
||||
// libraries that depend on different loggers.
|
||||
//
|
||||
// The provided logger must not be nil. NewHook panics if logger is nil.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewHook(logger *slog.Logger, opts *HookOptions) *Hook {
|
||||
if logger == nil {
|
||||
panic("cannot create hook from nil logger")
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &HookOptions{}
|
||||
}
|
||||
return &Hook{
|
||||
logger: logger,
|
||||
opts: *opts,
|
||||
}
|
||||
}
|
||||
|
||||
// toSlogLevel maps a Logrus level using LevelMapper or the default mapping.
|
||||
func (h *Hook) toSlogLevel(level logrus.Level) slog.Level {
|
||||
if h.opts.LevelMapper != nil {
|
||||
return h.opts.LevelMapper(level)
|
||||
}
|
||||
return Level(level).Level()
|
||||
}
|
||||
|
||||
// Levels always returns all levels, since slog allows controlling level
|
||||
// enabling based on context.
|
||||
func (h *Hook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
// Fire forwards the provided logrus Entry to the underlying slog.Logger's
|
||||
// Handler, mapping it to a slog.Record. Time and caller information are
|
||||
// preserved when available, and Entry.Data is converted to attributes.
|
||||
// If Entry.Context is nil, context.Background() is used.
|
||||
func (h *Hook) Fire(entry *logrus.Entry) error {
|
||||
ctx := entry.Context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
// Track this logger to guard against recursive forwarding.
|
||||
ctx = withLogrusLogger(ctx, entry.Logger)
|
||||
|
||||
level := h.toSlogLevel(entry.Level)
|
||||
handler := h.logger.Handler()
|
||||
if !handler.Enabled(ctx, level) {
|
||||
return nil
|
||||
}
|
||||
attrs := make([]slog.Attr, 0, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
attrs = append(attrs, slog.Any(k, v))
|
||||
}
|
||||
var pc uintptr
|
||||
if entry.Caller != nil {
|
||||
pc = entry.Caller.PC
|
||||
}
|
||||
r := slog.NewRecord(entry.Time, level, entry.Message, pc)
|
||||
r.AddAttrs(attrs...)
|
||||
return handler.Handle(ctx, r)
|
||||
}
|
||||
Vendored
+6
-5
@@ -80,8 +80,8 @@ github.com/containerd/containerd/api/services/tasks/v1
|
||||
github.com/containerd/containerd/api/services/version/v1
|
||||
github.com/containerd/containerd/api/types
|
||||
github.com/containerd/containerd/api/types/task
|
||||
# github.com/containerd/log v0.1.0
|
||||
## explicit; go 1.20
|
||||
# github.com/containerd/log v0.2.0
|
||||
## explicit; go 1.23
|
||||
github.com/containerd/log
|
||||
# github.com/containerd/ttrpc v1.2.10
|
||||
## explicit; go 1.25.0
|
||||
@@ -416,7 +416,7 @@ github.com/moby/spdystream/spdy
|
||||
# github.com/moby/sys/mountinfo v0.7.2
|
||||
## explicit; go 1.17
|
||||
github.com/moby/sys/mountinfo
|
||||
# github.com/moby/sys/userns v0.1.0
|
||||
# github.com/moby/sys/userns v0.2.1
|
||||
## explicit; go 1.21
|
||||
github.com/moby/sys/userns
|
||||
# github.com/moby/term v0.5.2
|
||||
@@ -472,7 +472,7 @@ github.com/onsi/gomega/matchers/support/goraph/edge
|
||||
github.com/onsi/gomega/matchers/support/goraph/node
|
||||
github.com/onsi/gomega/matchers/support/goraph/util
|
||||
github.com/onsi/gomega/types
|
||||
# github.com/opencontainers/cgroups v0.0.7
|
||||
# github.com/opencontainers/cgroups v0.1.0
|
||||
## explicit; go 1.24.0
|
||||
github.com/opencontainers/cgroups
|
||||
github.com/opencontainers/cgroups/devices/config
|
||||
@@ -536,6 +536,7 @@ github.com/russross/blackfriday/v2
|
||||
# github.com/sirupsen/logrus v1.10.2
|
||||
## explicit; go 1.23
|
||||
github.com/sirupsen/logrus
|
||||
github.com/sirupsen/logrus/hooks/slog
|
||||
# github.com/soheilhy/cmux v0.1.5
|
||||
## explicit; go 1.11
|
||||
github.com/soheilhy/cmux
|
||||
@@ -1302,6 +1303,6 @@ sigs.k8s.io/structured-merge-diff/v7/value
|
||||
## explicit; go 1.22
|
||||
sigs.k8s.io/yaml
|
||||
sigs.k8s.io/yaml/kyaml
|
||||
# tags.cncf.io/container-device-interface/specs-go v1.1.0
|
||||
# tags.cncf.io/container-device-interface/specs-go v1.1.1
|
||||
## explicit; go 1.19
|
||||
tags.cncf.io/container-device-interface/specs-go
|
||||
+68
-85
@@ -19,47 +19,46 @@ package specs
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// CurrentVersion is the current version of the Spec.
|
||||
const CurrentVersion = "1.1.0"
|
||||
|
||||
const (
|
||||
// CurrentVersion is the current version of the Spec.
|
||||
CurrentVersion = "1.1.0"
|
||||
|
||||
// vCurrent is the current version as a semver-comparable type
|
||||
vCurrent version = "v" + CurrentVersion
|
||||
|
||||
// These represent the released versions of the CDI specification
|
||||
v010 version = "v0.1.0"
|
||||
v020 version = "v0.2.0"
|
||||
v030 version = "v0.3.0"
|
||||
v040 version = "v0.4.0"
|
||||
v050 version = "v0.5.0"
|
||||
v060 version = "v0.6.0"
|
||||
v070 version = "v0.7.0"
|
||||
v080 version = "v0.8.0"
|
||||
v100 version = "v1.0.0"
|
||||
v110 version = "v1.1.0"
|
||||
v010 version = "0.1.0"
|
||||
v020 version = "0.2.0"
|
||||
v030 version = "0.3.0"
|
||||
v040 version = "0.4.0"
|
||||
v050 version = "0.5.0"
|
||||
v060 version = "0.6.0"
|
||||
v070 version = "0.7.0"
|
||||
v080 version = "0.8.0"
|
||||
v100 version = "1.0.0"
|
||||
v110 version = "1.1.0"
|
||||
|
||||
// vEarliest is the earliest supported version of the CDI specification
|
||||
vEarliest version = v030
|
||||
)
|
||||
|
||||
// validSpecVersions stores a map of spec versions to functions to check the required versions.
|
||||
// validSpecVersions stores the known spec versions in newest-to-oldest order,
|
||||
// together with functions to check whether a version is required.
|
||||
// Adding new fields / spec versions requires that a `requiredFunc` be implemented and
|
||||
// this map be updated.
|
||||
var validSpecVersions = requiredVersionMap{
|
||||
v010: nil,
|
||||
v020: nil,
|
||||
v030: nil,
|
||||
v040: requiresV040,
|
||||
v050: requiresV050,
|
||||
v060: requiresV060,
|
||||
v070: requiresV070,
|
||||
v080: requiresV080,
|
||||
v100: requiresV100,
|
||||
v110: requiresV110,
|
||||
// this list be updated.
|
||||
var validSpecVersions = []struct {
|
||||
version version
|
||||
isRequired requiredFunc
|
||||
}{
|
||||
{v110, requiresV110},
|
||||
{v100, requiresV100},
|
||||
{v080, requiresV080},
|
||||
{v070, requiresV070},
|
||||
{v060, requiresV060},
|
||||
{v050, requiresV050},
|
||||
{v040, requiresV040},
|
||||
{v030, nil},
|
||||
{v020, nil},
|
||||
{v010, nil},
|
||||
}
|
||||
|
||||
// ValidateVersion checks whether the specified spec version is valid.
|
||||
@@ -67,14 +66,15 @@ var validSpecVersions = requiredVersionMap{
|
||||
// the spec is inspected to determine whether the features used are available in specified
|
||||
// version.
|
||||
func ValidateVersion(spec *Spec) error {
|
||||
if !validSpecVersions.isValidVersion(spec.Version) {
|
||||
specVersion := newVersion(spec.Version)
|
||||
if !isValidVersion(specVersion) {
|
||||
return fmt.Errorf("invalid version %q", spec.Version)
|
||||
}
|
||||
minVersion, err := MinimumRequiredVersion(spec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine minimum required version: %w", err)
|
||||
}
|
||||
if newVersion(minVersion).isGreaterThan(newVersion(spec.Version)) {
|
||||
if versionIndex(version(minVersion)) < versionIndex(specVersion) {
|
||||
return fmt.Errorf("the spec version must be at least v%v", minVersion)
|
||||
}
|
||||
return nil
|
||||
@@ -82,64 +82,50 @@ func ValidateVersion(spec *Spec) error {
|
||||
|
||||
// MinimumRequiredVersion determines the minimum spec version for the input spec.
|
||||
func MinimumRequiredVersion(spec *Spec) (string, error) {
|
||||
minVersion := validSpecVersions.requiredVersion(spec)
|
||||
return minVersion.String(), nil
|
||||
minVersion := requiredVersion(spec)
|
||||
return string(minVersion), nil
|
||||
}
|
||||
|
||||
// version represents a semantic version string
|
||||
// version represents a CDI specification version.
|
||||
type version string
|
||||
|
||||
// newVersion creates a version that can be used for semantic version comparisons.
|
||||
// newVersion normalizes a specification version by removing an optional leading v.
|
||||
func newVersion(v string) version {
|
||||
return version("v" + strings.TrimPrefix(v, "v"))
|
||||
}
|
||||
|
||||
// String returns the string representation of the version.
|
||||
// This trims a leading v if present.
|
||||
func (v version) String() string {
|
||||
return strings.TrimPrefix(string(v), "v")
|
||||
}
|
||||
|
||||
// isGreaterThan checks with a version is greater than the specified version.
|
||||
func (v version) isGreaterThan(o version) bool {
|
||||
return semver.Compare(string(v), string(o)) > 0
|
||||
}
|
||||
|
||||
// isLatest checks whether the version is the latest supported version
|
||||
func (v version) isLatest() bool {
|
||||
return v == vCurrent
|
||||
return version(strings.TrimPrefix(v, "v"))
|
||||
}
|
||||
|
||||
type requiredFunc func(*Spec) bool
|
||||
|
||||
type requiredVersionMap map[version]requiredFunc
|
||||
|
||||
// isValidVersion checks whether the specified version is valid.
|
||||
// A version is valid if it is contained in the required version map.
|
||||
func (r requiredVersionMap) isValidVersion(specVersion string) bool {
|
||||
_, ok := validSpecVersions[newVersion(specVersion)]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// requiredVersion returns the minimum version required for the given spec
|
||||
func (r requiredVersionMap) requiredVersion(spec *Spec) version {
|
||||
minVersion := vEarliest
|
||||
|
||||
for v, isRequired := range validSpecVersions {
|
||||
if isRequired == nil {
|
||||
continue
|
||||
}
|
||||
if isRequired(spec) && v.isGreaterThan(minVersion) {
|
||||
minVersion = v
|
||||
}
|
||||
// If we have already detected the latest version then no later version could be detected
|
||||
if minVersion.isLatest() {
|
||||
break
|
||||
// A version is valid if it is contained in the list of known spec versions.
|
||||
func isValidVersion(specVersion version) bool {
|
||||
for _, known := range validSpecVersions {
|
||||
if known.version == specVersion {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return minVersion
|
||||
// requiredVersion returns the minimum version required for the given spec.
|
||||
func requiredVersion(spec *Spec) version {
|
||||
for _, known := range validSpecVersions {
|
||||
if known.isRequired != nil && known.isRequired(spec) {
|
||||
return known.version
|
||||
}
|
||||
}
|
||||
return vEarliest
|
||||
}
|
||||
|
||||
// versionIndex returns the index of v in validSpecVersions, which is ordered
|
||||
// newest-to-oldest. It returns -1 for an unknown version.
|
||||
func versionIndex(v version) int {
|
||||
for i, known := range validSpecVersions {
|
||||
if known.version == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// requiresV110 returns true if the spec uses v1.1.0 features.
|
||||
@@ -209,23 +195,20 @@ func requiresV070(spec *Spec) bool {
|
||||
// requiresV060 returns true if the spec uses v0.6.0 features
|
||||
func requiresV060(spec *Spec) bool {
|
||||
// The v0.6.0 spec allows annotations to be specified at a spec level
|
||||
for range spec.Annotations {
|
||||
if len(spec.Annotations) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// The v0.6.0 spec allows annotations to be specified at a device level
|
||||
for _, d := range spec.Devices {
|
||||
for range d.Annotations {
|
||||
if len(d.Annotations) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// The v0.6.0 spec allows dots "." in Kind name label (class)
|
||||
if !strings.Contains(spec.Kind, "/") {
|
||||
return false
|
||||
}
|
||||
class := strings.SplitN(spec.Kind, "/", 2)[1]
|
||||
return strings.Contains(class, ".")
|
||||
_, class, ok := strings.Cut(spec.Kind, "/")
|
||||
return ok && strings.Contains(class, ".")
|
||||
}
|
||||
|
||||
// requiresV050 returns true if the spec uses v0.5.0 features
|
||||
|
||||
Reference in new issue
Block a user